Skip to content

Instantly share code, notes, and snippets.

@EduenSarceno
Last active April 27, 2021 01:32
Show Gist options
  • Save EduenSarceno/e21bc388fc40b200868d4c47067a5803 to your computer and use it in GitHub Desktop.
Save EduenSarceno/e21bc388fc40b200868d4c47067a5803 to your computer and use it in GitHub Desktop.
Obtener info de archivos .ass en php
<?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 &sections()
{
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