Completed
Push — id3-metadata-objects ( a3a88a...ecc500 )
by Daniel
03:22
created

Metadata::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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\ID3v1;
9
10
use GravityMedia\Metadata\ID3v1\Enum\Genre;
11
use GravityMedia\Metadata\ID3v1\Enum\Version;
12
use GravityMedia\Stream\Stream;
13
14
/**
15
 * ID3v1 metadata class.
16
 *
17
 * @package GravityMedia\Metadata\ID3v1
18
 */
19
class Metadata
20
{
21
    /**
22
     * @var Stream
23
     */
24
    protected $stream;
25
26
    /**
27
     * Create ID3v1 metadata.
28
     *
29
     * @param Stream $stream
30
     */
31
    public function __construct(Stream $stream)
32
    {
33
        $this->stream = $stream;
34
    }
35
36
    /**
37
     * Returns whether ID3v1 metadata exists.
38
     *
39
     * @return bool
40
     */
41 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...
42
    {
43
        if ($this->stream->getSize() < 128) {
44
            return false;
45
        }
46
47
        $this->stream->seek(-128, SEEK_END);
48
49
        return 'TAG' === $this->stream->read(3);
50
    }
51
52
    /**
53
     * Strip ID3v1 metadata.
54
     *
55
     * @return $this
56
     */
57
    public function strip()
58
    {
59
        if (!$this->exists()) {
60
            return $this;
61
        }
62
63
        $this->stream->seek(0);
64
        $this->stream->truncate($this->stream->getSize() - 128);
65
66
        return $this;
67
    }
68
69
    /**
70
     * Read ID3v1 tag version.
71
     *
72
     * @return int
73
     */
74
    protected function readVersion()
75
    {
76
        $this->stream->seek(-3, SEEK_END);
77
        if ("\x00" === $this->stream->read(1) && "\x00" !== $this->stream->read(1)) {
78
            return Version::VERSION_11;
79
        }
80
81
        return Version::VERSION_10;
82
    }
83
84
    /**
85
     * Trim data.
86
     *
87
     * @param string $data The data to trim
88
     *
89
     * @return string
90
     */
91
    protected function trimData($data)
92
    {
93
        return trim(substr($data, 0, strcspn($data, "\x00")));
94
    }
95
96
    /**
97
     * Read ID3v1 tag.
98
     *
99
     * @return null|Tag
100
     */
101
    public function read()
102
    {
103
        if (!$this->exists()) {
104
            return null;
105
        }
106
107
        $version = $this->readVersion();
108
        $tag = new Tag($version);
109
110
        $this->stream->seek(-125, SEEK_END);
111
        $tag
112
            ->setTitle($this->trimData($this->stream->read(30)))
113
            ->setArtist($this->trimData($this->stream->read(30)))
114
            ->setAlbum($this->trimData($this->stream->read(30)))
115
            ->setYear(intval($this->trimData($this->stream->read(4)), 10));
116
117
        if (Version::VERSION_11 === $version) {
118
            $tag->setComment($this->trimData($this->stream->read(28)));
119
            $this->stream->seek(1, SEEK_CUR);
120
            $tag->setTrack($this->stream->readInt8());
121
        } else {
122
            $tag->setComment($this->trimData($this->stream->read(30)));
123
        }
124
125
        $genre = $this->stream->readInt8();
126
        if (in_array($genre, Genre::values())) {
127
            $tag->setGenre($genre);
128
        }
129
130
        return $tag;
131
    }
132
133
    /**
134
     * Pad data.
135
     *
136
     * @param string $data   The data to pad
137
     * @param int    $length The final length
138
     * @param int    $type   The type of padding
139
     *
140
     * @return string
141
     */
142
    protected function padData($data, $length, $type)
143
    {
144
        return str_pad(trim(substr($data, 0, $length)), $length, "\x00", $type);
145
    }
146
147
    /**
148
     * Write ID3v1 tag.
149
     *
150
     * @param Tag $tag The tag to write.
151
     *
152
     * @return $this
153
     */
154
    public function write(Tag $tag)
155
    {
156
        $offset = 0;
157
        if ($this->exists()) {
158
            $offset = -128;
159
        }
160
161
        $this->stream->seek($offset, SEEK_END);
162
163
        $data = 'TAG';
164
        $data .= $this->padData($tag->getTitle(), 30, STR_PAD_RIGHT);
165
        $data .= $this->padData($tag->getArtist(), 30, STR_PAD_RIGHT);
166
        $data .= $this->padData($tag->getAlbum(), 30, STR_PAD_RIGHT);
167
        $data .= $this->padData($tag->getYear(), 4, STR_PAD_LEFT);
168
169
        if (Version::VERSION_11 === $tag->getVersion()) {
170
            $data .= $this->padData($tag->getComment(), 28, STR_PAD_RIGHT);
171
            $data .= "\x00";
172
            $data .= chr($tag->getTrack());
173
        } else {
174
            $data .= $this->padData($tag->getComment(), 30, STR_PAD_RIGHT);
175
        }
176
177
        $data .= chr($tag->getGenre());
178
179
        $this->stream->write($data);
180
181
        return $this;
182
    }
183
}
184