swf2ass/src/ass/ASSRenderer.php

206 lines
7.6 KiB
PHP

<?php
namespace swf2ass\ass;
use swf2ass\ClipPath;
use swf2ass\Constants;
use swf2ass\DrawPath;
use swf2ass\DrawPathList;
use swf2ass\FillStyleRecord;
use swf2ass\FrameInformation;
use swf2ass\Gradient;
use swf2ass\MatrixTransform;
use swf2ass\Rectangle;
use swf2ass\RenderedFrame;
use swf2ass\RenderedObject;
use swf2ass\Vector2;
class ASSRenderer {
private ?string $header;
/** @var ASSLine[] */
private array $runningBuffer = [];
private static array $settings = [];
public static function getSetting($name, $default = null){
return self::$settings[$name] ?? $default;
}
public static function setSettings(array $settings){
self::$settings = $settings;
}
public function __construct(float $frameRate, Rectangle $viewPort) {
$display = $viewPort->toPixel();
$width = $display->getWidth() * self::getSetting("videoScaleMultiplier", 1);
$height = $display->getHeight() * self::getSetting("videoScaleMultiplier", 1);
$ar = sprintf("%.6F", $width / $height);
/*if(($frameRate * 2) <= 60){
$frameRate *= 2;
}*/
$frameRate *= self::getSetting("videoRateMultiplier", 1);
$timerPrecision = sprintf("%.4F", (100 / self::getSetting("timerSpeed", 100)) * 100);
$this->header = <<<ASSHEADER
[Script Info]
; Script generated by swf2ass ASSRenderer
; https://git.gammaspectra.live/WeebDataHoarder/swf2ass
Title: swf2ass
ScriptType: v4.00+
; TODO: maybe set WrapStyle: 2
WrapStyle: 0
ScaledBorderAndShadow: yes
YCbCr Matrix: PC.709
PlayResX: {$width}
PlayResY: {$height}
Timer: {$timerPrecision}
[Aegisub Project Garbage]
Last Style Storage: f
Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c
Video AR Value: {$ar}
Active Line: 0
Video Zoom Percent: 2.000000
[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,&H00000000,&H00000000,&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;
}
/**
* @param RenderedObject $object
* @return RenderedObject
*/
public static function bakeGradients(RenderedObject $object) : RenderedObject{
$baked = null;
$drawPathList = new DrawPathList();
foreach ($object->drawPathList->commands as $command){
if($command->style instanceof FillStyleRecord and $command->style->fill instanceof Gradient){
$baked = $baked ?? new RenderedObject($object->depth, $object->objectId, $drawPathList, $object->colorTransform, $object->matrixTransform, $object->clip);
$gradientClip = new ClipPath($command->commands);
//Convert gradients to many tags
foreach ($command->style->fill->getInterpolatedDrawPaths(0, self::getSetting("gradientSlices", Gradient::AUTO_SLICES))->commands as $gradientPath){
//echo (new drawTag($gradientPath->commands, 1))->encode(new ASSEventTime(1, 1, 1)) . "\n";
$newPath = DrawPath::fill($gradientPath->style, $gradientClip->intersect(new ClipPath($gradientPath->commands))->getShape());
//echo (new drawTag($newPath->commands, 1))->encode(new ASSEventTime(1, 1, 1)) . "\n\n";
if(count($newPath->commands->getRecords()) === 0){
continue;
}
$drawPathList->commands[] = $newPath;
}
}else{
$drawPathList->commands[] = $command;
}
}
return $baked ?? $object;
}
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"]);
$runningBuffer = [];
$scale = MatrixTransform::scale(new Vector2(self::getSetting("videoScaleMultiplier", 1), self::getSetting("videoScaleMultiplier", 1)));
$animated = 0;
foreach ($objects as $object) {
$object = clone self::bakeGradients($object);
$object->matrixTransform = $scale->multiply($object->matrixTransform); //TODO order?
$depth = $object->getDepth();
/** @var ASSLine $tagsToTransition */
$tagsToTransition = [];
foreach ($this->runningBuffer as $i => $tag) {
if($depth === $tag->layer and $object->objectId === $tag->objectId) {
$tagsToTransition[] = $tag;
unset($this->runningBuffer[$i]);
}
}
$canTransition = true;
$transitionedTags = [];
foreach ($tagsToTransition as $tag){
$tag = $tag->transition($information, $object);
if($tag !== null){
$transitionedTags[] = $tag;
$tag->dropCache();
}else{
$canTransition = false;
break;
}
}
if($canTransition and count($transitionedTags) > 0){
$animated += count($transitionedTags);
$runningBuffer = array_merge($runningBuffer, $transitionedTags);
}else{
$this->runningBuffer = array_merge($this->runningBuffer, $tagsToTransition);
foreach (ASSLine::fromRenderObject($information, $object, self::getSetting("bakeTransforms", false)) as $line) {
$line->style = "f";
$line->dropCache();
$runningBuffer[] = $line;
}
}
}
echo "[ASS] Total " . count($objects) . " objects, " . count($this->runningBuffer) . " flush, " . count($runningBuffer) ." buffer, $animated animated tags.\n";
//Flush non dupes
foreach ($this->runningBuffer as $line) {
$line->name .= " f:{$line->start}>{$line->end}~".($line->end - $line->start + 1);
$line->dropCache();
yield $line->encode($information->getFrameDurationMilliSeconds() * (self::getSetting("timerSpeed", 100) / 100), self::getSetting("timePrecision"));
}
$this->runningBuffer = $runningBuffer;
}
public function flush(FrameInformation $information): \Generator {
foreach ($this->runningBuffer as $line) {
$line->name .= " f:{$line->start}>{$line->end}~".($line->end - $line->start + 1);
$line->dropCache();
yield $line->encode($information->getFrameDurationMilliSeconds() * (self::getSetting("timerSpeed", 100) / 100), self::getSetting("timePrecision"));
}
$this->runningBuffer = [];
}
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;
}
}