Last active
April 27, 2021 01:32
-
-
Save EduenSarceno/e21bc388fc40b200868d4c47067a5803 to your computer and use it in GitHub Desktop.
Obtener info de archivos .ass en php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class AssVariable | |
implements ArrayAccess | |
{ | |
/* sección donde fue definida */ | |
public $section; | |
/* nombre */ | |
public $name; | |
/* valor textual */ | |
public $content = false; | |
/* almacena otras variables con el mismo nombre */ | |
public $array = false; | |
/* valor con formato */ | |
public $obj = false; | |
/* | |
* Analiza una variable | |
* @return false si es inválida | |
*/ | |
public static function parse(&$line, &$format) | |
{ | |
$var = new AssVariable(); | |
$rest = 0; | |
if (sscanf($line, '%[^:]: %n', $var->name, $rest) !== 2) { | |
return false; | |
} | |
$var->content = substr($line, $rest); | |
// Format: field1, field2, ... | |
if ($var->name === 'Format') { | |
$var->obj = explode(', ', $var->content); | |
} else if (isset($format) && $format !== false) { | |
// Debemos darle formato | |
$var->obj = new stdclass(); | |
$tmp = explode(',', $var->content); | |
foreach ($format as $i => $field) { | |
$var->obj->$field = $tmp[$i]; | |
} | |
} | |
return $var; | |
} | |
/* | |
* Agrega otra variable | |
*/ | |
public function add(&$variable) | |
{ | |
if (isset($variable) && $variable->array !== false) { | |
throw new TypeError('no se permiten variables anidadas'); | |
} | |
if ($this->array === false) { | |
$tmp = clone $this; | |
$this->content = false; | |
$this->obj = false; | |
$this->array = array($tmp); | |
} | |
$this->array[] = $variable; | |
} | |
/* | |
* Guarda los cambios efecutados en $this->obj | |
*/ | |
public function save() | |
{ | |
if ($this->obj !== false) { | |
$this->content = implode(',', array_values((array) $this->obj)); | |
} | |
} | |
/* Override */ | |
public function __toString() | |
{ | |
return $this->name.": ".$this->content; | |
} | |
/* @Override */ | |
public function offsetExists($offset) | |
{ | |
return $this->obj && isset($this->obj->$offset); | |
} | |
/* @Override */ | |
public function &offsetGet($offset) | |
{ | |
return $this->obj->$offset; | |
} | |
/* @Override */ | |
public function offsetSet($offset, $value) | |
{ | |
return $this->obj->$offset = $value; | |
} | |
/* @Override */ | |
public function offsetUnset($offset) | |
{ | |
unset($this->obj->$offset); | |
} | |
} | |
class AssBuffer | |
{ | |
public const CHUNK_SIZE = 4000; // 4KiB | |
private const BUFFER_COUNT = 2; | |
/* Fuente */ | |
private $stream; | |
/* Posición actual del buffer */ | |
private $caret = 0; | |
/* Posición del siguiente token */ | |
private $next = 0; | |
/* Buffers */ | |
private $buffers = array(); | |
/* Buffer actual */ | |
private $current = -1; | |
/* Posición actual del stream */ | |
private $tell; | |
/* | |
* Construye un buffer para un stream dado | |
* @param $stream el stream a buffear | |
*/ | |
public function __construct($stream) | |
{ | |
$this->stream = $stream; | |
} | |
/* | |
* Rellena un nuevo buffer | |
* @return el nuevo buffer | |
*/ | |
private function &new_buffer() | |
{ | |
$next = ($this->current + 1) % self::BUFFER_COUNT; | |
$this->current = $next; | |
$this->buffers[$next] = fread($this->stream, self::CHUNK_SIZE); | |
$this->tell = ftell($this->stream); | |
return $this->buffers[$next]; | |
} | |
/* | |
* Obtiene la siguiente línea del stream, sin "\n" | |
*/ | |
public function next_line() | |
{ | |
$prev = NULL; | |
$ret = NULL; | |
$caret = $this->caret; | |
$next = $this->next; | |
$buffer = @$this->buffers[$this->current]; | |
// No hemos cargado ningún buffer | |
if ($buffer === NULL) { | |
$buffer = $this->new_buffer(); | |
} else if ($this->caret >= strlen($buffer)) { | |
// Debemos cargar otro buffer | |
$buffer = $this->new_buffer(); | |
$this->caret = $caret = 0; | |
$this->next = $next = 0; | |
} | |
find_next: | |
if ($buffer === '' || $buffer === false) | |
return false; | |
while ($next < strlen($buffer) && $buffer[$next++] !== "\n"); | |
// EL salto de línea se encuentra en otro buffer | |
if ($buffer[$next - 1] !== "\n") { | |
$prev = substr($buffer, $this->caret); | |
$buffer = $this->new_buffer(); | |
$caret = $next = 0; | |
goto find_next; | |
} | |
$ret = substr($buffer, $caret, $next - $caret); | |
$ret = rtrim($ret, "\r\n"); | |
if ($prev) | |
$ret = $prev . $ret; | |
$this->next = $next; | |
$this->caret = $next; | |
return $ret; | |
} | |
/* | |
* Devuelve la posición actual relativa al stream | |
*/ | |
public function tell() | |
{ | |
$buffer = $this->buffers[$this->current]; | |
return $this->tell - strlen($buffer) + $this->caret; | |
} | |
} | |
class AssInfo | |
implements ArrayAccess | |
{ | |
private const S_INITIAL = 255; | |
private const S_INSIDE_SECTION = 256; | |
/* Stream fuente */ | |
public $source; | |
/* Metadatos clave-valor */ | |
private $sections = array(); | |
/* Guarda la posición de la sección "Events" */ | |
public $eventPos = false; | |
public function __destruct() | |
{ | |
$this->source = NULL; | |
$this->buffer = NULL; | |
$this->sections = NULL; | |
} | |
/* | |
* Obtiene la información de un .ass | |
* @param $stream el stream del archivo a analizar. | |
*/ | |
public function __construct($stream) | |
{ | |
$state = self::S_INITIAL; | |
$buffer = NULL; | |
$line = NULL; | |
$section = NULL; | |
$var = NULL; | |
if (!is_resource($stream)) { | |
$stream = fopen(strval($stream), 'r'); | |
} | |
$this->source = $stream; | |
$buffer = new AssBuffer($stream); | |
$line = $buffer->next_line(); | |
// Eliminamos el molesto BOM | |
self::remove_bom($line); | |
rewind($stream); | |
// Leemos el archivo línea por línea | |
while ($line !== false) { | |
// Ignoramos lineas vacías y comentarios | |
if ($line === '' || $line[0] === ';') | |
goto next_line; | |
switch($state) { | |
case self::S_INSIDE_SECTION: | |
if ($line[0] !== '[') { | |
$this->addVar($section, $line); | |
break; | |
} | |
$state = self::S_INITIAL; | |
/* fallback */ | |
case self::S_INITIAL: | |
if ($line[0] === '[') { | |
if (sscanf($line, '[%[^]]]', $section) == 1) { | |
if ($section === 'Events') { | |
$this->eventPos = $buffer->tell(); | |
break; | |
} | |
$state = self::S_INSIDE_SECTION; | |
$this->sections[$section] = array(); | |
} | |
} | |
} | |
next_line: | |
$line = $buffer->next_line(); | |
} | |
} | |
/* | |
* Guarda cambios | |
* @param $dest stream destino | |
*/ | |
public function save($dest) | |
{ | |
$buffer = NULL; | |
$line = NULL; | |
$i = 0; | |
if (!is_resource($dest)) { | |
$dest = fopen(strval($dest), 'w'); | |
} | |
// Escribimos las secciones; | |
foreach($this->sections as $section => $vars) { | |
if ($i++ !== 0) | |
fwrite($dest, PHP_EOL); | |
fwrite($dest, "[$section]"); | |
fwrite($dest, PHP_EOL); | |
foreach($vars as $var) { | |
if ($var->array) { | |
foreach($var->array as $var) { | |
fwrite($dest, strval($var)); | |
fwrite($dest, PHP_EOL); | |
} | |
} else { | |
fwrite($dest, strval($var)); | |
fwrite($dest, PHP_EOL); | |
} | |
} | |
} | |
} | |
/* | |
* Lee los eventos en el script. | |
*/ | |
public function &getEvents() | |
{ | |
$buffer = NULL; | |
$line = NULL; | |
$format = NULL; | |
$event = NULL; | |
if (array_key_exists('Events', $this->sections)) | |
return $this->sections['Events']; | |
$this->sections['Events'] = array(); | |
$buffer = new AssBuffer($this->source); | |
fseek($this->source, $this->eventPos); | |
while (($line = $buffer->next_line()) !== false) { | |
// Ignoramos lineas vacías, y comentarios | |
if ($line === '' || $line[0] === ';') | |
continue; | |
// Se inicia otra sección, debemos finalizar. | |
if ($line[0] === '[') | |
break; | |
$this->addVar('Events', $line); | |
} | |
return $this->sections['Events']; | |
} | |
/* | |
* Devuelve las secciones del script | |
*/ | |
public function §ions() | |
{ | |
return $this->sections; | |
} | |
private function addVar($sectName, $var) | |
{ | |
$section = &$this->sections[$sectName]; | |
if (array_key_exists('Format', $section)) | |
$format = $section['Format']->obj; | |
else | |
$format = false; | |
$var = AssVariable::parse($var, $format); | |
if ($var === false) | |
return; | |
$var->section = $sectName; | |
if (!array_key_exists($var->name, $section)) { | |
$section[$var->name] = $var; | |
} else { | |
// Agregamos la variable al grupo | |
$group = $section[$var->name]; | |
$group->add($var); | |
} | |
} | |
/* @Override */ | |
public function offsetExists($offset) | |
{ | |
return array_key_exists($offset, $this->sections); | |
} | |
/* @Override */ | |
public function &offsetGet($offset) | |
{ | |
return $this->sections[$offset]; | |
} | |
/* @Override */ | |
public function offsetSet($offset, $value) | |
{ | |
return $this->sections[$offset] = $value; | |
} | |
/* @Override */ | |
public function offsetUnset($offset) | |
{ | |
unset($this->sections[$offset]); | |
} | |
private static function remove_bom(&$line) | |
{ | |
$BOM = pack('H*', 'EFBBBF'); | |
$line = str_replace($BOM, '', $line); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment