Completed
Push — id3-metadata-objects ( 168cf3...e37014 )
by Daniel
08:47
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\FrameFlag;
15
use GravityMedia\Metadata\ID3v2\Flag\HeaderFlag;
16
use GravityMedia\Metadata\ID3v2\Metadata\ExtendedHeaderHandler;
17
use GravityMedia\Metadata\ID3v2\Metadata\Frame\CommentFrame;
18
use GravityMedia\Metadata\ID3v2\Metadata\Frame\TextFrame;
19
use GravityMedia\Metadata\ID3v2\Metadata\FrameHandler;
20
use GravityMedia\Metadata\ID3v2\Metadata\HeaderHandler;
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
    private $stream;
35
36
    /**
37
     * @var CompressionFilter
38
     */
39
    private $compressionFilter;
40
41
    /**
42
     * @var UnsynchronisationFilter
43
     */
44
    private $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
        $tag = new Tag($version, $this->readRevision());
166
167
        $headerHandler = new HeaderHandler($this->stream, $version);
168
        $length = $headerHandler->getSize();
169
170
        $this->stream->seek(10);
171
        $data = $this->stream->read($length);
172
        if ($headerHandler->isFlagEnabled(HeaderFlag::FLAG_COMPRESSION)) {
173
            $data = $this->compressionFilter->decode($data);
174
        }
175
        if ($headerHandler->isFlagEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
176
            $data = $this->unsynchronisationFilter->decode($data);
177
        }
178
179
        $tagStream = $this->createReadableStreamFromData($data);
180
        $tagLength = $tagStream->getSize();
181
182
        if ($headerHandler->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
183
            $extendedHeaderHandler = new ExtendedHeaderHandler($tagStream, $version);
184
            $tagLength -= $extendedHeaderHandler->getSize();
185
186
            $tag->setPadding($extendedHeaderHandler->getPadding());
187
            $tag->setCrc32($extendedHeaderHandler->getCrc32());
188
            $tag->setRestrictions($extendedHeaderHandler->getRestrictions());
189
        }
190
191
        if ($headerHandler->isFlagEnabled(HeaderFlag::FLAG_FOOTER_PRESENT)) {
192
            // TODO: Read footer metadata.
193
194
            $tagLength -= 10;
195
        }
196
197
        while ($tagLength > 0) {
198
            $frameHandler = new FrameHandler($tagStream, $version);
199
200
            $frameName = $frameHandler->getName();
201
            $frameLength = $frameHandler->getSize();
202
            $tagLength -= $frameLength;
203
204
            if (0 === $frameLength) {
205
                break;
206
            }
207
208
            $data = $tagStream->read($frameHandler->getDataLength());
209
            if ($frameHandler->isFlagEnabled(FrameFlag::FLAG_COMPRESSION)) {
210
                $data = $this->compressionFilter->decode($data);
211
            }
212
            if ($frameHandler->isFlagEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
213
                $data = $this->unsynchronisationFilter->decode($data);
214
            }
215
216
            $frameStream = $this->createReadableStreamFromData($data);
217
218
            $frame = new Frame();
219
            $frame->setName($frameName);
220
            if ('COMM' === $frameName) {
221
                $commentFrame = new CommentFrame($frameStream);
222
                $frame->setContent($commentFrame->getText());
223
224
            } elseif ('T' === substr($frameName, 0, 1)) {
225
                $textFrame = new TextFrame($frameStream);
226
                $frame->setContent($textFrame->getText());
227
228
                if ('TXXX' === $frameName) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
229
                    // TODO: Read user defined text frame.
230
                }
231
            }
232
233
            $tag->addFrame($frame);
234
        }
235
236
        return $tag;
237
    }
238
239
    /**
240
     * Write ID3v2 tag.
241
     *
242
     * @param Tag $tag The tag to write.
243
     *
244
     * @return $this
245
     */
246
    public function write(Tag $tag)
247
    {
248
        $stream = Stream::fromResource(fopen('php://temp', 'w'));
249
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
250
251
        $stream->write('ID3');
252
        $stream->writeUInt8($tag->getVersion());
253
        $stream->writeUInt8($tag->getRevision());
254
255
        //$headerHandler = new HeaderHandler($stream, $tag->getVersion());
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% 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
        $tagStream = Stream::fromResource(fopen('php://temp', 'w'));
258
        $tagStream->setByteOrder(ByteOrder::BIG_ENDIAN);
259
        
260
        foreach ($tag->getFrames() as $frame) {
261
            $frameHandler = new FrameHandler($tagStream, $tag->getVersion());
262
            $frameHandler->setName($frame->getName());
263
264
        }
265
266
        return $this;
267
    }
268
}
269