Passed
Push — master ( d91ac9...ee5b76 )
by Sébastien
01:50
created

VideoInfo::getAudioBitrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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