Passed
Push — master ( 7f9bd8...8d36fd )
by Sébastien
02:37
created

ScanCommand   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 95.83%

Importance

Changes 0
Metric Value
eloc 106
dl 0
loc 184
ccs 92
cts 96
cp 0.9583
rs 10
c 0
b 0
f 0
wmc 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getVideoFiles() 0 18 2
A configure() 0 8 1
A outputTable() 0 2 1
F execute() 0 119 15
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 ScriptFUSION\Byte\ByteFormatter;
15
use Soluble\MediaTools\Cli\Exception\MissingFFProbeBinaryException;
16
use Soluble\MediaTools\Video\Exception as VideoException;
17
use Soluble\MediaTools\Video\SeekTime;
18
use Soluble\MediaTools\Video\VideoInfoReaderInterface;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Helper\ProgressBar;
21
use Symfony\Component\Console\Helper\Table;
22
use Symfony\Component\Console\Helper\TableCell;
23
use Symfony\Component\Console\Helper\TableSeparator;
24
use Symfony\Component\Console\Helper\TableStyle;
25
use Symfony\Component\Console\Input\InputDefinition;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Input\InputOption;
28
use Symfony\Component\Console\Output\OutputInterface;
29
use Symfony\Component\Finder\Finder;
30
31
class ScanCommand extends Command
32
{
33
    /** @var VideoInfoReaderInterface */
34
    private $reader;
35
36
    /** @var string[] */
37
    private $supportedVideoExtensions = [
38
        'mov',
39
        'mp4',
40
        'm4v',
41
        'mkv',
42
        'flv',
43
        'webm',
44
    ];
45
46 3
    public function __construct(VideoInfoReaderInterface $videoInfoReader)
47
    {
48 3
        $this->reader = $videoInfoReader;
49 3
        parent::__construct();
50 3
    }
51
52
    /**
53
     * Configures the command.
54
     */
55 3
    protected function configure(): void
56
    {
57
        $this
58 3
            ->setName('scan:videos')
59 3
            ->setDescription('Scan for video')
60 3
            ->setDefinition(
61 3
                new InputDefinition([
62 3
                    new InputOption('dir', 'd', InputOption::VALUE_REQUIRED),
63
                ])
64
            );
65 3
    }
66
67 3
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    {
69 3
        if (!$input->hasOption('dir')) {
70
            throw new \InvalidArgumentException('Missing dir argument, use <command> <dir>');
71
        }
72 3
        $videoPath = $input->hasOption('dir') ? $input->getOption('dir') : null;
73 3
        if (!is_string($videoPath) || !is_dir($videoPath)) {
74 1
            throw new \InvalidArgumentException(sprintf(
75 1
                'Video dir %s does not exists',
76 1
                is_string($videoPath) ? $videoPath : json_encode($videoPath)
77
            ));
78
        }
79
80 2
        $output->writeln(sprintf('Scanning %s', $videoPath));
81
82
        // Get the videos in path
83 2
        $videos = $this->getVideoFiles($videoPath);
84
85 2
        $progressBar = new ProgressBar($output, count($videos));
86 2
        $progressBar->start();
87
88 2
        $bitRateFormatter = new ByteFormatter();
89
        //$bitRateFormatter->setUnitDecorator(new SymbolDecorator(SymbolDecorator::SUFFIX_NONE));
90
91 2
        $sizeFormatter = new ByteFormatter();
92
93 2
        $rows      = [];
94 2
        $warnings  = [];
95 2
        $totalSize = 0;
96
97
        /** @var \SplFileInfo $video */
98 2
        foreach ($videos as $video) {
99 2
            $videoFile = $video->getPathname();
100
            try {
101 2
                $info     = $this->reader->getInfo($videoFile);
102 1
                $vStream  = $info->getVideoStreams()->getFirst();
103 1
                $pixFmt   = $vStream->getPixFmt();
104 1
                $bitRate  = $vStream->getBitRate();
105 1
                $fileSize = (int) filesize($videoFile);
106
                $row      = [
107 1
                    $video,
108 1
                    preg_replace('/\.([0-9])+$/', '', SeekTime::convertSecondsToHMSs(round($info->getDuration(), 1))),
109 1
                    $vStream->getCodecName(),
110 1
                    sprintf('%sx%s', $vStream->getWidth(), $vStream->getHeight()),
111 1
                    ($bitRate > 0 ? $bitRateFormatter->format((int) $bitRate) . '/s' : ''),
112 1
                    $sizeFormatter->format($fileSize),
113 1
                    $pixFmt,
114
                ];
115 1
                $rows[] = $row;
116 1
                $totalSize += $fileSize;
117 2
            } catch (VideoException\MissingFFProbeBinaryException $e) {
118 1
                throw new MissingFFProbeBinaryException('Unable to run ffprobe binary, check your config and ensure it\'s properly installed');
119 1
            } catch (VideoException\InfoReaderExceptionInterface $e) {
120 1
                $warnings[] = [$videoFile];
121
            }
122
123 1
            $progressBar->advance();
124
        }
125
126 1
        $output->writeln('');
127
128 1
        $table = new Table($output);
129
130 1
        $rightAlignstyle = new TableStyle();
131 1
        $rightAlignstyle->setPadType(STR_PAD_LEFT);
132
133
        //$table->setStyle($tableStyle);
134 1
        $table->setStyle('box');
135 1
        $table->setHeaders([
136 1
            'file',
137
            'duration',
138
            'codec',
139
            'size',
140
            'bitrate',
141
            'filesize',
142
            'pix_fmt',
143
        ]);
144
145 1
        $previousPath = null;
146 1
        $first        = true;
147
148 1
        foreach ($rows as $row) {
149
            /** @var \SplFileInfo $file */
150 1
            $file   = $row[0];
151 1
            $row[0] = $file->getBasename();
152 1
            if ($previousPath !== $file->getPath()) {
153 1
                if (!$first) {
154
                    $table->addRow(new TableSeparator(['colspan' => count($row)]));
155
                }
156
                //$table->addRow([new TableCell(sprintf('<fg=yellow>%s</>', $file->getPath()), ['colspan' => count($row)])]);
157 1
                $table->addRow([new TableCell(sprintf('<fg=yellow>%s</>', $file->getPath()))]);
158 1
                $table->addRow(new TableSeparator(['colspan' => count($row)]));
159 1
                $previousPath = $file->getPath();
160
            }
161 1
            $table->addRow($row);
162 1
            $first = false;
163
        }
164 1
        foreach ($colIndexes = [1, 2, 3, 4, 5, 6] as $idx) {
0 ignored issues
show
Unused Code introduced by
The assignment to $colIndexes is dead and can be removed.
Loading history...
165 1
            $table->setColumnStyle($idx, $rightAlignstyle);
166
        }
167 1
        $table->addRow(new TableSeparator());
168 1
        $table->addRow(['<fg=cyan>Total</>', '', '', '', '', sprintf('<fg=cyan>%s</>', $sizeFormatter->format($totalSize))]);
169
170 1
        $table->render();
171
172
        // display warnings
173 1
        if (count($warnings) > 0) {
174 1
            $table = new Table($output);
175 1
            $table->setHeaders([
176 1
                'Unsupported files',
177
            ]);
178 1
            $table->setStyle('box');
179 1
            $table->setRows($warnings);
180 1
            $table->render();
181
        }
182
183 1
        $output->writeln("\nFinished");
184
185 1
        return 0;
186
    }
187
188
    private function outputTable(array $rows): void
0 ignored issues
show
Unused Code introduced by
The method outputTable() 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 $rows 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

188
    private function outputTable(/** @scrutinizer ignore-unused */ array $rows): void

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...
189
    {
190
    }
191
192
    /**
193
     * @param string $videoPath
194
     *
195
     * @return array<\SplFileInfo>
196
     */
197 2
    public function getVideoFiles(string $videoPath): array
198
    {
199 2
        $files = (new Finder())->files()
200 2
            ->in($videoPath)
201 2
            ->ignoreUnreadableDirs()
202 2
            ->name(sprintf(
203 2
                '/\.(%s)$/',
204 2
                implode('|', $this->supportedVideoExtensions)
205
            ));
206
207 2
        $videos = [];
208
209
        /** @var \SplFileInfo $file */
210 2
        foreach ($files as $file) {
211 2
            $videos[] = $file;
212
        }
213
214 2
        return $videos;
215
    }
216
}
217