id3tag 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|