ConvertDirCommand   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 82.22%

Importance

Changes 0
Metric Value
wmc 14
eloc 94
dl 0
loc 184
ccs 74
cts 90
cp 0.8222
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A configure() 0 13 1
A getPreset() 0 3 1
C execute() 0 140 11
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @see       https://github.com/soluble-io/soluble-mediatools-cli for the canonical repository
7
 *
8
 * @copyright Copyright (c) 2018-2019 Sébastien Vanvelthem. (https://github.com/belgattitude)
9
 * @license   https://github.com/soluble-io/soluble-mediatools-cli/blob/master/LICENSE.md MIT
10
 */
11
12
namespace Soluble\MediaTools\Cli\Command;
13
14
use Soluble\MediaTools\Cli\FileSystem\DirectoryScanner;
15
use Soluble\MediaTools\Cli\Media\FileExtensions;
16
use Soluble\MediaTools\Cli\Media\MediaScanner;
17
use Soluble\MediaTools\Cli\Service\MediaToolsServiceInterface;
18
use Soluble\MediaTools\Cli\Util\FFMpegProgress;
19
use Soluble\MediaTools\Common\Exception\ProcessException;
20
use Soluble\MediaTools\Preset\PresetInterface;
21
use Soluble\MediaTools\Preset\PresetLoader;
22
use Symfony\Component\Console\Command\Command;
23
use Symfony\Component\Console\Helper\ProgressBar;
24
use Symfony\Component\Console\Input\InputDefinition;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Question\ConfirmationQuestion;
29
use Webmozart\Assert\Assert;
30
31
class ConvertDirCommand extends Command
32
{
33
    /** @var MediaToolsServiceInterface */
34
    private $mediaTools;
35
36
    /** @var PresetLoader */
37
    private $presetLoader;
38
39
    /** @var string[] */
40
    private $supportedVideoExtensions;
41
42 3
    public function __construct(MediaToolsServiceInterface $mediaTools, PresetLoader $presetLoader)
43
    {
44 3
        $this->mediaTools               = $mediaTools;
45 3
        $this->presetLoader             = $presetLoader;
46 3
        $this->supportedVideoExtensions = (new FileExtensions())->getMediaExtensions();
47 3
        parent::__construct();
48 3
    }
49
50 3
    protected function configure(): void
51
    {
52
        $this
53 3
            ->setName('convert:directory')
54 3
            ->setDescription('Convert all media files in a directory using a preset')
55 3
            ->setDefinition(
56 3
                new InputDefinition([
57 3
                    new InputOption('dir', ['d'], InputOption::VALUE_REQUIRED, 'Input directory to scan for medias'),
58 3
                    new InputOption('preset', ['p'], InputOption::VALUE_REQUIRED, 'Conversion preset to use'),
59 3
                    new InputOption('exts', ['e', 'extensions'], InputOption::VALUE_OPTIONAL, 'File extensions to process (ie. m4v,mp4,mov)'),
60 3
                    new InputOption('output', ['o', 'out'], InputOption::VALUE_REQUIRED, 'Output directory'),
61 3
                    new InputOption('recursive', 'r', InputOption::VALUE_NONE, 'Recursive mode'),
62 3
                    new InputOption('no-interaction', 'n', InputOption::VALUE_NONE, 'No interaction, set yes to all'),
63
                ])
64
            );
65 3
    }
66
67 3
    /**
68
     * @psalm-suppress PossiblyInvalidArgument
69 3
     */
70
    protected function execute(InputInterface $input, OutputInterface $output): int
71
    {
72
        $helper = $this->getHelper('question');
73
74
        // ########################
75 3
        // Step 1: Check directory
76 3
        // ########################
77 2
78
        $directory = $input->getOption('dir');
79
        Assert::stringNotEmpty($directory);
80
        Assert::directory($directory);
81
82
        // ########################
83 1
        // Step 2: Init preset
84 1
        // ########################
85
86
        Assert::stringNotEmpty($input->getOption('preset'));
87
        $preset = $this->getPreset($input->getOption('preset'));
88
89
        // ########################
90 1
        // Step 3: Output dir
91 1
        // ########################
92 1
93 1
        if ($input->getOption('output') !== null) {
94
            $outputDir = $input->getOption('output');
95
            Assert::directory($outputDir);
96
            Assert::writable($outputDir);
97 1
        } else {
98
            $outputDir = $directory;
99
        }
100
        Assert::stringNotEmpty($outputDir);
101
102
        // ########################
103 1
        // Step 4: Extensions
104 1
        // ########################
105 1
106 1
        if ($input->getOption('exts') !== null) {
107 1
            $tmp = $input->getOption('exts');
108 1
            Assert::stringNotEmpty($tmp);
109 1
            $exts = array_filter(
110
                array_map(
111
                    'trim',
112 1
                    explode(',', $tmp)
0 ignored issues
show
Bug introduced by
It seems like $tmp can also be of type string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

112
                    explode(',', /** @scrutinizer ignore-type */ $tmp)
Loading history...
113
                )
114
            );
115
            Assert::minCount($exts, 1);
116
        } else {
117 1
            $exts = FileExtensions::BUILTIN_EXTENSIONS;
118
        }
119 1
120
        $recursive = $input->getOption('recursive') === true;
121
122
        $no_interaction = $input->getOption('no-interaction') === true;
123
124
        // ########################
125 1
        // Step 5: Scanning dir
126
        // ########################
127
128 1
        $output->writeln(sprintf('* Scanning %s for media files...', $directory));
0 ignored issues
show
Bug introduced by
It seems like $directory can also be of type string[]; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
        $output->writeln(sprintf('* Scanning %s for media files...', /** @scrutinizer ignore-type */ $directory));
Loading history...
129
130 1
        // Get the videos in path
131
        $files = (new DirectoryScanner())->findFiles($directory, $exts, $recursive);
132 1
133 1
        $output->writeln('* Reading metadata...');
134
135
        $readProgress = new ProgressBar($output, count($files));
136 1
        $readProgress->start();
137 1
138
        $medias = (new MediaScanner($this->mediaTools->getReader()))->getMedias($files, function () use ($readProgress): void {
139 1
            $readProgress->advance();
140
        });
141
142 1
        $readProgress->finish();
143
144 1
        // Ask confirmation
145
        ScanCommand::renderMediaInTable($output, $medias['rows'], $medias['totalSize']);
146 1
147
        $question = new ConfirmationQuestion('Convert files ?', false);
148
149
        if (!$no_interaction && !$helper->ask($input, $output, $question)) {
150 1
            return 0;
151
        }
152
153 1
        $converter = $this->mediaTools->getConverter();
154 1
155
        // @var \SplFileInfo $file
156
        foreach ($medias['rows'] as $row) {
157 1
            $file = $row['file'];
158 1
159
            try {
160 1
                $outputFile = sprintf(
161 1
                    '%s/%s%s',
162
                    $outputDir,
163
                    $file->getBasename($file->getExtension()),
164 1
                    $preset->getFileExtension()
165
                );
166 1
167
                $tmpFile = sprintf('%s.tmp', $outputFile);
168
169
                if (realpath($outputFile) === realpath((string) $file)) {
170 1
                    throw new \RuntimeException(sprintf('Conversion error, input and output files are the same: %s', $outputFile));
171 1
                }
172 1
173 1
                if (!file_exists($outputFile)) {
174
                    $progress    = new FFMpegProgress();
175 1
                    $progressBar = new ProgressBar($output, (int) $row['total_time']);
176
                    $params      = $preset->getParams($file->getPathname());
177
178 1
                    $output->writeln(sprintf('Convert %s to %s', $file->getBasename(), $outputFile));
179 1
180 1
                    $converter->convert((string) $file, $tmpFile, $params, function (string $stdOut, string $stdErr) use ($progressBar, $progress): void {
181
                        $info = $progress->getProgress($stdErr);
182
                        if (!is_array($info)) {
183 1
                            return;
184
                        }
185
                        $progressBar->setProgress((int) $info['time']);
186
                    });
187
                    $progressBar->finish();
188
                    $output->writeln('');
189
                    $success = rename($tmpFile, $outputFile);
190
                    if (!$success) {
191
                        throw new \RuntimeException(sprintf(
192
                            'Cannot rename temp file %s to %s',
193
                            basename($tmpFile),
194
                            $outputFile
195
                        ));
196
                    }
197
198
                    $output->writeln(sprintf('<fg=green>- Converted:</> %s.', $file));
199 1
                } else {
200 1
                    $output->writeln(sprintf('<fg=yellow>- Skipped:</> %s : Output file already exists (%s).', (string) $file, $outputFile));
201
                }
202
            } catch (ProcessException $e) {
203
                $output->writeln(sprintf('<fg=red>- Skipped:</> %s : Not a valid media file.', (string) $file));
204 1
            }
205
        }
206 1
207
        $output->writeln('');
208
209
        return 0;
210
    }
211
212
    private function getPreset(string $presetName): PresetInterface
213
    {
214 1
        return $this->presetLoader->getPreset($presetName);
215
    }
216
}
217