Improve the performance of burning text/ass subtitle streams into the video.

This commit is contained in:
Abdulmhsen B. A. A.
2024-08-12 16:43:07 +03:00
parent 15095c5981
commit c80c42f5fe

View File

@@ -8,9 +8,11 @@ use App\Libs\Attributes\Route\Get;
use App\Libs\DataUtil;
use App\Libs\Enums\Http\Status;
use App\Libs\Stream;
use DateInterval;
use JsonException;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Psr\Log\LoggerInterface as iLogger;
use Psr\SimpleCache\CacheInterface as iCache;
use Psr\SimpleCache\InvalidArgumentException;
use RuntimeException;
@@ -26,7 +28,7 @@ readonly class Segments
'dvd_subtitle',
];
public function __construct(private iCache $cache)
public function __construct(private iCache $cache, private iLogger $logger)
{
}
@@ -69,11 +71,13 @@ readonly class Segments
$incr = 0;
$subIndex = [];
$internalSubs = [];
foreach (ag($json, 'streams', []) as $id => $stream) {
if ('subtitle' !== ag($stream, 'codec_type')) {
continue;
}
$subIndex[$id] = $incr;
$internalSubs[$incr] = $stream;
$incr++;
}
@@ -235,8 +239,22 @@ readonly class Segments
$cmd[] = '-vf';
$cmd[] = "subtitles={$tmpSubFile}" . ($isIntel ? ',format=nv12,hwupload' : '');
} elseif (null !== $subtitle && !$overlay) {
$subStreamIndex = (int)$subIndex[$subtitle];
$tmpSubFile = r("{path}/t-{name}-internal-sub-{index}.{type}", [
'path' => sys_get_temp_dir(),
'name' => $token,
'index' => $subStreamIndex,
'type' => $internalSubs[$subStreamIndex]['codec_name'],
]);
$streamLink = $this->extractTextSubTitle(
$tmpVidFile,
$internalSubs[$subStreamIndex]['codec_name'],
$subStreamIndex,
$tmpSubFile
);
$cmd[] = '-vf';
$cmd[] = "subtitles={$tmpVidFile}:stream_index=" . (int)$subIndex[$subtitle] . ($isIntel ? ',format=nv12,hwupload' : '');
$cmd[] = "subtitles={$streamLink}" . ($isIntel ? ',format=nv12,hwupload' : '');
} else {
$cmd[] = '-sn';
}
@@ -316,6 +334,58 @@ readonly class Segments
}
}
/**
* @throws InvalidArgumentException
*/
private function extractTextSubTitle(string $path, string $type, int $stream, string $cacheFile): string
{
$cacheKey = md5("{$path}:" . filesize($path) . ":{$stream}");
if (null !== ($cached = $this->cache->get($cacheKey, null))) {
$stream = Stream::make($cacheFile, 'w');
$stream->write($cached);
$stream->close();
return $cacheFile;
}
$cmd = [
'ffmpeg',
'-xerror',
'-hide_banner',
'-loglevel',
'error',
'-i',
'file:' . $path,
'-map',
"0:s:{$stream}",
'-f',
$type,
'pipe:1',
];
try {
$process = new Process($cmd);
$process->setTimeout($stream ? 120 : 60);
$process->start();
$process->wait();
if (!$process->isSuccessful()) {
$this->logger->error(join(' ', $cmd) . $process->getErrorOutput());
return "{$path}:stream_index={$stream}";
}
$body = $process->getOutput();
$this->cache->set($cacheKey, $body, new DateInterval('PT1H'));
$stream = Stream::make($cacheFile, 'w');
$stream->write($body);
$stream->close();
return $cacheFile;
} catch (Throwable $e) {
$this->logger->error($e->getMessage(), ['trace' => $e->getTrace()]);
return "{$path}:stream_index={$stream}";
}
}
private function getStream(array $streams, int $index): array
{
foreach ($streams as $stream) {