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);