Completed
Push — id3-metadata-objects ( c855dc...168cf3 )
by Daniel
03:05
created

Metadata::readVersion()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
ccs 0
cts 11
cp 0
rs 9.2
cc 4
eloc 10
nc 4
nop 0
crap 20
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\InvalidArgumentException;
11
use GravityMedia\Metadata\Exception\RuntimeException;
12
use GravityMedia\Metadata\ID3v2\Filter\CompressionFilter;
13
use GravityMedia\Metadata\ID3v2\Filter\UnsynchronisationFilter;
14
use GravityMedia\Metadata\ID3v2\Flag\ExtendedHeaderFlag;
15
use GravityMedia\Metadata\ID3v2\Flag\FrameFlag;
16
use GravityMedia\Metadata\ID3v2\Flag\HeaderFlag;
17
use GravityMedia\Metadata\ID3v2\Metadata\ExtendedHeaderMetadata;
18
use GravityMedia\Metadata\ID3v2\Metadata\Frame\TextInformationFrame;
19
use GravityMedia\Metadata\ID3v2\Metadata\FrameMetadata;
20
use GravityMedia\Metadata\ID3v2\Metadata\HeaderMetadata;
21
use GravityMedia\Stream\ByteOrder;
22
use GravityMedia\Stream\Stream;
23
24
/**
25
 * ID3v2 metadata class.
26
 *
27
 * @package GravityMedia\Metadata\ID3v2
28
 */
29
class Metadata
30
{
31
    /**
32
     * @var Stream
33
     */
34
    protected $stream;
35
36
    /**
37
     * @var CompressionFilter
38
     */
39
    protected $compressionFilter;
40
41
    /**
42
     * @var UnsynchronisationFilter
43
     */
44
    protected $unsynchronisationFilter;
45
46
    /**
47
     * Create ID3v2 metadata object from resource.
48
     *
49
     * @param resource $resource
50
     *
51
     * @throws InvalidArgumentException An exception will be thrown for invalid resource arguments.
52
     *
53
     * @return static
54
     */
55
    public static function fromResource($resource)
56
    {
57
        if (!is_resource($resource)) {
58
            throw new InvalidArgumentException('Invalid resource');
59
        }
60
61
        $stream = Stream::fromResource($resource);
62
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
63
64
        $metadata = new static();
65
        $metadata->stream = $stream;
66
        $metadata->compressionFilter = new CompressionFilter();
67
        $metadata->unsynchronisationFilter = new UnsynchronisationFilter();
68
69
        return $metadata;
70
    }
71
72
    /**
73
     * Returns whether ID3v2 metadata exists.
74
     *
75
     * @return bool
76
     */
77 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...
78
    {
79
        if ($this->stream->getSize() < 10) {
80
            return false;
81
        }
82
83
        $this->stream->seek(0);
84
85
        return 'ID3' === $this->stream->read(3);
86
    }
87
88
    /**
89
     * Strip ID3v2 metadata.
90
     *
91
     * @return $this
92
     */
93
    public function strip()
94
    {
95
        // TODO: implement
96
97
        return $this;
98
    }
99
100
    /**
101
     * Read ID3v2 version.
102
     *
103
     * @throws RuntimeException An exception is thrown on invalid versions.
104
     *
105
     * @return int
106
     */
107
    protected function readVersion()
108
    {
109
        $this->stream->seek(3);
110
111
        switch ($this->stream->readUInt8()) {
112
            case 2:
113
                return Version::VERSION_22;
114
            case 3:
115
                return Version::VERSION_23;
116
            case 4:
117
                return Version::VERSION_24;
118
        }
119
120
        throw new RuntimeException('Invalid version.');
121
    }
122
123
    /**
124
     * Read ID3v2 revision.
125
     *
126
     * @return int
127
     */
128
    protected function readRevision()
129
    {
130
        $this->stream->seek(4);
131
132
        return $this->stream->readUInt8();
133
    }
134
135
    /**
136
     * Create readable stream from data.
137
     *
138
     * @param string $data
139
     *
140
     * @return Stream
141
     */
142
    protected function createReadableStreamFromData($data)
143
    {
144
        $filename = tempnam(sys_get_temp_dir(), 'php');
145
        file_put_contents($filename, $data);
146
147
        $stream = Stream::fromResource(fopen($filename, 'r'));
148
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
149
150
        return $stream;
151
    }
152
153
    /**
154
     * Read ID3v2 tag.
155
     *
156
     * @return null|Tag
157
     */
158
    public function read()
159
    {
160
        if (!$this->exists()) {
161
            return null;
162
        }
163
164
        $version = $this->readVersion();
165
        $revision = $this->readRevision();
166
        $tag = new Tag($version, $revision);
167
168
        $headerMetadata = new HeaderMetadata($this->stream, $version);
169
        $size = $headerMetadata->readSize();
170
        $headerFlags = new Flags($headerMetadata->readFlags());
171
172
        $this->stream->seek(10);
173
        $data = $this->stream->read($size);
174
175
        if ($headerFlags->isEnabled(HeaderFlag::FLAG_COMPRESSION)) {
176
            $data = $this->compressionFilter->decode($data);
177
        }
178
179
        if ($headerFlags->isEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
180
            $data = $this->unsynchronisationFilter->decode($data);
181
        }
182
183
        $tagStream = $this->createReadableStreamFromData($data);
184
        $tagSize = $tagStream->getSize();
185
186
        if ($headerFlags->isEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
187
            $extendedHeaderMetadata = new ExtendedHeaderMetadata($tagStream, $version);
188
            $tagSize -= $extendedHeaderMetadata->readSize();
189
            $extendedHeaderFlags = new Flags($extendedHeaderMetadata->readFlags());
190
191
            $tag->setPadding($extendedHeaderMetadata->readPadding());
192
193
            if ($extendedHeaderFlags->isEnabled(ExtendedHeaderFlag::FLAG_TAG_IS_AN_UPDATE)) {
194
                $tagStream->seek(1, SEEK_CUR);
195
            }
196
197
            if ($extendedHeaderFlags->isEnabled(ExtendedHeaderFlag::FLAG_CRC_DATA_PRESENT)) {
198
                $tag->setCrc32($extendedHeaderMetadata->readCrc32());
199
            }
200
201
            if ($extendedHeaderFlags->isEnabled(ExtendedHeaderFlag::FLAG_TAG_RESTRICTIONS)) {
202
                $tag->setRestrictions($extendedHeaderMetadata->readRestrictions());
203
            }
204
        }
205
206
        if ($headerFlags->isEnabled(HeaderFlag::FLAG_FOOTER_PRESENT)) {
207
            // TODO: Read footer metadata.
208
209
            $tagSize -= 10;
210
        }
211
212
        $frameMetadata = new FrameMetadata($tagStream, $version);
213
214
        while ($tagSize > 0) {
215
            $frameName = $frameMetadata->readName();
216
            $frameSize = $frameMetadata->readSize();
217
            $tagSize -= $frameSize;
218
219
            if (0 === $frameSize) {
220
                break;
221
            }
222
223
            $frame = new Frame();
224
            $frame->setName($frameName);
225
226
            $frameFlags = new Flags($frameMetadata->readFlags());
227
228
            if ($frameFlags->isEnabled(FrameFlag::FLAG_DATA_LENGT_INDICATOR)) {
229
                $frame->setDataLength($frameMetadata->readDataLength());
230
231
                $frameSize -= 4;
232
            }
233
234
            $data = $tagStream->read($frameSize);
235
236
            if ($frameFlags->isEnabled(FrameFlag::FLAG_COMPRESSION)) {
237
                $data = $this->compressionFilter->decode($data);
238
            }
239
240
            if ($frameFlags->isEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
241
                $data = $this->unsynchronisationFilter->decode($data);
242
            }
243
244
            $frame->setData($data);
245
246
            $tag->addFrame($frame);
247
248
            $frameStream = $this->createReadableStreamFromData($data);
249
250
            if ('T' === substr($frameName, 0, 1)) {
251
                $textInformationFrame = new TextInformationFrame($frameStream, $version);
252
                $textEncoding = $textInformationFrame->readTextEncoding();
0 ignored issues
show
Unused Code introduced by
$textEncoding is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
253
254
                //var_dump($textEncoding);
255
                //var_dump($textInformationFrame->readInformation($textEncoding));
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
256
            }
257
        }
258
259
        return $tag;
260
    }
261
262
    /**
263
     * Write ID3v2 tag.
264
     *
265
     * @param Tag $tag The tag to write.
266
     *
267
     * @return $this
268
     */
269
    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...
270
    {
271
        // TODO: implement
272
273
        return $this;
274
    }
275
}
276