Refactor files : new ViewLayout based system

This commit is contained in:
DataHoarder 2021-12-29 01:12:28 +01:00
parent d0265ca581
commit b4087dd93e
76 changed files with 2373 additions and 665 deletions

2
.gitignore vendored
View file

@ -6,6 +6,8 @@
/*.zip
/*.mp4
/*.mp3
/*.flac
/*.xml
/*.webm
/*.mkv
/*.swf

View file

@ -1,7 +1,7 @@
{
"name": "weebdatahoarder/swf2ass",
"type": "project",
"license": "MIT",
"license": "GPL-3.0-or-later",
"autoload": {
"psr-4": {
"swf2ass\\": "src/"

View file

@ -3,13 +3,12 @@
require_once __DIR__ . "/vendor/autoload.php";
const SWFMILL_BINARY = "swfmill";
const TWIP_SIZE = 20;
const ADVANCED_MOTION_INTERPOLATION = true;
function normalizePath($path) {
$newPath = [];
$lastCursor = new \swf2ass\Coordinate(0, 0);
$lastCursor = new \swf2ass\Vector2(0, 0);
foreach ($path as $e) {
/** @var \swf2ass\LineRecord|\swf2ass\MoveRecord $e */
@ -127,12 +126,12 @@ function cleanupPath(array $path) {
}
/**
* @param \swf2ass\Coordinate $corner
* @param \swf2ass\Vector2 $corner
* @param \swf2ass\LineRecord[] $path
*
* @return ?\swf2ass\LineRecord
*/
function findNextCorner(\swf2ass\Coordinate $corner, array &$path, array &$cornerMap): ?\swf2ass\LineRecord {
function findNextCorner(\swf2ass\Vector2 $corner, array &$path, array &$cornerMap): ?\swf2ass\LineRecord {
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
unset($path[$i]);
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
@ -147,7 +146,7 @@ function findNextCorner(\swf2ass\Coordinate $corner, array &$path, array &$corne
return null;
}
function offsetPath($path, \swf2ass\Coordinate $offset): array {
function offsetPath($path, \swf2ass\Vector2 $offset): array {
$newPath = [];
foreach ($path as $e) {
@ -164,7 +163,7 @@ function offsetPath($path, \swf2ass\Coordinate $offset): array {
}
function getBoundingBox($path) {
$bb = new \swf2ass\Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new \swf2ass\Coordinate(PHP_INT_MIN, PHP_INT_MIN));
$bb = new \swf2ass\Rectangle(new \swf2ass\Vector2(PHP_INT_MAX, PHP_INT_MAX), new \swf2ass\Vector2(PHP_INT_MIN, PHP_INT_MIN));
/*if(count($path) > 0){
$first = reset($path);
if($first instanceof MoveRecord or $first instanceof LineRecord){
@ -197,9 +196,9 @@ function getBoundingBox($path) {
function calculateAreaOfPath($path) {
$totalArea = 0;
/** @var \swf2ass\Coordinate[][] $subPolygons */
/** @var \swf2ass\Vector2[][] $subPolygons */
$subPolygons = [];
/** @var \swf2ass\Coordinate[] $currentPolygon */
/** @var \swf2ass\Vector2[] $currentPolygon */
$currentPolygon = [];
foreach ($path as $n) {
if ($n instanceof \swf2ass\MoveRecord) {
@ -264,7 +263,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
$colors[$k] = $color;
$paths[$k] = [];
}
foreach ((new \swf2ass\Square(new \swf2ass\Coordinate($x, $y), 1))->draw() as $p) {
foreach ((new \swf2ass\Square(new \swf2ass\Vector2($x, $y), 1))->draw() as $p) {
$paths[$k][] = $p;
}
}
@ -288,7 +287,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
$k0 = array_key_first($optimizedPaths);
$path = $optimizedPaths[$k0];
$optimizedPaths[$k0] = [];
foreach ((new \swf2ass\Rectangle(new \swf2ass\Coordinate(0, 0), new \swf2ass\Coordinate($resX, $resY)))->draw() as $p) {
foreach ((new \swf2ass\Rectangle(new \swf2ass\Vector2(0, 0), new \swf2ass\Vector2($resX, $resY)))->draw() as $p) {
$optimizedPaths[$k0][] = $p;
}
}
@ -296,7 +295,7 @@ function renderFrame(array $pixelMatrix, int $resX, int $resY, bool $doFullFrame
$assLines = [];
foreach ($optimizedPaths as $k => $path) {
$pos = new \swf2ass\Coordinate(0, 0);
$pos = new \swf2ass\Vector2(0, 0);
$bb = getBoundingBox($path);
$pos = $bb->topLeft;
/*$first = reset($path);

9
src/ActionList.php Normal file
View file

@ -0,0 +1,9 @@
<?php
namespace swf2ass;
class ActionList {
}

View file

@ -11,14 +11,14 @@ class ActivePath {
/**
* @param $styleId
* @param Coordinate $start
* @param Vector2 $start
*/
public function __construct($styleId, Coordinate $start) {
public function __construct($styleId, Vector2 $start) {
$this->style = $styleId;
$this->segment = new PathSegment($start);
}
public function add_point(VisitedPoint $point){
public function add_point(VisitedPoint $point) {
$this->segment->add_point($point);
}

View file

@ -6,18 +6,18 @@ namespace swf2ass;
class BitmapConverter {
/** @var Color[][] */
private array $pixelMatrix;
private Coordinate $resolution;
private Vector2 $resolution;
public function __construct(array $pixelMatrix) {
$this->pixelMatrix = $pixelMatrix;
$this->resolution = new Coordinate(count($pixelMatrix[0]), count($pixelMatrix));
$this->resolution = new Vector2(count($pixelMatrix[0]), count($pixelMatrix));
}
private static function calculateAreaOfPath($path) {
$totalArea = 0;
/** @var Coordinate[][] $subPolygons */
/** @var Vector2[][] $subPolygons */
$subPolygons = [];
/** @var Coordinate[] $currentPolygon */
/** @var Vector2[] $currentPolygon */
$currentPolygon = [];
foreach ($path as $n) {
if ($n instanceof MoveRecord) {
@ -65,7 +65,7 @@ class BitmapConverter {
private static function normalizePath($path) {
$newPath = [];
$lastCursor = new Coordinate(0, 0);
$lastCursor = new Vector2(0, 0);
foreach ($path as $e) {
/** @var LineRecord|MoveRecord $e */
@ -178,12 +178,12 @@ class BitmapConverter {
}
/**
* @param Coordinate $corner
* @param Vector2 $corner
* @param LineRecord[] $path
*
* @return ?LineRecord
*/
private static function findNextCorner(Coordinate $corner, array &$path, array &$cornerMap): ?LineRecord {
private static function findNextCorner(Vector2 $corner, array &$path, array &$cornerMap): ?LineRecord {
foreach ($cornerMap[$corner->x . "_" . $corner->y] as $i => $p) {
unset($path[$i]);
unset($cornerMap[$p->start->x . "_" . $p->start->y][$i]);
@ -200,7 +200,7 @@ class BitmapConverter {
private static function getBoundingBox($path) {
$bb = new Rectangle(new \swf2ass\Coordinate(PHP_INT_MAX, PHP_INT_MAX), new Coordinate(PHP_INT_MIN, PHP_INT_MIN));
$bb = new Rectangle(new \swf2ass\Vector2(PHP_INT_MAX, PHP_INT_MAX), new Vector2(PHP_INT_MIN, PHP_INT_MIN));
/*if(count($path) > 0){
$first = reset($path);
if($first instanceof MoveRecord or $first instanceof LineRecord){
@ -241,7 +241,7 @@ class BitmapConverter {
$colors[$k] = $color;
$paths[$k] = [];
}
foreach ((new Square(new Coordinate($x, $y), 1))->draw() as $p) {
foreach ((new Square(new Vector2($x, $y), 1))->draw() as $p) {
$paths[$k][] = $p;
}
}
@ -265,7 +265,7 @@ class BitmapConverter {
$k0 = array_key_first($optimizedPaths);
$path = $optimizedPaths[$k0];
$optimizedPaths[$k0] = [];
foreach ((new Rectangle(new Coordinate(0, 0), $this->resolution))->draw() as $p) {
foreach ((new Rectangle(new Vector2(0, 0), $this->resolution))->draw() as $p) {
$optimizedPaths[$k0][] = $p;
}
}
@ -273,19 +273,19 @@ class BitmapConverter {
/** @var DrawPath[] $commands */
$commands = [];
foreach ($optimizedPaths as $k => $path){
foreach ($optimizedPaths as $k => $path) {
$edges = [];
$bb = self::getBoundingBox($path);
$pos = $bb->topLeft->multiply(TWIP_SIZE);
$pos = $bb->topLeft->multiply(Constants::TWIP_SIZE);
$edges[] = new MoveRecord($pos, new Coordinate(0, 0));
$edges[] = new MoveRecord($pos, new Vector2(0, 0));
foreach ($path as $edge){
if($edge instanceof MoveRecord){
$edges[] = new MoveRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
}else if($edge instanceof LineRecord){
$edges[] = new LineRecord($edge->coord->multiply(TWIP_SIZE), $edge->start->multiply(TWIP_SIZE));
}else{
foreach ($path as $edge) {
if ($edge instanceof MoveRecord) {
$edges[] = new MoveRecord($edge->coord->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
} else if ($edge instanceof LineRecord) {
$edges[] = new LineRecord($edge->coord->multiply(Constants::TWIP_SIZE), $edge->start->multiply(Constants::TWIP_SIZE));
} else {
var_dump($edge);
throw new \Exception();
}

View file

@ -4,13 +4,13 @@ namespace swf2ass;
class BitmapDefinition implements ObjectDefinition {
public $id;
public Coordinate $size;
public int $id;
public Vector2 $size;
/** @var Color[][] */
public $pixels;
private DrawPathList $drawPathList;
public function __construct($id, Coordinate $size, array $pixels) {
public function __construct(int $id, Vector2 $size, array $pixels) {
$this->id = $id;
$this->size = $size;
$this->pixels = $pixels;
@ -19,16 +19,17 @@ class BitmapDefinition implements ObjectDefinition {
$this->drawPathList = $converter->render(true);
}
public function getId(){
public function getObjectId(): int {
return $this->id;
}
public function getShapeList() : DrawPathList{
public function getShapeList(): DrawPathList {
return $this->drawPathList;
}
static function fromXML(\DOMElement $element): BitmapDefinition {
//Utils::dump_element($element);
$size = new Coordinate((int)$element->getAttribute("width"), (int)$element->getAttribute("height"));
$size = new Vector2((int)$element->getAttribute("width"), (int)$element->getAttribute("height"));
$pixels = array_fill(0, $size->y, array_fill(0, $size->x, new Color(0, 0, 0)));
$format = (int)$element->getAttribute("format");
switch ($format) {
@ -70,7 +71,7 @@ class BitmapDefinition implements ObjectDefinition {
return new BitmapDefinition($element->getAttribute("objectID"), $size, $pixels);
}
public function getPixel(Coordinate $c) {
public function getPixel(Vector2 $c) {
return $this->pixels[$c->y][$c->x];
}
@ -82,7 +83,7 @@ class BitmapDefinition implements ObjectDefinition {
$iterator = $im->getPixelIterator();
foreach ($iterator as $row => $pixels) {
foreach ($pixels as $col => $pixel) {
$c = $this->getPixel(new Coordinate($col, $row));
$c = $this->getPixel(new Vector2($col, $row));
$colorStr = "#" . bin2hex(chr($c->r)) . bin2hex(chr($c->g)) . bin2hex(chr($c->b)) . bin2hex(chr(255 - ($c->a ?? 0)));
$pixel->setColor($colorStr);
}

View file

@ -0,0 +1,17 @@
<?php
namespace swf2ass;
class ClippingViewLayout extends ViewLayout {
private int $clipDepth;
public function __construct(int $clipDepth, int $objectId, ?ObjectDefinition $object, ?ViewLayout $parent = null) {
$this->clipDepth = $clipDepth;
parent::__construct($objectId, $object, $parent);
}
public function getClipDepth(): int {
return $this->clipDepth;
}
}

View file

@ -8,24 +8,24 @@ class Color {
public $b;
public $alpha;
public function __construct($r, $g, $b, $alpha = null) {
public function __construct($r, $g, $b, $alpha = 0) {
$this->r = $r;
$this->g = $g;
$this->b = $b;
$this->alpha = $alpha;
}
public function equals(?Color $other): bool {
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and $other->alpha == $this->alpha;
public function equals(?Color $other, $alpha = true): bool {
return $other !== null and $other->r === $this->r and $other->g === $this->g and $other->b === $this->b and (!$alpha or $other->alpha == $this->alpha);
}
public function distance(Color $color): float {
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2));
public function distance(Color $color, $alpha = false): float {
return sqrt(pow($color->r - $this->r, 2) + pow($color->g - $this->g, 2) + pow($color->b - $this->b, 2) + ($alpha ? pow($color->alpha - $this->alpha, 2) : 0));
}
public function toString($nullAlpha = false) {
$c = "\\1c&H" . strtoupper(Utils::padHex(dechex($this->b ?? 0))) . strtoupper(Utils::padHex(dechex($this->g ?? 0))) . strtoupper(Utils::padHex(dechex($this->r ?? 0))) . "&";
if (($this->alpha === null or $this->alpha === 0) and $nullAlpha) {
if (($this->alpha === 0) and $nullAlpha) {
} else {
$c .= "\\1a&H" . strtoupper(Utils::padHex(dechex($this->alpha ?? 0))) . "&";
@ -34,11 +34,7 @@ class Color {
return $c;
}
public function applyTransform(ColorTransform $transform): Color {
return new Color(max(0, min((($this->r * ($transform->mult->r ?? 256)) / 256) + ($transform->add->r ?? 0), 255)), max(0, min((($this->g * ($transform->mult->g ?? 256)) / 256) + ($transform->add->g ?? 0), 255)), max(0, min((($this->b * ($transform->mult->b ?? 256)) / 256) + ($transform->add->b ?? 0), 255)), max(0, min((($this->alpha * ($transform->mult->alpha ?? 256)) / 256) + ($transform->add->alpha ?? 0), 255)),);
}
public static function fromXML(\DOMElement $element): Color {
return new Color($element->getAttribute("red"), $element->getAttribute("green"), $element->getAttribute("blue"), $element->hasAttribute("alpha") ? $element->getAttribute("alpha") : null);
return new Color((int)$element->getAttribute("red"), (int)$element->getAttribute("green"), (int)$element->getAttribute("blue"), $element->hasAttribute("alpha") ? (int)$element->getAttribute("alpha") : 0);
}
}

View file

@ -4,14 +4,18 @@ namespace swf2ass;
class ColorTransform {
public $mult;
public $add;
public Color $mult;
public Color $add;
public function __construct(Color $mult = null, Color $add = null) {
public function __construct(Color $mult, Color $add) {
$this->mult = $mult;
$this->add = $add;
}
public static function identity(): ColorTransform {
return new ColorTransform(new Color(256, 256, 256, 256), new Color(0, 0, 0));
}
public function applyToStyleContainer(StyleContainer $styles): StyleContainer {
if ($styles->original_color1 instanceof Color) {
$styles->color1 = $styles->original_color1->applyTransform($this);
@ -23,9 +27,30 @@ class ColorTransform {
return $styles;
}
public function applyMultiplyToColor(Color $color): Color {
return new Color(max(0, min(($this->mult->r * $color->r) / 256, 255)), max(0, min(($this->mult->g * $color->g) / 256, 255)), max(0, min(($this->mult->b * $color->b) / 256, 255)), max(0, min(($this->mult->alpha * $color->alpha) / 256, 255)),);
}
public function applyAdditionToColor(Color $color): Color {
return new Color(max(0, min($this->add->r + $color->r, 255)), max(0, min($this->add->g + $color->g, 255)), max(0, min($this->add->b + $color->b, 255)), max(0, min($this->add->alpha + $color->alpha, 255)),);
}
public function applyToColor(Color $color): Color {
return new Color(max(0, min((($this->mult->r * $color->r) / 256) + $this->add->r, 255)), max(0, min((($this->mult->g * $color->g) / 256) + $this->add->g, 255)), max(0, min((($this->mult->b * $color->b) / 256) + $this->add->b, 255)), max(0, min((($this->mult->alpha * $color->alpha) / 256) + $this->add->alpha, 255)),);
}
public function combine(ColorTransform $transform) {
//TODO: maybe these get altered all at once?
return new ColorTransform($this->applyMultiplyToColor($transform->mult), $this->applyAdditionToColor($transform->add));
}
public function equals(ColorTransform $other, $epsilon = Constants::EPSILON): bool {
return ($this->mult === $other or ($this->mult !== null and $this->mult->equals($other->mult))) and ($this->add === $other or ($this->add !== null and $this->add->equals($other->add)));
}
public static function fromXML(\DOMElement $element): ColorTransform {
$add = new Color(null, null, null, null);
$mult = new Color(null, null, null, null);
$add = new Color(0, 0, 0, 0);
$mult = new Color(256, 256, 256, 256);
if ($element->hasAttribute("factorRed")) {
$mult->r = (int)$element->getAttribute("factorRed");
$mult->g = (int)$element->getAttribute("factorGreen");

8
src/Constants.php Normal file
View file

@ -0,0 +1,8 @@
<?php
namespace swf2ass;
abstract class Constants {
const TWIP_SIZE = 20;
const EPSILON = 0.000001;
}

View file

@ -1,46 +0,0 @@
<?php
namespace swf2ass;
class Coordinate {
public $x;
public $y;
public function __construct($x, $y) {
$this->x = $x;
$this->y = $y;
}
public function equals(Coordinate $b): bool {
return $b->x === $this->x and $b->y === $this->y;
}
public function distance(Coordinate $b): float {
return sqrt(pow($this->x - $b->x, 2) + pow($this->y - $b->y, 2));
}
public function applyTransform(MatrixTransform $transform): Coordinate {
return new Coordinate($this->x * ($transform->scaleX ?? 1) + $this->y * ($transform->skewY ?? 0) + ($transform->transX ?? 0), $this->y * ($transform->scaleY ?? 1) + $this->x * ($transform->skewX ?? 0) + ($transform->transY ?? 0));
}
public function add(Coordinate $b): Coordinate {
return new Coordinate($this->x + $b->x, $this->y + $b->y);
}
public function sub(Coordinate $b): Coordinate {
return new Coordinate($this->x - $b->x, $this->y - $b->y);
}
public function multiply($size): Coordinate {
return new Coordinate($this->x * $size, $this->y * $size);
}
public function divide($size): Coordinate {
return new Coordinate($this->x / $size, $this->y / $size);
}
public function toPixel($twipSize = TWIP_SIZE): Coordinate {
return $this->divide($twipSize);
}
}

View file

@ -3,19 +3,19 @@
namespace swf2ass;
class CubicCurveRecord implements Record {
public Coordinate $start;
public Coordinate $control1;
public Coordinate $control2;
public Coordinate $anchor;
public Vector2 $start;
public Vector2 $control1;
public Vector2 $control2;
public Vector2 $anchor;
public function __construct(Coordinate $control1, Coordinate $control2, Coordinate $anchor, Coordinate $start) {
public function __construct(Vector2 $control1, Vector2 $control2, Vector2 $anchor, Vector2 $start) {
$this->control1 = $control1;
$this->control2 = $control2;
$this->anchor = $anchor;
$this->start = $start;
}
public function getStart(): Coordinate {
public function getStart(): Vector2 {
return $this->start;
}
@ -23,12 +23,11 @@ class CubicCurveRecord implements Record {
return new CubicCurveRecord($this->control2, $this->control1, $this->start, $this->anchor);
}
public function applyMatrixTransform(MatrixTransform $transform): CubicCurveRecord {
return new CubicCurveRecord($transform->applyToVector($this->control1), $transform->applyToVector($this->control2), $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
}
public static function fromQuadraticRecord(QuadraticCurveRecord $q): CubicCurveRecord {
return new CubicCurveRecord(
$q->start->add($q->control->multiply(2))->divide(3),
$q->anchor->add($q->control->multiply(2))->divide(3),
$q->anchor,
$q->start
);
return new CubicCurveRecord($q->start->add($q->control->multiply(2))->divide(3), $q->anchor->add($q->control->multiply(2))->divide(3), $q->anchor, $q->start);
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace swf2ass;
class CubicSplineCurveRecord implements Record {
public Vector2 $start;
/** @var Vector2[] */
public array $control;
public Vector2 $anchor;
/**
* @param Vector2[] $control
* @param Vector2 $anchor
* @param Vector2 $start
*/
public function __construct(array $control, Vector2 $anchor, Vector2 $start) {
$this->control = $control;
$this->anchor = $anchor;
$this->start = $start;
}
public function getStart(): Vector2 {
return $this->start;
}
public function reverse(): CubicSplineCurveRecord {
return new CubicSplineCurveRecord(array_reverse($this->control), $this->start, $this->anchor);
}
public function applyMatrixTransform(MatrixTransform $transform): CubicSplineCurveRecord {
$control = [];
foreach ($this->control as $c) {
$control[] = $transform->applyToVector($c);
}
return new CubicSplineCurveRecord($control, $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
}
public function append(Record $record): ?CubicSplineCurveRecord {
if ($record instanceof CubicCurveRecord) {
}
return null;
}
}

View file

@ -24,7 +24,7 @@ class CurrentState {
$this->frameRate = 60;
$this->frameCount = 100;
$this->frameNumber = 0;
$this->viewPort = new Rectangle(new Coordinate(0, 0), new Coordinate(480, 360));
$this->viewPort = new Rectangle(new Vector2(0, 0), new Vector2(480, 360));
$this->objects = [];
$this->displayList = [];
$this->clipList = [];

View file

@ -1,166 +0,0 @@
<?php
namespace swf2ass;
class DisplayEntry {
public $object;
public $currentColorTransform = null;
public $currentTransform = null;
/** @var FrameInformation[] */
public $frames = [];
public $startFrame;
public $depth;
public $clipDepth = 0;
public function getLines(CurrentState $currentState) {
if ($this->clipDepth > 0) {
return [];
}
if ($this->depth != 2) {
//return [];
}
if($currentState->frameOffset === null){
return [];
}
$dialogue = [];
$startFrame = $this->startFrame - $currentState->frameOffset;
$frameList = [];
foreach ($this->frames as $frame => $info){
$fn = $frame - $currentState->frameOffset;
if($fn < 0){
continue;
}
$frameList[$fn] = clone $info;
$frameList[$fn]->frame = $fn;
}
if(count($frameList) === 0){
return [];
}
$startTime = $startFrame * (1 / $currentState->frameRate);
$endTime = ($startFrame + 1) * (1 / $currentState->frameRate);
if ($this->object instanceof ObjectDefinition) {
$shapeDef = $this->object;
foreach ($shapeDef->getShapeList()->commands as $path){
$currentFrameStyles = [new StyleContainer()];
if($path->style instanceof LineStyleRecord){ //Stroke
$currentFrameStyles[0]->original_color3 = $currentFrameStyles[0]->color3 = $path->style->color;
$currentFrameStyles[0]->bord = round($path->style->width / TWIP_SIZE, 1);
}else if($path->style instanceof FillStyleRecord){ //Fill
$currentFrameStyles[0]->original_color1 = $currentFrameStyles[0]->color1 = $path->style->fill instanceof Gradient ? $path->style->fill->getItems()[0]->color : $path->style->fill; //TODO: gradient?
}
$currentFrameStyles[0]->shad = 0;
$assLine = "{\\an7\\shad0\\p1}";
foreach ($path->commands->edges as $edge) {
if ($edge instanceof MoveRecord) {
$coords = $edge->coord->toPixel();
$assLine .= "m " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
} else if ($edge instanceof LineRecord) {
$coords = $edge->coord->toPixel();
$assLine .= "l " . round($coords->x, 2) . " " . round($coords->y, 2) . " ";
} else if ($edge instanceof QuadraticCurveRecord) {
$edge = CubicCurveRecord::fromQuadraticRecord($edge);
$control1 = $edge->control1->toPixel();
$control2 = $edge->control2->toPixel();
$anchor = $edge->anchor->toPixel();
$assLine .= "b " . round($control1->x, 2) . " " . round($control1->y, 2) . " " . round($control2->x, 2) . " " . round($control2->y, 2) . " " . round($anchor->x, 2) . " " . round($anchor->y, 2) . " ";
} else if ($edge instanceof CubicCurveRecord) {
$control1 = $edge->control1->toPixel();
$control2 = $edge->control2->toPixel();
$anchor = $edge->anchor->toPixel();
$assLine .= "b " . round($control1->x, 2) . " " . round($control1->y, 2) . " " . round($control2->x, 2) . " " . round($control2->y, 2) . " " . round($anchor->x, 2) . " " . round($anchor->y, 2) . " ";
}
}
$assLine .= "{\\p0}";
$lastLineStart = $startTime;
$lastFrameStart = $startFrame;
$debugInfo = "oid:".$shapeDef->getId();
$savedFrameStyles = $currentFrameStyles;
$af = 0;
$frames = 0;
foreach ($frameList as $frame => $frameInformation) {
$currentTime = $frame * (1 / $currentState->frameRate);
$endCurrentTime = $currentTime + (1 / $currentState->frameRate);
if ($endCurrentTime > $endTime) {
$endTime = $endCurrentTime;
}
$lastStyle = end($currentFrameStyles);
$frameStyle = clone $lastStyle;
$frameStyle->resetTransformedStyles();
if ($frameInformation->transform instanceof MatrixTransform) {
$frameInformation->transform->applyToStyleContainer($frameStyle);
}
if ($frameInformation->colorTransform instanceof ColorTransform) {
$frameInformation->colorTransform->applyToStyleContainer($frameStyle);
}
$lastSavedStyle = end($savedFrameStyles);
$savedStyle = clone $lastSavedStyle;
$savedStyle->resetTransformedStyles();
if ($frameInformation->transform instanceof MatrixTransform) {
$frameInformation->transform->applyToStyleContainer($savedStyle);
}
if ($frameInformation->colorTransform instanceof ColorTransform) {
$frameInformation->colorTransform->applyToStyleContainer($savedStyle);
}
if ($frames === 0) {
$currentFrameStyles[array_key_last($currentFrameStyles)] = $frameStyle;
} else if (!$lastStyle->isEqualish($frameStyle)) {
if (ADVANCED_MOTION_INTERPOLATION) {
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, max(0, $frame - 1), $frame);
} else {
$transition = $lastStyle->doTransitionTo($frameStyle, $frame, $frame, $frame, $frame);
}
if ($transition === false) {
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($currentTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
foreach ($currentFrameStyles as $i => $style) {
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
}
$dialogue[] = $assHeader . $assLine;
$lastLineStart = $currentTime;
$lastFrameStart = $frame;
$currentFrameStyles = [$savedStyle];
} else if ($transition instanceof StyleContainer) {
$currentFrameStyles[] = $transition;
$savedFrameStyles[] = $savedStyle;
}
}
++$frames;
}
$assHeader = "Dialogue: {$this->depth}," . Utils::timeToStamp($lastLineStart) . "," . Utils::timeToStamp($endTime) . ",f,$debugInfo,0,0,0,af:" . ($af++) . ",";
foreach ($currentFrameStyles as $i => $style) {
$assHeader .= "{" . $style->toString($lastFrameStart, (1 / $currentState->frameRate), $currentFrameStyles[$i - 1] ?? null, $i === 0) . "}";
}
$dialogue[] = $assHeader . $assLine;
}
}
return $dialogue;
}
}

View file

@ -7,7 +7,7 @@ class FillStyleRecord implements StyleRecord {
/** @var Gradient|Color */
public $fill;
public function __construct($fill){
public function __construct($fill) {
$this->fill = $fill;
}
}

View file

@ -3,11 +3,62 @@
namespace swf2ass;
class FrameInformation {
public int $frame;
private int $frameNumber;
private int $frameOffset = 0;
/** @var ?ColorTransform */
public ?ColorTransform $colorTransform = null;
private float $frameRate;
/** @var ?MatrixTransform */
public ?MatrixTransform $transform = null;
private ?ViewFrame $frame;
public function __construct(int $frameNumber, float $frameRate, ?ViewFrame $frame) {
$this->frameNumber = $frameNumber;
$this->frameRate = $frameRate;
$this->frame = $frame;
}
public function setFrameOffset(int $frameOffset) {
$this->frameOffset = $frameOffset;
}
public function getFrameOffset(): int {
return $this->frameOffset;
}
public function getFrame(): ?ViewFrame {
return $this->frame;
}
public function getFrameNumber(): int {
return $this->frameNumber - $this->frameOffset;
}
public function getStartTimeSeconds(): float {
return $this->getFrameNumber() / $this->frameRate;
}
public function getStartTimeMilliSeconds(): int {
return $this->getStartTimeSeconds() * 1000;
}
public function getEndTimeSeconds(): float {
return $this->getStartTimeSeconds() + $this->getFrameDurationSeconds();
}
public function getEndTimeMilliSeconds(): int {
return $this->getEndTimeSeconds() * 1000;
}
public function getFrameDurationSeconds(): float {
return 1 / $this->frameRate;
}
public function getFrameDurationMilliSeconds(): int {
return $this->getFrameDurationSeconds() * 1000;
}
public function diff(FrameInformation $other): FrameInformation {
return new FrameInformation($this->getFrameNumber() - $other->getFrameNumber(), $this->frameRate, null);
}
}

View file

@ -7,6 +7,7 @@ interface Gradient {
/**
* @return GradientItem[]
*/
public function getItems() : array;
public function getMatrixTransform() : ?MatrixTransform;
public function getItems(): array;
public function getMatrixTransform(): ?MatrixTransform;
}

View file

@ -31,7 +31,7 @@ class JPEGBitmapDefinition extends BitmapDefinition {
$im->transformImageColorspace(\Imagick::COLORSPACE_SRGB);
$resolution = $im->getImageGeometry();
$size = new Coordinate($resolution["width"], $resolution["height"]);
$size = new Vector2($resolution["width"], $resolution["height"]);
$pixels = array_fill(0, $size->y, array_fill(0, $size->x, new Color(0, 0, 0)));

View file

@ -3,15 +3,15 @@
namespace swf2ass;
class LineRecord implements Record {
public Coordinate $start;
public Coordinate $coord;
public Vector2 $start;
public Vector2 $coord;
public function __construct(Coordinate $coord, Coordinate $start) {
public function __construct(Vector2 $coord, Vector2 $start) {
$this->coord = $coord;
$this->start = $start;
}
public function getStart(): Coordinate {
public function getStart(): Vector2 {
return $this->start;
}
@ -19,7 +19,11 @@ class LineRecord implements Record {
return new LineRecord($this->start, $this->coord);
}
public static function fromXML(\DOMElement $element, Coordinate $cursor): LineRecord {
return new LineRecord($cursor->add(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
public static function fromXML(\DOMElement $element, Vector2 $cursor): LineRecord {
return new LineRecord($cursor->add(new Vector2((int)$element->getAttribute("x"), (int)$element->getAttribute("y"))), $cursor);
}
public function applyMatrixTransform(MatrixTransform $transform): LineRecord {
return new LineRecord($transform->applyToVector($this->coord), $transform->applyToVector($this->start));
}
}

View file

@ -3,7 +3,7 @@
namespace swf2ass;
class LineStyleRecord implements StyleRecord {
class LineStyleRecord implements StyleRecord {
public int $width;
public Color $color;
@ -12,7 +12,7 @@ class LineStyleRecord implements StyleRecord {
//TODO: join/cap/etc style
public bool $allow_close = true;
public function __construct(int $width, Color $color){
public function __construct(int $width, Color $color) {
$this->width = $width;
$this->color = $color;

View file

@ -12,10 +12,12 @@ class LinearGradient implements Gradient {
$this->colors = $colors;
$this->transform = $transform;
}
public function getItems() : array {
public function getItems(): array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
public function getMatrixTransform(): ?MatrixTransform {
return $this->transform;
}

203
src/Matrix2D.php Normal file
View file

@ -0,0 +1,203 @@
<?php
namespace swf2ass;
class Matrix2D {
/** @var \SplFixedArray<numeric> */
private \SplFixedArray $data;
private Vector2 $size;
/**
* @param Vector2 $size
* @param \SplFixedArray<numeric> $values
* @throws \Exception
*/
public function __construct(Vector2 $size, ?\SplFixedArray $initialize = null) {
$this->size = $size;
$this->data = $initialize !== null ? $initialize : \SplFixedArray::fromArray(array_fill(0, $size->x * $size->y, 0));
if ($this->data->getSize() !== $size->x * $size->y) {
throw new \InvalidArgumentException("Wrong sized Matrix initialization");
}
}
public static function from2DArray(array $data): Matrix2D {
$size = new Vector2(count($data[0]), count($data));
$newData = new \SplFixedArray($size->x * $size->y);
foreach ($data as $y => $row) {
foreach ($row as $x => $value) {
$i = $size->x * $y + $x;
$newData->offsetSet($i, $value);
}
}
return new Matrix2D($size, $newData);
}
/**
* @param Vector2 $position
* @param int|float $value
* @throws \Exception
*/
public function set(Vector2 $position, $value) {
$i = $this->size->x * $position->y + $position->x;
if (!$this->data->offsetExists($i)) {
throw new \OutOfRangeException("Index [{$position->x}, {$position->y}] out of range");
}
$this->data->offsetSet($i, $value);
}
/**
* @param Vector2 $position
* @return float|int
*/
public function get(Vector2 $position) {
$i = $this->size->x * $position->y + $position->x;
if (!$this->data->offsetExists($i)) {
throw new \OutOfRangeException("Index [{$position->x}, {$position->y}] out of range");
}
return $this->data->offsetGet($i);
}
private function internalGet($x, $y) {
return $this->data->offsetGet($this->size->x * $y + $x);
}
private function internalSet($x, $y, $value) {
$this->data->offsetSet($this->size->x * $y + $x, $value);
}
public function isSquare(): bool {
return $this->size->equals($this->size->invert());
}
public function getSize(): Vector2 {
return $this->size;
}
public function __clone() {
$this->data = clone $this->data;
}
public function add(Matrix2D $matrix): Matrix2D {
if (!$this->size->equals($matrix->size)) {
throw new \LogicException("Matrix sizes not equal");
}
$result = clone $this;
foreach ($result->data as $i => $value) {
$result->data->offsetSet($i, $value + $matrix->data->offsetGet($i));
}
return $result;
}
public function substract(Matrix2D $matrix): Matrix2D {
if (!$this->size->equals($matrix->size)) {
throw new \LogicException("Matrix sizes not equal");
}
$result = clone $this;
foreach ($result->data as $i => $value) {
$result->data->offsetSet($i, $value - $matrix->data->offsetGet($i));
}
return $result;
}
public function transpose(): Matrix2D {
$result = new Matrix2D($this->size->invert());
foreach ($result->data as $i => $value) {
$x = $i % $this->size->x;
$y = intdiv($i, $this->size->x);
$result->internalSet($y, $x, $value);
}
return $result;
}
private function isMatrixTransform(): bool {
return $this->size->x === 3 and $this->size->y === 3 and $this->internalGet(0, 2) === 0 and $this->internalGet(1, 2) === 0 and $this->internalGet(2, 2) === 1;
}
private function isVector2(): bool {
return $this->size->x === 3 and $this->size->y === 1 and $this->internalGet(1, 0) === 1;
}
/**
* Naive Matrix product, O(n^3), with some special cases optimizations
*
* @param Matrix2D $matrix
* @return Matrix2D
* @throws \Exception
*/
public function product(Matrix2D $matrix): Matrix2D {
if ($this->size->x !== $matrix->size->y) {
throw new \LogicException("Matrix A columns != B rows");
}
if ($matrix->isMatrixTransform()) {
// http://www.senocular.com/flash/tutorials/transformmatrix/
if ($this->isMatrixTransform()) {
$a1 = $this->internalGet(0, 0);
$b1 = $this->internalGet(1, 0);
$c1 = $this->internalGet(0, 1);
$d1 = $this->internalGet(1, 1);
$tx1 = $this->internalGet(0, 2);
$ty1 = $this->internalGet(1, 2);
$a2 = $matrix->internalGet(0, 0);
$b2 = $matrix->internalGet(1, 0);
$c2 = $matrix->internalGet(0, 1);
$d2 = $matrix->internalGet(1, 1);
$tx2 = $matrix->internalGet(0, 2);
$ty2 = $matrix->internalGet(1, 2);
return Matrix2D::from2DArray([[$a1 * $a2 + $b1 * $c2, $a1 * $b2 + $b1 * $d2, 0], [$c1 * $a2 + $d1 * $c2, $c1 + $b2 + $d1 * $d2, 0], [$tx1 * $a2 + $ty1 * $c2 + $tx2, $tx1 * $b2 + $ty1 * $d2 + $ty2, 1]]);
} elseif ($this->isVector2()) {
$x = $this->internalGet(0, 0);
$y = $this->internalGet(1, 0);
$a = $matrix->internalGet(0, 0);
$b = $matrix->internalGet(1, 0);
$c = $matrix->internalGet(0, 1);
$d = $matrix->internalGet(1, 1);
$tx = $matrix->internalGet(0, 2);
$ty = $matrix->internalGet(1, 2);
return Matrix2D::from2DArray([[$x * $a + $y * $c + $tx, $x * $b + $y * $d + $ty, 1]]);
}
}
//Fallback for all other types
$result = new Matrix2D(new Vector2($this->size->x, $matrix->size->y));
for ($i = 0; $i < $result->size->x; ++$i) {
for ($j = 0; $j < $result->size->y; ++$j) {
$sum = 0;
for ($k = 0; $k < $this->size->y; ++$k) {
$sum += $this->internalGet($i, $k) * $matrix->internalGet($k, $j);
}
$result->internalSet($i, $j, $sum);
}
}
return $result;
}
public function equals(Matrix2D $other, $epsilon = Constants::EPSILON): bool {
if (!$this->size->equals($other->size)) {
return false;
}
foreach ($this->data as $i => $value) {
if (abs($value - $other->data->offsetGet($i)) > $epsilon) {
return false;
}
}
return true;
}
}

View file

@ -4,53 +4,124 @@ namespace swf2ass;
class MatrixTransform {
public $scaleX;
public $scaleY;
public $skewX;
public $skewY;
public $transX;
public $transY;
private const IDENTITY = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
public function __construct($scaleX = 1, $scaleY = 1, $skewX = 0, $skewY = 0, $transX = 0, $transY = 0) {
$this->scaleX = $scaleX;
$this->scaleY = $scaleY;
$this->skewX = $skewX;
$this->skewY = $skewY;
$this->transX = $transX;
$this->transY = $transY;
private Matrix2D $matrix;
public function __construct(?Vector2 $scale, ?Vector2 $rotateSkew, ?Vector2 $translation) {
$matrix = self::IDENTITY;
if ($scale !== null) {
$matrix[0][0] = $scale->x;
$matrix[1][1] = $scale->y;
}
if ($rotateSkew !== null) {
$matrix[1][0] = $rotateSkew->x;
$matrix[0][1] = $rotateSkew->y;
}
if ($translation !== null) {
$matrix[2][0] = $translation->x;
$matrix[2][1] = $translation->y;
}
$this->matrix = Matrix2D::from2DArray($matrix);
}
public static function identity(): MatrixTransform {
return new MatrixTransform(new Vector2(1, 1), new Vector2(0, 0), new Vector2(0, 0));
}
public function combine(MatrixTransform $other): MatrixTransform {
$result = clone $this;
$result->matrix = $this->matrix->product($other->matrix);
return $result;
}
public function getScaleX() {
return $this->matrix->get(new Vector2(0, 0));
}
public function getScaleY() {
return $this->matrix->get(new Vector2(1, 1));
}
public function getScale(): Vector2 {
return new Vector2($this->getScaleX(), $this->getScaleY());
}
public function getRotateSkewX() {
return $this->matrix->get(new Vector2(0, 1));
}
public function getRotateSkewY() {
return $this->matrix->get(new Vector2(1, 0));
}
public function getRotateSkew(): Vector2 {
return new Vector2($this->getRotateSkewX(), $this->getRotateSkewY());
}
public function getTranslationX() {
return $this->matrix->get(new Vector2(0, 2));
}
public function getTranslationY() {
return $this->matrix->get(new Vector2(1, 2));
}
public function getTranslation(): Vector2 {
return new Vector2($this->getTranslationX(), $this->getTranslationY());
}
public function applyToVector(Vector2 $vector): Vector2 {
$result = Matrix2D::from2DArray([[$vector->x, $vector->y, 1]])->product($this->matrix);
return new Vector2($result->get(new Vector2(0, 0)), $result->get(new Vector2(1, 0)));
/*
return
$vector->vectorMultiply($this->getScale()) //scale
->add($vector->vectorMultiply($this->getRotateSkew())->invert()) //skew
->add($this->getTranslation()); //translate
*/
}
public function applyToShape(Shape $shape): Shape {
$newShape = new Shape();
foreach ($shape->edges as $edge) {
$newShape->edges[] = $edge->applyMatrixTransform($this);
}
return $newShape;
}
public function applyToStyleContainer(StyleContainer $styles): StyleContainer {
if ($this->scaleX !== null) {
if ($this->scaleX < 0) {
if ($this->scale !== null) {
if ($this->scale->x < 0) {
$styles->fry = 180;
}
$styles->fscx = round(abs($this->scaleX) * 100, 2);
$styles->fscx = round(abs($this->scale->x) * 100, 2);
if ($this->scale->y < 0) {
$styles->frx = 180;
}
$styles->fscy = round(abs($this->scale->y) * 100, 2);
} else {
$styles->fry = null;
$styles->fscx = null;
}
if ($this->scaleY !== null) {
if ($this->scaleY < 0) {
$styles->frx = 180;
}
$styles->fscy = round(abs($this->scaleY) * 100, 2);
} else {
$styles->frx = null;
$styles->fscy = null;
}
if ($this->skewX !== null) {
$styles->fax = round($this->skewX, 4);
if ($this->skew !== null) {
$styles->fax = round($this->skew->x, 4);
$styles->fay = round($this->skew->y, 4);
} else {
$styles->fax = null;
}
if ($this->skewY !== null) {
$styles->fay = round($this->skewY, 4);
} else {
$styles->fay = null;
}
if ($this->transX !== null) {
$styles->pos = new Coordinate((int)$this->transX, (int)$this->transY);
if ($this->translation !== null) {
$styles->pos = ($styles->pos ?? new Vector2(0, 0))->add($this->translation);
} else {
$styles->pos = null;
}
@ -58,8 +129,12 @@ class MatrixTransform {
return $styles;
}
public function equals(MatrixTransform $other, $epsilon = Constants::EPSILON): bool {
return $this->matrix->equals($other->matrix, $epsilon);
}
static function fromXML(\DOMElement $element): MatrixTransform {
return new MatrixTransform($element->hasAttribute("scaleX") ? $element->getAttribute("scaleX") : null, $element->hasAttribute("scaleY") ? $element->getAttribute("scaleY") : null, $element->hasAttribute("skewX") ? $element->getAttribute("skewX") : null, $element->hasAttribute("skewY") ? $element->getAttribute("skewY") : null, $element->hasAttribute("transX") ? $element->getAttribute("transX") : null, $element->hasAttribute("transY") ? $element->getAttribute("transY") : null);
return new MatrixTransform($element->hasAttribute("scaleX") ? new Vector2(floatval($element->getAttribute("scaleX")), floatval($element->getAttribute("scaleY"))) : null, $element->hasAttribute("skewX") ? new Vector2(floatval($element->getAttribute("skewX")), floatval($element->getAttribute("skewY"))) : null, $element->hasAttribute("transX") ? new Vector2(intval($element->getAttribute("transX")), intval($element->getAttribute("transY"))) : null,);
}
}

View file

@ -3,15 +3,15 @@
namespace swf2ass;
class MoveRecord implements Record {
public Coordinate $start;
public Coordinate $coord;
public Vector2 $start;
public Vector2 $coord;
public function __construct(Coordinate $coord, Coordinate $start) {
public function __construct(Vector2 $coord, Vector2 $start) {
$this->coord = $coord;
$this->start = $start;
}
public function getStart(): Coordinate {
public function getStart(): Vector2 {
return $this->start;
}
@ -19,7 +19,11 @@ class MoveRecord implements Record {
return new MoveRecord($this->start, $this->coord);
}
public static function fromXML(\DOMElement $element, Coordinate $cursor): MoveRecord {
return new MoveRecord(new Coordinate((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
public static function fromXML(\DOMElement $element, Vector2 $cursor): MoveRecord {
return new MoveRecord(new Vector2((int)$element->getAttribute("x"), (int)$element->getAttribute("y")), $cursor);
}
public function applyMatrixTransform(MatrixTransform $transform): MoveRecord {
return new MoveRecord($transform->applyToVector($this->coord), $transform->applyToVector($this->start));
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace swf2ass;
interface MultiFrameObjectDefinition extends ObjectDefinition {
public function nextFrame(): ViewFrame;
public function hasFrame(): bool;
}

View file

@ -4,6 +4,7 @@ namespace swf2ass;
interface ObjectDefinition {
public function getId();
public function getShapeList() : DrawPathList;
public function getObjectId(): int;
public function getShapeList(): DrawPathList;
}

View file

@ -2,15 +2,12 @@
namespace swf2ass;
use mysql_xdevapi\Exception;
class PathSegment {
/** @var VisitedPoint[] */
public array $points;
public function __construct(Coordinate $start) {
public function __construct(Vector2 $start) {
$this->points = [new VisitedPoint($start, false)];
}
@ -18,15 +15,15 @@ class PathSegment {
$this->points = array_reverse($this->points, false);
}
public function add_point(VisitedPoint $point){
public function add_point(VisitedPoint $point) {
$this->points[] = $point;
}
public function start(): Coordinate {
public function start(): Vector2 {
return reset($this->points)->pos;
}
public function end(): Coordinate {
public function end(): Vector2 {
return end($this->points)->pos;
}
@ -38,27 +35,27 @@ class PathSegment {
return $this->start()->equals($this->end());
}
public function swap(PathSegment $other){
public function swap(PathSegment $other) {
[$this->points, $other->points] = [$other->points, $this->points];
}
public function merge(PathSegment $other){
public function merge(PathSegment $other) {
$this->points = array_merge($this->points, array_slice($other->points, 1));
}
public function try_merge(PathSegment $other, bool $directed) : bool{
if($other->end()->equals($this->start())){
public function try_merge(PathSegment $other, bool $directed): bool {
if ($other->end()->equals($this->start())) {
$this->swap($other);
$this->merge($other);
return true;
}else if($this->end()->equals($other->start())){
} else if ($this->end()->equals($other->start())) {
$this->merge($other);
return true;
}else if(!$directed and $this->end()->equals($other->end())){
} else if (!$directed and $this->end()->equals($other->end())) {
$other->flip();
$this->merge($other);
return true;
}else if(!$directed and $this->start()->equals($other->start())){
} else if (!$directed and $this->start()->equals($other->start())) {
$other->flip();
$this->swap($other);
$this->merge($other);
@ -68,25 +65,25 @@ class PathSegment {
return false;
}
public function getShape() : Shape {
if($this->is_empty()){
throw new Exception();
public function getShape(): Shape {
if ($this->is_empty()) {
throw new \Exception();
}
$shape = new Shape();
$first = reset($this->points);
$pos = new Coordinate(0, 0);
$pos = new Vector2(0, 0);
$shape->edges[] = new MoveRecord($first->pos, $pos);
$pos = $first->pos;
while (($point = next($this->points)) !== false){
if(!$point->is_bezier_control){
while (($point = next($this->points)) !== false) {
if (!$point->is_bezier_control) {
$shape->edges[] = new LineRecord($point->pos, $pos);
$pos = $point->pos;
}else{
} else {
$end = next($this->points);
if($end === false){
if ($end === false) {
throw new \Exception("Bezier without endpoint");
}

View file

@ -5,7 +5,7 @@ namespace swf2ass;
class PendingPath {
/** @var PathSegment[] */
/** @var PathSegment[] */
public array $segments = [];
@ -17,28 +17,28 @@ class PendingPath {
* @param PathSegment $new_segment
* @param bool $directed
*/
public function merge_path(PathSegment $new_segment, bool $directed){
if(!$new_segment->is_empty()){
public function merge_path(PathSegment $new_segment, bool $directed) {
if (!$new_segment->is_empty()) {
$merged = null;
foreach ($this->segments as $i => $segment){
if($segment->try_merge($new_segment, $directed)){
foreach ($this->segments as $i => $segment) {
if ($segment->try_merge($new_segment, $directed)) {
unset($this->segments[$i]);
$merged = $segment;
break;
}
}
if($merged === null){
if ($merged === null) {
$this->segments[] = $new_segment;
}else{
} else {
$this->merge_path($merged, $directed);
}
}
}
public function getShape() : Shape {
public function getShape(): Shape {
$shape = new Shape();
foreach ($this->segments as $segment){
foreach ($this->segments as $segment) {
$shape->edges = array_merge($shape->edges, $segment->getShape()->edges);
}

View file

@ -12,8 +12,8 @@ class PendingPathMap {
}
public function merge_path(ActivePath $path, bool $directed){
if(!isset($this->map[$path->style])){
public function merge_path(ActivePath $path, bool $directed) {
if (!isset($this->map[$path->style])) {
$this->map[$path->style] = new PendingPath();
}

View file

@ -3,17 +3,17 @@
namespace swf2ass;
class QuadraticCurveRecord implements Record {
public Coordinate $start;
public Coordinate $control;
public Coordinate $anchor;
public Vector2 $start;
public Vector2 $control;
public Vector2 $anchor;
public function __construct(Coordinate $control, Coordinate $anchor, Coordinate $start) {
public function __construct(Vector2 $control, Vector2 $anchor, Vector2 $start) {
$this->control = $control;
$this->anchor = $anchor;
$this->start = $start;
}
public function getStart(): Coordinate {
public function getStart(): Vector2 {
return $this->start;
}
@ -21,9 +21,13 @@ class QuadraticCurveRecord implements Record {
return new QuadraticCurveRecord($this->control, $this->start, $this->anchor);
}
public static function fromXML(\DOMElement $element, Coordinate $cursor): QuadraticCurveRecord {
$control = $cursor->add(new Coordinate((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
$anchor = $control->add(new Coordinate((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
public function applyMatrixTransform(MatrixTransform $transform): QuadraticCurveRecord {
return new QuadraticCurveRecord($transform->applyToVector($this->control), $transform->applyToVector($this->anchor), $transform->applyToVector($this->start));
}
public static function fromXML(\DOMElement $element, Vector2 $cursor): QuadraticCurveRecord {
$control = $cursor->add(new Vector2((int)$element->getAttribute("x1"), (int)$element->getAttribute("y1")));
$anchor = $control->add(new Vector2((int)$element->getAttribute("x2"), (int)$element->getAttribute("y2")));
return new QuadraticCurveRecord($control, $anchor, $cursor);
}

View file

@ -12,10 +12,12 @@ class RadialGradient implements Gradient {
$this->colors = $colors;
$this->transform = $transform;
}
public function getItems() : array {
public function getItems(): array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
public function getMatrixTransform(): ?MatrixTransform {
return $this->transform;
}

View file

@ -3,7 +3,9 @@
namespace swf2ass;
interface Record {
public function getStart(): Coordinate;
public function getStart(): Vector2;
public function reverse(): Record;
public function applyMatrixTransform(MatrixTransform $transform): Record;
}

View file

@ -6,15 +6,15 @@ namespace swf2ass;
class Rectangle implements ComplexShape {
public Coordinate $topLeft;
public Coordinate $bottomRight;
public Vector2 $topLeft;
public Vector2 $bottomRight;
public function __construct(Coordinate $topLeft, Coordinate $bottomRight) {
public function __construct(Vector2 $topLeft, Vector2 $bottomRight) {
$this->topLeft = $topLeft;
$this->bottomRight = $bottomRight;
}
public function inBounds(Coordinate $pos): bool {
public function inBounds(Vector2 $pos): bool {
return $pos->x >= $this->topLeft->x and $pos->y >= $this->topLeft->y and $pos->x <= $this->bottomRight->x and $pos->y <= $this->bottomRight->y;
}
@ -31,27 +31,28 @@ class Rectangle implements ComplexShape {
}
public function draw(): array {
return [new LineRecord(new Coordinate($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Coordinate($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Coordinate($this->bottomRight->x, $this->topLeft->y), $this->bottomRight), new LineRecord($this->topLeft, new Coordinate($this->bottomRight->x, $this->topLeft->y)),];
return [new LineRecord(new Vector2($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Vector2($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Vector2($this->bottomRight->x, $this->topLeft->y), $this->bottomRight), new LineRecord($this->topLeft, new Vector2($this->bottomRight->x, $this->topLeft->y)),];
}
public function drawOpen(): array {
return [new LineRecord(new Coordinate($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Coordinate($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Coordinate($this->bottomRight->x, $this->topLeft->y), $this->bottomRight),];
return [new LineRecord(new Vector2($this->topLeft->x, $this->bottomRight->y), $this->topLeft), new LineRecord($this->bottomRight, new Vector2($this->topLeft->x, $this->bottomRight->y)), new LineRecord(new Vector2($this->bottomRight->x, $this->topLeft->y), $this->bottomRight),];
}
public function divide($size): Rectangle {
return new Rectangle($this->topLeft->divide($size), $this->bottomRight->divide($size));
}
public function multiply($size): Rectangle {
return new Rectangle($this->topLeft->multiply($size), $this->bottomRight->multiply($size));
}
public function toPixel($twipSize = TWIP_SIZE): Rectangle {
public function toPixel($twipSize = Constants::TWIP_SIZE): Rectangle {
return $this->divide($twipSize);
}
public static function fromXML(\DOMElement $element): Rectangle {
return new Rectangle(new Coordinate($element->getAttribute("left"), $element->getAttribute("top")), new Coordinate($element->getAttribute("right"), $element->getAttribute("bottom")));
return new Rectangle(new Vector2((int)$element->getAttribute("left"), (int)$element->getAttribute("top")), new Vector2((int)$element->getAttribute("right"), (int)$element->getAttribute("bottom")));
}
public static function fromData($bitdata, &$offset): Rectangle {
@ -71,6 +72,6 @@ class Rectangle implements ComplexShape {
$yMax = Utils::binary2dec(substr($bitdata, $offset, $nbits));
$offset += $nbits;
return new Rectangle(new Coordinate($xMin, $yMin), new Coordinate($xMax, $yMax));
return new Rectangle(new Vector2($xMin, $yMin), new Vector2($xMax, $yMax));
}
}

24
src/RenderedFrame.php Normal file
View file

@ -0,0 +1,24 @@
<?php
namespace swf2ass;
class RenderedFrame {
/** @var RenderedObject[] */
private array $list = [];
public function __construct() {
}
public function add(RenderedObject $ob) {
$this->list[] = $ob;
}
/**
* @return RenderedObject[]
*/
public function getObjects(): array {
return $this->list;
}
}

23
src/RenderedObject.php Normal file
View file

@ -0,0 +1,23 @@
<?php
namespace swf2ass;
class RenderedObject {
/** @var int[] */
public array $depth;
public int $objectId;
public DrawPathList $drawPathList;
public ?Shape $clip;
public ColorTransform $colorTransform;
public MatrixTransform $matrixTransform;
public function __construct(array $depth, int $objectId, DrawPathList $drawPathList, ColorTransform $colorTransform, MatrixTransform $matrixTransform, ?Shape $clip = null) {
$this->depth = $depth;
$this->objectId = $objectId;
$this->drawPathList = $drawPathList;
$this->colorTransform = $colorTransform;
$this->matrixTransform = $matrixTransform;
$this->clip = $clip;
}
}

91
src/SWFProcessor.php Normal file
View file

@ -0,0 +1,91 @@
<?php
namespace swf2ass;
class SWFProcessor extends SWFTreeProcessor {
const BACKGROUND_OBJECT_ID = 0;
const BACKGROUND_OBJECT_DEPTH = 0;
private FillStyleRecord $background;
private float $frameRate;
private $audio = null;
private Rectangle $viewPort;
public function __construct(\DOMElement $root) {
parent::__construct(0, $root);
$this->background = new FillStyleRecord(new Color(255, 255, 255));
$rect = $root->getElementsByTagName("size")->item(0)->getElementsByTagName("Rectangle")->item(0);
if ($rect instanceof \DOMElement and $rect->nodeName === "Rectangle") {
$this->viewPort = Rectangle::fromXML($rect);
} else {
throw new \Exception("Could not find viewport");
}
$this->frameRate = $root->getAttribute("framerate");
}
public function getFrameRate(): float {
return $this->frameRate;
}
public function getViewPort(): Rectangle {
return $this->viewPort;
}
public function getAudio() {
return $this->audio;
}
protected function process(): ?string {
$node = $this->current();
if ($node === null) {
return null;
}
switch ($node->nodeName) {
case "SetBackgroundColor":
//TODO: implement Gradient
$this->background = new FillStyleRecord(Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0)));
return $node->nodeName;
case "SoundStreamHead":
case "SoundStreamHead2":
$this->audio = (object)["node" => $node, "start" => null, "content" => [],];
return $node->nodeName;
case "DefineSound":
$this->audio = (object)["node" => $node, "start" => $this->getFrame(), "content" => []];
return $node->nodeName;
case "SoundStreamBlock":
if ($this->audio !== null) {
if ($this->audio->start === null) {
$this->audio->start = $this->getFrame();
}
//$audio .= substr($data, 2);
$this->audio->content[] = [$this->getFrame(), base64_decode(trim($node->textContent))];
}
return $node->nodeName;
}
return parent::process();
}
public function nextFrameOutput(): ?FrameInformation {
$actions = $actions ?? new ActionList();
$frame = $this->nextFrame($actions);
if ($frame === null) {
return null;
}
//TODO: actions?
$frame->addChild(self::BACKGROUND_OBJECT_DEPTH, new ViewFrame(self::BACKGROUND_OBJECT_ID, new DrawPathList([DrawPath::fill($this->background, new Shape($this->getViewPort()->draw()))])));
return new FrameInformation($this->frame - 1, $this->frameRate, $frame);
}
}

198
src/SWFTreeProcessor.php Normal file
View file

@ -0,0 +1,198 @@
<?php
namespace swf2ass;
class SWFTreeProcessor {
protected \DOMElement $root;
protected ViewLayout $layout;
/** @var ObjectDefinition[] */
protected array $objects = [];
protected ?\DOMElement $currentElement = null;
protected int $frame;
public function __construct(int $objectId, \DOMElement $root) {
$this->frame = 0;
$this->root = $root;
$this->layout = new ViewLayout($objectId, null);
$e = $this->root->getElementsByTagName("tags")->item(0);
if ($e instanceof \DOMElement) {
$e = $e->firstChild;
do {
$e = $e->nextSibling;
} while ($e !== null and !($e instanceof \DOMElement));
$this->currentElement = $e;
} else {
throw new \Exception("Could not find elements");
}
}
public function getFrame(): int {
return $this->frame;
}
protected function next() {
$e = $this->currentElement;
do {
$e = $e->nextSibling;
} while ($e !== null and !($e instanceof \DOMElement));
$this->currentElement = $e;
}
protected function current(): ?\DOMElement {
return $this->currentElement;
}
/**
* @return ObjectDefinition[]
*/
public function getObjects(): array {
return $this->objects;
}
protected function process(): ?string {
$node = $this->current();
if ($node === null) {
return null;
}
switch ($node->nodeName) {
case "DefineShape":
case "DefineShape2":
case "DefineShape3":
case "DefineShape4":
case "DefineShape5":
$shape = ShapeDefinition::fromXML($node);
$this->objects[$shape->getObjectId()] = $shape;
break;
case "DefineSprite":
$objectID = (int)$node->getAttribute("objectID");
$framesCount = (int)$node->getAttribute("frames");
$spriteTree = new SWFTreeProcessor($objectID, $node);
$actions = new ActionList();
/** @var ViewFrame[] $frames */
$frames = [];
while (($frame = $spriteTree->nextFrame($actions)) !== null) {
$frames[] = $frame;
}
$sprite = new SpriteDefinition($objectID, $frames);
$this->objects[$sprite->getObjectId()] = $sprite;
break;
case "DefineBitsLossless":
case "DefineBitsLossless2":
$bitmap = BitmapDefinition::fromXML($node);
$this->objects[$bitmap->getObjectId()] = $bitmap;
break;
case "DefineBitsJPEG2":
case "DefineBitsJPEG3":
$bitmap = JPEGBitmapDefinition::fromXML($node);
$this->objects[$bitmap->getObjectId()] = $bitmap;
break;
case "RemoveObject":
case "RemoveObject2":
$this->layout->remove((int)$node->getAttribute("depth"));
break;
case "PlaceObject2":
case "PlaceObject3":
$depth = (int)$node->getAttribute("depth");
$objectID = $node->hasAttribute("objectID") ? (int)$node->getAttribute("objectID") : null;
$clipDepth = $node->hasAttribute("clipDepth") ? (int)$node->getAttribute("clipDepth") : null;
$replace = $node->getAttribute("replace") === "1";
$object = $objectID === null ? $this->layout->get($depth) : ($this->objects[$objectID] ?? null);
if ($object === null) {
var_dump("Object oid:$objectID depth:$depth not found");
/*if($replace){
$this->layout->remove($depth);
}*/
//TODO: insert bogus one
$this->layout->remove($depth);
break;
}
$transform = null;
$transformNodes = $node->getElementsByTagName("transform");
if ($transformNodes->count() > 0) {
$transform = MatrixTransform::fromXML($transformNodes->item(0)->getElementsByTagName("Transform")->item(0));
//TODO: multiple transforms!???
}
$colorTransform = null;
$colorTransformNodes = $node->getElementsByTagName("colorTransform");
if ($colorTransformNodes->count() > 0) {
//TODO: add more modes?
$colorTransform = ColorTransform::fromXML($colorTransformNodes->item(0)->getElementsByTagName("ColorTransform2")->item(0));
}
$currentObject = $this->layout->get($depth);
if ($replace and $currentObject !== null) {
if ($currentObject->getObjectId() === $object->getObjectId()) {
if ($transform !== null) {
$currentObject->setMatrixTransform($transform);
}
if ($colorTransform !== null) {
$currentObject->setColorTransform($colorTransform);
}
break;
}
}
$view = $clipDepth !== null ? new ClippingViewLayout($clipDepth, $objectID, $object, $this->layout) : new ViewLayout($objectID, $object, $this->layout);
$view->setMatrixTransform($transform);
$view->setColorTransform($colorTransform);
if ($replace) {
$this->layout->replace($depth, $view);
} else {
$this->layout->place($depth, $view);
}
break;
case "ShowFrame":
break;
case "End":
break;
default:
//TODO
var_dump($node->nodeName);
}
return $node->nodeName;
}
public function getViewLayout(): ViewLayout {
return $this->layout;
}
public function nextFrame(ActionList $actions): ?ViewFrame {
while (($nodeName = $this->process()) !== null) {
$this->next();
if ($nodeName === "ShowFrame") {
break;
} else if ($nodeName === "End" and $this->frame === 0) {
break;
}
}
if ($nodeName === null) {
return null;
}
++$this->frame;
//TODO $this->layout->hasFrame();
return $this->layout->nextFrame($actions);
}
}

View file

@ -13,4 +13,8 @@ class Shape {
public function __construct(array $edges = []) {
$this->edges = $edges;
}
public function merge(Shape $shape): Shape {
return new Shape(array_merge($this->edges, $shape->edges));
}
}

View file

@ -11,7 +11,7 @@ class ShapeConverter {
private ?ActivePath $fill_style1 = null;
private ?ActivePath $line_style = null;
private Coordinate $position;
private Vector2 $position;
private PendingPathMap $fills;
private PendingPathMap $strokes;
@ -19,10 +19,9 @@ class ShapeConverter {
public DrawPathList $commands;
public function __construct(\DOMElement $element, StyleList $currentStyles) {
$this->styles = $currentStyles;
$this->position = new Coordinate(0, 0);
$this->position = new Vector2(0, 0);
$this->fills = new PendingPathMap();
$this->strokes = new PendingPathMap();
$this->commands = new DrawPathList([]);
@ -45,7 +44,7 @@ class ShapeConverter {
if ($node->hasAttribute("fillStyle1")) {
if($this->fill_style1 !== null){
if ($this->fill_style1 !== null) {
$this->fills->merge_path($this->fill_style1, true);
}
@ -56,8 +55,8 @@ class ShapeConverter {
}
if ($node->hasAttribute("fillStyle0")) {
if($this->fill_style0 !== null){
if(!$this->fill_style0->segment->is_empty()){
if ($this->fill_style0 !== null) {
if (!$this->fill_style0->segment->is_empty()) {
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
@ -68,7 +67,7 @@ class ShapeConverter {
}
if ($node->hasAttribute("lineStyle")) {
if($this->line_style !== null){
if ($this->line_style !== null) {
$this->strokes->merge_path($this->line_style, false);
}
@ -77,13 +76,13 @@ class ShapeConverter {
$this->line_style = $id > 0 ? new ActivePath($id, $this->position) : null;
}
}else if ($node->nodeName === "LineTo") {
$curve = LineRecord::fromXML($node, $this->position);
} else if ($node->nodeName === "LineTo") {
$line = LineRecord::fromXML($node, $this->position);
$this->visit_point($curve->coord, false);
$this->visit_point($line->coord, false);
$this->position = $curve->coord;
}else if ($node->nodeName === "CurveTo") {
$this->position = $line->coord;
} else if ($node->nodeName === "CurveTo") {
$curve = QuadraticCurveRecord::fromXML($node, $this->position);
$this->visit_point($curve->control, true);
@ -96,51 +95,51 @@ class ShapeConverter {
$this->flush_layer();
}
public function visit_point(Coordinate $coordinate, bool $isBezierControlPoint){
public function visit_point(Vector2 $coordinate, bool $isBezierControlPoint) {
$point = new VisitedPoint($coordinate, $isBezierControlPoint);
if($this->fill_style0 !== null){
if ($this->fill_style0 !== null) {
$this->fill_style0->add_point($point);
}
if($this->fill_style1 !== null){
if ($this->fill_style1 !== null) {
$this->fill_style1->add_point($point);
}
if($this->line_style !== null){
if ($this->line_style !== null) {
$this->line_style->add_point($point);
}
}
public function flush_paths(){
if($this->fill_style1 !== null){
$this->fills->merge_path($this->fill_style1, true);
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
public function flush_paths() {
if ($this->fill_style1 !== null) {
$this->fills->merge_path($this->fill_style1, true);
$this->fill_style1 = new ActivePath($this->fill_style1->style, $this->position);
}
if($this->fill_style0 !== null){
if(!$this->fill_style0->segment->is_empty()){
if ($this->fill_style0 !== null) {
if (!$this->fill_style0->segment->is_empty()) {
$this->fill_style0->flip();
$this->fills->merge_path($this->fill_style0, true);
}
$this->fill_style0 = new ActivePath($this->fill_style0->style, $this->position);
}
if($this->line_style !== null){
if ($this->line_style !== null) {
$this->strokes->merge_path($this->line_style, false);
$this->line_style = new ActivePath($this->line_style->style, $this->position);
}
}
public function flush_layer(){
public function flush_layer() {
$this->flush_paths();
$this->fill_style0 = null;
$this->fill_style1 = null;
$this->line_style = null;
foreach ($this->fills->map as $styleId => $path){
foreach ($this->fills->map as $styleId => $path) {
assert($styleId > 0 && $styleId < count($this->styles->fillStyles)); // ?????
$style = $this->styles->getFillStyle($styleId - 1);
if($style === null){
if ($style === null) {
var_dump($this->styles);
var_dump($styleId);
}
@ -149,16 +148,16 @@ class ShapeConverter {
}
$this->fills->map = [];
foreach ($this->strokes->map as $styleId => $path){
foreach ($this->strokes->map as $styleId => $path) {
assert($styleId > 0 && $styleId < count($this->styles->lineStyles)); // ?????
$style = $this->styles->getLineStyle($styleId - 1);
foreach ($path->segments as $segment){
foreach ($path->segments as $segment) {
$segmentStyle = $style;
//Close non-closed segments by double drawing backwards
if(!$segment->is_closed()){
if (!$segment->is_closed()) {
$other = clone $segment;
$other->flip();
$segment->merge($other);

View file

@ -3,25 +3,24 @@
namespace swf2ass;
class ShapeDefinition implements ObjectDefinition {
public $id;
public int $id;
public Rectangle $bounds;
public DrawPathList $shapeList;
public function __construct($id, Rectangle $bounds, DrawPathList $shapes) {
public function __construct(int $id, Rectangle $bounds, DrawPathList $shapes) {
$this->id = $id;
$this->bounds = $bounds;
$this->shapeList = $shapes;
}
public function getId(){
public function getObjectId(): int {
return $this->id;
}
public function getShapeList() : DrawPathList{
public function getShapeList(): DrawPathList {
return $this->shapeList;
}
static function fromXML(\DOMElement $element): ShapeDefinition {
$styles = StyleList::fromXML($element->getElementsByTagName("styles")->item(0)->getElementsByTagName("StyleList")->item(0));
//Utils::dump_element($element->getElementsByTagName("styles")->item(0));
@ -32,6 +31,6 @@ class ShapeDefinition implements ObjectDefinition {
$drawPathList = $drawPathList->merge(DrawPathList::fromXML($node, $styles));
}
}
return new ShapeDefinition($element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
return new ShapeDefinition((int)$element->getAttribute("objectID"), Rectangle::fromXML($element->getElementsByTagName("bounds")->item(0)->getElementsByTagName("Rectangle")->item(0)), $drawPathList);
}
}

View file

@ -15,10 +15,12 @@ class ShiftedRadialGradient implements Gradient {
$this->transform = $transform;
$this->attributes = $attributes;
}
public function getItems() : array {
public function getItems(): array {
return $this->colors;
}
public function getMatrixTransform() : ?MatrixTransform{
public function getMatrixTransform(): ?MatrixTransform {
return $this->transform;
}
@ -28,10 +30,6 @@ class ShiftedRadialGradient implements Gradient {
$colors[] = GradientItem::fromXML($item);
}
$matrix = $element->getElementsByTagName("matrix");
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, [
"spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null,
"interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null,
"shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,
]);
return new ShiftedRadialGradient($colors, $matrix->count() > 0 ? MatrixTransform::fromXML($matrix->item(0)) : null, ["spreadMode" => $element->hasAttribute("spreadMode") ? $element->getAttribute("spreadMode") : null, "interpolationMode" => $element->hasAttribute("interpolationMode") ? $element->getAttribute("interpolationMode") : null, "shift" => $element->hasAttribute("shift") ? $element->getAttribute("shift") : null,]);
}
}

56
src/SpriteDefinition.php Normal file
View file

@ -0,0 +1,56 @@
<?php
namespace swf2ass;
class SpriteDefinition implements MultiFrameObjectDefinition {
public int $id;
/** @var ViewFrame[] */
public array $frames;
public int $frameCounter;
public bool $hasFrames = true;
/**
* @param int $id
* @param ViewFrame[] $frames
*/
public function __construct(int $id, array $frames, int $frameCounter = 0) {
$this->id = $id;
$this->frames = $frames;
$this->frameCounter = $frameCounter % count($this->frames);
}
public function getObjectId(): int {
return $this->id;
}
public function getShapeList(): DrawPathList {
$list = new DrawPathList();
foreach ($this->frames[$this->frameCounter]->render(0, [], ColorTransform::identity(), MatrixTransform::identity())->getObjects() as $object) {
$list = $list->merge($object->drawPathList);
}
return $list;
}
public function hasFrame(): bool {
return $this->hasFrames;
}
public function getFrameCounter(): int {
return $this->frameCounter;
}
public function getFrame(int $frameNumber): ?ViewFrame {
return $this->frames[$frameNumber] ?? null;
}
public function nextFrame(): ViewFrame {
$f = $this->frames[$this->frameCounter];
++$this->frameCounter;
if ($this->frameCounter >= count($this->frames)) {
$this->frameCounter = count($this->frames) - 1;
$this->hasFrames = false; //TODO LOOP
}
return $f;
}
}

View file

@ -4,7 +4,7 @@ namespace swf2ass;
class Square extends Rectangle {
public function __construct(Coordinate $topLeft, $size = 1) {
parent::__construct($topLeft, $topLeft->add(new Coordinate($size, $size)));
public function __construct(Vector2 $topLeft, $size = 1) {
parent::__construct($topLeft, $topLeft->add(new Vector2($size, $size)));
}
}

View file

@ -8,8 +8,8 @@ class StyleContainer {
public $fscx = null, $fscy = null;
public $fax = null, $fay = null;
public ?Coordinate $pos = null;
/** @var ?Coordinate[] */
public ?Vector2 $pos = null;
/** @var ?Vector2[] */
public $move = null;
public ?Color $color1 = null;

View file

@ -43,7 +43,7 @@ class StyleList {
//TODO
Utils::dump_element($node);
$fillStyles[] = new FillStyleRecord(new Color(0, 0, 0, 255 / 2));
}else{
} else {
Utils::dump_element($node);
throw new \Exception("Unknown style " . $node->nodeName);
}
@ -61,18 +61,18 @@ class StyleList {
if ($colors->count() > 0) {
$color = Color::fromXML($colors->item(0)->getElementsByTagName("Color")->item(0));
}else if ($colors2->count() > 0) {
} else if ($colors2->count() > 0) {
$color = Color::fromXML($colors2->item(0));
} else if ($grads->count() > 0) {
//TODO: other gradients?
//$fill = new FillStyleRecord(LinearGradient::fromXML($grads->item(0)->getElementsByTagName("LinearGradient")->item(0)));
}
if($color === null){
if ($color === null) {
Utils::dump_element($node);
}
$lineStyles[] = new LineStyleRecord(max(TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
$lineStyles[] = new LineStyleRecord(max(Constants::TWIP_SIZE, (int)$node->getAttribute("width")), $color, null);
}
}
return new StyleList($fillStyles, $lineStyles);

View file

@ -26,220 +26,6 @@ abstract class Utils {
return str_pad($h, $l, "0", STR_PAD_LEFT);
}
static function processSWF(\DOMElement $element, &$audio = null) {
$currentState = new CurrentState();
$currentState->frameRate = $element->getAttribute("framerate");
$currentState->frameCount = $element->getAttribute("frames");
$rect = $element->getElementsByTagName("size")->item(0)->getElementsByTagName("Rectangle")->item(0);
if ($rect instanceof \DOMElement and $rect->nodeName === "Rectangle") {
$currentState->viewPort = Rectangle::fromXML($rect)->toPixel();
}
$sound = null;
$width = $currentState->viewPort->getWidth();
$height = $currentState->viewPort->getHeight();
$ar = $width/$height;
$durationSeconds = (int) ceil($currentState->frameRate * $currentState->frameCount);
$header = <<<ASSHEADER
[Script Info]
; Script generated by Aegisub 3.2.2
; http://www.aegisub.org/
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709
PlayResX: {$width}
PlayResY: {$height}
[Aegisub Project Garbage]
Last Style Storage: Default
Video File: ?dummy:{$currentState->frameRate}:{$durationSeconds}:{$width}:{$height}:160:160:160:c
Video AR Value: {$ar}
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: f,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
ASSHEADER;
foreach (explode("\n", $header) as $line){
yield $line . "\n";
}
$rootTag = $element->getElementsByTagName("tags")->item(0);
$e = [];
if ($rootTag instanceof \DOMElement) {
foreach ($rootTag->childNodes as $node) {
if ($node instanceof \DOMElement) {
if ($node->nodeName === "SoundStreamHead") {
$sound = $node;
} else if ($node->nodeName === "DefineSound") {
if($currentState->frameOffset === null){
$currentState->frameOffset = $currentState->frameNumber;
}
} else if ($node->nodeName === "SoundStreamBlock" and $sound !== null) {
if($currentState->frameOffset === null){
$currentState->frameOffset = $currentState->frameNumber;
}
$data = base64_decode(trim($node->textContent));
$audio .= substr($data, 2);
} else if ($node->nodeName === "SetBackgroundColor") {
$currentState->backgroundColor = Color::fromXML($node->getElementsByTagName("color")->item(0)->getElementsByTagName("Color")->item(0));
$currentState->displayList[0] = new DisplayEntry();
$currentState->displayList[0]->object = new ShapeDefinition(0, $currentState->viewPort->multiply(TWIP_SIZE), new DrawPathList([DrawPath::fill(new FillStyleRecord($currentState->backgroundColor), new Shape($currentState->viewPort->multiply(TWIP_SIZE)->draw()))]));
$currentState->displayList[0]->startFrame = $currentState->frameNumber;
$currentState->displayList[0]->depth = 0;
} else if ($node->nodeName === "DefineMorphShape") {
//$shapeDef = MorphShapeDefinition::fromXML($node);
//$currentState->objects[$shapeDef->id] = $shapeDef;
//TODO
var_dump("TODO DefineMorphShape");
} else if ($node->nodeName === "DefineShape" or $node->nodeName === "DefineShape2" or $node->nodeName === "DefineShape3" or $node->nodeName === "DefineShape4" or $node->nodeName === "DefineShape5") {
$shapeDef = ShapeDefinition::fromXML($node);
$currentState->objects[$shapeDef->id] = $shapeDef;
} else if ($node->nodeName === "RemoveObject" or $node->nodeName === "RemoveObject2") {
$depth = (int)$node->getAttribute("depth");
if ($currentState->displayList[$depth] ?? null instanceof DisplayEntry) {
//TODO: clips
foreach ($currentState->displayList[$depth]->getLines($currentState) as $line) {
yield $line . PHP_EOL;
}
}
unset($currentState->displayList[$depth]);
unset($currentState->clipList[$depth]);
} else if ($node->nodeName === "PlaceObject2" or $node->nodeName === "PlaceObject3") {
$depth = (int)$node->getAttribute("depth");
$transform = null;
$transformNodes = $node->getElementsByTagName("transform");
if ($transformNodes->count() > 0) {
$transform = MatrixTransform::fromXML($transformNodes->item(0)->getElementsByTagName("Transform")->item(0));
}
$colorTransform = null;
$colorTransformNodes = $node->getElementsByTagName("colorTransform");
if ($colorTransformNodes->count() > 0) {
$colorTransform = ColorTransform::fromXML($colorTransformNodes->item(0)->getElementsByTagName("ColorTransform2")->item(0));
}
$objectID = $node->getAttribute("objectID");
if ($objectID !== "") {
$ob = new DisplayEntry();
$objectID = $node->getAttribute("objectID");
if (!isset($currentState->objects[$objectID])) {
var_dump("Unknown object id $objectID");
continue;
throw new \Exception("Unknown object id $objectID");
}
$ob->startFrame = $currentState->frameNumber;
$ob->depth = $depth;
if ($node->hasAttribute("clipDepth")) {
$ob->clipDepth = (int)$node->getAttribute("clipDepth");
$currentState->clipList[$ob->clipDepth] = $ob;
}
$ob->object = $currentState->objects[$objectID];
if ($currentState->displayList[$depth] ?? null instanceof DisplayEntry) {
//TODO: clips
foreach ($currentState->displayList[$depth]->getLines($currentState) as $line) {
yield $line . PHP_EOL;
}
if ($node->getAttribute("replace") === "1") {
$ob->currentTransform = $currentState->displayList[$depth]->currentTransform;
$ob->currentColorTransform = $currentState->displayList[$depth]->currentColorTransform;
}
}
$currentState->displayList[$depth] = $ob;
}
$ob = $currentState->displayList[$depth] ?? null;
if (!$ob instanceof DisplayEntry) {
continue;
throw new \Exception("Unknown depth id $depth");
}
if ($transform !== null) {
$ob->currentTransform = $transform;
}
if ($colorTransform !== null) {
$ob->currentColorTransform = $colorTransform;
}
} else if ($node->nodeName === "ShowFrame") {
//fwrite(STDERR, "Frame {$currentState->frameNumber}". PHP_EOL);
var_dump("Frame {$currentState->frameNumber} ~{$currentState->frameOffset} (" . count($currentState->displayList) .")");
ksort($currentState->displayList);
foreach ($currentState->displayList as $depth => $displayItem) {
/** @var DisplayEntry $displayItem */
$info = new FrameInformation();
$info->transform = $displayItem->currentTransform;
$info->colorTransform = $displayItem->currentColorTransform;
$info->frame = $currentState->frameNumber;
$displayItem->frames[$currentState->frameNumber] = $info;
}
++$currentState->frameNumber;
} else if ($node->nodeName === "DefineSprite") {
//TODO do actual animation
var_dump("TODO DefineSprite");
//Utils::dump_element($node);
foreach ($node->getElementsByTagName("tags")->item(0)->childNodes as $node2) {
if ($node2 instanceof \DOMElement) {
if($node2->nodeName === "PlaceObject2" and isset($currentState->objects[$node2->getAttribute("objectID")])){
//TODO
$currentState->objects[$node->getAttribute("objectID")] = $currentState->objects[$node2->getAttribute("objectID")];
var_dump($node->getAttribute("objectID") . " === " . $node2->getAttribute("objectID") . " " . get_class($currentState->objects[$node->getAttribute("objectID")]));
break;
}
}
}
} else if ($node->nodeName === "DefineBitsJPEG2" || $node->nodeName === "DefineBitsJPEG3") {
$bitmapDef = JPEGBitmapDefinition::fromXML($node);
$currentState->objects[$bitmapDef->id] = $bitmapDef;
//file_put_contents("image_" . $bitmapDef->id .".png", $bitmapDef->toPNG());
} else if ($node->nodeName === "DefineBitsLossless" || $node->nodeName === "DefineBitsLossless2") {
$bitmapDef = BitmapDefinition::fromXML($node);
$currentState->objects[$bitmapDef->id] = $bitmapDef;
//file_put_contents("image_" . $bitmapDef->id .".png", $bitmapDef->toPNG());
} else if ($node->nodeName === "DoAction") {
Utils::dump_element($node);
} else if ($node->nodeName === "FileAttributes") {
Utils::dump_element($node);
} else {
echo $node->nodeName . PHP_EOL;
}
}
}
}
foreach ($currentState->displayList as $depth => $entry){
foreach ($entry->getLines($currentState) as $line) {
yield $line . PHP_EOL;
}
}
return $currentState;
}
static function dump_element(\DOMElement $element) {
var_dump($element->ownerDocument->saveXML($element));
}

64
src/Vector2.php Normal file
View file

@ -0,0 +1,64 @@
<?php
namespace swf2ass;
class Vector2 {
/** @var numeric */
public $x;
/** @var numeric */
public $y;
public function __construct($x, $y) {
$this->x = $x;
$this->y = $y;
}
public function equals(Vector2 $b, $epsilon = Constants::EPSILON): bool {
return ($this->x === $b->x or abs($b->x - $this->x) <= $epsilon) and ($this->y === $b->y or abs($b->y - $this->y) <= $epsilon);
}
public function distance(Vector2 $b): float {
return sqrt(pow($this->x - $b->x, 2) + pow($this->y - $b->y, 2));
}
public function invert(): Vector2 {
return new Vector2($this->y, $this->x);
}
public function add(Vector2 $b): Vector2 {
return new Vector2($this->x + $b->x, $this->y + $b->y);
}
public function sub(Vector2 $b): Vector2 {
return new Vector2($this->x - $b->x, $this->y - $b->y);
}
public function abs(): Vector2 {
return new Vector2(abs($this->x), abs($this->y));
}
/**
* @param Vector2 $b
* @return numeric
*/
public function dot(Vector2 $b) {
return $this->x * $b->x + $this->y * $b->y;
}
public function vectorMultiply(Vector2 $b): Vector2 {
return new Vector2($this->x * $b->x, $this->y * $b->y);
}
public function multiply($size): Vector2 {
return new Vector2($this->x * $size, $this->y * $size);
}
public function divide($size): Vector2 {
return new Vector2($this->x / $size, $this->y / $size);
}
public function toPixel($twipSize = Constants::TWIP_SIZE): Vector2 {
return $this->divide($twipSize);
}
}

113
src/ViewFrame.php Normal file
View file

@ -0,0 +1,113 @@
<?php
namespace swf2ass;
class ViewFrame {
private int $objectId;
/** @var ViewFrame[] */
private array $depthMap = [];
/** @var ViewFrame[] */
private ?array $clipDepthMap = null;
private ?DrawPathList $drawPathList;
private ?ColorTransform $colorTransform = null;
private ?MatrixTransform $matrixTransform = null;
public function __construct(int $objectId, ?DrawPathList $drawPathList) {
$this->objectId = $objectId;
$this->drawPathList = $drawPathList;
}
public function getObjectId(): int {
return $this->objectId;
}
public function setClipDepthMap(array $clipDepthMap) {
$this->clipDepthMap = $clipDepthMap;
//TODO: process this
}
/**
* @return ViewFrame[]|null
*/
public function getClipDepthMap(): ?array {
return $this->clipDepthMap;
}
public function addChild(int $depth, ViewFrame $frame) {
if ($this->drawPathList !== null) {
throw new \Exception();
}
$this->depthMap[$depth] = $frame;
}
/**
* @return ViewFrame[]
*/
public function getDepthMap(): array {
return $this->depthMap;
}
public function setColorTransform(?ColorTransform $transform) {
$this->colorTransform = $transform;
}
public function setMatrixTransform(?MatrixTransform $transform) {
$this->matrixTransform = $transform;
}
public function getColorTransform(): ?ColorTransform {
return $this->colorTransform;
}
public function getMatrixTransform(): ?MatrixTransform {
return $this->matrixTransform;
}
public function render(int $baseDepth, array $depthChain, ColorTransform $parentColor, MatrixTransform $parentMatrix): RenderedFrame {
$depthChain[] = $baseDepth;
$matrixTransform = $this->matrixTransform !== null ? $parentMatrix->combine($this->matrixTransform) : $parentMatrix;
$colorTransform = $this->colorTransform !== null ? $parentColor->combine($this->colorTransform) : $parentColor;
$renderedFrame = new RenderedFrame();
$clipShape = null;
if ($this->clipDepthMap !== null) {
$colorIdentity = ColorTransform::identity();
$matrixIdentity = MatrixTransform::identity();
$clipShape = new Shape();
foreach ($this->clipDepthMap as $clipDepth => $clipFrame) {
//TODO: detect rectangle clips? for \iclip
//TODO: add \clip for bounds
foreach ($clipFrame->render($clipDepth, $depthChain, $colorIdentity, $matrixIdentity)->getObjects() as $clipObject) {
foreach ($clipObject->drawPathList->commands as $clipPath) {
//TODO: is transform here needed?
$clipShape = $clipShape->merge($clipObject->matrixTransform->applyToShape($clipPath->commands));
}
}
}
}
if ($this->drawPathList !== null) {
$renderedFrame->add(new RenderedObject($depthChain, $this->getObjectId(), $this->drawPathList, $colorTransform, $matrixTransform, $clipShape));
} else {
foreach ($this->depthMap as $depth => $frame) {
$objects = $frame->render($depth, $depthChain, $colorTransform, $matrixTransform)->getObjects();
foreach ($objects as $object) {
if ($object->clip !== null and $clipShape !== null) {
$object->clip = $object->clip->merge($clipShape);
} else if ($clipShape !== null) {
$object->clip = $clipShape;
}
$renderedFrame->add($object);
}
}
}
return $renderedFrame;
}
}

150
src/ViewLayout.php Normal file
View file

@ -0,0 +1,150 @@
<?php
namespace swf2ass;
class ViewLayout {
private ?ViewLayout $parent;
/** @var ViewLayout[] */
private array $depthMap = [];
private ?ObjectDefinition $object;
private ?ColorTransform $colorTransform = null;
private ?MatrixTransform $matrixTransform = null;
private int $objectId;
public function __construct(int $objectId, ?ObjectDefinition $object, ?ViewLayout $parent = null) {
$this->objectId = $objectId;
if ($object !== null and $objectId !== $object->getObjectId()) {
throw new \LogicException();
}
$this->parent = $parent;
$this->object = $object;
}
public function getObjectId(): int {
return $this->object !== null ? $this->object->getObjectId() : -1;
}
public function getObject(): ?ObjectDefinition {
return $this->object;
}
/**
* @return ViewLayout[]
*/
public function getDepthMap(): array {
return $this->depthMap;
}
public function get(int $depth): ?ViewLayout {
return $this->depthMap[$depth] ?? null;
}
public function place(int $depth, ViewLayout $ob) {
if ($this->object !== null) {
throw new \LogicException("Cannot have ObjectDefinition and children at the same time");
}
if (isset($this->depthMap[$depth]) and $this->depthMap[$depth] !== $ob) {
throw new \Exception("Depth $depth already exists: tried replacing object " . $this->depthMap[$depth]->getObjectId() . " with " . $ob->getObjectId());
}
$this->depthMap[$depth] = $ob;
}
public function replace(int $depth, ViewLayout $ob) {
if ($this->object !== null) {
throw new \LogicException("Cannot have ObjectDefinition and children at the same time");
}
$oldObject = $this->get($depth);
if ($oldObject !== null) {
$ob->colorTransform = $ob->colorTransform ?? $oldObject->colorTransform;
$ob->matrixTransform = $ob->matrixTransform ?? $oldObject->matrixTransform;
}
$this->depthMap[$depth] = $ob;
}
public function remove(int $depth) {
unset($this->depthMap[$depth]);
}
public function hasFrame(): bool {
if ($this->object !== null) {
if ($this->object instanceof MultiFrameObjectDefinition) {
if ($this->object->hasFrame()) {
return true;
}
}
} else {
foreach ($this->depthMap as $ob) {
if ($ob->hasFrame()) {
return true;
}
}
}
//TODO
return false;
}
public function getMatrixTransform(): ?MatrixTransform {
return $this->matrixTransform;
}
public function getColorTransform(): ?ColorTransform {
return $this->colorTransform;
}
public function setMatrixTransform(?MatrixTransform $transform) {
$this->matrixTransform = $transform;
}
public function setColorTransform(?ColorTransform $transform) {
$this->colorTransform = $transform;
}
public function nextFrame(ActionList $actionList): ViewFrame {
if ($this->object !== null) {
if ($this->object instanceof MultiFrameObjectDefinition) {
$frame = $this->object->nextFrame();
} else {
$frame = new ViewFrame($this->getObjectId(), $this->object->getShapeList());
}
} else {
$frame = new ViewFrame($this->getObjectId(), null);
/** @var ClippingViewLayout[] $clipMap */
$clipMap = [];
ksort($this->depthMap);
foreach ($this->depthMap as $depth => $child) {
if ($child instanceof ClippingViewLayout) {
$clipMap[$depth] = $child;
} else {
/** @var ViewFrame[] $clips */
$clips = []; //TODO: make something else?
foreach ($clipMap as $clipDepth => $clip) {
//$targetDepth = $clip->getClipDepth() + $clipDepth;
if ($clip->getClipDepth() > $depth and $clipDepth < $depth) {
$clips[$clipDepth] = $clip->nextFrame($actionList);
}
}
$f = $child->nextFrame($actionList);
if (count($clips) > 0) {
$f->setClipDepthMap($clips);
}
$frame->addChild($depth, $f);
}
}
}
$frame->setColorTransform($this->colorTransform);
$frame->setMatrixTransform($this->matrixTransform);
return $frame;
}
}

View file

@ -4,10 +4,10 @@ namespace swf2ass;
class VisitedPoint {
public Coordinate $pos;
public Vector2 $pos;
public bool $is_bezier_control;
public function __construct(Coordinate $pos, bool $is_bezier_control = false) {
public function __construct(Vector2 $pos, bool $is_bezier_control = false) {
$this->pos = $pos;
$this->is_bezier_control = $is_bezier_control;
}

10
src/ass/ASSColorTag.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace swf2ass\ass;
use swf2ass\ColorTransform;
use swf2ass\MatrixTransform;
interface ASSColorTag extends ASSTag {
public function transitionColor(ColorTransform $transform): ?ASSColorTag;
}

53
src/ass/ASSLine.php Normal file
View file

@ -0,0 +1,53 @@
<?php
namespace swf2ass\ass;
use swf2ass\FrameInformation;
use swf2ass\RenderedFrame;
use swf2ass\RenderedObject;
class ASSLine {
const HOURS_MS = 1000 * 3600;
const MINUTES_MS = 1000 * 60;
public int $layer;
public int $start;
public int $frames = 0;
public int $end;
public string $style;
public string $name = "";
public int $marginLeft = 0;
public int $marginRight = 0;
public int $marginVertical = 0;
public string $effect = "";
public bool $isComment = false;
public string $text;
public function __construct(string $text) {
$this->text = $text;
}
public static function encodeTime(int $ms, $msPrecision = 2): string {
if ($ms < 0) {
throw new \LogicException("ms less than 0: $ms");
}
$hours = intdiv($ms, self::HOURS_MS);
$ms -= $hours * self::HOURS_MS;
$minutes = intdiv($ms, self::MINUTES_MS);
$ms -= $minutes * self::MINUTES_MS;
$s = explode(".", strval(round($ms / 1000, $msPrecision)));
if (!isset($s[1])) {
$s[1] = 0;
}
return ((string)$hours) . ":" . str_pad((string)$minutes, 2, "0", STR_PAD_LEFT) . ':' . str_pad($s[0], 2, "0", STR_PAD_LEFT) . "." . str_pad($s[1], $msPrecision, "0", STR_PAD_RIGHT);
}
public function encode(): string {
return ($this->isComment ? "Comment" : "Dialogue") . ": " . $this->layer . "," . self::encodeTime($this->start) . "," . self::encodeTime($this->end) . "," . $this->style . "," . $this->name . "," . $this->marginLeft . "," . $this->marginRight . "," . $this->marginVertical . "," . $this->effect . "," . $this->text;
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace swf2ass\ass;
use swf2ass\MatrixTransform;
interface ASSPositioningTag extends ASSTag {
public function transitionMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag;
public static function fromMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag;
}

136
src/ass/ASSRenderer.php Normal file
View file

@ -0,0 +1,136 @@
<?php
namespace swf2ass\ass;
use swf2ass\FrameInformation;
use swf2ass\Rectangle;
use swf2ass\RenderedFrame;
use swf2ass\RenderedObject;
class ASSRenderer {
private ?string $header;
/** @var ASSLine[] */
private array $dupeBuffer = [];
public function __construct(int $frameRate, Rectangle $viewPort) {
$display = $viewPort->toPixel();
$width = $display->getWidth();
$height = $display->getHeight();
$ar = $width / $height;
$this->header = <<<ASSHEADER
[Script Info]
; Script generated by swf2ass ASSRenderer
; https://git.gammaspectra.live/WeebDataHoarder/swf2ass
ScriptType: v4.00+
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709
PlayResX: {$width}
PlayResY: {$height}
[Aegisub Project Garbage]
Last Style Storage: Default
Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c
Video AR Value: {$ar}
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: f,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,7,0,0,0,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
ASSHEADER;
}
public function renderFrame(FrameInformation $information, RenderedFrame $frame): \Generator {
if ($this->header !== null) {
foreach (explode("\n", $this->header) as $line) {
yield $line;
}
$this->header = null;
}
$objects = $frame->getObjects();
usort($objects, [self::class, "depthSort"]);
$dupeBuffer = [];
foreach ($objects as $object) {
foreach ($this->renderObject($object) as $line) {
$line->layer = $object->depth[0] === 0 ? $object->depth[1] : $object->depth[0];
$line->start = $information->getStartTimeMilliSeconds();
$line->end = $information->getEndTimeMilliSeconds();
$line->frames = 1;
$line->style = "f";
foreach ($this->dupeBuffer as $i => $dup) {
if ($dup->layer === $line->layer and $dup->text === $line->text) {
$line->start = $dup->start;
$line->frames += $dup->frames;
unset($this->dupeBuffer[$i]);
break;
}
}
$dupeBuffer[] = $line;
}
}
//Flush non dupes
foreach ($this->dupeBuffer as $line) {
$line->name .= " dup:{$line->frames}";
yield $line->encode();
}
$this->dupeBuffer = $dupeBuffer;
}
public function flush(): \Generator {
foreach ($this->dupeBuffer as $line) {
$line->name .= " dup:{$line->frames}";
yield $line->encode();
}
$this->dupeBuffer = [];
}
public static function depthSort(RenderedObject $a, RenderedObject $b) {
if (count($b->depth) > count($a->depth)) {
foreach ($b->depth as $i => $depth) {
$otherDepth = $a->depth[$i] ?? 0;
if ($depth !== $otherDepth) {
return $otherDepth - $depth;
}
}
} else {
foreach ($a->depth as $i => $depth) {
$otherDepth = $b->depth[$i] ?? 0;
if ($depth !== $otherDepth) {
return $depth - $otherDepth;
}
}
}
return 0;
}
/**
* @param RenderedObject $object
* @return ASSLine[]
*/
public function renderObject(RenderedObject $object): array {
$lines = [];
foreach ($object->drawPathList->commands as $drawPath) {
$container = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform);
$line = new ASSLine("{" . $container->encode() . "}");
$line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1));
$lines[] = $line;
}
return $lines;
}
}

11
src/ass/ASSStyleTag.php Normal file
View file

@ -0,0 +1,11 @@
<?php
namespace swf2ass\ass;
use swf2ass\StyleRecord;
interface ASSStyleTag extends ASSTag {
public function transitionStyleRecord(StyleRecord $record): ?ASSStyleTag;
public static function fromStyleRecord(StyleRecord $record): ?ASSStyleTag;
}

13
src/ass/ASSTag.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace swf2ass\ass;
use swf2ass\ColorTransform;
use swf2ass\MatrixTransform;
interface ASSTag {
public function equals(ASSTag $tag): bool;
public function encode(): string;
}

View file

@ -0,0 +1,33 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\StyleRecord;
class blurEdgesGaussianTag implements ASSStyleTag {
/** @var int|float */
private $strength;
public function __construct($strength = 0) {
$this->strength = $strength;
}
public function transitionStyleRecord(StyleRecord $record): ?blurEdgesGaussianTag {
return self::fromStyleRecord($record);
}
public function encode(): string {
return "\\blur{$this->strength}";
}
public static function fromStyleRecord(StyleRecord $record): ?blurEdgesGaussianTag {
//TODO?
return new blurEdgesGaussianTag(0);
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and abs($this->strength - $tag->strength) <= Constants::EPSILON;
}
}

32
src/ass/blurEdgesTag.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\StyleRecord;
class blurEdgesTag implements ASSStyleTag {
private int $strength;
public function __construct(int $strength = 0) {
$this->strength = $strength;
}
public function transitionStyleRecord(StyleRecord $record): ?blurEdgesTag {
return self::fromStyleRecord($record);
}
public function encode(): string {
return "\\be{$this->strength}";
}
public static function fromStyleRecord(StyleRecord $record): ?blurEdgesTag {
//TODO?
return new blurEdgesTag(0);
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and abs($this->strength - $tag->strength) <= Constants::EPSILON;
}
}

36
src/ass/borderTag.php Normal file
View file

@ -0,0 +1,36 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\LineStyleRecord;
use swf2ass\StyleRecord;
class borderTag implements ASSStyleTag {
/** @var int|float */
private $size;
public function __construct($size = 0) {
$this->size = $size;
}
public function transitionStyleRecord(StyleRecord $record): ?borderTag {
return self::fromStyleRecord($record);
}
public function encode(): string {
return "\\bord{$this->size}";
}
public static function fromStyleRecord(StyleRecord $record): ?borderTag {
if ($record instanceof LineStyleRecord) {
return new borderTag($record->width / Constants::TWIP_SIZE);
}
return new borderTag();
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and abs($this->size - $tag->size) <= Constants::EPSILON;
}
}

10
src/ass/clipTag.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace swf2ass\ass;
class clipTag extends drawingTag {
public function encode(): string {
return "\\clip(" . implode(" ", $this->getCommands()) . ")";
}
}

33
src/ass/colorTag.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace swf2ass\ass;
use swf2ass\Color;
use swf2ass\ColorTransform;
use swf2ass\FillStyleRecord;
use swf2ass\Gradient;
use swf2ass\StyleRecord;
use swf2ass\Utils;
abstract class colorTag implements ASSStyleTag, ASSColorTag {
protected Color $color;
public function __construct(Color $color) {
$this->color = $color;
}
public static abstract function fromStyleRecord(StyleRecord $record): ?colorTag;
public function transitionStyleRecord(StyleRecord $record): ?ASSStyleTag {
return $this::fromStyleRecord($record);
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->color->equals($tag->color);
}
public function transitionColor(ColorTransform $transform): ?colorTag {
return new $this($transform->applyToColor($this->color));
}
}

136
src/ass/containerTag.php Normal file
View file

@ -0,0 +1,136 @@
<?php
namespace swf2ass\ass;
use swf2ass\ColorTransform;
use swf2ass\DrawPath;
use swf2ass\MatrixTransform;
use swf2ass\Shape;
use swf2ass\StyleRecord;
class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag {
/** @var ASSTag[] */
private array $tags = [];
public function transitionColor(ColorTransform $transform): ?containerTag {
$container = new containerTag();
foreach ($this->tags as $tag) {
if ($tag instanceof ASSColorTag) {
$newTag = $tag->transitionColor($transform);
if ($newTag === null) {
return null;
}
$container->tags[] = $newTag;
} else {
$container->tags[] = $tag;
}
}
return $container;
}
public function transitionMatrixTransform(MatrixTransform $transform): ?containerTag {
$container = new containerTag();
foreach ($this->tags as $tag) {
if ($tag instanceof ASSPositioningTag) {
$newTag = $tag->transitionMatrixTransform($transform);
if ($newTag === null) {
return null;
}
$container->tags[] = $newTag;
} else {
$container->tags[] = $tag;
}
}
return $container;
}
public function transitionStyleRecord(StyleRecord $record): ?containerTag {
$container = new containerTag();
foreach ($this->tags as $tag) {
if ($tag instanceof ASSStyleTag) {
$newTag = $tag->transitionStyleRecord($record);
if ($newTag === null) {
return null;
}
$container->tags[] = $newTag;
} else {
$container->tags[] = $tag;
}
}
return $container;
}
protected function try_append(?ASSTag $tag, $throw_on_null = false) {
if ($tag !== null) {
$this->tags[] = $tag;
return;
}
if ($throw_on_null) {
throw new \Exception();
}
}
public static function fromPathEntry(DrawPath $path, ?Shape $clip, ?ColorTransform $colorTransform, ?MatrixTransform $matrixTransform): containerTag {
$container = new containerTag();
$container->try_append(borderTag::fromStyleRecord($path->style));
$container->try_append(shadowTag::fromStyleRecord($path->style));
$container->try_append(lineColorTag::fromStyleRecord($path->style));
$container->try_append(fillColorTag::fromStyleRecord($path->style));
if ($matrixTransform !== null) {
$container->try_append(matrixTransformTag::fromMatrixTransform($matrixTransform));
}
if ($colorTransform !== null) {
$container = $container->transitionColor($colorTransform);
}
if ($clip !== null) {
//TODO: matrix transform?
//$container->try_append(new clipTag($clip));
}
$container->try_append(new drawTag($path->commands));
return $container;
}
public static function fromMatrixTransform(MatrixTransform $transform): ?ASSPositioningTag {
throw new \Exception();
}
public static function fromStyleRecord(StyleRecord $record): ?ASSStyleTag {
throw new \Exception();
}
public function equals(ASSTag $tag): bool {
if ($tag instanceof $this and count($this->tags) === count($tag->tags)) {
$tags = $this->tags;
$otherTags = $tag->tags;
foreach ($tags as $i => $t) {
foreach ($otherTags as $j => $t2) {
if ($t->equals($t2)) {
unset($tags[$i]);
unset($otherTags[$j]);
}
break;
}
if (isset($tags[$i])) {
break;
}
}
return count($tags) === 0 and count($otherTags) === 0;
}
return false;
}
public function encode(): string {
$ret = "";
foreach ($this->tags as $tag) {
$ret .= $tag->encode();
}
return $ret;
}
}

10
src/ass/drawTag.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace swf2ass\ass;
class drawTag extends drawingTag {
public function encode(): string {
return "\\p1}" . implode(" ", $this->getCommands()) . "{\\p0";
}
}

83
src/ass/drawingTag.php Normal file
View file

@ -0,0 +1,83 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\CubicCurveRecord;
use swf2ass\CubicSplineCurveRecord;
use swf2ass\LineRecord;
use swf2ass\LineStyleRecord;
use swf2ass\MatrixTransform;
use swf2ass\MoveRecord;
use swf2ass\QuadraticCurveRecord;
use swf2ass\Record;
use swf2ass\Shape;
use swf2ass\StyleRecord;
abstract class drawingTag implements ASSTag {
const PRECISION = 2;
protected Shape $shape;
public function __construct(Shape $shape) {
$this->shape = $shape;
}
public function applyMatrixTransform(MatrixTransform $transform): drawingTag {
return new $this($transform->applyToShape($this->shape));
}
/**
* @return string[]
*/
protected function getCommands(): array {
$commands = [];
/** @var ?Record $lastEdge */
$lastEdge = null;
foreach ($this->shape->edges as $edge) {
if ($edge instanceof MoveRecord) {
$coords = $edge->coord->toPixel();
$commands[] = "m " . round($coords->x, self::PRECISION) . " " . round($coords->y, self::PRECISION) . " ";
} else if ($edge instanceof LineRecord) {
$coords = $edge->coord->toPixel();
$commands[] = "l " . round($coords->x, self::PRECISION) . " " . round($coords->y, self::PRECISION) . " ";
} else if ($edge instanceof QuadraticCurveRecord or $edge instanceof CubicCurveRecord or $edge instanceof CubicSplineCurveRecord) {
if ($edge instanceof QuadraticCurveRecord) {
$edge = CubicCurveRecord::fromQuadraticRecord($edge);
}
if ($edge instanceof CubicCurveRecord) {
$control1 = $edge->control1->toPixel();
$control2 = $edge->control2->toPixel();
$anchor = $edge->anchor->toPixel();
$commands[] = "b " . round($control1->x, self::PRECISION) . " " . round($control1->y, self::PRECISION) . " " . round($control2->x, self::PRECISION) . " " . round($control2->y, self::PRECISION) . " " . round($anchor->x, self::PRECISION) . " " . round($anchor->y, self::PRECISION) . " ";
}
//TODO
if ($edge instanceof CubicSplineCurveRecord) {
$anchor = $edge->anchor->toPixel();
$controlPoints = [];
foreach ($edge->control as $control) {
$control = $control->toPixel();
$controlPoints[] = round($control->x, self::PRECISION) . " " . round($control->y, self::PRECISION);
}
$commands[] = "s " . implode(" ", $controlPoints) . " " . round($anchor->x, self::PRECISION) . " " . round($anchor->y, self::PRECISION) . " ";
}
} else {
var_dump($edge);
throw new \Exception("Invalid edge " . get_class($edge));
}
$lastEdge = $edge;
}
return $commands;
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->encode() === $tag->encode();
}
}

31
src/ass/fillColorTag.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace swf2ass\ass;
use swf2ass\Color;
use swf2ass\FillStyleRecord;
use swf2ass\Gradient;
use swf2ass\StyleRecord;
use swf2ass\Utils;
class fillColorTag extends colorTag {
public static function fromStyleRecord(StyleRecord $record): ?fillColorTag {
if ($record instanceof FillStyleRecord) {
/** @var ?Color $color */
$color = null;
if ($record->fill instanceof Color) {
$color = $record->fill;
} else if ($record->fill instanceof Gradient) { //TODO: split this elsewhere
$color = $record->fill->getItems()[0]->color;
} else {
throw new \Exception("Invalid Fill record");
}
return new fillColorTag($color);
}
return new fillColorTag(new Color(0, 0, 0, 255));
}
public function encode(): string {
return "\\1c&H" . strtoupper(Utils::padHex(dechex($this->color->b))) . strtoupper(Utils::padHex(dechex($this->color->g))) . strtoupper(Utils::padHex(dechex($this->color->r))) . "&\\1a&H" . strtoupper(Utils::padHex(dechex($this->color->alpha))) . "&";
}
}

10
src/ass/insideClipTag.php Normal file
View file

@ -0,0 +1,10 @@
<?php
namespace swf2ass\ass;
class insideClipTag extends drawingTag {
public function encode(): string {
return "\\iclip(" . implode(" ", $this->getCommands()) . ")";
}
}

21
src/ass/lineColorTag.php Normal file
View file

@ -0,0 +1,21 @@
<?php
namespace swf2ass\ass;
use swf2ass\Color;
use swf2ass\LineStyleRecord;
use swf2ass\StyleRecord;
use swf2ass\Utils;
class lineColorTag extends colorTag {
public static function fromStyleRecord(StyleRecord $record): ?lineColorTag {
if ($record instanceof LineStyleRecord) {
return new lineColorTag($record->color);
}
return new lineColorTag(new Color(0, 0, 0, 255));
}
public function encode(): string {
return "\\3c&H" . strtoupper(Utils::padHex(dechex($this->color->b))) . strtoupper(Utils::padHex(dechex($this->color->g))) . strtoupper(Utils::padHex(dechex($this->color->r))) . "&\\3a&H" . strtoupper(Utils::padHex(dechex($this->color->alpha))) . "&";
}
}

View file

@ -0,0 +1,65 @@
<?php
namespace swf2ass\ass;
use swf2ass\MatrixTransform;
class matrixTransformTag implements ASSPositioningTag {
private ?scaleTag $scale = null;
private ?rotationTag $rotation = null;
private ?shearingTag $shear = null;
private ?positionTag $position = null;
public function __construct() {
}
public function transitionMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
if ($this->position !== null and $this->position->transitionMatrixTransform($transform) === null) {
return null;
}
if ($this->scale !== null and $this->scale->transitionMatrixTransform($transform) === null) {
return null;
}
if ($this->rotation !== null and $this->rotation->transitionMatrixTransform($transform) === null) {
return null;
}
if ($this->shear !== null and $this->shear->transitionMatrixTransform($transform) === null) {
return null;
}
return self::fromMatrixTransform($transform);
}
public function encode(): string {
$tag = "";
if ($this->scale !== null) {
$tag .= $this->scale->encode();
}
if ($this->rotation !== null) {
$tag .= $this->rotation->encode();
}
if ($this->shear !== null) {
$tag .= $this->shear->encode();
}
if ($this->position !== null) {
$tag .= $this->position->encode();
}
return $tag;
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->position->equals($tag->position);
}
public static function fromMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
$tag = new matrixTransformTag();
$tag->scale = scaleTag::fromMatrixTransform($transform);
$tag->rotation = rotationTag::fromMatrixTransform($transform);
$tag->shear = shearingTag::fromMatrixTransform($transform);
$tag->position = positionTag::fromMatrixTransform($transform);
return $tag;
}
}

35
src/ass/positionTag.php Normal file
View file

@ -0,0 +1,35 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\MatrixTransform;
use swf2ass\StyleRecord;
use swf2ass\Vector2;
class positionTag implements ASSPositioningTag {
private Vector2 $position;
public function __construct(Vector2 $position) {
$this->position = $position;
}
public function transitionMatrixTransform(MatrixTransform $transform): ?positionTag {
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
return $this->position->equals($translation) ? self::fromMatrixTransform($transform) : null;
}
public function encode(): string {
return "\\pos({$this->position->x},{$this->position->y})";
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->position->equals($tag->position);
}
public static function fromMatrixTransform(MatrixTransform $transform): ?positionTag {
$translation = $transform->getTranslation()->divide(Constants::TWIP_SIZE);
return new positionTag($translation);
}
}

36
src/ass/rotationTag.php Normal file
View file

@ -0,0 +1,36 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\MatrixTransform;
use swf2ass\StyleRecord;
use swf2ass\Vector2;
class rotationTag implements ASSPositioningTag {
private Vector2 $rotate;
public function __construct(Vector2 $rotate) {
$this->rotate = $rotate;
}
public function transitionMatrixTransform(MatrixTransform $transform): ?rotationTag {
return self::fromMatrixTransform($transform);
}
public function encode(): string {
return "\\frx{$this->rotate->x}\\fry{$this->rotate->y}";
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->rotate->equals($tag->rotate);
}
public static function fromMatrixTransform(MatrixTransform $transform): ?rotationTag {
$scale = $transform->getScale();
//$rotation = $transform->getRotateSkew(); ????
return new rotationTag(new Vector2($scale->y < 0 ? 180 : 0, $scale->x < 0 ? 180 : 0));
}
}

35
src/ass/scaleTag.php Normal file
View file

@ -0,0 +1,35 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\MatrixTransform;
use swf2ass\StyleRecord;
use swf2ass\Vector2;
class scaleTag implements ASSPositioningTag {
private Vector2 $scale;
public function __construct(Vector2 $scale) {
$this->scale = $scale;
}
public function transitionMatrixTransform(MatrixTransform $transform): ?scaleTag {
return self::fromMatrixTransform($transform);
}
public function encode(): string {
return "\\fscx{$this->scale->x}\\fscy{$this->scale->y}";
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->scale->equals($tag->scale);
}
public static function fromMatrixTransform(MatrixTransform $transform): ?scaleTag {
$scale = $transform->getScale();
return new scaleTag($scale->abs()->multiply(100));
}
}

33
src/ass/shadowTag.php Normal file
View file

@ -0,0 +1,33 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\StyleRecord;
class shadowTag implements ASSStyleTag {
/** @var int|float */
private $depth;
public function __construct($depth = 0) {
$this->depth = $depth;
}
public function transitionStyleRecord(StyleRecord $record): ?shadowTag {
return self::fromStyleRecord($record);
}
public function encode(): string {
return "\\shad{$this->depth}";
}
public static function fromStyleRecord(StyleRecord $record): ?shadowTag {
//TODO?
return new shadowTag(0);
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and abs($this->depth - $tag->depth) <= Constants::EPSILON;
}
}

36
src/ass/shearingTag.php Normal file
View file

@ -0,0 +1,36 @@
<?php
namespace swf2ass\ass;
use swf2ass\Constants;
use swf2ass\MatrixTransform;
use swf2ass\StyleRecord;
use swf2ass\Vector2;
class shearingTag implements ASSPositioningTag {
private Vector2 $shear;
public function __construct(Vector2 $shear) {
$this->shear = $shear;
}
public function transitionMatrixTransform(MatrixTransform $transform): ?shearingTag {
return self::fromMatrixTransform($transform);
}
public function encode(): string {
return "\\fax{$this->shear->x}\\fay{$this->shear->y}";
}
public function equals(ASSTag $tag): bool {
return $tag instanceof $this and $this->shear->equals($tag->shear);
}
public static function fromMatrixTransform(MatrixTransform $transform): ?shearingTag {
$skew = $transform->getRotateSkew();
//TODO? maybe have to split this
return new shearingTag($skew);
}
}

View file

@ -22,9 +22,57 @@ $fp = fopen($argv[2], "w+");
$headerTag = $swf->getElementsByTagName("Header")->item(0);
if ($headerTag instanceof DOMElement) {
foreach (\swf2ass\Utils::processSWF($headerTag, $soundStream) as $line) {
$processor = new \swf2ass\SWFProcessor($headerTag);
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort());
$keyFrameInterval = 10 * $processor->getFrameRate(); //kf every 10 seconds
$frameOffset = 0;
while(($frame = $processor->nextFrameOutput()) !== null){
$audio = $processor->getAudio();
if($audio !== null and $frameOffset === 0){
if($audio->start === null){
continue;
}
$frameOffset = $audio->start;
}
$frame->setFrameOffset($frameOffset);
$rendered = $frame->getFrame()->render(0, [], \swf2ass\ColorTransform::identity(), \swf2ass\MatrixTransform::identity());
$drawCalls = 0;
$drawItems = 0;
$clipCalls = 0;
$clipItems = 0;
foreach ($rendered->getObjects() as $object){
if($object->clip !== null){
++$clipCalls;
$clipItems += count($object->clip->edges);
}
foreach ($object->drawPathList->commands as $path){
++$drawCalls;
$drawItems += count($path->commands->edges);
}
}
echo "=== frame ".$frame->getFrameNumber()." ~ $frameOffset : Depth count: " . count($frame->getFrame()->getDepthMap()) . " :: Object count: " . count($rendered->getObjects()) ." :: Paths: $drawCalls draw calls, $drawItems items :: Clips: $clipCalls draw calls, $clipItems items" . PHP_EOL;
foreach ($assRenderer->renderFrame($frame, $rendered) as $line){
fwrite($fp, $line . "\n");
}
if($frame->getFrameNumber() > 0 and $frame->getFrameNumber() % $keyFrameInterval === 0){
foreach ($assRenderer->flush() as $line){
fwrite($fp, $line . "\n");
}
}
}
foreach ($assRenderer->flush() as $line){
fwrite($fp, $line . "\n");
}
/*foreach (\swf2ass\Utils::processSWF($headerTag, $soundStream) as $line) {
fwrite($fp, $line);
};
};*/
}
/*
@ -32,6 +80,6 @@ if($soundStream !== ""){
file_put_contents($argv[2] . ".mp3", $soundStream);
}*/
fclose($fp);