From f80897e7789e11a74d3985cdf0fdbf8da2cfc631 Mon Sep 17 00:00:00 2001 From: jygaulier Date: Wed, 2 Oct 2024 18:49:22 +0200 Subject: [PATCH] PS-670-rendition-video_enhance1 (#458) * add mime+family to video output ; add video options * rename (ffmpeg) threads option --- .../src/Command/CreateCommand.php | 3 +- .../src/FileFamilyGuesser.php | 7 ++- .../Video/FFMpegTransformerModule.php | 61 ++++++++++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/lib/php/rendition-factory/src/Command/CreateCommand.php b/lib/php/rendition-factory/src/Command/CreateCommand.php index 013ac1aef..f1821e0ad 100644 --- a/lib/php/rendition-factory/src/Command/CreateCommand.php +++ b/lib/php/rendition-factory/src/Command/CreateCommand.php @@ -32,7 +32,7 @@ protected function configure(): void $this->addArgument('src', InputArgument::REQUIRED, 'The source file'); $this->addArgument('build-config', InputArgument::REQUIRED, 'The build config YAML file'); - $this->addOption('type', 't', InputOption::VALUE_REQUIRED, 'The MIME type of file'); + $this->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'Force the MIME type of file'); $this->addOption('working-dir', 'w', InputOption::VALUE_REQUIRED, 'The working directory. Defaults to system temp directory'); $this->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'The output file name WITHOUT extension'); $this->addOption('debug', 'd', InputOption::VALUE_NONE, 'set to debug mode (keep files in working directory)'); @@ -56,6 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (null === $mimeType) { $mimeType = $this->mimeTypeGuesser->guessMimeTypeFromPath($src); + $output->writeln(sprintf('MIME type guessed: %s', $mimeType)); } $buildConfig = $this->yamlLoader->load($input->getArgument('build-config')); diff --git a/lib/php/rendition-factory/src/FileFamilyGuesser.php b/lib/php/rendition-factory/src/FileFamilyGuesser.php index bd7623a30..164780cea 100644 --- a/lib/php/rendition-factory/src/FileFamilyGuesser.php +++ b/lib/php/rendition-factory/src/FileFamilyGuesser.php @@ -38,7 +38,12 @@ public function getFamily(string $src, string $mimeType): FamilyEnum } return match ($mimeType) { - 'application/pdf', 'text/rtf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.oasis.opendocument.spreadsheet' => FamilyEnum::Document, + 'application/pdf', 'text/rtf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.oasis.opendocument.spreadsheet' => FamilyEnum::Document, + + 'application/mxf', 'application/ogg' => FamilyEnum::Video, + default => FamilyEnum::Unknown, }; } diff --git a/lib/php/rendition-factory/src/Transformer/Video/FFMpegTransformerModule.php b/lib/php/rendition-factory/src/Transformer/Video/FFMpegTransformerModule.php index f965622a4..dee9188bf 100644 --- a/lib/php/rendition-factory/src/Transformer/Video/FFMpegTransformerModule.php +++ b/lib/php/rendition-factory/src/Transformer/Video/FFMpegTransformerModule.php @@ -2,12 +2,11 @@ namespace Alchemy\RenditionFactory\Transformer\Video; -use Alchemy\RenditionFactory\Context\TransformationContext; use Alchemy\RenditionFactory\Context\TransformationContextInterface; -use Alchemy\RenditionFactory\DTO\FamilyEnum; use Alchemy\RenditionFactory\DTO\InputFileInterface; use Alchemy\RenditionFactory\DTO\OutputFile; use Alchemy\RenditionFactory\DTO\OutputFileInterface; +use Alchemy\RenditionFactory\FileFamilyGuesser; use Alchemy\RenditionFactory\Transformer\TransformerModuleInterface; use FFMpeg; use FFMpeg\Coordinate\TimeCode; @@ -43,7 +42,7 @@ public function transform(InputFileInterface $inputFile, array $options, Transfo throw new \InvalidArgumentException(sprintf('Invalid format %s', $format)); } - private function doVideo(string $format, string $extension, InputFileInterface $inputFile, array $options, TransformationContext $context): OutputFileInterface + private function doVideo(string $format, string $extension, InputFileInterface $inputFile, array $options, TransformationContextInterface $context): OutputFileInterface { $fqcnFormat = 'FFMpeg\\Format\\Video\\'.$format; /** @var FormatInterface $ouputFormat */ @@ -61,8 +60,52 @@ private function doVideo(string $format, string $extension, InputFileInterface $ } $ouputFormat->setAudioCodec($audioCodec); } + if (null !== ($videoKilobitrate = $options['video_kilobitrate'] ?? null)) { + if(!method_exists($ouputFormat, 'setKiloBitrate')) { + throw new \InvalidArgumentException(sprintf('format %s does not support video_kilobitrate', $format)); + } + if(!is_int($videoKilobitrate)) { + throw new \InvalidArgumentException('Invalid video kilobitrate'); + } + $ouputFormat->setKiloBitrate($videoKilobitrate); + } + if (null !== ($audioKilobitrate = $options['audio_kilobitrate'] ?? null)) { + if(!method_exists($ouputFormat, 'setAudioKiloBitrate')) { + throw new \InvalidArgumentException(sprintf('format %s does not support audio_kilobitrate', $format)); + } + if(!is_int($audioKilobitrate)) { + throw new \InvalidArgumentException('Invalid audio kilobitrate'); + } + $ouputFormat->setAudioKiloBitrate($audioKilobitrate); + } + if (null !== ($passes = $options['passes'] ?? null)) { + if(!method_exists($ouputFormat, 'setPasses')) { + throw new \InvalidArgumentException(sprintf('format %s does not support passes', $format)); + } + if(!is_int($passes) || $passes < 1) { + throw new \InvalidArgumentException('Invalid passes count'); + } + if(0 === $videoKilobitrate) { + throw new \InvalidArgumentException('passes must not be set if video_kilobitrate is 0'); + } + $ouputFormat->setPasses($passes); + } + + $ffmpegOptions = []; + if ($timeout = $options['timeout'] ?? null) { + if(!is_int($timeout)) { + throw new \InvalidArgumentException('Invalid timeout'); + } + $ffmpegOptions['timeout'] = $timeout; + } + if ($threads = $options['threads'] ?? null) { + if(!is_int($threads) || $threads < 1) { + throw new \InvalidArgumentException('Invalid threads count'); + } + $ffmpegOptions['ffmpeg.threads'] = $threads; + } + $ffmpeg = FFMpeg\FFMpeg::create($ffmpegOptions, $context->getLogger()); - $ffmpeg = FFMpeg\FFMpeg::create([], $context->getLogger()); /** @var Video $video */ $video = $ffmpeg->open($inputFile->getPath()); @@ -98,10 +141,14 @@ private function doVideo(string $format, string $extension, InputFileInterface $ unset($clip); unset($video); + $mimeType = $context->guessMimeTypeFromPath($outputPath); + $fileFamilyGuesser = new FileFamilyGuesser(); + $family = $fileFamilyGuesser->getFamily($outputPath, $mimeType); + return new OutputFile( $outputPath, - 'application/octet-stream', - FamilyEnum::Unknown + $mimeType, + $family ); } @@ -124,7 +171,7 @@ private function doAudio(string $format, string $extension, InputFileInterface $ throw new \InvalidArgumentException('Audio transformation not implemented'); } - private function preClip(Video $video, array $options, TransformationContext $context): Clip + private function preClip(Video $video, array $options, TransformationContextInterface $context): Clip { $start = $options['start'] ?? 0; $duration = $options['duration'] ?? null;