245 lines
8.9 KiB
PHP
245 lines
8.9 KiB
PHP
<?php
|
|
|
|
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
|
|
"gradientSlices" => /*24,*/\swf2ass\Gradient::AUTO_SLICES,
|
|
];
|
|
|
|
$swfContent = file_get_contents($argv[1]);
|
|
$signature = hash("sha256", $swfContent, false);
|
|
|
|
|
|
$swf = new \swf\SWF($swfContent);
|
|
$swfContent = null;
|
|
unset($swfContent);
|
|
|
|
$fp = fopen($argv[2], "w+");
|
|
|
|
$fromFrame = isset($argv[3]) ? (int) $argv[3] : null;
|
|
$frameEnd = isset($argv[4]) ? (int) $argv[4] : null;
|
|
if($fromFrame !== null and $frameEnd === null){
|
|
$frameEnd = $fromFrame;
|
|
}
|
|
|
|
class RemovalEntry{
|
|
//TODO: accept names as well?
|
|
public ?int $objectId;
|
|
public ?array $depth;
|
|
|
|
/**
|
|
* @param int $objectId
|
|
* @param int[] $depth
|
|
*/
|
|
public function __construct(?int $objectId, ?array $depth){
|
|
$this->objectId = $objectId;
|
|
$this->depth = $depth;
|
|
}
|
|
|
|
public function equals(\swf2ass\RenderedObject $object): bool {
|
|
return ($this->objectId === null or $object->objectId === $this->objectId) and ($this->depth === null or (count($object->depth) >= count($this->depth) and array_slice($object->depth, 0, count($this->depth)) === $this->depth));
|
|
}
|
|
}
|
|
|
|
|
|
$frameOffset = 0;
|
|
|
|
$objectRemovalEntries = [];
|
|
|
|
//TODO: make this a JSON file elsewhere
|
|
$knownFlashSignatures = [
|
|
"52e75b7d6831293ebf4e5b28574a60f5bce10b1eae8afa4e69ab213a98b0b008" => [
|
|
"name" => "IJSW.swf",
|
|
"remove" => [
|
|
//removes playback menus
|
|
new RemovalEntry(null /*2*/, [0, 31]),
|
|
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])){
|
|
$e = $knownFlashSignatures[$signature];
|
|
echo "Found known signature for " . $e["name"] .", adding rules\n";
|
|
$objectRemovalEntries = $e["remove"];
|
|
if(isset($e["frameOffset"])){
|
|
$frameOffset = $e["frameOffset"];
|
|
}
|
|
if(isset($e["frameEnd"])){
|
|
$frameEnd = $e["frameEnd"];
|
|
}
|
|
}
|
|
|
|
//Function to decide whether to display object or not
|
|
function filterObject(\swf2ass\RenderedObject $object) : bool{
|
|
global $objectRemovalEntries;
|
|
/** @var RemovalEntry[] $objectRemovalEntries */
|
|
foreach ($objectRemovalEntries as $entry){
|
|
if($entry->equals($object)){
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
$testVectors = [];
|
|
|
|
if ($swf->header["signature"]) {
|
|
$processor = new \swf2ass\SWFProcessor($swf);
|
|
\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;
|
|
while(($frame = $processor->nextFrameOutput()) !== null){
|
|
$lastFrame = $frame;
|
|
if(!$processor->isPlaying() or $processor->getLoops() > 0){
|
|
break;
|
|
}
|
|
$audio = $processor->getAudio();
|
|
if($audio !== null and $frameOffset === 0){
|
|
if($audio->getStartFrame() === null){
|
|
continue;
|
|
}
|
|
$frameOffset = $audio->getStartFrame();
|
|
}
|
|
|
|
$frame->setFrameOffset($frameOffset);
|
|
|
|
$rendered = $frame->getFrame()->render(0, [], null, null);
|
|
|
|
if($frame->getFrameNumber() === 0){
|
|
foreach ($rendered->getObjects() as $ob){
|
|
echo "frame 0: object {$ob->objectId} depth: " . implode(",", $ob->depth) . PHP_EOL;
|
|
}
|
|
}
|
|
|
|
$filteredRendered = new \swf2ass\RenderedFrame();
|
|
|
|
$drawCalls = 0;
|
|
$drawItems = 0;
|
|
$filteredObjects = 0;
|
|
$clipCalls = 0;
|
|
$clipItems = 0;
|
|
foreach ($rendered->getObjects() as $object){
|
|
if(filterObject($object)){
|
|
++$filteredObjects;
|
|
continue;
|
|
}
|
|
if($object->clip !== null){
|
|
++$clipCalls;
|
|
$clipItems += count($object->clip->getShape()->getRecords());
|
|
}
|
|
foreach ($object->drawPathList->commands as $path){
|
|
++$drawCalls;
|
|
$drawItems += count($path->commands->getRecords());
|
|
}
|
|
$filteredRendered->add($object);
|
|
}
|
|
echo "=== frame ".$frame->getFrameNumber()."/".$processor->getExpectedFrameCount()." ~ $frameOffset : Depth count: " . count($frame->getFrame()->getDepthMap()) . " :: Object count: " . count($filteredRendered->getObjects()) ." :: Paths: $drawCalls draw calls, $drawItems items :: Filtered: $filteredObjects :: Clips: $clipCalls draw calls, $clipItems items" . PHP_EOL;
|
|
|
|
|
|
if($fromFrame !== null){
|
|
if($frame->getFrameNumber() < $fromFrame){
|
|
continue;
|
|
}else{
|
|
foreach ($rendered->getObjects() as $object){
|
|
$count = 0;
|
|
|
|
foreach ($object->drawPathList->commands as $i => $path){
|
|
foreach ($path->commands->getRecords() as $j => $record){
|
|
$v = [
|
|
"objectId" => $object->objectId,
|
|
"depth" => implode(".", $object->getDepth()) . "[$i][$j]",
|
|
"transform" => $object->matrixTransform->toArray(false)
|
|
];
|
|
|
|
if($record instanceof \swf2ass\MoveRecord or $record instanceof \swf2ass\LineRecord){
|
|
$v["vector"] = $record->start->toArray();
|
|
$testVectors[] = $v;
|
|
$v["vector"] = $record->to->toArray();
|
|
$testVectors[] = $v;
|
|
}else if($record instanceof \swf2ass\QuadraticCurveRecord){
|
|
$v["vector"] = $record->start->toArray();
|
|
$testVectors[] = $v;
|
|
$v["vector"] = $record->control->toArray();
|
|
$testVectors[] = $v;
|
|
$v["vector"] = $record->anchor->toArray();
|
|
$testVectors[] = $v;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($assRenderer->renderFrame($frame, $filteredRendered) as $line){
|
|
fwrite($fp, $line . "\n");
|
|
}
|
|
|
|
if($frame->getFrameNumber() > 0 and $frame->getFrameNumber() % $keyFrameInterval === 0){
|
|
foreach ($assRenderer->flush($frame) as $line){
|
|
fwrite($fp, $line . "\n");
|
|
}
|
|
}
|
|
|
|
if($frameEnd !== null and $frame->getFrameNumber() >= $frameEnd){
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach ($assRenderer->flush($lastFrame) as $line){
|
|
fwrite($fp, $line . "\n");
|
|
}
|
|
|
|
if($processor->getAudio() !== null and $processor->getAudio()->getFormat() === \swf2ass\AudioStream::FORMAT_MP3){
|
|
$audioFp = fopen($argv[2] . ".mp3", "w+");
|
|
fwrite($audioFp, $processor->getAudio()->getAudioData());
|
|
fclose($audioFp);
|
|
}
|
|
}
|
|
|
|
if(count($testVectors) > 0){
|
|
file_put_contents($argv[2] . ".test.json", json_encode($testVectors, JSON_PRETTY_PRINT));
|
|
}
|
|
|
|
fclose($fp);
|
|
|
|
|