136 lines
4.2 KiB
PHP
136 lines
4.2 KiB
PHP
<?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;
|
|
}
|
|
} |