Completed
Push — id3-metadata-objects ( 491068...1cf97b )
by Daniel
09:08
created

Metadata   B

Complexity

Total Complexity 29

Size/Duplication

Total Lines 264
Duplicated Lines 3.79 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 0%

Importance

Changes 17
Bugs 0 Features 3
Metric Value
wmc 29
c 17
b 0
f 3
lcom 1
cbo 17
dl 10
loc 264
ccs 0
cts 128
cp 0
rs 7.8571

7 Methods

Rating   Name   Duplication   Size   Complexity  
A fromResource() 0 16 2
A exists() 10 10 2
A strip() 0 6 1
A readVersion() 0 20 4
A createReadableStreamFromData() 0 10 1
D read() 0 113 17
A write() 0 20 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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\Frame\CommentFrame;
17
use GravityMedia\Metadata\ID3v2\Frame\PictureFrame;
18
use GravityMedia\Metadata\ID3v2\Frame\TextFrame;
19
use GravityMedia\Metadata\ID3v2\Reader\ExtendedHeaderReader;
20
use GravityMedia\Metadata\ID3v2\Reader\FrameHeaderReader;
21
use GravityMedia\Metadata\ID3v2\Reader\HeaderReader;
22
use GravityMedia\Metadata\ID3v2\Reader\LanguageTextFrameReader;
23
use GravityMedia\Metadata\ID3v2\Reader\PictureFrameReader;
24
use GravityMedia\Metadata\ID3v2\Reader\TextFrameReader;
25
use GravityMedia\Metadata\ID3v2\Writer\FrameHeaderWriter;
26
use GravityMedia\Stream\ByteOrder;
27
use GravityMedia\Stream\Stream;
28
29
/**
30
 * ID3v2 metadata class.
31
 *
32
 * @package GravityMedia\Metadata\ID3v2
33
 */
34
class Metadata
35
{
36
    /**
37
     * @var Stream
38
     */
39
    private $stream;
40
41
    /**
42
     * @var CompressionFilter
43
     */
44
    private $compressionFilter;
45
46
    /**
47
     * @var UnsynchronisationFilter
48
     */
49
    private $unsynchronisationFilter;
50
51
    /**
52
     * Create ID3v2 metadata object from resource.
53
     *
54
     * @param resource $resource
55
     *
56
     * @throws InvalidArgumentException An exception will be thrown for invalid resource arguments.
57
     *
58
     * @return static
59
     */
60
    public static function fromResource($resource)
61
    {
62
        if (!is_resource($resource)) {
63
            throw new InvalidArgumentException('Invalid resource');
64
        }
65
66
        $stream = Stream::fromResource($resource);
67
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
68
69
        $metadata = new static();
70
        $metadata->stream = $stream;
71
        $metadata->compressionFilter = new CompressionFilter();
72
        $metadata->unsynchronisationFilter = new UnsynchronisationFilter();
73
74
        return $metadata;
75
    }
76
77
    /**
78
     * Returns whether ID3v2 metadata exists.
79
     *
80
     * @return bool
81
     */
82 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...
83
    {
84
        if ($this->stream->getSize() < 10) {
85
            return false;
86
        }
87
88
        $this->stream->seek(0);
89
90
        return 'ID3' === $this->stream->read(3);
91
    }
92
93
    /**
94
     * Strip ID3v2 metadata.
95
     *
96
     * @return $this
97
     */
98
    public function strip()
99
    {
100
        // TODO: implement
101
102
        return $this;
103
    }
104
105
    /**
106
     * Read ID3v2 version.
107
     *
108
     * @throws RuntimeException An exception is thrown on invalid versions.
109
     *
110
     * @return int
111
     */
112
    protected function readVersion()
113
    {
114
        $this->stream->seek(3);
115
116
        $version = $this->stream->readUInt8();
117
118
        if (2 === $version) {
119
            return Version::VERSION_22;
120
        }
121
122
        if (3 === $version) {
123
            return Version::VERSION_23;
124
        }
125
126
        if (4 === $version) {
127
            return Version::VERSION_24;
128
        }
129
130
        throw new RuntimeException('Invalid version.');
131
    }
132
133
    /**
134
     * Create readable stream from data.
135
     *
136
     * @param string $data
137
     *
138
     * @return Stream
139
     */
140
    protected function createReadableStreamFromData($data)
141
    {
142
        $filename = tempnam(sys_get_temp_dir(), 'php');
143
        file_put_contents($filename, $data);
144
145
        $stream = Stream::fromResource(fopen($filename, 'r'));
146
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
147
148
        return $stream;
149
    }
150
151
    /**
152
     * Read ID3v2 tag.
153
     *
154
     * @return null|Tag
155
     */
156
    public function read()
157
    {
158
        if (!$this->exists()) {
159
            return null;
160
        }
161
162
        $version = $this->readVersion();
163
        $tag = new Tag($version);
164
165
        $this->stream->seek(4);
166
        $headerReader = new HeaderReader($this->stream, $version);
167
168
        $tag->setRevision($headerReader->getRevision());
169
        $length = $headerReader->getSize();
170
171
        $this->stream->seek(10);
172
        $data = $this->stream->read($length);
173
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_COMPRESSION)) {
174
            $data = $this->compressionFilter->decode($data);
175
        }
176
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
177
            $data = $this->unsynchronisationFilter->decode($data);
178
        }
179
180
        $tagStream = $this->createReadableStreamFromData($data);
181
        $tagLength = $tagStream->getSize();
182
183
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
184
            $extendedHeaderReader = new ExtendedHeaderReader($tagStream, $version);
185
            $tagLength -= $extendedHeaderReader->getSize();
186
187
            $tag->setPadding($extendedHeaderReader->getPadding());
188
            $tag->setCrc32($extendedHeaderReader->getCrc32());
189
            $tag->setRestrictions($extendedHeaderReader->getRestrictions());
190
        }
191
192
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_FOOTER_PRESENT)) {
193
            // TODO: Read footer metadata.
194
195
            $tagLength -= 10;
196
        }
197
198
        while ($tagLength > 0) {
199
            $frameHeaderReader = new FrameHeaderReader($tagStream, $version);
200
201
            $frameName = $frameHeaderReader->getName();
202
            $frameLength = $frameHeaderReader->getSize();
203
            $tagLength -= $frameLength;
204
205
            if (0 === $frameLength) {
206
                break;
207
            }
208
209
            $data = $tagStream->read($frameHeaderReader->getDataLength());
210
            if ($frameHeaderReader->isFlagEnabled(FrameFlag::FLAG_COMPRESSION)) {
211
                $data = $this->compressionFilter->decode($data);
212
            }
213
            if ($frameHeaderReader->isFlagEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
214
                $data = $this->unsynchronisationFilter->decode($data);
215
            }
216
217
            $frameStream = $this->createReadableStreamFromData($data);
218
219
            if ('UFID' === $frameName) {
220
                $frame = new Frame();
221
                $frame->setName($frameName);
222
                // TODO: Read unique file identifier.
223
            } elseif ('T' === substr($frameName, 0, 1)) {
224
                $frameReader = new TextFrameReader($frameStream);
225
226
                $frame = new TextFrame();
227
                $frame->setName($frameName);
228
                $frame->setText($frameReader->getText());
229
230
                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...
231
                    // TODO: Read user defined text frame.
232
                }
233
            } elseif ('W' === substr($frameName, 0, 1)) {
234
                $frame = new Frame();
235
                $frame->setName($frameName);
236
                // TODO: Read URL link frame.
237
                if ('WXXX' === $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...
238
                    // TODO: Read user defined URL link frame.
239
                }
240
            } elseif ('APIC' === $frameName) {
241
                $frameReader = new PictureFrameReader($frameStream);
242
243
                $frame = new PictureFrame();
244
                $frame->setName($frameName);
245
                $frame->setMimeType($frameReader->getMimeType());
246
                $frame->setType($frameReader->getType());
247
                $frame->setDescription($frameReader->getDescription());
248
                $frame->setData($frameReader->getData());
249
            } elseif ('COMM' === $frameName) {
250
                $frameReader = new LanguageTextFrameReader($frameStream);
251
                $text = $frameReader->getText();
252
                $description = array_shift($text);
253
254
                $frame = new CommentFrame();
255
                $frame->setName($frameName);
256
                $frame->setLanguage($frameReader->getLanguage());
257
                $frame->setDescription($description);
258
                $frame->setText($text);
259
            } else {
260
                $frame = new Frame();
261
                $frame->setName($frameName);
262
            }
263
264
            $tag->addFrame($frame);
265
        }
266
267
        return $tag;
268
    }
269
270
    /**
271
     * Write ID3v2 tag.
272
     *
273
     * @param Tag $tag The tag to write.
274
     *
275
     * @return $this
276
     */
277
    public function write(Tag $tag)
278
    {
279
        $stream = Stream::fromResource(fopen('php://temp', 'w'));
280
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
281
282
        $stream->write('ID3');
283
        $stream->writeUInt8($tag->getVersion());
284
        $stream->writeUInt8($tag->getRevision());
285
286
        $tagStream = Stream::fromResource(fopen('php://temp', 'w'));
287
        $tagStream->setByteOrder(ByteOrder::BIG_ENDIAN);
288
289
        foreach ($tag->getFrames() as $frame) {
290
            $frameHeaderWriter = new FrameHeaderWriter($tagStream, $tag->getVersion());
0 ignored issues
show
Unused Code introduced by
The call to FrameHeaderWriter::__construct() has too many arguments starting with $tag->getVersion().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
291
            $frameHeaderWriter->setName($frame->getName());
292
293
        }
294
295
        return $this;
296
    }
297
}
298