Passed
Push — master ( e25d55...6a7e00 )
by Sébastien
01:44
created

ConvertDirCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
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 2
    public function __construct(MediaToolsServiceInterface $mediaTools, PresetLoader $presetLoader)
43
    {
44 2
        $this->mediaTools               = $mediaTools;
45 2
        $this->presetLoader             = $presetLoader;
46 2
        $this->supportedVideoExtensions = (new FileExtensions())->getMediaExtensions();
47 2
        parent::__construct();
48 2
    }
49
50 2
    protected function configure(): void
51
    {
52
        $this
53 2
            ->setName('convert:directory')
54 2
            ->setDescription('Convert all media files in a directory using a preset')
55 2
            ->setDefinition(
56 2
                new InputDefinition([
57 2
                    new InputOption('dir', ['d'], InputOption::VALUE_REQUIRED, 'Input directory to scan for medias'),
58 2
                    new InputOption('preset', ['p'], InputOption::VALUE_REQUIRED, 'Conversion preset to use'),
59 2
                    new InputOption('exts', ['e', 'extensions'], InputOption::VALUE_OPTIONAL, 'File extensions to process (ie. m4v,mp4,mov)'),
60 2
                    new InputOption('output', ['o', 'out'], InputOption::VALUE_REQUIRED, 'Output directory'),
61 2
                    new InputOption('recursive', 'r', InputOption::VALUE_NONE, 'Recursive mode'),
62
                ])
63
            );
64 2
    }
65
66 2
    protected function execute(InputInterface $input, OutputInterface $output): int
67
    {
68 2
        $helper = $this->getHelper('question');
69
70
        // ########################
71
        // Step 1: Check directory
72
        // ########################
73
74 2
        $directory = $input->getOption('dir');
75 2
        Assert::stringNotEmpty($directory);
76 1
        Assert::directory($directory);
77
78
        // ########################
79
        // Step 2: Init preset
80
        // ########################
81
82
        Assert::stringNotEmpty($input->getOption('preset'));
83
        $preset = $this->getPreset($input->getOption('preset'));
84
85
        // ########################
86
        // Step 3: Output dir
87
        // ########################
88
89
        if ($input->getOption('output') !== null) {
90
            $outputDir = $input->getOption('output');
91
            Assert::directory($outputDir);
92
            Assert::writable($outputDir);
93
        } else {
94
            $outputDir = $directory;
95
        }
96
        Assert::stringNotEmpty($outputDir);
97
98
        // ########################
99
        // Step 4: Extensions
100
        // ########################
101
102
        if ($input->getOption('exts') !== null) {
103
            $tmp = $input->getOption('exts');
104
            Assert::stringNotEmpty($tmp);
105
            $exts = array_filter(
106
                array_map(
107
                    'trim',
108
                    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

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

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

205
    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...
206
    {
207
        return 1;
208
    }
209
210
    private function getPreset(string $presetName): PresetInterface
211
    {
212
        return $this->presetLoader->getPreset($presetName);
213
    }
214
}
215