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

Metadata::readHeaderFlags()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 17

Duplication

Lines 13
Ratio 46.43 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 13
loc 28
ccs 0
cts 17
cp 0
rs 8.8571
cc 3
eloc 17
nc 3
nop 1
crap 12
1
<?php
2
/**
3
 * This file is part of the Metadata package.
4
 *
5
 * @author Daniel Schröder <[email protected]>
6
 */
7
8
namespace GravityMedia\Metadata\ID3v2;
9
10
use GravityMedia\Metadata\Exception\RuntimeException;
11
use GravityMedia\Stream\Stream;
12
13
/**
14
 * ID3v2 metadata class.
15
 *
16
 * @package GravityMedia\Metadata\ID3v2
17
 */
18
class Metadata
19
{
20
    /**
21
     * @var Stream
22
     */
23
    protected $stream;
24
25
    /**
26
     * Create ID3v2 metadata object.
27
     *
28
     * @param Stream $stream
29
     */
30
    public function __construct(Stream $stream)
31
    {
32
        $this->stream = $stream;
33
    }
34
35
    /**
36
     * Read synchsafe unsigned 32-bit integer (long) data from the stream.
37
     *
38
     * @return int
39
     */
40
    public function readSynchsafeUInt32()
41
    {
42
        $value = $this->stream->readUInt32();
43
44
        return ($value & 0x7f) | ($value & 0x7f00) >> 1 | ($value & 0x7f0000) >> 2 | ($value & 0x7f000000) >> 3;
45
    }
46
47
    /**
48
     * Returns whether ID3v2 metadata exists.
49
     *
50
     * @return bool
51
     */
52 View Code Duplication
    public function exists()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
53
    {
54
        if ($this->stream->getSize() < 10) {
55
            return false;
56
        }
57
58
        $this->stream->seek(0);
59
60
        return 'ID3' === $this->stream->read(3);
61
    }
62
63
    /**
64
     * Strip ID3v2 metadata.
65
     *
66
     * @return $this
67
     */
68
    public function strip()
69
    {
70
        // TODO: implement
71
72
        return $this;
73
    }
74
75
    /**
76
     * Read ID3v2 header version.
77
     *
78
     * @throws RuntimeException An exception is thrown on invalid versions.
79
     *
80
     * @return int
81
     */
82
    protected function readHeaderVersion()
83
    {
84
        $this->stream->seek(3);
85
86
        switch ($this->stream->readUInt8()) {
87
            case 2:
88
                return Version::VERSION_22;
89
            case 3:
90
                return Version::VERSION_23;
91
            case 4:
92
                return Version::VERSION_24;
93
        }
94
95
        throw new RuntimeException('Invalid version.');
96
    }
97
98
    /**
99
     * Read ID3v2 header revision.
100
     *
101
     * @return int
102
     */
103
    protected function readHeaderRevision()
104
    {
105
        $this->stream->seek(4);
106
107
        return $this->stream->readUInt8();
108
    }
109
110
    /**
111
     * Read ID3v2 header flags.
112
     *
113
     * @param int $version
114
     *
115
     * @return array
116
     */
117
    protected function readHeaderFlags($version)
118
    {
119
        $this->stream->seek(5);
120
121
        $flags = $this->stream->readUInt8();
122
123 View Code Duplication
        if ($version === Version::VERSION_22) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
            return [
125
                HeaderFlag::FLAG_UNSYNCHRONISATION => (bool)($flags & 0x80),
126
                HeaderFlag::FLAG_COMPRESSION => (bool)($flags & 0x40)
127
            ];
128
        }
129
130 View Code Duplication
        if ($version === Version::VERSION_23) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
            return [
132
                HeaderFlag::FLAG_UNSYNCHRONISATION => (bool)($flags & 0x80),
133
                HeaderFlag::FLAG_EXTENDED_HEADER => (bool)($flags & 0x40),
134
                HeaderFlag::FLAG_EXPERIMENTAL_INDICATOR => (bool)($flags & 0x20)
135
            ];
136
        }
137
138
        return [
139
            HeaderFlag::FLAG_UNSYNCHRONISATION => (bool)($flags & 0x80),
140
            HeaderFlag::FLAG_EXTENDED_HEADER => (bool)($flags & 0x40),
141
            HeaderFlag::FLAG_EXPERIMENTAL_INDICATOR => (bool)($flags & 0x20),
142
            HeaderFlag::FLAG_FOOTER_PRESENT => (bool)($flags & 0x10)
143
        ];
144
    }
145
146
    /**
147
     * Read ID3v2 header size.
148
     *
149
     * @return int
150
     */
151
    public function readHeaderSize()
152
    {
153
        $this->stream->seek(6);
154
155
        return $this->readSynchsafeUInt32();
156
    }
157
158
    /**
159
     * Read data.
160
     *
161
     * @param Header $header
162
     *
163
     * @return string
164
     */
165
    protected function readData(Header $header)
166
    {
167
        $this->stream->seek(10);
168
        $data = $this->stream->read($header->getSize());
169
170
        if ($header->isFlagEnabled(HeaderFlag::FLAG_COMPRESSION)) {
171
            $data = gzuncompress($data);
172
        }
173
174
        if ($header->isFlagEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
175
            $data = Unsynchronisation::decode($data);
176
        }
177
178
        return $data;
179
    }
180
181
    /**
182
     * Create data stream.
183
     *
184
     * @param string $data
185
     *
186
     * @return Stream
187
     */
188
    protected function createDataStream($data)
189
    {
190
        $resource = fopen('php://temp', 'r+b');
191
        $stream = Stream::fromResource($resource);
192
        $stream->write($data);
193
        $stream->rewind();
194
195
        return $stream;
196
    }
197
198
    /**
199
     * Read ID3v2 tag.
200
     *
201
     * @return null|Tag
202
     */
203
    public function read()
204
    {
205
        if (!$this->exists()) {
206
            return null;
207
        }
208
209
        $version = $this->readHeaderVersion();
210
211
        $header = new Header($version);
212
        $header->setRevision($this->readHeaderRevision());
213
        $header->setFlags($this->readHeaderFlags($version));
214
        $header->setSize($this->readHeaderSize());
215
216
        $data = $this->readData($header);
217
        $stream = $this->createDataStream($data);
218
219
        $tagReader = new TagReader($stream, $header);
220
        $tag = new Tag($header);
221
        $size = $stream->getSize();
222
223
        if ($header->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
224
            $extendedHeader = $tagReader->readExtendedHeader();
225
            $tag->setExtendedHeader($extendedHeader);
226
227
            $size -= $extendedHeader->getSize();
228
        }
229
230
        if ($header->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
231
            // TODO: Read footer from stream
232
233
            $size -= 10;
234
        }
235
236
        while ($size > 0) {
237
            $frame = $tagReader->readFrame();
238
            if (0 === $frame->getSize()) {
239
                break;
240
            }
241
242
            $tag->addFrame($frame);
243
244
            $size -= $frame->getSize();
245
        }
246
247
        return $tag;
248
    }
249
250
    /**
251
     * Write ID3v2 tag.
252
     *
253
     * @param Tag $tag The tag to write.
254
     *
255
     * @return $this
256
     */
257
    public function write(Tag $tag)
0 ignored issues
show
Unused Code introduced by
The parameter $tag is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
258
    {
259
        // TODO: implement
260
261
        return $this;
262
    }
263
}
264