Skip to content

Commit

Permalink
Merge branch 'PS-670-rendition-video_enhance3' of github.com:alchemy-…
Browse files Browse the repository at this point in the history
…fr/phraseanet-services into w2445
  • Loading branch information
nmaillat committed Nov 14, 2024
2 parents 671ff29 + bb8e419 commit 9c56f7a
Show file tree
Hide file tree
Showing 12 changed files with 982 additions and 116 deletions.
13 changes: 13 additions & 0 deletions lib/php/rendition-factory-bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ services:
autoconfigure: true

Alchemy\RenditionFactory\Command\CreateCommand: ~
Alchemy\RenditionFactory\Command\ConfigCommand: ~

Alchemy\RenditionFactory\Context\TransformationContextFactory: ~
Alchemy\RenditionFactory\FileFamilyGuesser: ~
Expand Down Expand Up @@ -81,6 +82,18 @@ services:
tags:
- { name: !php/const Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\FormatInterface::TAG }

Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\WavFormat:
tags:
- { name: !php/const Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\FormatInterface::TAG }

Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\AacFormat:
tags:
- { name: !php/const Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\FormatInterface::TAG }

Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\Mp3Format:
tags:
- { name: !php/const Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\FormatInterface::TAG }


Imagine\Imagick\Imagine: ~
Imagine\Image\ImagineInterface: '@Imagine\Imagick\Imagine'
Expand Down
169 changes: 169 additions & 0 deletions lib/php/rendition-factory/src/Command/ConfigCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
<?php

declare(strict_types=1);

namespace Alchemy\RenditionFactory\Command;

use Alchemy\RenditionFactory\Config\YamlLoader;
use Alchemy\RenditionFactory\DTO\FamilyEnum;
use Alchemy\RenditionFactory\Transformer\TransformerModuleInterface;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;

#[AsCommand('alchemy:rendition-factory:config')]
class ConfigCommand extends Command
{
public function __construct(
private readonly YamlLoader $yamlLoader,
#[TaggedLocator(TransformerModuleInterface::TAG, defaultIndexMethod: 'getName')]
private readonly ServiceLocator $transformers,
) {
parent::__construct();
}

protected function configure(): void
{
parent::configure();

$this->addArgument('build-config', InputArgument::OPTIONAL, 'A build config YAML file to validate')
->addOption('module', 'm', InputOption::VALUE_REQUIRED, 'Display optiond for a specific module')
->setHelp('Display the options for a module, or validate a build-config YAML file.')
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
if ($transformerName = $input->getOption('module')) {
/** @var TransformerModuleInterface $transformer */
$transformer = $this->transformers->get($transformerName);
$output->writeln($this->getTransformerDocumentation($transformerName, $transformer));
}

if ($buildConfigPath = $input->getArgument('build-config')) {
$buildConfig = $this->yamlLoader->load($buildConfigPath);

foreach (FamilyEnum::cases() as $family) {
$familyConfig = $buildConfig->getFamily($family);
if (null === $familyConfig) {
continue;
}

$output->writeln(sprintf('Family "%s":', $family->name));
foreach ($familyConfig->getTransformations() as $transformation) {
$transformerName = $transformation->getModule();
$output->writeln(sprintf(' - %s', $transformerName));

/** @var TransformerModuleInterface $transformer */
$transformer = $this->transformers->get($transformerName);
$output->writeln($this->getTransformerDocumentation($transformerName, $transformer, $transformation->getOptions()));
}
}
} else {
$transformers = array_flip($this->transformers->getProvidedServices());
ksort($transformers);
$last_parent = null;
foreach ($transformers as $fqcn => $transformerName) {
/** @var TransformerModuleInterface $transformer */
$transformer = $this->transformers->get($transformerName);
// $parent = get_parent_class($transformer);
// if ($parent !== $last_parent) {
// if ($last_parent) {
// // $output->writeln("\n\n## parent foot: $last_parent\n");
// $output->writeln($last_parent::getDocumentationFooter());
// }
// if ($parent) {
// // $output->writeln("\n\n## parent head: $parent\n");
// $output->writeln($parent::getDocumentationHeader());
// }
// $last_parent = $parent;
// }
$output->writeln($this->no_getTransformerDocumentation($fqcn, $transformerName, $transformer));
}
}

return 0;
}

private function getTransformerDocumentation(string $transformerName, TransformerModuleInterface $transformer, array $options): string
{
$doc = "\n\n## $transformerName\n";

if (method_exists($transformer, 'getDocumentationHeader')) {
$doc .= $transformer->getDocumentationHeader()."\n";
}

if (method_exists($transformer, 'buildConfiguration')) {
$treeBuilder = new TreeBuilder('root');
$transformer->buildConfiguration($treeBuilder->getRootNode()->children());

$node = $treeBuilder->buildTree();
$dumper = new YamlReferenceDumper();

$t = $dumper->dumpNode($node);
$t = preg_replace("#^root:(\n( {4})?|\s+\[])#", "-\n", (string) $t);
$t = str_replace("\n\n", "\n", $t);
$t = str_replace("\n", "\n ", $t);
// $t = preg_replace("#^root:(\n( {4})?|\s+\[])#", '', (string)$t);
// $t = preg_replace("#\n {4}#", "\n", $t);
// $t = preg_replace("#\n\n#", "\n", $t);
// $t = trim(preg_replace("#^\n+#", '', $t));

$doc .= "```yaml\n".$t."```\n";
// var_dump($options);

$processor = new Processor();
$processor->process($treeBuilder->buildTree(), ['root' => $options]);

}

if (method_exists($transformer, 'getDocumentationFooter')) {
$doc .= $transformer->getDocumentationFooter()."\n";
}

return $doc;
}

private function no_getTransformerDocumentation(string $fqcn, string $transformerName, TransformerModuleInterface $transformer): string
{
$doc = "\n\n## $transformerName\n";

$reflectionClass = new \ReflectionClass($fqcn);
if ($reflectionClass->hasMethod('getDocumentationHeader') && $reflectionClass->getMethod('getDocumentationHeader')->class == $fqcn) {
$doc .= $transformer->getDocumentationHeader()."\n";
}

if (method_exists($transformer, 'buildConfiguration')) {
$treeBuilder = new TreeBuilder('root');
$transformer->buildConfiguration($treeBuilder->getRootNode()->children());

$node = $treeBuilder->buildTree();
$dumper = new YamlReferenceDumper();

$t = $dumper->dumpNode($node);
$t = preg_replace("#^root:(\n( {4})?|\s+\[])#", "-\n", (string) $t);
$t = str_replace("\n\n", "\n", $t);
$t = str_replace("\n", "\n ", $t);
// $t = preg_replace("#^root:(\n( {4})?|\s+\[])#", '', (string)$t);
// $t = preg_replace("#\n {4}#", "\n", $t);
// $t = preg_replace("#\n\n#", "\n", $t);
// $t = trim(preg_replace("#^\n+#", '', $t));

$doc .= "```yaml\n".$t."```\n";
}

if ($reflectionClass->hasMethod('getDocumentationFooter') && $reflectionClass->getMethod('getDocumentationFooter')->class == $fqcn) {
$doc .= $transformer->getDocumentationFooter()."\n";
}

return $doc;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,88 @@

use Alchemy\RenditionFactory\Config\ModuleOptionsResolver;
use Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\FormatInterface;
use Imagine\Image\ImagineInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;

abstract readonly class AbstractVideoTransformer
{
public function __construct(#[AutowireLocator(FormatInterface::TAG, defaultIndexMethod: 'getFormat')] protected ServiceLocator $formats,
protected ModuleOptionsResolver $optionsResolver,
protected ImagineInterface $imagine,
) {
}

public static function no_getDocumentationHeader(): ?string
{
return <<<_DOC_
# Rendition Factory for video-input modules (wip)
## Common options
### `enabled` (optional)
Used to disable a whole module from the build chain.
__default__: true
### `format` (mandatory)
A format defines the output file :
- family (image, video, audio, animation, document, unknown)
- mime type (unique mime type for this type of file)
- extension (possible extenstion(s) for this type of file)
For a specific module, only a subset of formats may be available, e.g.:
Since `video_to_frame` extracts one image from the video, the only supported output format(s)
are ones of family=image.
see below "Output formats" for the list of available formats.
--------------------------------------------
# Modules
_DOC_;
}

public static function no_getDocumentationFooter(): ?string
{
return <<<_DOC_
--------------------------------------------
## Output formats
| format | family | mime type | extension(s) |
|-----------------|-----------|------------------|--------------|
| animated-gif | Animation | image/gif | gif |
| animated-png | Animation | image/png | apng, png |
| animated-webp | Animation | image/webp | webp |
| image-jpeg | Image | image/jpeg | jpg, jpeg |
| video-mkv | Video | video/x-matroska | mkv |
| video-mpeg4 | Video | video/mp4 | mp4 |
| video-mpeg | Video | video/mpeg | mpeg |
| video-quicktime | Video | video/quicktime | mov |
| video-webm | Video | video/webm | webm |
--------------------------------------------
## Resize modes
### `inset`
The output is garanteed to fit in the requested size (width, height) and the aspect ratio is kept.
- If only one dimension is provided, the other is computed.
- If both dimensions are provided, the output is resize so the biggest dimension fits into the rectangle.
- If no dimension is provided, the output is the same size as the input.
--------------------------------------------
## twig context
input.width
input.height
input.duration
_DOC_;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format;

use Alchemy\RenditionFactory\DTO\FamilyEnum;
use Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\Audio\Aac;

class AacFormat implements FormatInterface
{
private Aac $format;

public function __construct()
{
$this->format = new Aac();
}

public static function getAllowedExtensions(): array
{
return ['aac', 'm4a'];
}

public static function getMimeType(): string
{
return 'audio/aac';
}

public static function getFormat(): string
{
return 'audio-aac';
}

public static function getFamily(): FamilyEnum
{
return FamilyEnum::Audio;
}

public function getFFMpegFormat(): Aac
{
return $this->format;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file replaces FFMpeg\Format\Audio\aac because alpine lacks libfdk
*/

namespace Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format\Audio;

use FFMpeg\Format\Audio\DefaultAudio;

/**
* The AAC audio format.
*/
class Aac extends DefaultAudio
{
public function __construct()
{
$this->audioCodec = 'aac';
}

public function getAvailableAudioCodecs()
{
return ['aac'];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Alchemy\RenditionFactory\Transformer\Video\FFMpeg\Format;

use Alchemy\RenditionFactory\DTO\FamilyEnum;
use FFMpeg\Format\Audio\Mp3;

class Mp3Format implements FormatInterface
{
private Mp3 $format;

public function __construct()
{
$this->format = new Mp3();
}

public static function getAllowedExtensions(): array
{
return ['mp3'];
}

public static function getMimeType(): string
{
return 'audio/mp3';
}

public static function getFormat(): string
{
return 'audio-mp3';
}

public static function getFamily(): FamilyEnum
{
return FamilyEnum::Audio;
}

public function getFFMpegFormat(): Mp3
{
return $this->format;
}
}
Loading

0 comments on commit 9c56f7a

Please sign in to comment.