Completed
Push — master ( f72769...eb1d3e )
by Sébastien
08:28 queued 06:05
created

VideoInfo::getMetadataByStreamType()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.3357

Importance

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