id3tag 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -9
- data/Gemfile.lock +36 -14
- data/README.md +73 -0
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/id3tag.gemspec +75 -19
- data/lib/id3tag.rb +34 -0
- data/lib/id3tag/audio_file.rb +90 -0
- data/lib/id3tag/frame_id_advisor.rb +50 -0
- data/lib/id3tag/frames/util/genre_names.rb +140 -0
- data/lib/id3tag/frames/v1/comments_frame.rb +24 -0
- data/lib/id3tag/frames/v1/genre_frame.rb +19 -0
- data/lib/id3tag/frames/v1/text_frame.rb +27 -0
- data/lib/id3tag/frames/v1/track_nr_frame.rb +18 -0
- data/lib/id3tag/frames/v2/basic_frame.rb +26 -0
- data/lib/id3tag/frames/v2/comments_frame.rb +44 -0
- data/lib/id3tag/frames/v2/frame_fabricator.rb +36 -0
- data/lib/id3tag/frames/v2/genre_frame.rb +34 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser.rb +36 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser_24.rb +15 -0
- data/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24.rb +39 -0
- data/lib/id3tag/frames/v2/text_frame.rb +37 -0
- data/lib/id3tag/frames/v2/unique_file_id_frame.rb +25 -0
- data/lib/id3tag/id3_v1_frame_parser.rb +87 -0
- data/lib/id3tag/id3_v2_frame_parser.rb +72 -0
- data/lib/id3tag/id3_v2_tag_header.rb +61 -0
- data/lib/id3tag/number_util.rb +14 -0
- data/lib/id3tag/string_util.rb +7 -0
- data/lib/id3tag/synchsafe_integer.rb +28 -0
- data/lib/id3tag/tag.rb +121 -0
- data/spec/fixtures/id3v1_and_v2.mp3 +0 -0
- data/spec/fixtures/id3v1_with_track_nr.mp3 +0 -0
- data/spec/fixtures/id3v1_without_track_nr.mp3 +0 -0
- data/spec/fixtures/id3v2.mp3 +0 -0
- data/spec/lib/id3tag/audio_file_spec.rb +40 -0
- data/spec/lib/id3tag/frames/util/genre_name_by_id_finder_spec.rb +18 -0
- data/spec/lib/id3tag/frames/v1/comments_frame_spec.rb +31 -0
- data/spec/lib/id3tag/frames/v1/genre_frame_spec.rb +20 -0
- data/spec/lib/id3tag/frames/v1/text_frame_spec.rb +14 -0
- data/spec/lib/id3tag/frames/v1/track_nr_frame_spec.rb +19 -0
- data/spec/lib/id3tag/frames/v2/basic_frame_spec.rb +25 -0
- data/spec/lib/id3tag/frames/v2/comments_frame_spec.rb +45 -0
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_24_spec.rb +26 -0
- data/spec/lib/id3tag/frames/v2/genre_frame/genre_parser_pre_24_spec.rb +48 -0
- data/spec/lib/id3tag/frames/v2/genre_frame_spec.rb +44 -0
- data/spec/lib/id3tag/frames/v2/text_frame_spec.rb +59 -0
- data/spec/lib/id3tag/frames/v2/unique_file_id_frame_spec.rb +31 -0
- data/spec/lib/id3tag/id3_v1_frame_parser_spec.rb +67 -0
- data/spec/lib/id3tag/id3_v2_frame_parser_spec.rb +18 -0
- data/spec/lib/id3tag/id3_v2_tag_header_spec.rb +60 -0
- data/spec/lib/id3tag/id3tag_spec.rb +17 -0
- data/spec/lib/id3tag/number_util_spec.rb +24 -0
- data/spec/lib/id3tag/string_util_spec.rb +21 -0
- data/spec/lib/id3tag/synchsafe_integer_spec.rb +14 -0
- data/spec/lib/id3tag/tag_spec.rb +84 -0
- data/spec/spec_helper.rb +9 -1
- data/spec/support/mp3_fixtures.rb +4 -0
- metadata +102 -38
- data/README.rdoc +0 -19
- data/spec/id3tag_spec.rb +0 -7
@@ -0,0 +1,50 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
class FrameIdAdvisor
|
3
|
+
COMMON_FRAME_IDS_BY_VERSION = {
|
4
|
+
'v1.x' => {
|
5
|
+
:artist => :artist,
|
6
|
+
:title => :title,
|
7
|
+
:album => :album,
|
8
|
+
:year => :year,
|
9
|
+
:comments => :comments,
|
10
|
+
:genre => :genre,
|
11
|
+
:track_nr => :track_nr
|
12
|
+
},
|
13
|
+
'v2.2' => {
|
14
|
+
:artist => :TP1,
|
15
|
+
:title => :TT2,
|
16
|
+
:album => :TAL,
|
17
|
+
:year => :TYE,
|
18
|
+
:comments => :COM,
|
19
|
+
:genre => :TCO,
|
20
|
+
:track_nr => :TRK
|
21
|
+
},
|
22
|
+
'v2.3' => {
|
23
|
+
:artist => :TPE1,
|
24
|
+
:title => :TIT2,
|
25
|
+
:album => :TALB,
|
26
|
+
:year => :TYER,
|
27
|
+
:comments => :COMM,
|
28
|
+
:genre => :TCON,
|
29
|
+
:track_nr => :TRCK
|
30
|
+
},
|
31
|
+
'v2.4' => {
|
32
|
+
:artist => :TPE1,
|
33
|
+
:title => :TIT2,
|
34
|
+
:album => :TALB,
|
35
|
+
:year => :TDRC,
|
36
|
+
:comments => :COMM,
|
37
|
+
:genre => :TCON,
|
38
|
+
:track_nr => :TRCK
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
def initialize(version, major_version)
|
43
|
+
@version, @major_version = version, major_version
|
44
|
+
end
|
45
|
+
|
46
|
+
def advise(frame_name)
|
47
|
+
COMMON_FRAME_IDS_BY_VERSION["v#{@version}.#{@major_version}"][frame_name]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module Util
|
4
|
+
class GenreNames
|
5
|
+
LIST = [
|
6
|
+
'Blues',
|
7
|
+
'Classic Rock',
|
8
|
+
'Country',
|
9
|
+
'Dance',
|
10
|
+
'Disco',
|
11
|
+
'Funk',
|
12
|
+
'Grunge',
|
13
|
+
'Hip-Hop',
|
14
|
+
'Jazz',
|
15
|
+
'Metal',
|
16
|
+
'New Age',
|
17
|
+
'Oldies',
|
18
|
+
'Other',
|
19
|
+
'Pop',
|
20
|
+
'R&B',
|
21
|
+
'Rap',
|
22
|
+
'Reggae',
|
23
|
+
'Rock',
|
24
|
+
'Techno',
|
25
|
+
'Industrial',
|
26
|
+
'Alternative',
|
27
|
+
'Ska',
|
28
|
+
'Death Metal',
|
29
|
+
'Pranks',
|
30
|
+
'Soundtrack',
|
31
|
+
'Euro-Techno',
|
32
|
+
'Ambient',
|
33
|
+
'Trip-Hop',
|
34
|
+
'Vocal',
|
35
|
+
'Jazz+Funk',
|
36
|
+
'Fusion',
|
37
|
+
'Trance',
|
38
|
+
'Classical',
|
39
|
+
'Instrumental',
|
40
|
+
'Acid',
|
41
|
+
'House',
|
42
|
+
'Game',
|
43
|
+
'Sound Clip',
|
44
|
+
'Gospel',
|
45
|
+
'Noise',
|
46
|
+
'AlternRock',
|
47
|
+
'Bass',
|
48
|
+
'Soul',
|
49
|
+
'Punk',
|
50
|
+
'Space',
|
51
|
+
'Meditative',
|
52
|
+
'Instrumental Pop',
|
53
|
+
'Instrumental Rock',
|
54
|
+
'Ethnic',
|
55
|
+
'Gothic',
|
56
|
+
'Darkwave',
|
57
|
+
'Techno-Industrial',
|
58
|
+
'Electronic',
|
59
|
+
'Pop-Folk',
|
60
|
+
'Eurodance',
|
61
|
+
'Dream',
|
62
|
+
'Southern Rock',
|
63
|
+
'Comedy',
|
64
|
+
'Cult',
|
65
|
+
'Gangsta',
|
66
|
+
'Top 40',
|
67
|
+
'Christian Rap',
|
68
|
+
'Pop/Funk',
|
69
|
+
'Jungle',
|
70
|
+
'Native American',
|
71
|
+
'Cabaret',
|
72
|
+
'New Wave',
|
73
|
+
'Psychadelic',
|
74
|
+
'Rave',
|
75
|
+
'Showtunes',
|
76
|
+
'Trailer',
|
77
|
+
'Lo-Fi',
|
78
|
+
'Tribal',
|
79
|
+
'Acid Punk',
|
80
|
+
'Acid Jazz',
|
81
|
+
'Polka',
|
82
|
+
'Retro',
|
83
|
+
'Musical',
|
84
|
+
'Rock & Roll',
|
85
|
+
'Hard Rock',
|
86
|
+
'Folk',
|
87
|
+
'Folk-Rock',
|
88
|
+
'National Folk',
|
89
|
+
'Swing',
|
90
|
+
'Fast Fusion',
|
91
|
+
'Bebob',
|
92
|
+
'Latin',
|
93
|
+
'Revival',
|
94
|
+
'Celtic',
|
95
|
+
'Bluegrass',
|
96
|
+
'Avantgarde',
|
97
|
+
'Gothic Rock',
|
98
|
+
'Progressive Rock',
|
99
|
+
'Psychedelic Rock',
|
100
|
+
'Symphonic Rock',
|
101
|
+
'Slow Rock',
|
102
|
+
'Big Band',
|
103
|
+
'Chorus',
|
104
|
+
'Easy Listening',
|
105
|
+
'Acoustic',
|
106
|
+
'Humour',
|
107
|
+
'Speech',
|
108
|
+
'Chanson',
|
109
|
+
'Opera',
|
110
|
+
'Chamber Music',
|
111
|
+
'Sonata',
|
112
|
+
'Symphony',
|
113
|
+
'Booty Bass',
|
114
|
+
'Primus',
|
115
|
+
'Porn Groove',
|
116
|
+
'Satire',
|
117
|
+
'Slow Jam',
|
118
|
+
'Club',
|
119
|
+
'Tango',
|
120
|
+
'Samba',
|
121
|
+
'Folklore',
|
122
|
+
'Ballad',
|
123
|
+
'Power Ballad',
|
124
|
+
'Rhythmic Soul',
|
125
|
+
'Freestyle',
|
126
|
+
'Duet',
|
127
|
+
'Punk Rock',
|
128
|
+
'Drum Solo',
|
129
|
+
'A capella',
|
130
|
+
'Euro-House',
|
131
|
+
'Dance Hall'
|
132
|
+
]
|
133
|
+
|
134
|
+
def self.find_by_id(id)
|
135
|
+
LIST[id.to_i]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V1
|
4
|
+
class CommentsFrame < TextFrame
|
5
|
+
def language
|
6
|
+
'unknown'
|
7
|
+
end
|
8
|
+
|
9
|
+
def desciption
|
10
|
+
'unknown'
|
11
|
+
end
|
12
|
+
|
13
|
+
def text
|
14
|
+
content
|
15
|
+
end
|
16
|
+
|
17
|
+
def content
|
18
|
+
@content.encode(destination_encoding, source_encoding)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V1
|
4
|
+
class GenreFrame
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(id, content)
|
8
|
+
@id, @content = id, content
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
nr = @content.unpack(NumberUtil::FORMAT_FOR_8_BIT_SIGNED_INTEGER).first
|
13
|
+
nr && Util::GenreNames.find_by_id(nr)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V1
|
4
|
+
class TextFrame
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(id, content)
|
8
|
+
@id, @content = id, content
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
@content.encode(destination_encoding, source_encoding)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def destination_encoding
|
18
|
+
Encoding::UTF_8
|
19
|
+
end
|
20
|
+
|
21
|
+
def source_encoding
|
22
|
+
Encoding::ISO8859_1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V1
|
4
|
+
class TrackNrFrame
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(id, content)
|
8
|
+
@id, @content = id, content
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
@content.unpack(NumberUtil::FORMAT_FOR_8_BIT_SIGNED_INTEGER).first.to_s
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V2
|
4
|
+
class BasicFrame
|
5
|
+
attr_reader :id
|
6
|
+
def initialize(id, content, flags, major_version_number)
|
7
|
+
@id, @raw_content, @flags, @major_version_number = id.to_sym, content, flags, major_version_number
|
8
|
+
end
|
9
|
+
|
10
|
+
def content
|
11
|
+
@raw_content
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"<#{self.class.name} #{id}: #{inspect_content}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def inspect_content
|
21
|
+
content
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V2
|
4
|
+
class CommentsFrame < TextFrame
|
5
|
+
|
6
|
+
# language code according to https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
|
7
|
+
def language
|
8
|
+
@language ||= get_language
|
9
|
+
end
|
10
|
+
|
11
|
+
def description
|
12
|
+
@desciption ||= encoded_text_and_content_parts.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def text
|
16
|
+
@text ||= encoded_text_and_content_parts.last
|
17
|
+
end
|
18
|
+
|
19
|
+
def content
|
20
|
+
text
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def encoded_text_and_content_parts
|
26
|
+
@encoded_text_and_content_parts ||= encoded_text_and_content.split("\00",2)
|
27
|
+
end
|
28
|
+
|
29
|
+
def encoded_text_and_content
|
30
|
+
raw_text_and_content.encode(destination_encoding, source_encoding)
|
31
|
+
end
|
32
|
+
|
33
|
+
def raw_text_and_content
|
34
|
+
content_without_encoding_byte[3..-1]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_language
|
38
|
+
content_without_encoding_byte[0..2].downcase
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V2
|
4
|
+
class FrameFabricator
|
5
|
+
class << self
|
6
|
+
def fabricate(id, content, flags, major_version_number)
|
7
|
+
new(id, content, flags, major_version_number).fabricate
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(id, content, flags, major_version_number)
|
12
|
+
@id, @content, @flags, @major_version_number = id, content, flags, major_version_number
|
13
|
+
end
|
14
|
+
|
15
|
+
def fabricate
|
16
|
+
frame_class.new(@id, @content, @flags, @major_version_number)
|
17
|
+
end
|
18
|
+
|
19
|
+
def frame_class
|
20
|
+
case @id
|
21
|
+
when /^(TCON|TCO)$/
|
22
|
+
GenreFrame
|
23
|
+
when /^T/
|
24
|
+
TextFrame
|
25
|
+
when /^(COM|COMM)$/
|
26
|
+
CommentsFrame
|
27
|
+
when /^UFID$/
|
28
|
+
UniqueFileIdFrame
|
29
|
+
else
|
30
|
+
BasicFrame
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V2
|
4
|
+
class GenreFrame < BasicFrame
|
5
|
+
class MissingGenreParser < StandardError; end
|
6
|
+
|
7
|
+
def genres
|
8
|
+
@genres ||= get_genres
|
9
|
+
end
|
10
|
+
|
11
|
+
def content
|
12
|
+
genres.join(", ")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def get_genres
|
18
|
+
genre_parser.new(@raw_content).genres
|
19
|
+
end
|
20
|
+
|
21
|
+
def genre_parser
|
22
|
+
case @major_version_number
|
23
|
+
when 0...4
|
24
|
+
GenreParserPre24
|
25
|
+
when 4
|
26
|
+
GenreParser24
|
27
|
+
else
|
28
|
+
raise(MissingGenreParser,"Missing genre parser for tag version v.2.#{@major_version_number}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module ID3Tag
|
2
|
+
module Frames
|
3
|
+
module V2
|
4
|
+
class GenreFrame
|
5
|
+
class GenreParser
|
6
|
+
NUMERIC = /\A\d+\z/
|
7
|
+
attr_reader :genre_string
|
8
|
+
|
9
|
+
def initialize(genre_string)
|
10
|
+
@genre_string = genre_string
|
11
|
+
end
|
12
|
+
|
13
|
+
def expand_abbreviation(abbreviation)
|
14
|
+
case abbreviation
|
15
|
+
when 'CR'
|
16
|
+
'Cover'
|
17
|
+
when 'RX'
|
18
|
+
'Remix'
|
19
|
+
else
|
20
|
+
if abbreviation =~ NUMERIC
|
21
|
+
Util::GenreNames.find_by_id(abbreviation.to_i)
|
22
|
+
else
|
23
|
+
abbreviation
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def genres
|
29
|
+
raise "#genres is not implemented for class #{self.class}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|