Fixed matrix transforms, fixed transitions, added ASS timing system, fixed stroke shape changing from closed to open segments, fixed clip matrix transform from parent
This commit is contained in:
parent
0b5e7f8635
commit
75507b2b9e
|
@ -5,7 +5,6 @@ namespace swf2ass;
|
|||
|
||||
class DrawPath {
|
||||
public StyleRecord $style;
|
||||
public bool $is_closed;
|
||||
public Shape $commands;
|
||||
|
||||
|
||||
|
@ -13,15 +12,14 @@ class DrawPath {
|
|||
$p = new DrawPath();
|
||||
$p->style = $style;
|
||||
$p->commands = $shape;
|
||||
$p->is_closed = true;
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function stroke(LineStyleRecord $style, Shape $shape, bool $is_closed): DrawPath {
|
||||
public static function stroke(LineStyleRecord $style, Shape $shape): DrawPath {
|
||||
//TODO: stroke to fill conversion
|
||||
$p = new DrawPath();
|
||||
$p->style = $style;
|
||||
$p->commands = $shape;
|
||||
$p->is_closed = $is_closed;
|
||||
return $p;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,19 @@ class LineStyleRecord implements StyleRecord {
|
|||
public int $width;
|
||||
public Color $color;
|
||||
|
||||
const LINE_JOIN_MITER = 0;
|
||||
const LINE_JOIN_MITER_CLIP = 1;
|
||||
const LINE_JOIN_ROUND = 2;
|
||||
const LINE_JOIN_BEVEL = 3;
|
||||
|
||||
const LINE_CAP_BUTT = 0;
|
||||
const LINE_CAP_SQUARE = 1;
|
||||
const LINE_CAP_ROUND = 2;
|
||||
|
||||
public int $start_cap = self::LINE_CAP_BUTT;
|
||||
public int $end_cap = self::LINE_CAP_BUTT;
|
||||
public int $line_join = self::LINE_JOIN_MITER;
|
||||
|
||||
public function __construct(int $width, Color $color) {
|
||||
$this->width = $width;
|
||||
$this->color = $color;
|
||||
|
|
|
@ -13,9 +13,9 @@ class MatrixTransform {
|
|||
|
||||
public function __construct(?Vector2 $scale, ?Vector2 $rotateSkew, ?Vector2 $translation) {
|
||||
$this->matrix = new RealMatrix([
|
||||
[$scale !== null ? $scale->x : 1 /* a */, /* b */ $rotateSkew !== null ? $rotateSkew->x : 0, $translation !== null ? $translation->x : 0],
|
||||
[$rotateSkew !== null ? $rotateSkew->y : 0 /* c */, /* d */ $scale !== null ? $scale->y : 1, $translation !== null ? $translation->y : 0],
|
||||
[0, 0, 1]
|
||||
[$scale !== null ? $scale->x : 1 /* a */, /* c */ $rotateSkew !== null ? $rotateSkew->y : 0, 0],
|
||||
[$rotateSkew !== null ? $rotateSkew->x : 0 /* b */, /* d */ $scale !== null ? $scale->y : 1, 0],
|
||||
[$translation !== null ? $translation->x : 0, $translation !== null ? $translation->y : 0, 1]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -32,9 +32,9 @@ class MatrixTransform {
|
|||
public function toArray(bool $translation = true) : array{
|
||||
if($translation){
|
||||
return [
|
||||
[$this->get_a()->toFixed(), $this->get_b()->toFixed(), $this->get_tx()->toFixed()],
|
||||
[$this->get_c()->toFixed(), $this->get_d()->toFixed(), $this->get_ty()->toFixed()],
|
||||
[0, 0, 1]
|
||||
[$this->get_a()->toFixed(), $this->get_b()->toFixed(), 0],
|
||||
[$this->get_c()->toFixed(), $this->get_d()->toFixed(), 0],
|
||||
[$this->get_tx()->toFixed(), $this->get_ty()->toFixed(), 1]
|
||||
];
|
||||
}else{
|
||||
return [
|
||||
|
@ -72,7 +72,7 @@ class MatrixTransform {
|
|||
return new MatrixTransform(null, new Vector2(0, tan($angle)), null);
|
||||
}
|
||||
|
||||
public function combine(MatrixTransform $other): MatrixTransform {
|
||||
public function multiply(MatrixTransform $other): MatrixTransform {
|
||||
$result = clone $this;
|
||||
$result->matrix = $this->matrix->multiply($other->matrix);
|
||||
return $result;
|
||||
|
@ -83,11 +83,11 @@ class MatrixTransform {
|
|||
}
|
||||
|
||||
public function get_b() : RealNumber{
|
||||
return $this->matrix->get(0, 1);
|
||||
return $this->matrix->get(1, 0);
|
||||
}
|
||||
|
||||
public function get_c() : RealNumber{
|
||||
return $this->matrix->get(1, 0);
|
||||
return $this->matrix->get(0, 1);
|
||||
}
|
||||
|
||||
public function get_d() : RealNumber{
|
||||
|
@ -95,11 +95,11 @@ class MatrixTransform {
|
|||
}
|
||||
|
||||
public function get_tx() : RealNumber{
|
||||
return $this->matrix->get(2, 0);
|
||||
return $this->matrix->get(0, 2);
|
||||
}
|
||||
|
||||
public function get_ty() : RealNumber{
|
||||
return $this->matrix->get(2, 1);
|
||||
return $this->matrix->get(1, 2);
|
||||
}
|
||||
|
||||
public function getMatrix() : RealMatrix{
|
||||
|
@ -112,11 +112,11 @@ class MatrixTransform {
|
|||
|
||||
public function applyToVector(Vector2 $vector, bool $applyTranslation = true): Vector2 {
|
||||
if($applyTranslation){
|
||||
$result = $this->matrix->multiply(MatrixFactory::createFromColumnVector([$vector->x, $vector->y, 1]));
|
||||
$result = (new RealMatrix([[$vector->x, $vector->y, 1]]))->multiply($this->matrix);
|
||||
}else{
|
||||
$result = $this->matrix->submatrix(0, 0, 1, 1)->multiply(MatrixFactory::createFromColumnVector([$vector->x, $vector->y]));
|
||||
$result = (new RealMatrix([[$vector->x, $vector->y]]))->multiply($this->matrix->submatrix(0, 0, 1, 1));
|
||||
}
|
||||
return new Vector2($result->get(0, 0)->toFloat(), $result->get(1, 0)->toFloat());
|
||||
return new Vector2($result->get(0, 0)->toFloat(), $result->get(0, 1)->toFloat());
|
||||
}
|
||||
|
||||
public function applyToShape(Shape $shape, bool $applyTranslation = true): Shape {
|
||||
|
@ -129,7 +129,7 @@ class MatrixTransform {
|
|||
}
|
||||
|
||||
public function equals(MatrixTransform $other): bool {
|
||||
return $this->matrix->isEqual($other->matrix);
|
||||
return $this->matrix->isEqual($other->matrix, new RealNumber(0.0001));
|
||||
}
|
||||
|
||||
static function fromSWFArray(array $element): MatrixTransform {
|
||||
|
|
|
@ -29,6 +29,7 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
|
||||
public function getShapeList(?float $ratio): DrawPathList {
|
||||
//TODO: cache shapes by ratio
|
||||
//TOD: refactor this to use color transforms (and if able) matrix transforms
|
||||
if($ratio === null or abs($ratio) < Constants::EPSILON){
|
||||
return $this->startShapeList;
|
||||
}
|
||||
|
@ -81,7 +82,7 @@ class MorphShapeDefinition implements ObjectDefinition {
|
|||
throw new \Exception();
|
||||
}
|
||||
}else if($c1->style instanceof LineStyleRecord and $c2->style instanceof LineStyleRecord){
|
||||
$drawPathList->commands[] = DrawPath::stroke(new LineStyleRecord(self::lerpInteger($c1->style->width, $c2->style->width, $ratio), self::lerpColor($c1->style->color, $c2->style->color, $ratio)), $shape, $c1->is_closed);
|
||||
$drawPathList->commands[] = DrawPath::stroke(new LineStyleRecord(self::lerpInteger($c1->style->width, $c2->style->width, $ratio), self::lerpColor($c1->style->color, $c2->style->color, $ratio)), $shape);
|
||||
}else{
|
||||
var_dump($c1->style);
|
||||
var_dump($c2->style);
|
||||
|
|
|
@ -28,10 +28,10 @@ class PendingPath {
|
|||
}
|
||||
}
|
||||
|
||||
if ($merged === null) {
|
||||
$this->segments[] = $new_segment;
|
||||
} else {
|
||||
if ($merged !== null) {
|
||||
$this->merge_path($merged, $directed);
|
||||
} else {
|
||||
$this->segments[] = $new_segment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,18 @@ class Shape {
|
|||
$this->edges[] = $record;
|
||||
}
|
||||
|
||||
public function start(): Vector2 {
|
||||
return reset($this->edges)->getStart();
|
||||
}
|
||||
|
||||
public function end(): Vector2 {
|
||||
return end($this->edges)->getEnd();
|
||||
}
|
||||
|
||||
public function is_closed(): bool {
|
||||
return $this->start()->equals($this->end());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the intersection between two Shape.
|
||||
* Shapes part of the clips need to be flat (or they will be flattened)
|
||||
|
@ -46,10 +58,9 @@ class Shape {
|
|||
$self = $this->flatten();
|
||||
$other = $other->flatten();
|
||||
var_dump((new Shape($self->getRecords()))->getArea());
|
||||
var_dump((new \swf2ass\ass\drawTag(new Shape($self->getRecords()), 1))->encode(new \swf2ass\ass\ASSLine(), 1));
|
||||
var_dump((new \swf2ass\ass\drawTag(new Shape($self->getRecords()), 1))->encode(new \swf2ass\ass\ASSEventTime(1, 1, 1)));
|
||||
var_dump((new Shape($other->getRecords()))->getArea());
|
||||
var_dump((new \swf2ass\ass\drawTag(new Shape($other->getRecords()), 1))->encode(new \swf2ass\ass\ASSLine(), 1));
|
||||
fgets(STDIN);
|
||||
var_dump((new \swf2ass\ass\drawTag(new Shape($other->getRecords()), 1))->encode(new \swf2ass\ass\ASSEventTime(1, 1, 1)));
|
||||
return [$this]; //TODO: fix this breakage, some clips being overlapping shapes????
|
||||
}
|
||||
}
|
||||
|
@ -97,10 +108,10 @@ class Shape {
|
|||
$pos = $point;
|
||||
}
|
||||
|
||||
if($shape->getArea() > Constants::EPSILON){ //TODO
|
||||
//if($shape->getArea() > Constants::EPSILON){ //TODO
|
||||
$shape->addRecord(new LineRecord($start, $pos)); //Close shape
|
||||
$shapes[] = $shape;
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
return $shapes;
|
||||
|
|
|
@ -200,22 +200,23 @@ class ShapeConverter {
|
|||
|
||||
$style = $this->styles->getLineStyle($styleId - 1);
|
||||
|
||||
|
||||
//wrap around all segments, even if closed. ASS does NOT like them otherwise. so we draw everything backwards to have border around the line, not just on one side
|
||||
$newSegments = new PendingPath();
|
||||
foreach ($path->segments as $segment) {
|
||||
$segmentStyle = $style;
|
||||
//Close non-closed segments by double drawing backwards
|
||||
if (!$segment->is_closed()) {
|
||||
$other = clone $segment;
|
||||
$other->flip();
|
||||
$segment->merge($other);
|
||||
$other = clone $segment;
|
||||
$other->flip();
|
||||
$segment->merge($other);
|
||||
|
||||
//Reduce width of line style to account for double border
|
||||
$segmentStyle = clone $style;
|
||||
$segmentStyle->width /= 2;
|
||||
}
|
||||
|
||||
$this->commands->commands[] = DrawPath::stroke($segmentStyle, $segment->getShape(), $segment->is_closed());
|
||||
$newSegments->merge_path($segment, false);
|
||||
}
|
||||
|
||||
if(count($newSegments->segments) > 0){
|
||||
//Reduce width of line style to account for double border
|
||||
$fixedStyle = clone $style;
|
||||
$fixedStyle->width /= 2;
|
||||
$this->commands->commands[] = DrawPath::stroke($fixedStyle, $newSegments->getShape());
|
||||
}
|
||||
//TODO: leave this as-is and create a fill in renderer
|
||||
}
|
||||
$this->strokes->map = [];
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ class Vector2 {
|
|||
|
||||
|
||||
public function equals(Vector2 $b, $epsilon = Constants::EPSILON): bool {
|
||||
return abs($b->x - $this->x) <= $epsilon and abs($b->y - $this->y) <= $epsilon;
|
||||
return ($b->x === $this->x or abs($b->x - $this->x) <= $epsilon) and ($b->y === $this->y or abs($b->y - $this->y) <= $epsilon);
|
||||
}
|
||||
|
||||
public function distance(Vector2 $b): float {
|
||||
|
|
|
@ -70,7 +70,7 @@ class ViewFrame {
|
|||
|
||||
$matrixTransform = $parentMatrix;
|
||||
if($this->matrixTransform !== null){
|
||||
$matrixTransform = $parentMatrix !== null ? $parentMatrix->combine($this->matrixTransform) : $this->matrixTransform;
|
||||
$matrixTransform = $parentMatrix !== null ? $this->matrixTransform->multiply($parentMatrix) : $this->matrixTransform;
|
||||
}
|
||||
|
||||
$colorTransform = $parentColor;
|
||||
|
@ -84,16 +84,18 @@ class ViewFrame {
|
|||
$clipPath = null;
|
||||
if ($this->clipDepthMap !== null) {
|
||||
$colorIdentity = ColorTransform::identity();
|
||||
$matrixIdentity = MatrixTransform::identity();
|
||||
$matrixIdentity = $parentMatrix;
|
||||
foreach ($this->clipDepthMap as $clipDepth => $clipFrame) {
|
||||
//TODO: detect rectangle clips?
|
||||
//TODO: clip clips?
|
||||
foreach ($clipFrame->render($clipDepth, $depthChain, $colorIdentity, $matrixIdentity)->getObjects() as $clipObject) {
|
||||
$clipShape = new ClipPath();
|
||||
foreach ($clipObject->drawPathList->commands as $p) {
|
||||
$s = $p->commands->flatten();
|
||||
if($s->getArea() > Constants::EPSILON){
|
||||
$clipShape->addShape($s);
|
||||
if($p->style instanceof FillStyleRecord){ //Only clip with fills
|
||||
$s = $p->commands->flatten();
|
||||
if($s->getArea() > Constants::EPSILON){
|
||||
$clipShape->addShape($s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -119,19 +119,22 @@ class ViewLayout {
|
|||
$frame = new ViewFrame($this->getObjectId(), null);
|
||||
/** @var ClippingViewLayout[] $clipMap */
|
||||
$clipMap = [];
|
||||
/** @var ViewFrame[] $clipFrame */
|
||||
$clipFrame = [];
|
||||
|
||||
ksort($this->depthMap);
|
||||
|
||||
foreach ($this->depthMap as $depth => $child) {
|
||||
if ($child instanceof ClippingViewLayout) {
|
||||
$clipMap[$depth] = $child;
|
||||
$clipFrame[$depth] = $child->nextFrame($actionList);
|
||||
} 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);
|
||||
$clips[$clipDepth] = $clipFrame[$clipDepth];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
55
src/ass/ASSEventTime.php
Normal file
55
src/ass/ASSEventTime.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
class ASSEventTime {
|
||||
|
||||
/**
|
||||
* Frame duration in milliseconds
|
||||
* @var float
|
||||
*/
|
||||
public float $frame_duration;
|
||||
|
||||
public ASSTime $start;
|
||||
public int $start_frame;
|
||||
|
||||
public ASSTime $end;
|
||||
public int $end_frame;
|
||||
|
||||
public int $duration;
|
||||
|
||||
public function __construct(int $startFrame, int $duration, float $frameDurationMilliseconds, int $decimalPrecision = 2){
|
||||
$this->frame_duration = $frameDurationMilliseconds;
|
||||
$this->start_frame = $startFrame;
|
||||
$this->start = new ASSTime($this->start_frame * $frameDurationMilliseconds, $decimalPrecision, true);
|
||||
$this->end_frame = $startFrame + $duration;
|
||||
$this->end = new ASSTime($this->end_frame * $frameDurationMilliseconds, $decimalPrecision, false);
|
||||
$this->duration = $duration;
|
||||
}
|
||||
|
||||
|
||||
public function getMillisecondsFromStartOffset(int $frameOffset) : int {
|
||||
if(($this->start_frame + $frameOffset) > $this->end_frame){
|
||||
throw new \Exception("Out of bounds: {$this->start_frame} + $frameOffset > {$this->end_frame}");
|
||||
}
|
||||
|
||||
return ($this->frame_duration * $frameOffset) + $this->start->adjusted_ms_error;
|
||||
}
|
||||
|
||||
|
||||
public function getMillisecondsFromEndOffset(int $frameOffset) : int {
|
||||
if($frameOffset > $this->duration){
|
||||
throw new \Exception("Out of bounds: $frameOffset > {$this->duration}");
|
||||
}
|
||||
|
||||
return $this->getMillisecondsFromStartOffset($this->duration - $frameOffset);
|
||||
}
|
||||
|
||||
public function slice(int $frameOffset, int $frameDuration = 1) : ASSEventTime{
|
||||
if(($this->start_frame + $frameOffset + $frameDuration) > $this->end_frame){
|
||||
throw new \Exception("Out of bounds: {$this->start_frame} + $frameOffset + $frameDuration > {$this->end_frame}");
|
||||
}
|
||||
|
||||
return new ASSEventTime($this->start_frame + $frameOffset, $frameDuration, $this->start->adjusted_ms_precision);
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@ use swf2ass\RenderedFrame;
|
|||
use swf2ass\RenderedObject;
|
||||
|
||||
class ASSLine {
|
||||
const HOURS_MS = 1000 * 3600;
|
||||
const MINUTES_MS = 1000 * 60;
|
||||
|
||||
/** @var int[] */
|
||||
public array $layer;
|
||||
|
@ -26,7 +24,7 @@ class ASSLine {
|
|||
public bool $isComment = false;
|
||||
|
||||
/** @var ASSTag[] */
|
||||
public array $tags;
|
||||
public array $tags = [];
|
||||
|
||||
private ?string $cachedEncode = null;
|
||||
|
||||
|
@ -107,28 +105,6 @@ class ASSLine {
|
|||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: libass parses milliseconds in a way that anything different than 2 decimal precisions will make it fail.
|
||||
* TODO: use \t for exact timed line entries
|
||||
*
|
||||
* @param int $ms
|
||||
* @param int $msPrecision
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeTime(int $ms, int $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;
|
||||
|
||||
$msPadding = 3 + $msPrecision;
|
||||
return sprintf("%01d:%02d:%0{$msPadding}.{$msPrecision}F", $hours, $minutes, $ms / 1000);
|
||||
}
|
||||
|
||||
public function dropCache(){
|
||||
$this->cachedEncode = null;
|
||||
}
|
||||
|
@ -151,9 +127,22 @@ class ASSLine {
|
|||
if($frameDurationMs === 1000 and $msPrecision === 2 and $this->cachedEncode !== null){
|
||||
return $this->cachedEncode;
|
||||
}
|
||||
$line = ($this->isComment ? "Comment" : "Dialogue") . ": " . $this->getPackedLayer() . "," . self::encodeTime($this->start * $frameDurationMs, $msPrecision) . "," . self::encodeTime(($this->end + 1) * $frameDurationMs, $msPrecision) . "," . $this->style . "," . $this->name . "," . $this->marginLeft . "," . $this->marginRight . "," . $this->marginVertical . "," . $this->effect . ",";
|
||||
|
||||
$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($this, $frameDurationMs) . "}";
|
||||
$line .= "{" . $tag->encode($assEventTime) . "}";
|
||||
}
|
||||
if($frameDurationMs === 1000){
|
||||
$this->cachedEncode = $line;
|
||||
|
|
|
@ -14,25 +14,29 @@ class ASSRenderer {
|
|||
|
||||
/** @var ASSLine[] */
|
||||
private array $runningBuffer = [];
|
||||
private array $settings;
|
||||
private static array $settings = [];
|
||||
|
||||
|
||||
public function getSetting($name, $default = null){
|
||||
return $this->settings[$name] ?? $default;
|
||||
public static function getSetting($name, $default = null){
|
||||
return self::$settings[$name] ?? $default;
|
||||
}
|
||||
|
||||
public function __construct(float $frameRate, Rectangle $viewPort, array $settings = []) {
|
||||
$this->settings = $settings;
|
||||
public static function setSettings(array $settings){
|
||||
self::$settings = $settings;
|
||||
}
|
||||
|
||||
public function __construct(float $frameRate, Rectangle $viewPort) {
|
||||
$display = $viewPort->toPixel();
|
||||
$width = $display->getWidth() * $this->getSetting("videoScaleMultiplier", 1);
|
||||
$height = $display->getHeight() * $this->getSetting("videoScaleMultiplier", 1);
|
||||
$width = $display->getWidth() * self::getSetting("videoScaleMultiplier", 1);
|
||||
$height = $display->getHeight() * self::getSetting("videoScaleMultiplier", 1);
|
||||
$ar = $width / $height;
|
||||
|
||||
if(($frameRate * 2) <= 60){
|
||||
/*if(($frameRate * 2) <= 60){
|
||||
$frameRate *= 2;
|
||||
}
|
||||
}*/
|
||||
$frameRate *= self::getSetting("videoRateMultiplier", 1);
|
||||
|
||||
$timerPrecision = sprintf("%.4F", (100 / $this->getSetting("timerSpeed", 100)) * 100);
|
||||
$timerPrecision = sprintf("%.4F", (100 / self::getSetting("timerSpeed", 100)) * 100);
|
||||
|
||||
$this->header = <<<ASSHEADER
|
||||
[Script Info]
|
||||
|
@ -48,7 +52,7 @@ PlayResY: {$height}
|
|||
Timer: {$timerPrecision}
|
||||
|
||||
[Aegisub Project Garbage]
|
||||
Last Style Storage: Default
|
||||
Last Style Storage: f
|
||||
Video File: ?dummy:{$frameRate}:10000:{$width}:{$height}:160:160:160:c
|
||||
Video AR Value: {$ar}
|
||||
|
||||
|
@ -75,11 +79,12 @@ ASSHEADER;
|
|||
|
||||
$runningBuffer = [];
|
||||
|
||||
$scale = MatrixTransform::scale(new Vector2($this->getSetting("videoScaleMultiplier", 1), $this->getSetting("videoScaleMultiplier", 1)));
|
||||
$scale = MatrixTransform::scale(new Vector2(self::getSetting("videoScaleMultiplier", 1), self::getSetting("videoScaleMultiplier", 1)));
|
||||
|
||||
$animated = 0;
|
||||
foreach ($objects as $object) {
|
||||
$object = clone $object;
|
||||
$object->matrixTransform = $scale->combine($object->matrixTransform);
|
||||
$object->matrixTransform = $scale->multiply($object->matrixTransform); //TODO order?
|
||||
|
||||
$depth = $object->getDepth();
|
||||
|
||||
|
@ -99,6 +104,7 @@ ASSHEADER;
|
|||
$tag = $tag->transition($information, $object);
|
||||
if($tag !== null){
|
||||
$transitionedTags[] = $tag;
|
||||
$tag->dropCache();
|
||||
}else{
|
||||
$canTransition = false;
|
||||
break;
|
||||
|
@ -106,11 +112,12 @@ ASSHEADER;
|
|||
}
|
||||
|
||||
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, $this->getSetting("bakeTransforms", false)) as $line) {
|
||||
foreach (ASSLine::fromRenderObject($information, $object, self::getSetting("bakeTransforms", false)) as $line) {
|
||||
$line->style = "f";
|
||||
$line->dropCache();
|
||||
$runningBuffer[] = $line;
|
||||
|
@ -118,11 +125,13 @@ ASSHEADER;
|
|||
}
|
||||
}
|
||||
|
||||
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() * ($this->getSetting("timerSpeed", 100) / 100), $this->getSetting("timePrecision"));
|
||||
yield $line->encode($information->getFrameDurationMilliSeconds() * (self::getSetting("timerSpeed", 100) / 100), self::getSetting("timePrecision"));
|
||||
}
|
||||
|
||||
$this->runningBuffer = $runningBuffer;
|
||||
|
@ -132,7 +141,7 @@ ASSHEADER;
|
|||
foreach ($this->runningBuffer as $line) {
|
||||
$line->name .= " f:{$line->start}>{$line->end}~".($line->end - $line->start + 1);
|
||||
$line->dropCache();
|
||||
yield $line->encode($information->getFrameDurationMilliSeconds() * ($this->getSetting("timerSpeed", 100) / 100), $this->getSetting("timePrecision"));
|
||||
yield $line->encode($information->getFrameDurationMilliSeconds() * (self::getSetting("timerSpeed", 100) / 100), self::getSetting("timePrecision"));
|
||||
}
|
||||
$this->runningBuffer = [];
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@ interface ASSTag {
|
|||
|
||||
public function equals(ASSTag $tag): bool;
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string;
|
||||
public function encode(ASSEventTime $event): string;
|
||||
}
|
56
src/ass/ASSTime.php
Normal file
56
src/ass/ASSTime.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace swf2ass\ass;
|
||||
|
||||
class ASSTime {
|
||||
const HOURS_MS = 1000 * 3600;
|
||||
const MINUTES_MS = 1000 * 60;
|
||||
|
||||
public int $milliseconds_total;
|
||||
public int $milliseconds_total_adjusted;
|
||||
|
||||
public int $hours;
|
||||
public int $minutes;
|
||||
public int $seconds;
|
||||
public int $milliseconds;
|
||||
|
||||
public int $adjusted_ms;
|
||||
public int $adjusted_ms_precision;
|
||||
public int $adjusted_ms_error;
|
||||
|
||||
public function __construct(int $ms, int $decimalPrecision = 2, bool $roundDown = true){
|
||||
if ($ms < 0) {
|
||||
throw new \LogicException("ms less than 0: $ms");
|
||||
}
|
||||
|
||||
$this->milliseconds_total = $ms;
|
||||
$this->adjusted_ms_precision = $decimalPrecision;
|
||||
|
||||
$this->hours = intdiv($ms, self::HOURS_MS);
|
||||
$ms -= $this->hours * self::HOURS_MS;
|
||||
$this->minutes = intdiv($ms, self::MINUTES_MS);
|
||||
$ms -= $this->minutes * self::MINUTES_MS;
|
||||
|
||||
$this->seconds = intdiv($ms, 1000);
|
||||
$this->milliseconds = $ms - $this->seconds * 1000;
|
||||
|
||||
$ms_adjustement = (10 * (3 - $this->adjusted_ms_precision));
|
||||
$this->adjusted_ms = intdiv($this->milliseconds, $ms_adjustement);
|
||||
|
||||
$this->adjusted_ms_error = $this->milliseconds - $this->adjusted_ms * $ms_adjustement;
|
||||
|
||||
if(!$roundDown and $this->adjusted_ms_error > 0){
|
||||
$this->adjusted_ms++;
|
||||
$this->adjusted_ms_error -= $ms_adjustement;
|
||||
}
|
||||
$this->milliseconds_total_adjusted = $this->milliseconds_total + $this->adjusted_ms_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: libass parses milliseconds in a way that anything different than 2 decimal precisions will make it fail.
|
||||
* @return string
|
||||
*/
|
||||
public function encode() : string{
|
||||
return sprintf("%01d:%02d:%02d.%0{$this->adjusted_ms_precision}d", $this->hours, $this->minutes, $this->seconds, $this->adjusted_ms);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ class blurEdgesGaussianTag implements ASSStyleTag {
|
|||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return "\\blur" . round($this->strength, 4);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class blurEdgesTag implements ASSStyleTag {
|
|||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return "\\be{$this->strength}";
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class borderTag implements ASSStyleTag {
|
|||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
if($this->size->x === $this->size->y){
|
||||
return sprintf("\\bord%.02F", $this->size->x);
|
||||
}else{
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace swf2ass\ass;
|
|||
|
||||
class clipTag extends clippingTag {
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
$scaleMultiplier = 2 ** ($this->scale - 1);
|
||||
return $this->isNull ? "" : "\\clip({$this->scale}," . implode(" ", $this->getCommands($scaleMultiplier, $this->scale >= 5 ? 0 : 2)) . ")";
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use MathPHP\LinearAlgebra\MatrixFactory;
|
|||
use swf2ass\ClipPath;
|
||||
use swf2ass\ColorTransform;
|
||||
use swf2ass\DrawPath;
|
||||
use swf2ass\LineStyleRecord;
|
||||
use swf2ass\MatrixTransform;
|
||||
use swf2ass\Shape;
|
||||
use swf2ass\StyleRecord;
|
||||
|
@ -24,7 +25,7 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
public function transitionColor(ASSLine $line, ColorTransform $transform): ?containerTag {
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
$index = $line->end - $line->start;
|
||||
if(!isset($container->transitions[$index])){
|
||||
$container->transitions[$index] = [];
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
$index = $line->end - $line->start;
|
||||
if(!isset($container->transitions[$index])){
|
||||
$container->transitions[$index] = [];
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
public function transitionStyleRecord(ASSLine $line, StyleRecord $record): ?containerTag {
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
$index = $line->end - $line->start;
|
||||
if(!isset($container->transitions[$index])){
|
||||
$container->transitions[$index] = [];
|
||||
}
|
||||
|
@ -115,6 +116,10 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
|
||||
$container->try_append(new clipTag($clip));
|
||||
|
||||
if($path->style instanceof LineStyleRecord){ //Convert to fill
|
||||
|
||||
}
|
||||
|
||||
$container->try_append(borderTag::fromStyleRecord($path->style));
|
||||
$container->try_append(shadowTag::fromStyleRecord($path->style));
|
||||
$container->try_append(lineColorTag::fromStyleRecord($path->style)->applyColorTransform($colorTransform));
|
||||
|
@ -169,30 +174,40 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
return false;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
$ret = "";
|
||||
foreach ($this->tags as $tag) {
|
||||
if(!($tag instanceof drawingTag)){
|
||||
$ret .= $tag->encode($line, $frameDurationMs);
|
||||
$ret .= $tag->encode($event);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->transitions as $index => $transitions){
|
||||
if(count($transitions) === 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: clone $line?
|
||||
//TODO: animations with smoothing really don't play well. maybe allow them when only one animation "direction" exists, or smooth them manually?
|
||||
//Or just don't animate MatrixTransform / do it in a single tick
|
||||
$ret .= "\\t(" . (floor($frameDurationMs * $index) - 1) . "," . (floor($frameDurationMs * ($index + 1)) - 1) . ",";
|
||||
//$ret .= "\\t(" . floor($frameDurationMs * ($index + 1)) . "," . floor($frameDurationMs * ($index + 1)) . ",";
|
||||
|
||||
if(ASSRenderer::getSetting("smoothTransitions", false)){
|
||||
$startTime = $event->getMillisecondsFromStartOffset($index - 1) + 1;
|
||||
$endTime = $event->getMillisecondsFromStartOffset($index + 1) - 1;
|
||||
}else{
|
||||
$startTime = $event->getMillisecondsFromStartOffset($index) - 1;
|
||||
$endTime = $event->getMillisecondsFromStartOffset($index);
|
||||
}
|
||||
|
||||
$ret .= "\\t({$startTime},{$endTime},";
|
||||
foreach ($transitions as $tag){
|
||||
$ret .= $tag->encode($line, $frameDurationMs);
|
||||
$ret .= $tag->encode($event);
|
||||
}
|
||||
$ret .= ")";
|
||||
}
|
||||
foreach ($this->tags as $tag) {
|
||||
if($tag instanceof drawingTag){
|
||||
$ret .= $tag->encode($line, $frameDurationMs);
|
||||
$ret .= $tag->encode($event);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
|
@ -201,7 +216,7 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
public function transitionShape(ASSLine $line, Shape $shape): ?ASSPathTag {
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
$index = $line->end - $line->start;
|
||||
if(!isset($container->transitions[$index])){
|
||||
$container->transitions[$index] = [];
|
||||
}
|
||||
|
@ -223,7 +238,7 @@ class containerTag implements ASSColorTag, ASSPositioningTag, ASSStyleTag, ASSPa
|
|||
public function transitionClipPath(ASSLine $line, ?ClipPath $clip): ?ASSClipPathTag {
|
||||
$container = clone $this;
|
||||
|
||||
$index = $line->end - $line->start - 1;
|
||||
$index = $line->end - $line->start;
|
||||
if(!isset($container->transitions[$index])){
|
||||
$container->transitions[$index] = [];
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class drawTag extends drawingTag implements ASSPathTag {
|
|||
return $this->shape->equals($shape) ? $this : null;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
$scaleMultiplier = 2 ** ($this->scale - 1);
|
||||
return "\\p".$this->scale."}" . implode(" ", $this->getCommands($scaleMultiplier, $this->scale >= 5 ? 0 : 2)) . "{\\p0";
|
||||
}
|
||||
|
|
|
@ -6,13 +6,11 @@ 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;
|
||||
|
@ -87,6 +85,11 @@ abstract class drawingTag implements ASSTag {
|
|||
$lastEdge = $edge; //TODO
|
||||
}
|
||||
|
||||
/*if(!$this->shape->is_closed()){
|
||||
$coords = $this->shape->start()->multiply($scale / Constants::TWIP_SIZE);
|
||||
$commands[] = "n " . round($coords->x, $precision) . " " . round($coords->y, $precision);
|
||||
}*/
|
||||
|
||||
return $commands;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class fillColorTag extends colorTag {
|
|||
return new fillColorTag(null, null);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return $this->color === null ? "\\1a&HFF&" : ("\\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(255 - $this->color->alpha))) . "&");
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace swf2ass\ass;
|
|||
|
||||
class insideClipTag extends clippingTag {
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
$scaleMultiplier = 2 ** ($this->scale - 1);
|
||||
return $this->isNull ? "" : "\\iclip({$this->scale}," . implode(" ", $this->getCommands($scaleMultiplier, $this->scale >= 5 ? 0 : 2)) . ")";
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class lineColorTag extends colorTag {
|
|||
return new lineColorTag(null, null);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return $this->color === null ? "\\3a&HFF&" : ("\\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(255 - $this->color->alpha))) . "&");
|
||||
}
|
||||
}
|
|
@ -22,35 +22,84 @@ class matrixTransformTag implements ASSPositioningTag {
|
|||
}
|
||||
|
||||
public function transitionMatrixTransform(ASSLine $line, MatrixTransform $transform): ?matrixTransformTag {
|
||||
$new = self::fromMatrixTransform($transform);
|
||||
return $this->equals($new) ? $this : null; //TODO: check this
|
||||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
return sprintf("\\matrix(%.05F,%.05F,%.05F,%.05F,%.02F,%.02F)", $this->transform->get_a()->toFloat(), $this->transform->get_b()->toFloat(), $this->transform->get_c()->toFloat(), $this->transform->get_d()->toFloat(), $this->transform->get_tx()->divide(Constants::TWIP_SIZE)->toFloat(), $this->transform->get_ty()->divide(Constants::TWIP_SIZE)->toFloat())
|
||||
. $this->scale->encode($line, $frameDurationMs) . $this->rotation->encode($line, $frameDurationMs) . $this->shear->encode($line, $frameDurationMs);
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return /*sprintf("\\matrix(%.05F,%.05F,%.05F,%.05F,%.02F,%.02F)", $this->transform->get_a()->toFloat(), $this->transform->get_b()->toFloat(), $this->transform->get_c()->toFloat(), $this->transform->get_d()->toFloat(), $this->transform->get_tx()->divide(Constants::TWIP_SIZE)->toFloat(), $this->transform->get_ty()->divide(Constants::TWIP_SIZE)->toFloat())
|
||||
. */$this->scale->encode($event) . $this->rotation->encode($event) . $this->shear->encode($event);
|
||||
}
|
||||
|
||||
public function equals(ASSTag $tag): bool {
|
||||
return $tag instanceof $this and $this->transform->equals($tag->transform) and $this->scale->equals($tag->scale) and $this->rotation->equals($tag->rotation) and $this->shear->equals($tag->shear);
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
|
||||
$isZero = function (RealNumber $v) : bool{
|
||||
return $v->isZero(new RealNumber("0.00001")); ///TODO
|
||||
};
|
||||
private static function getByTransform_NumericallyStable(MatrixTransform $transform) : ?matrixTransformTag{
|
||||
|
||||
$scale_x = $scale_y = $frx = $fry = $frz = $fax = $fay = new RealNumber(0);
|
||||
//Numerically stable implementation by MrSmile
|
||||
|
||||
$a = $transform->get_a();
|
||||
$b = $transform->get_b();
|
||||
$c = $transform->get_c();
|
||||
$d = $transform->get_d();
|
||||
|
||||
if(($isZero($a) and $isZero($b)) or ($isZero($c) and $isZero($d))){
|
||||
throw new \Exception("Invalid transform");
|
||||
$ac2 = $a->power(2)->add($c->power(2));
|
||||
$bd2 = $b->power(2)->add($d->power(2));
|
||||
|
||||
$det = $a->multiply($d)->subtract($b->multiply($c));
|
||||
$dot = $a->multiply($b)->add($c->multiply($d));
|
||||
|
||||
$scale_x = $scale_y = $frx = $fry = $frz = $fax = $fay = new RealNumber(0);
|
||||
|
||||
if($ac2->greater($bd2)){
|
||||
if($ac2->greater(0)){
|
||||
$frz = new RealNumber(atan2($c->toFloat(), $a->toFloat()) * (180 / M_PI));
|
||||
$scale_x = $ac2->sqrt();
|
||||
$scale_y = $det->absolute()->divide($ac2->sqrt());
|
||||
$fax = $dot->divide($ac2);
|
||||
|
||||
if($det->lesser(0)){
|
||||
$frz = $frz->negate();
|
||||
$frx = new RealNumber(180);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if($bd2->greater(0)){
|
||||
$frz = new RealNumber(atan2($b->negate()->toFloat(), $d->toFloat()) * (180 / M_PI));
|
||||
$scale_x = $det->absolute()->divide($bd2->sqrt());
|
||||
$scale_y = $bd2->sqrt();
|
||||
$fay = $dot->divide($bd2);
|
||||
|
||||
if($det->lesser(0)){
|
||||
$frz = $frz->negate();
|
||||
$fry = new RealNumber(180);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$frz = $frz->negate();
|
||||
$fscx = $scale_x->absolute()->multiply(100);
|
||||
$fscy = $scale_y->absolute()->multiply(100);
|
||||
|
||||
return new matrixTransformTag($transform, new Vector2($fscx->toFloat(), $fscy->toFloat()), $frx->toFloat(), $fry->toFloat(), $frz->toFloat(), $fax->toFloat(), $fay->toFloat());
|
||||
}
|
||||
|
||||
|
||||
private static function getByTransform_NumericallyUnstable(MatrixTransform $transform) : ?matrixTransformTag{
|
||||
|
||||
//Numerically unstable implementation by Oneric
|
||||
|
||||
$isZero = function (RealNumber $v) : bool{
|
||||
return $v->isZero(new RealNumber("0.000001")); ///TODO
|
||||
};
|
||||
|
||||
$a = $transform->get_a();
|
||||
$b = $transform->get_b();
|
||||
$c = $transform->get_c();
|
||||
$d = $transform->get_d();
|
||||
|
||||
$scale_x = $scale_y = $frx = $fry = $frz = $fax = $fay = new RealNumber(0);
|
||||
|
||||
if(
|
||||
!(
|
||||
($isZero($a) and !$isZero($b))
|
||||
|
@ -115,15 +164,22 @@ class matrixTransformTag implements ASSPositioningTag {
|
|||
$scale_x = $a->power(2)->add($c->power(2))->sqrt();
|
||||
$frz = new RealNumber(atan($c->toFloat() / $a->toFloat()) * (180 / M_PI));
|
||||
if($a->lesser(0)){ // atan always yields positive cos
|
||||
$style->frz = $frz->add(180);
|
||||
$frz->frz = $frz->add(180);
|
||||
}
|
||||
}else {
|
||||
echo $transform . "\n";
|
||||
throw new \Exception("Invalid transform state. This should not happen");
|
||||
}
|
||||
|
||||
$frz = $frz->negate();
|
||||
|
||||
$fscx = $scale_x->absolute()->multiply(100);
|
||||
$fscy = $scale_y->absolute()->multiply(100);
|
||||
|
||||
return new matrixTransformTag($transform, new Vector2($fscx->toFloat(), $fscy->toFloat()), $frx->toFloat(), $fry->toFloat(), $frz->toFloat(), $fax->toFloat(), $fay->toFloat());
|
||||
}
|
||||
|
||||
public static function fromMatrixTransform(MatrixTransform $transform): ?matrixTransformTag {
|
||||
return self::getByTransform_NumericallyStable($transform);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ class originTag implements ASSTag {
|
|||
$this->origin = $origin;
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return "\\org(" . round($this->origin->x, 5) ."," . round($this->origin->y, 5) .")";
|
||||
}
|
||||
|
||||
|
|
|
@ -66,19 +66,18 @@ class positionTag implements ASSPositioningTag {
|
|||
}
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
$frame = $line->end - $line->start;
|
||||
public function encode(ASSEventTime $event): string {
|
||||
$hasMoved = $this->start !== $this->end;
|
||||
|
||||
$shift = $this->end - $this->start;
|
||||
|
||||
if($hasMoved){
|
||||
if($shift > 1){
|
||||
$start = ceil(($this->start - 1) * $frameDurationMs) + 1;
|
||||
$end = floor($this->end * $frameDurationMs) - 1;
|
||||
if($shift > 1 or ASSRenderer::getSetting("smoothTransitions", false)){
|
||||
$start = $event->getMillisecondsFromStartOffset($this->start - 1);
|
||||
$end = $event->getMillisecondsFromStartOffset($this->end);
|
||||
}else{
|
||||
$start = floor($this->start * $frameDurationMs) - 1;
|
||||
$end = floor(($this->end - 1) * $frameDurationMs) - 1;
|
||||
$start = $event->getMillisecondsFromStartOffset($this->start) - 1;
|
||||
$end = $event->getMillisecondsFromStartOffset($this->start);
|
||||
}
|
||||
return "\\move(" . $this->from->x ."," . $this->from->y ."," . $this->to->x ."," . $this->to->y .",".$start.",".$end.")";
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class rotationTag implements ASSPositioningTag {
|
|||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return sprintf("\\frx%.2F\\fry%.2F\\frz%.2F", $this->x, $this->y, $this->z);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class scaleTag implements ASSPositioningTag {
|
|||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return sprintf("\\fscx%.5F\\fscy%.5F", $this->scale->x, $this->scale->y);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class shadowTag implements ASSStyleTag {
|
|||
return self::fromStyleRecord($record);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return "\\shad{$this->depth}";
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class shearingTag implements ASSPositioningTag {
|
|||
return self::fromMatrixTransform($transform);
|
||||
}
|
||||
|
||||
public function encode(ASSLine $line, float $frameDurationMs): string {
|
||||
public function encode(ASSEventTime $event): string {
|
||||
return sprintf("\\fax%.5F\\fay%.5F", $this->shear->x, $this->shear->y);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ class RealMatrix extends ObjectMatrix{
|
|||
return $this->m === $this->n;
|
||||
}
|
||||
|
||||
public function isEqual(Matrix $B): bool
|
||||
public function isEqual(Matrix $B, ?RealNumber $epsilon = null): bool
|
||||
{
|
||||
if (!$this->isEqualSizeAndType($B)) {
|
||||
return false;
|
||||
|
@ -60,7 +60,7 @@ class RealMatrix extends ObjectMatrix{
|
|||
for ($i = 0; $i < $m; $i++) {
|
||||
for ($j = 0; $j < $n; $j++) {
|
||||
/** @var $B RealNumber[][] */
|
||||
if (!$B[$i][$j]->equalWithEpsilon($this->A[$i][$j])) {
|
||||
if (!$B[$i][$j]->equalWithEpsilon($this->A[$i][$j], $epsilon)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
33
swf2ass.php
33
swf2ass.php
|
@ -4,9 +4,13 @@ require_once __DIR__ . "/vendor/autoload.php";
|
|||
|
||||
$settings = [
|
||||
"videoScaleMultiplier" => 1, //TODO: not finished, leave at 1
|
||||
|
||||
//TODO: make this actually interpolate smooth transitions in a baked way
|
||||
"videoRateMultiplier" => 1, //Sets the "framerate" multiplier for output video file, not lines. Helps with smoothing transitions if enabled.
|
||||
"bakeTransforms" => false, //TODO: fix ASS matrix transform rendering and remove this
|
||||
"timerSpeed" => 100, //NOTE: libass does not implement "Timer:", which is used by this setting. Leave at 100 by default
|
||||
"timePrecision" => 2, //NOTE: libass does not implement anything different from 2. Leave at 2 by default,
|
||||
"smoothTransitions" => false, //All transitions will happen smoothly from start till finish instead of happening instantly on frame thresholds. NOTE: this can break objects that did not get selected to be animated, or matrix transforms
|
||||
];
|
||||
|
||||
$swfContent = file_get_contents($argv[1]);
|
||||
|
@ -59,7 +63,31 @@ $knownFlashSignatures = [
|
|||
new RemovalEntry(null /*3*/, [0, 145]),
|
||||
new RemovalEntry(null /*69*/, [0, 146]),
|
||||
]
|
||||
]
|
||||
],
|
||||
"229d7569ebf3b3b04fb03aa86162ab646d96be0353e8f4be32b1f6bf4e96af4e" => [
|
||||
"name" => "cirno's_arithmetic_school.swf",
|
||||
"remove" => [
|
||||
//remove lyrics button/mask
|
||||
new RemovalEntry(null /*30*/, [0, 259]),
|
||||
new RemovalEntry(null /*31*/, [0, 264]),
|
||||
]
|
||||
],
|
||||
"1203faf6504edf4845e72b957ce6b5c5d92597ecc0328aa36eacaef32eb7125a" => [
|
||||
"name" => "cirno's_arithmetic_school_2011.swf",
|
||||
"remove" => [
|
||||
//remove lyrics button/mask
|
||||
new RemovalEntry(null /*30*/, [0, 229]),
|
||||
new RemovalEntry(null /*31*/, [0, 234]),
|
||||
]
|
||||
],
|
||||
"3bdc7f8bbfb648e825f79dbe6095288871b1780b1d37f8cd2ad4c50cae40b5ca" => [
|
||||
"name" => "IOSYS_CirnoENG.swf",
|
||||
"remove" => [
|
||||
//remove lyrics button/mask
|
||||
new RemovalEntry(null /*33*/, [0, 1721]),
|
||||
new RemovalEntry(null /*34*/, [0, 1752]),
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
if(isset($knownFlashSignatures[$signature])){
|
||||
|
@ -91,7 +119,8 @@ $testVectors = [];
|
|||
|
||||
if ($swf->header["signature"]) {
|
||||
$processor = new \swf2ass\SWFProcessor($swf);
|
||||
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort(), $settings);
|
||||
\swf2ass\ass\ASSRenderer::setSettings($settings);
|
||||
$assRenderer = new \swf2ass\ass\ASSRenderer($processor->getFrameRate(), $processor->getViewPort());
|
||||
|
||||
$keyFrameInterval = 10 * $processor->getFrameRate(); //kf every 10 seconds TODO: make this dynamic, per-shape
|
||||
$lastFrame = null;
|
||||
|
|
Loading…
Reference in a new issue