Passed
Push — master ( b38e7e...085481 )
by Sébastien
03:00
created

VideoInfo::getFileSize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @see       https://github.com/soluble-io/soluble-mediatools 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/blob/master/LICENSE.md MIT
10
 */
11
12
namespace Soluble\MediaTools\Video;
13
14
use Soluble\MediaTools\Common\Exception\IOException;
15
use Soluble\MediaTools\Common\Exception\JsonParseException;
16
use Soluble\MediaTools\Video\Exception\InvalidArgumentException;
17
use Soluble\MediaTools\Video\Info\VideoStreamCollection;
18
19
class VideoInfo implements VideoInfoInterface
20
{
21
    /** @var array<string, mixed> */
22
    private $metadata;
23
24
    /** @var string */
25
    private $file;
26
27
    /** @var array|null */
28
    private $metadataByStreamType;
29
30
    /**
31
     * @var VideoStreamCollection|null;
32
     */
33
    private $cachedVideoStreams;
34
35
    /**
36
     * @param string $fileName reference to filename
37
     * @param array  $metadata metadata as parsed from ffprobe --json
38
     *
39
     * @throws IOException
40
     */
41 16
    public function __construct(string $fileName, array $metadata)
42
    {
43 16
        if (!file_exists($fileName)) {
44 1
            throw new IOException(sprintf(
45 1
                'File %s does not exists',
46 1
                $this->file
47
            ));
48
        }
49 15
        $this->metadata = $metadata;
50 15
        $this->file     = $fileName;
51 15
    }
52
53
    /**
54
     * @throws JsonParseException if json is invalid
55
     */
56 5
    public static function createFromFFProbeJson(string $fileName, string $ffprobeJson): self
57
    {
58 5
        if (trim($ffprobeJson) === '') {
59 1
            throw new JsonParseException('Cannot parse empty json string');
60
        }
61 4
        $decoded = json_decode($ffprobeJson, true);
62 4
        if ($decoded === null) {
63 1
            throw new JsonParseException('Cannot parse json');
64
        }
65
66 3
        return new self($fileName, $decoded);
67
    }
68
69 2
    public function getFile(): string
70
    {
71 2
        return $this->file;
72
    }
73
74
    /**
75
     * @throws IOException
76
     */
77 2
    public function getFileSize(): int
78
    {
79 2
        $size = @filesize($this->file);
80 2
        if ($size === false) {
81 1
            throw new IOException(sprintf(
82 1
                'Cannot get filesize of file %s',
83 1
                $this->file
84
            ));
85
        }
86
87 1
        return $size;
88
    }
89
90 1
    public function getVideoStreams(): VideoStreamCollection
91
    {
92 1
        if ($this->cachedVideoStreams === null) {
93 1
            $videoStreamsMetadata     = array_values($this->getStreamsMetadataByType(self::STREAM_TYPE_VIDEO));
94 1
            $this->cachedVideoStreams = new VideoStreamCollection($videoStreamsMetadata);
95
        }
96
97 1
        return $this->cachedVideoStreams;
98
    }
99
100
    /**
101
     * Format name as returned by ffprobe.
102
     */
103 2
    public function getFormatName(): string
104
    {
105 2
        return $this->metadata['format']['format_name'];
106
    }
107
108
    /**
109
     * @param string $streamType any of self::SUPPORTED_STREAM_TYPES
110
     */
111 2
    public function countStreams(?string $streamType = null): int
112
    {
113 2
        if ($streamType === null) {
114 2
            return count($this->metadata['streams'] ?? []);
115
        }
116
117 1
        return count($this->getStreamsMetadataByType($streamType));
118
    }
119
120 1
    public function getMetadata(): array
121
    {
122 1
        return $this->metadata;
123
    }
124
125 2
    public function getDuration(): float
126
    {
127 2
        return (float) ($this->metadata['format']['duration'] ?? 0.0);
128
    }
129
130
    /**
131
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
132
     *
133
     * @return array<string, int> associative array with 'height' and 'width'
134
     */
135 2
    public function getDimensions(int $streamIndex = 0): array
136
    {
137
        return [
138 2
            'width'  => $this->getWidth($streamIndex),
139 2
            'height' => $this->getHeight($streamIndex),
140
        ];
141
    }
142
143
    /**
144
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
145
     */
146 3
    public function getWidth(int $streamIndex = 0): int
147
    {
148 3
        $videoStream = $this->getVideoStreamsMetadata()[$streamIndex] ?? [];
149
150 3
        return (int) ($videoStream['width'] ?? 0);
151
    }
152
153
    /**
154
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
155
     */
156 3
    public function getHeight(int $streamIndex = 0): int
157
    {
158 3
        $videoStream = $this->getVideoStreamsMetadata()[$streamIndex] ?? [];
159
160 3
        return (int) ($videoStream['height'] ?? 0);
161
    }
162
163
    /**
164
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
165
     */
166 3
    public function getNbFrames(int $streamIndex = 0): int
167
    {
168 3
        $videoStream = $this->getVideoStreamsMetadata()[$streamIndex] ?? [];
169
170 3
        return (int) ($videoStream['nb_frames'] ?? 0);
171
    }
172
173
    /**
174
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
175
     */
176 1
    public function getVideoBitrate(int $streamIndex = 0): int
177
    {
178 1
        $videoStream = $this->getVideoStreamsMetadata()[$streamIndex] ?? [];
179
180 1
        return (int) ($videoStream['bit_rate'] ?? 0);
181
    }
182
183
    /**
184
     * Convenience method to get audio stream bitrate.
185
     *
186
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
187
     */
188 1
    public function getAudioBitrate(int $streamIndex = 0): int
189
    {
190 1
        $audioStream = $this->getAudioStreamsMetadata()[$streamIndex] ?? [];
191
192 1
        return (int) ($audioStream['bit_rate'] ?? 0);
193
    }
194
195
    /**
196
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
197
     */
198 1
    public function getAudioCodecName(int $streamIndex = 0): ?string
199
    {
200 1
        $audioStream = $this->getAudioStreamsMetadata()[$streamIndex] ?? [];
201
202 1
        return $audioStream['codec_name'] ?? null;
203
    }
204
205
    /**
206
     * @param int $streamIndex selected a specific stream by index, default: 0 = the first available
207
     */
208 1
    public function getVideoCodecName(int $streamIndex = 0): ?string
209
    {
210 1
        $videoStream = $this->getVideoStreamsMetadata()[$streamIndex] ?? [];
211
212 1
        return $videoStream['codec_name'] ?? null;
213
    }
214
215
    /**
216
     * @return array<int, array>
217
     */
218 2
    public function getAudioStreamsMetadata(): array
219
    {
220 2
        return array_values($this->getStreamsMetadataByType(self::STREAM_TYPE_AUDIO));
221
    }
222
223
    /**
224
     * @return array<int, array>
225
     */
226 7
    public function getVideoStreamsMetadata(): array
227
    {
228 7
        return array_values($this->getStreamsMetadataByType(self::STREAM_TYPE_VIDEO));
229
    }
230
231
    /**
232
     * @throws InvalidArgumentException
233
     *
234
     * @param string $streamType  any of self::SUPPORTED_STREAM_TYPES
235
     * @param int    $streamIndex selected a specific stream by index, default: 0 = the first available
236
     *
237
     * @return array<string|int, array>
238
     */
239 10
    public function getStreamsMetadataByType(string $streamType, int $streamIndex = 0): array
240
    {
241 10
        if (!in_array($streamType, self::SUPPORTED_STREAM_TYPES, true)) {
242 1
            throw new InvalidArgumentException(sprintf(
243 1
                'Invalid usage, unsupported param $streamType given: %s',
244 1
                $streamType
245
            ));
246
        }
247
248 9
        return $this->getMetadataByStreamType()[$streamType];
249
    }
250
251 9
    private function getMetadataByStreamType(): array
252
    {
253 9
        if ($this->metadataByStreamType === null) {
254
            $streams = [
255 9
                self::STREAM_TYPE_VIDEO => [],
256 9
                self::STREAM_TYPE_AUDIO => [],
257 9
                self::STREAM_TYPE_DATA  => [],
258
            ];
259 9
            foreach ($this->metadata['streams'] as $stream) {
260 9
                $type = mb_strtolower($stream['codec_type']);
261
                switch ($type) {
262 9
                    case self::STREAM_TYPE_VIDEO:
263 9
                        $streams[self::STREAM_TYPE_VIDEO][] = $stream;
264 9
                        break;
265 9
                    case self::STREAM_TYPE_AUDIO:
266 9
                        $streams[self::STREAM_TYPE_AUDIO][] = $stream;
267 9
                        break;
268
                    case self::STREAM_TYPE_DATA:
269
                        $streams[self::STREAM_TYPE_DATA][] = $stream;
270
                        break;
271
                    default:
272
                        $streams[$type][] = $stream;
273
                }
274
            }
275 9
            $this->metadataByStreamType = $streams;
276
        }
277
278 9
        return $this->metadataByStreamType;
279
    }
280
}
281