Passed
Push — master ( c03b7d...d95c98 )
by Sébastien
01:47
created

ConvertDirCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 13
ccs 11
cts 11
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    {
69 3
        $helper = $this->getHelper('question');
70
71
        // ########################
72
        // Step 1: Check directory
73
        // ########################
74
75 3
        $directory = $input->getOption('dir');
76 3
        Assert::stringNotEmpty($directory);
77 2
        Assert::directory($directory);
78
79
        // ########################
80
        // Step 2: Init preset
81
        // ########################
82
83 1
        Assert::stringNotEmpty($input->getOption('preset'));
84 1
        $preset = $this->getPreset($input->getOption('preset'));
85
86
        // ########################
87
        // Step 3: Output dir
88
        // ########################
89
90 1
        if ($input->getOption('output') !== null) {
91 1
            $outputDir = $input->getOption('output');
92 1
            Assert::directory($outputDir);
93 1
            Assert::writable($outputDir);
94
        } else {
95
            $outputDir = $directory;
96
        }
97 1
        Assert::stringNotEmpty($outputDir);
98
99
        // ########################
100
        // Step 4: Extensions
101
        // ########################
102
103 1
        if ($input->getOption('exts') !== null) {
104 1
            $tmp = $input->getOption('exts');
105 1
            Assert::stringNotEmpty($tmp);
106 1
            $exts = array_filter(
107 1
                array_map(
108 1
                    'trim',
109 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

109
                    explode(',', /** @scrutinizer ignore-type */ $tmp)
Loading history...
110
                )
111
            );
112 1
            Assert::minCount($exts, 1);
113
        } else {
114
            $exts = FileExtensions::BUILTIN_EXTENSIONS;
115
        }
116
117 1
        $recursive = $input->getOption('recursive') === true;
118
119 1
        $no_interaction = $input->getOption('no-interaction') === true;
120
121
        // ########################
122
        // Step 5: Scanning dir
123
        // ########################
124
125 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

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

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

208
    private function grepFrame(/** @scrutinizer ignore-unused */ string $line): int

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The method grepFrame() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
209
    {
210
        return 1;
211
    }
212
213 1
    private function getPreset(string $presetName): PresetInterface
214
    {
215 1
        return $this->presetLoader->getPreset($presetName);
216
    }
217
}
218