Completed
Push — id3-metadata-objects ( ecc500...384c4c )
by Daniel
04:34
created

TagReader::readFrameFlags()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 26
ccs 0
cts 19
cp 0
rs 8.8571
cc 2
eloc 19
nc 2
nop 0
crap 6
1
<?php
2
/**
3
 * This file is part of the Metadata project.
4
 *
5
 * @author Daniel Schröder <[email protected]>
6
 */
7
8
namespace GravityMedia\Metadata\ID3v2;
9
10
use GravityMedia\Stream\Stream;
11
12
/**
13
 * ID3v2 tag reader class.
14
 *
15
 * @package GravityMedia\Metadata\ID3v2
16
 */
17
class TagReader
18
{
19
    /**
20
     * @var Stream
21
     */
22
    protected $stream;
23
24
    /**
25
     * @var Header
26
     */
27
    protected $header;
28
29
    /**
30
     * Create ID3v2 tag reader object.
31
     *
32
     * @param Stream $stream
33
     * @param Header $header
34
     */
35
    public function __construct(Stream $stream, Header $header)
36
    {
37
        $this->stream = $stream;
38
        $this->header = $header;
39
    }
40
41
    /**
42
     * Read synchsafe unsigned 32-bit integer (long) data from the stream.
43
     *
44
     * @return int
45
     */
46
    public function readSynchsafeUInt32()
47
    {
48
        $value = $this->stream->readUInt32();
49
50
        return ($value & 0x7f) | ($value & 0x7f00) >> 1 | ($value & 0x7f0000) >> 2 | ($value & 0x7f000000) >> 3;
51
    }
52
53
    /**
54
     * Read ID3v2 extended header size.
55
     *
56
     * @return int
57
     */
58
    protected function readExtendedHeaderSize()
59
    {
60
        if (Version::VERSION_23 === $this->header->getVersion()) {
61
            return $this->stream->readUInt32();
62
        }
63
64
        return $this->readSynchsafeUInt32();
65
    }
66
67
    /**
68
     * Read ID3v2 extended header flags.
69
     *
70
     * @return array
71
     */
72
    protected function readExtendedHeaderFlags()
73
    {
74
        if (Version::VERSION_23 === $this->header->getVersion()) {
75
            $flags = $this->stream->readUInt16();
76
77
            return [
78
                ExtendedHeaderFlag::FLAG_CRC_DATA_PRESENT => (bool)($flags & 0x8000)
79
            ];
80
        }
81
82
        $this->stream->seek(1, SEEK_CUR);
83
84
        $flags = $this->stream->readUInt8();
85
86
        return [
87
            ExtendedHeaderFlag::FLAG_TAG_IS_AN_UPDATE => (bool)($flags & 0x40),
88
            ExtendedHeaderFlag::FLAG_CRC_DATA_PRESENT => (bool)($flags & 0x20),
89
            ExtendedHeaderFlag::FLAG_TAG_RESTRICTIONS => (bool)($flags & 0x10)
90
        ];
91
    }
92
93
    /**
94
     * Read ID3v2 extended header padding.
95
     *
96
     * @return int
97
     */
98
    protected function readExtendedHeaderPadding()
99
    {
100
        return $this->stream->readUInt32();
101
    }
102
103
    /**
104
     * Read ID3v2 extended header CRC-32 data.
105
     *
106
     * @return int
107
     */
108
    protected function readExtendedHeaderCrc32()
109
    {
110
        if (Version::VERSION_23 === $this->header->getVersion()) {
111
            return $this->stream->readUInt32();
112
        }
113
114
        $this->stream->seek(1, SEEK_CUR);
115
116
        return $this->stream->readUInt8() * (0xfffffff + 1) + $this->readSynchsafeUInt32();
117
    }
118
119
    /**
120
     * Read ID3v2 extended header restrictions.
121
     *
122
     * @return int
123
     */
124
    protected function readExtendedHeaderRestrictions()
125
    {
126
        $this->stream->seek(1, SEEK_CUR);
127
128
        return $this->stream->readUInt8();
129
    }
130
131
    /**
132
     * Read ID3v2 extended header.
133
     *
134
     * @return ExtendedHeader
135
     */
136
    public function readExtendedHeader()
137
    {
138
        $extendedHeader = new ExtendedHeader();
139
        $extendedHeader
140
            ->setSize($this->readExtendedHeaderSize())
141
            ->setFlags($this->readExtendedHeaderFlags());
142
143
        // Only in ID3v2.3
144
        if (Version::VERSION_23 === $this->header->getVersion()) {
145
            $extendedHeader->setPadding($this->readExtendedHeaderPadding());
146
        }
147
148
        // Only in ID3v2.3
149
        if ($extendedHeader->isFlagEnabled(ExtendedHeaderFlag::FLAG_TAG_IS_AN_UPDATE)) {
150
            $this->stream->seek(1, SEEK_CUR);
151
        }
152
153
        if ($extendedHeader->isFlagEnabled(ExtendedHeaderFlag::FLAG_CRC_DATA_PRESENT)) {
154
            $extendedHeader->setCrc32($this->readExtendedHeaderCrc32());
155
        }
156
157
        // Only in ID3v2.4
158
        if ($extendedHeader->isFlagEnabled(ExtendedHeaderFlag::FLAG_TAG_RESTRICTIONS)) {
159
            $extendedHeader->setRestrictions($this->readExtendedHeaderRestrictions());
160
        }
161
162
        return $extendedHeader;
163
    }
164
165
    /**
166
     * Read ID3v2 frame name.
167
     *
168
     * @return string
169
     */
170
    protected function readFrameName()
171
    {
172
        if (Version::VERSION_22 === $this->header->getVersion()) {
173
            return rtrim($this->stream->read(3));
174
        }
175
176
        return rtrim($this->stream->read(4));
177
    }
178
179
    /**
180
     * Read ID3v2 frame size.
181
     *
182
     * @return int
183
     */
184
    protected function readFrameSize()
185
    {
186
        if (Version::VERSION_22 === $this->header->getVersion()) {
187
            return $this->stream->readUInt24();
188
        }
189
190
        if (Version::VERSION_23 === $this->header->getVersion()) {
191
            return $this->stream->readUInt32();
192
        }
193
194
        return $this->readSynchsafeUInt32();
195
    }
196
197
    /**
198
     * Read ID3v2 frame flags.
199
     *
200
     * @return array
201
     */
202
    protected function readFrameFlags()
203
    {
204
        $flags = $this->stream->readUInt16();
205
206
        if (Version::VERSION_23 === $this->header->getVersion()) {
207
            return [
208
                FrameFlag::FLAG_TAG_ALTER_PRESERVATION => (bool)($flags & 0x8000),
209
                FrameFlag::FLAG_FILE_ALTER_PRESERVATION => (bool)($flags & 0x4000),
210
                FrameFlag::FLAG_READ_ONLY => (bool)($flags & 0x2000),
211
                FrameFlag::FLAG_COMPRESSION => (bool)($flags & 0x0080),
212
                FrameFlag::FLAG_ENCRYPTION => (bool)($flags & 0x0040),
213
                FrameFlag::FLAG_GROUPING_IDENTITY => (bool)($flags & 0x0020),
214
            ];
215
        }
216
217
        return [
218
            FrameFlag::FLAG_TAG_ALTER_PRESERVATION => (bool)($flags & 0x4000),
219
            FrameFlag::FLAG_FILE_ALTER_PRESERVATION => (bool)($flags & 0x2000),
220
            FrameFlag::FLAG_READ_ONLY => (bool)($flags & 0x1000),
221
            FrameFlag::FLAG_GROUPING_IDENTITY => (bool)($flags & 0x0040),
222
            FrameFlag::FLAG_COMPRESSION => (bool)($flags & 0x0008),
223
            FrameFlag::FLAG_ENCRYPTION => (bool)($flags & 0x0004),
224
            FrameFlag::FLAG_UNSYNCHRONISATION => (bool)($flags & 0x0002),
225
            FrameFlag::FLAG_DATA_LENGT_INDICATOR => (bool)($flags & 0x0001),
226
        ];
227
    }
228
229
    /**
230
     * Read ID3v2 frame data length.
231
     *
232
     * @return int
233
     */
234
    public function readFrameDataLength()
235
    {
236
        return $this->readSynchsafeUInt32();
237
    }
238
239
    /**
240
     * Read ID3v2 frame.
241
     *
242
     * @return Frame
243
     */
244
    public function readFrame()
245
    {
246
        $name = $this->readFrameName();
247
        $size = $this->readFrameSize();
248
249
        $frame = new Frame();
250
        $frame
251
            ->setName($name)
252
            ->setSize($size);
253
254
        // Return empty frame
255
        if (0 === $size) {
256
            return $frame;
257
        }
258
259
        // Only in ID3v2.3 and ID3v2.4
260
        if (Version::VERSION_22 !== $this->header->getVersion()) {
261
            $frame->setFlags($this->readFrameFlags());
262
        }
263
264
        // Only in ID3v2.4
265
        if ($frame->isFlagEnabled(FrameFlag::FLAG_DATA_LENGT_INDICATOR)) {
266
            $frame->setDataLength($this->readFrameDataLength());
267
268
            $size -= 4;
269
        }
270
271
        $data = $this->stream->read($size);
272
        if ($frame->isFlagEnabled(FrameFlag::FLAG_COMPRESSION)) {
273
            $data = gzuncompress($data);
274
        }
275
276
        // Only in ID3v2.4
277
        if ($frame->isFlagEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
278
            $data = Unsynchronisation::decode($data);
279
        }
280
281
        return $frame
282
            ->setData($data);
283
    }
284
}
285