Completed
Push — id3-metadata-objects ( 33018f...0cc3db )
by Daniel
07:46
created

Metadata   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 234
Duplicated Lines 4.27 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 62%

Importance

Changes 18
Bugs 0 Features 4
Metric Value
wmc 23
c 18
b 0
f 4
lcom 1
cbo 12
dl 10
loc 234
ccs 62
cts 100
cp 0.62
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A strip() 0 6 1
A fromResource() 0 17 2
A exists() 10 10 2
A readVersion() 0 20 4
A createReadableStreamFromData() 0 9 1
C read() 0 78 11
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 project.
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\FrameHeaderReader;
18
use GravityMedia\Metadata\ID3v2\Reader\HeaderReader;
19
use GravityMedia\Metadata\ID3v2\Writer\FrameHeaderWriter;
20
use GravityMedia\Stream\ByteOrder;
21
use GravityMedia\Stream\Stream;
22
23
/**
24
 * ID3v2 metadata class.
25
 *
26
 * @package GravityMedia\Metadata\ID3v2
27
 */
28
class Metadata
29
{
30
    /**
31
     * @var Stream
32
     */
33
    private $stream;
34
35
    /**
36
     * @var CompressionFilter
37
     */
38
    private $compressionFilter;
39
40
    /**
41
     * @var UnsynchronisationFilter
42
     */
43
    private $unsynchronisationFilter;
44
45
    /**
46
     * @var FrameFactory
47
     */
48
    private $frameFactory;
49
50
    /**
51
     * Create ID3v2 metadata object from resource.
52
     *
53
     * @param resource $resource
54
     *
55
     * @throws InvalidArgumentException An exception will be thrown for invalid resource arguments.
56
     *
57
     * @return static
58
     */
59 4
    public static function fromResource($resource)
60
    {
61 4
        if (!is_resource($resource)) {
62
            throw new InvalidArgumentException('Invalid resource');
63
        }
64
65 4
        $stream = Stream::fromResource($resource);
66 4
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
67
68 4
        $metadata = new static();
69 4
        $metadata->stream = $stream;
70 4
        $metadata->compressionFilter = new CompressionFilter();
71 4
        $metadata->unsynchronisationFilter = new UnsynchronisationFilter();
72 4
        $metadata->frameFactory = new FrameFactory();
73
74 4
        return $metadata;
75
    }
76
77
    /**
78
     * Returns whether ID3v2 metadata exists.
79
     *
80
     * @return bool
81
     */
82 4 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 4
        if ($this->stream->getSize() < 10) {
85
            return false;
86
        }
87
88 4
        $this->stream->seek(0);
89
90 4
        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 4
    protected function readVersion()
113
    {
114 4
        $this->stream->seek(3);
115
116 4
        $version = $this->stream->readUInt8();
117
118 4
        if (2 === $version) {
119
            return Version::VERSION_22;
120
        }
121
122 4
        if (3 === $version) {
123 2
            return Version::VERSION_23;
124
        }
125
126 2
        if (4 === $version) {
127 2
            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 4
    protected function createReadableStreamFromData($data)
141
    {
142 4
        $stream = Stream::fromResource(fopen('php://temp', 'r+'));
143 4
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
144 4
        $stream->write($data);
145 4
        $stream->rewind();
146
147 4
        return $stream;
148
    }
149
150
    /**
151
     * Read ID3v2 tag.
152
     *
153
     * @return null|Tag
154
     */
155 4
    public function read()
156
    {
157 4
        if (!$this->exists()) {
158
            return null;
159
        }
160
161 4
        $version = $this->readVersion();
162 4
        $tag = new Tag($version);
163
164 4
        $this->stream->seek(4);
165 4
        $headerReader = new HeaderReader($this->stream, $version);
166
167 4
        $tag->setRevision($headerReader->getRevision());
168 4
        $length = $headerReader->getSize();
169
170 4
        $this->stream->seek(10);
171 4
        $data = $this->stream->read($length);
172 4
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_COMPRESSION)) {
173
            $data = $this->compressionFilter->decode($data);
174
        }
175 4
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_UNSYNCHRONISATION)) {
176
            $data = $this->unsynchronisationFilter->decode($data);
177
        }
178
179 4
        $tagStream = $this->createReadableStreamFromData($data);
180 4
        $tagLength = $tagStream->getSize();
181
182 4
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_EXTENDED_HEADER)) {
183
            $extendedHeaderReader = new ExtendedHeaderReader($tagStream, $version);
184
            $tagLength -= $extendedHeaderReader->getSize();
185
            $tagLength -= $extendedHeaderReader->getPadding();
186
187
            $tag->setPadding($extendedHeaderReader->getPadding());
188
            $tag->setCrc32($extendedHeaderReader->getCrc32());
189
            $tag->setRestrictions($extendedHeaderReader->getRestrictions());
190
        }
191
192 4
        if ($headerReader->isFlagEnabled(HeaderFlag::FLAG_FOOTER_PRESENT)) {
193
            // TODO: Eventually read footer metadata.
194
195
            $tagLength -= 10;
196
        }
197
198 4
        while ($tagLength > 0) {
199 4
            $frameHeaderReader = new FrameHeaderReader($tagStream, $version);
200
201 4
            $frameName = $frameHeaderReader->getName();
202 4
            if ('' === $frameName) {
203 4
                break;
204
            }
205
206 4
            $frameLength = $frameHeaderReader->getSize();
207 4
            if (0 === $frameLength) {
208
                break;
209
            }
210
211 4
            $tagLength -= $frameLength;
212
213 4
            $data = $tagStream->read($frameHeaderReader->getDataLength());
214 4
            if ($frameHeaderReader->isFlagEnabled(FrameFlag::FLAG_COMPRESSION)) {
215
                $data = $this->compressionFilter->decode($data);
216
            }
217 4
            if ($frameHeaderReader->isFlagEnabled(FrameFlag::FLAG_UNSYNCHRONISATION)) {
218
                $data = $this->unsynchronisationFilter->decode($data);
219
            }
220
221 4
            $frameStream = $this->createReadableStreamFromData($data);
222 4
            $frame = $this->frameFactory->createFrame($frameStream, $frameName);
223
224 4
            $tag->addFrame($frame);
225
226 4
            $frameStream->close();
227 2
        }
228
229 4
        $tagStream->close();
230
231 4
        return $tag;
232
    }
233
234
    /**
235
     * Write ID3v2 tag.
236
     *
237
     * @param Tag $tag The tag to write.
238
     *
239
     * @return $this
240
     */
241
    public function write(Tag $tag)
242
    {
243
        $stream = Stream::fromResource(fopen('php://temp', 'w+'));
244
        $stream->setByteOrder(ByteOrder::BIG_ENDIAN);
245
246
        $stream->write('ID3');
247
        $stream->writeUInt8($tag->getVersion());
248
        $stream->writeUInt8($tag->getRevision());
249
250
        $tagStream = Stream::fromResource(fopen('php://temp', 'w+'));
251
        $tagStream->setByteOrder(ByteOrder::BIG_ENDIAN);
252
253
        foreach ($tag->getFrames() as $frame) {
254
            $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...
255
            $frameHeaderWriter->setName($frame->getName());
256
257
        }
258
259
        return $this;
260
    }
261
}
262