swf2ass/src/ass/ASSLine.php

160 lines
5.8 KiB
PHP

<?php
namespace swf2ass\ass;
use swf2ass\ClipPath;
use swf2ass\DrawPath;
use swf2ass\FillStyleRecord;
use swf2ass\FrameInformation;
use swf2ass\Gradient;
use swf2ass\MatrixTransform;
use swf2ass\RenderedFrame;
use swf2ass\RenderedObject;
class ASSLine {
/** @var int[] */
public array $layer;
public int $shapeIndex;
public int $objectId;
public int $start;
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;
/** @var ASSTag[] */
public array $tags = [];
private ?string $cachedEncode = null;
public function __construct() {
}
public function transition(FrameInformation $information, RenderedObject $object) : ?ASSLine{
$line = clone $this;
$line->end = $information->getFrameNumber();
$line->tags = [];
//TODO: clip?
if($object->getDepth() === $this->layer and $object->objectId === $this->objectId) {
$command = $object->drawPathList->commands[$line->shapeIndex] ?? null;
if($command === null){
return null;
}
foreach ($this->tags as $tag){
if($tag instanceof ASSPositioningTag){
$tag = $tag->transitionMatrixTransform($line, $object->matrixTransform);
if($tag === null){
return null;
}
}
if($tag instanceof ASSColorTag){
$tag = $tag->transitionColor($line, $object->colorTransform);
if($tag === null){
return null;
}
}
if($tag instanceof ASSPathTag){
$tag = $tag->transitionShape($line, $command->commands);
if($tag === null){
return null;
}
}
if($tag instanceof ASSClipPathTag){
$tag = $tag->transitionClipPath($line, $object->clip);
if($tag === null){
return null;
}
}
$line->tags[] = $tag;
}
}
$line->dropCache();
return $line;
}
/**
* @param FrameInformation $information
* @param RenderedObject $object
* @return ASSLine[]
*/
public static function fromRenderObject(FrameInformation $information, RenderedObject $object, bool $bakeTransforms = false): array {
$lines = [];
foreach ($object->drawPathList->commands as $i => $drawPath) {
$line = new ASSLine();
$line->layer = $object->getDepth();
$line->shapeIndex = $i;
$line->objectId = $object->objectId;
$line->start = $information->getFrameNumber();
$line->end = $information->getFrameNumber();
$line->tags[] = containerTag::fromPathEntry($drawPath, $object->clip, $object->colorTransform, $object->matrixTransform, $bakeTransforms);
$line->name = "o:{$object->objectId} d:" . implode(".", array_slice($object->depth, 1));
$lines[] = $line;
}
return $lines;
}
public function dropCache(){
$this->cachedEncode = null;
}
public function getPackedLayer() : int{
//Segment depth into specific layers, leaving 2^16 for first, at least 2^8 for second (and 2^8 for third), if no third it'll use whole for second TODO: handle higher depths gracefully
//It is known layers CAN overlap, TODO: check if limiting range might make sense?
//TODO: change this to a truly dynamic mode. might need 2-pass to check for hole overlap
$layer = $this->layer[0] << 16;
if(isset($this->layer[2])){
$layer |= $this->layer[1] & 0xFF;
$layer |= $this->layer[2] & 0xFF;
}else{
$layer |= $this->layer[1] ?? 0;
}
return $layer;
}
public function encode($frameDurationMs, int $msPrecision = 2): string {
if($frameDurationMs === 1000 and $msPrecision === 2 and $this->cachedEncode !== null){
return $this->cachedEncode;
}
$assEventTime = new ASSEventTime($this->start, $this->end - $this->start + 1, $frameDurationMs, $msPrecision);
$line = ($this->isComment ? "Comment" : "Dialogue") . ": " . $this->getPackedLayer() . "," . $assEventTime->start->encode() . "," . $assEventTime->end->encode() . "," . $this->style . "," . $this->name . "," . $this->marginLeft . "," . $this->marginRight . "," . $this->marginVertical . "," . $this->effect . ",";
if($assEventTime->start->adjusted_ms_error !== 0 or $assEventTime->end->adjusted_ms_error !== 0){
//Maybe use fade?
$frameStartTime = $assEventTime->getMillisecondsFromStartOffset(0);
$frameEndTime = $assEventTime->getMillisecondsFromEndOffset(0);
//TODO: maybe needs to be -1?
$line .= "{\\fade(255,0,255,{$frameStartTime},{$frameStartTime},{$frameEndTime},{$frameEndTime})\\err({$assEventTime->start->milliseconds}~{$assEventTime->start->adjusted_ms_error},{$assEventTime->end->milliseconds}~{$assEventTime->end->adjusted_ms_error})}";
}
foreach ($this->tags as $tag){
$line .= "{" . $tag->encode($assEventTime) . "}";
}
if($frameDurationMs === 1000){
$this->cachedEncode = $line;
}
return $line;
}
public function equalish(ASSLine $line): bool {
return $this->layer === $line->layer and $this->objectId === $line->objectId and count($this->tags) === count($line->tags) and $this->encode(1000) === $line->encode(1000);
}
}