Completed
Push — id3-metadata-objects ( e37014...ee91c4 )
by Daniel
12:20
created

Metadata::exists()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 7
Bugs 0 Features 2
Metric Value
c 7
b 0
f 2
dl 10
loc 10
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 6
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\Reader\ExtendedHeaderReader;
17
use GravityMedia\Metadata\ID3v2\Reader\Frame\CommentFrameReader;
18
use GravityMedia\Metadata\ID3v2\Reader\Frame\TextFrameReader;
19
use GravityMedia\Metadata\ID3v2\Reader\FrameReader;
20
use GravityMedia\Metadata\ID3v2\Reader\HeaderReader;
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
     * Create readable stream from data.
125
     *
126
     * @param string $data
127
     *
128
     * @return Stream
129
     */
130
    protected function createReadableStreamFromData($data)
131
    {
132
        $filename = tempnam(sys_get_temp_dir(), 'php');
133
        file_put_contents($filename, $data);
134
135
        $stream = Stream::fromResource(fopen($filename, 'r'));
136
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
137
138
        return $stream;
139
    }
140
141
    /**
142
     * Read ID3v2 tag.
143
     *
144
     * @return null|Tag
145
     */
146
    public function read()
147
    {
148
        if (!$this->exists()) {
149
            return null;
150
        }
151
152
        $version = $this->readVersion();
153
        $tag = new Tag($version);
154
155
        $this->stream->seek(4);
156
        $headerReader = new HeaderReader($this->stream, $version);
157
158
        $tag->setRevision($headerReader->getRevision());
159
        $length = $headerReader->getSize();
160
161
        $this->stream->seek(10);
162
        $data = $this->stream->read($length);
163
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_COMPRESSION)) {
164
            $data = $this->compressionFilter->decode($data);
165
        }
166
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
167
            $data = $this->unsynchronisationFilter->decode($data);
168
        }
169
170
        $tagStream = $this->createReadableStreamFromData($data);
171
        $tagLength = $tagStream->getSize();
172
173
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
174
            $extendedHeaderReader = new ExtendedHeaderReader($tagStream, $version);
175
            $tagLength -= $extendedHeaderReader->getSize();
176
177
            $tag->setPadding($extendedHeaderReader->getPadding());
178
            $tag->setCrc32($extendedHeaderReader->getCrc32());
179
            $tag->setRestrictions($extendedHeaderReader->getRestrictions());
180
        }
181
182
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_FOOTER_PRESENT)) {
183
            // TODO: Read footer metadata.
184
185
            $tagLength -= 10;
186
        }
187
188
        while ($tagLength > 0) {
189
            $frameReader = new FrameReader($tagStream, $version);
190
191
            $frameName = $frameReader->getName();
192
            $frameLength = $frameReader->getSize();
193
            $tagLength -= $frameLength;
194
195
            if (0 === $frameLength) {
196
                break;
197
            }
198
199
            $data = $tagStream->read($frameReader->getDataLength());
200
            if ($frameReader->isFlagEnabled(FrameFlag::FLAG_COMPRESSION)) {
201
                $data = $this->compressionFilter->decode($data);
202
            }
203
            if ($frameReader->isFlagEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
204
                $data = $this->unsynchronisationFilter->decode($data);
205
            }
206
207
            $frameStream = $this->createReadableStreamFromData($data);
208
209
            $frame = new Frame();
210
            $frame->setName($frameName);
211
            if ('COMM' === $frameName) {
212
                $commentFrameReader = new CommentFrameReader($frameStream);
213
                $frame->setContent($commentFrameReader->getText());
214
215
            } elseif ('T' === substr($frameName, 0, 1)) {
216
                $textFrameReader = new TextFrameReader($frameStream);
217
                $frame->setContent($textFrameReader->getText());
218
219
                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...
220
                    // TODO: Read user defined text frame.
221
                }
222
            }
223
224
            $tag->addFrame($frame);
225
        }
226
227
        return $tag;
228
    }
229
230
    /**
231
     * Write ID3v2 tag.
232
     *
233
     * @param Tag $tag The tag to write.
234
     *
235
     * @return $this
236
     */
237
    public function write(Tag $tag)
238
    {
239
        $stream = Stream::fromResource(fopen('php://temp', 'w'));
240
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
241
242
        $stream->write('ID3');
243
        $stream->writeUInt8($tag->getVersion());
244
        $stream->writeUInt8($tag->getRevision());
245
246
        //$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...
247
248
        $tagStream = Stream::fromResource(fopen('php://temp', 'w'));
249
        $tagStream->setByteOrder(ByteOrder::BIG_ENDIAN);
250
251
        foreach ($tag->getFrames() as $frame) {
252
            $frameHandler = new FrameReader($tagStream, $tag->getVersion());
253
            $frameHandler->setName($frame->getName());
254
255
        }
256
257
        return $this;
258
    }
259
}
260