id3 0.5.0 → 1.0.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +14 -0
- data/LICENSE.html +8 -1
- data/README.md +39 -0
- data/docs/ID3_comparison.html +10 -2
- data/docs/ID3_comparison2.html +29 -21
- data/docs/ID3v2_frames_overview.txt +172 -35
- data/{lib → docs}/hexdump.rb +0 -0
- data/docs/index.html +29 -9
- data/lib/helpers/hash_extensions.rb +20 -0
- data/lib/helpers/hexdump.rb +136 -0
- data/lib/helpers/invert_hash.rb +128 -0
- data/lib/helpers/recursive_helper.rb +39 -0
- data/lib/helpers/restricted_ordered_hash.rb +88 -0
- data/lib/helpers/ruby_1.8_1.9_compatibility.rb +62 -0
- data/lib/id3.rb +23 -1252
- data/lib/id3/audiofile.rb +261 -0
- data/lib/id3/constants.rb +292 -0
- data/lib/id3/frame.rb +178 -0
- data/lib/id3/frame_array.rb +19 -0
- data/lib/id3/generic_tag.rb +73 -0
- data/lib/id3/id3.rb +159 -0
- data/lib/id3/io_extensions.rb +44 -0
- data/lib/id3/module_methods.rb +127 -0
- data/lib/id3/string_extensions.rb +40 -0
- data/lib/id3/tag1.rb +131 -0
- data/lib/id3/tag2.rb +261 -0
- metadata +87 -58
- data/README +0 -18
- data/docs/ID3v2_frames_comparison.txt +0 -197
- data/lib/invert_hash.rb +0 -105
@@ -0,0 +1,127 @@
|
|
1
|
+
# ----------------------------------------------------------------------------
|
2
|
+
# Module ID3 - MODULE METHODS
|
3
|
+
# ----------------------------------------------------------------------------
|
4
|
+
module ID3
|
5
|
+
|
6
|
+
# The ID3 module methods are to query or modify files directly by filename.
|
7
|
+
# They check directly if a file has a ID3-tag, but they don't parse the tags!
|
8
|
+
|
9
|
+
# ----------------------------------------------------------------------------
|
10
|
+
# id3_versions
|
11
|
+
|
12
|
+
def ID3.id3_versions
|
13
|
+
[ hasID3v1tag?(filename) ,hasID3v2tag?(filename) ].compact # returns Array of ID3 tag versions found
|
14
|
+
end
|
15
|
+
|
16
|
+
# ----------------------------------------------------------------------------
|
17
|
+
# hasID3v1tag?
|
18
|
+
# returns string with version 1.0 or 1.1 if tag was found
|
19
|
+
# returns false otherwise
|
20
|
+
|
21
|
+
def ID3.hasID3v1tag?(filename)
|
22
|
+
hasID3v1tag = false
|
23
|
+
|
24
|
+
# be careful with empty or corrupt files..
|
25
|
+
return false if File.size(filename) < ID3v1tagSize
|
26
|
+
|
27
|
+
f = File.open(filename, 'rb:binary')
|
28
|
+
f.seek(-ID3v1tagSize, IO::SEEK_END)
|
29
|
+
if (f.read(3) == "TAG")
|
30
|
+
f.seek(-ID3v1tagSize + ID3v1versionbyte, IO::SEEK_END)
|
31
|
+
c = f.get_byte # this is character 125 of the tag
|
32
|
+
if (c == 0)
|
33
|
+
hasID3v1tag = "1.0"
|
34
|
+
else
|
35
|
+
hasID3v1tag = "1.1"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
f.close
|
39
|
+
return hasID3v1tag
|
40
|
+
end
|
41
|
+
|
42
|
+
# ----------------------------------------------------------------------------
|
43
|
+
# hasID3v2tag?
|
44
|
+
# returns string with version 2.2.0, 2.3.0 or 2.4.0 if tag found
|
45
|
+
# returns false otherwise
|
46
|
+
|
47
|
+
def ID3.hasID3v2tag?(filename)
|
48
|
+
hasID3v2tag = false
|
49
|
+
|
50
|
+
f = File.open(filename, 'rb:binary')
|
51
|
+
if (f.read(3) == "ID3")
|
52
|
+
major = f.get_byte
|
53
|
+
minor = f.get_byte
|
54
|
+
version = "2." + major.to_s + '.' + minor.to_s
|
55
|
+
hasID3v2tag = version
|
56
|
+
end
|
57
|
+
f.close
|
58
|
+
return hasID3v2tag
|
59
|
+
end
|
60
|
+
|
61
|
+
# ----------------------------------------------------------------------------
|
62
|
+
# hasID3tag?
|
63
|
+
# returns string with all versions found, space separated
|
64
|
+
# returns false otherwise
|
65
|
+
|
66
|
+
def ID3.hasID3tag?(filename)
|
67
|
+
v1 = ID3.hasID3v1tag?(filename)
|
68
|
+
v2 = ID3.hasID3v2tag?(filename)
|
69
|
+
|
70
|
+
return false if !v1 && !v2
|
71
|
+
return v1 if !v2
|
72
|
+
return v2 if !v1
|
73
|
+
return "#{v1} #{v2}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# ----------------------------------------------------------------------------
|
77
|
+
# removeID3v1tag
|
78
|
+
# returns nil if no v1 tag was found, or it couldn't be removed
|
79
|
+
# returns true if v1 tag found and it was removed..
|
80
|
+
#
|
81
|
+
# in the future:
|
82
|
+
# returns ID3.Tag1 object if a v1 tag was found and removed
|
83
|
+
|
84
|
+
def ID3.removeID3v1tag(filename)
|
85
|
+
stat = File.stat(filename)
|
86
|
+
if stat.file? && stat.writable? && ID3.hasID3v1tag?(filename)
|
87
|
+
|
88
|
+
# CAREFUL: this does not check if there really is a valid tag,
|
89
|
+
# that's why we need to check above!!
|
90
|
+
|
91
|
+
newsize = stat.size - ID3v1tagSize
|
92
|
+
File.open(filename, "r+") { |f| f.truncate(newsize) }
|
93
|
+
|
94
|
+
return true
|
95
|
+
else
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# ----------------------------------------------------------------------
|
101
|
+
# convert the 4 bytes found in the id3v2 header and return the size
|
102
|
+
def ID3.unmungeSize(bytes)
|
103
|
+
size = 0
|
104
|
+
j = 0; i = 3
|
105
|
+
while i >= 0
|
106
|
+
size += 128**i * (bytes.getbyte(j) & 0x7f)
|
107
|
+
j += 1
|
108
|
+
i -= 1
|
109
|
+
end
|
110
|
+
return size
|
111
|
+
end
|
112
|
+
# ----------------------------------------------------------------------
|
113
|
+
# convert the size into 4 bytes to be written into an id3v2 header
|
114
|
+
def ID3.mungeSize(size)
|
115
|
+
bytes = Array.new(4,0)
|
116
|
+
j = 0; i = 3
|
117
|
+
while i >= 0
|
118
|
+
bytes[j],size = size.divmod(128**i)
|
119
|
+
j += 1
|
120
|
+
i -= 1
|
121
|
+
end
|
122
|
+
return bytes
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
# ----------------------------------------------------------------------------
|
127
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# EXTENSIONS to Class String
|
3
|
+
#
|
4
|
+
# if you have a (partial) MP3-file stored in a String.. you can check if it contains ID3 tags
|
5
|
+
|
6
|
+
class String
|
7
|
+
# str = File.open(filename, 'rb:binary').read; 1
|
8
|
+
# str.hasID3v2tag?
|
9
|
+
# str.hasID3v1tag?
|
10
|
+
|
11
|
+
def id3_versions
|
12
|
+
[ hasID3v1tag? ,hasID3v2tag? ].compact # returns an Array of version numbers
|
13
|
+
end
|
14
|
+
|
15
|
+
def hasID3tag?
|
16
|
+
hasID3v2tag? || hasID3v1tag? ? true : false # returns true or false
|
17
|
+
end
|
18
|
+
|
19
|
+
def hasID3v2tag? # returns either nil or the version number -- this can be used in a boolean comparison
|
20
|
+
return nil if self !~ /^ID3/
|
21
|
+
major = self.getbyte(ID3::ID3v2major)
|
22
|
+
minor = self.getbyte(ID3::ID3v2minor)
|
23
|
+
version = "2." + major.to_s + '.' + minor.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
# we also need a method to return the size of the ID3v2 tag ,
|
27
|
+
# e.g. needed when we need to determine the buffersize to read the tag from a file or from a remote location
|
28
|
+
def ID3v2_tag_size
|
29
|
+
return 0 if self !~ /^ID3/
|
30
|
+
return ID3::ID3v2headerSize + ID3.unmungeSize( self[ID3::ID3v2tagSize..ID3::ID3v2tagSize+4] )
|
31
|
+
end
|
32
|
+
|
33
|
+
def hasID3v1tag? # returns either nil or the version number -- this can be used in a boolean comparison
|
34
|
+
return nil if size < ID3::ID3v1tagSize # if the String is too small to contain a tag
|
35
|
+
size = self.bytesize
|
36
|
+
tag = self[size-128,size] # get the last 128 bytes
|
37
|
+
return nil if tag !~/^TAG/
|
38
|
+
return tag[ID3::ID3v1versionbyte] == ZEROBYTE ? "1.0" : "1.1" # return version number otherwise
|
39
|
+
end
|
40
|
+
end
|
data/lib/id3/tag1.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
module ID3
|
2
|
+
|
3
|
+
# ==============================================================================
|
4
|
+
# Class Tag1 ID3 Version 1.x Tag
|
5
|
+
#
|
6
|
+
# parses ID3v1 tags from a binary array
|
7
|
+
# dumps ID3v1 tags into a binary array
|
8
|
+
# allows to modify tag's contents
|
9
|
+
|
10
|
+
class Tag1 < GenericTag
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
@version = '1.1'
|
15
|
+
end
|
16
|
+
|
17
|
+
# ----------------------------------------------------------------------
|
18
|
+
# read reads a version 1.x ID3tag
|
19
|
+
#
|
20
|
+
|
21
|
+
def read(filename)
|
22
|
+
f = File.open(filename, 'r')
|
23
|
+
f.seek(-ID3::ID3v1tagSize, IO::SEEK_END)
|
24
|
+
hastag = (f.read(3) == 'TAG')
|
25
|
+
if hastag
|
26
|
+
f.seek(-ID3::ID3v1tagSize, IO::SEEK_END)
|
27
|
+
@raw = f.read(ID3::ID3v1tagSize)
|
28
|
+
|
29
|
+
# self.parse!(raw) # we should use "parse!" instead of duplicating code!
|
30
|
+
|
31
|
+
if (raw.getbyte(ID3v1versionbyte) == 0)
|
32
|
+
@version = "1.0"
|
33
|
+
else
|
34
|
+
@version = "1.1"
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@raw = @version = nil
|
38
|
+
end
|
39
|
+
f.close
|
40
|
+
#
|
41
|
+
# now parse all the fields
|
42
|
+
|
43
|
+
ID3::SUPPORTED_SYMBOLS[@version].each{ |key,val|
|
44
|
+
if val.class == Range
|
45
|
+
# self[key] = @raw[val].squeeze(" \000").chomp(" ").chomp("\000")
|
46
|
+
self[key] = @raw[val].strip
|
47
|
+
elsif val.class == Fixnum
|
48
|
+
self[key] = @raw.getbyte(val).to_s
|
49
|
+
else
|
50
|
+
# this can't happen the way we defined the hash..
|
51
|
+
# printf "unknown key/val : #{key} / #{val} ; val-type: %s\n", val.type
|
52
|
+
end
|
53
|
+
}
|
54
|
+
hastag
|
55
|
+
end
|
56
|
+
# ----------------------------------------------------------------------
|
57
|
+
# write writes a version 1.x ID3tag
|
58
|
+
#
|
59
|
+
# not implemented yet..
|
60
|
+
#
|
61
|
+
# need to loacte old tag, and remove it, then append new tag..
|
62
|
+
#
|
63
|
+
# always upgrade version 1.0 to 1.1 when writing
|
64
|
+
|
65
|
+
# not yet implemented, because AudioFile.write does the job better
|
66
|
+
|
67
|
+
# ----------------------------------------------------------------------
|
68
|
+
# this routine modifies self, e.g. the Tag1 object
|
69
|
+
#
|
70
|
+
# tag.parse!(raw) returns boolean value, showing if parsing was successful
|
71
|
+
|
72
|
+
def parse!(raw)
|
73
|
+
|
74
|
+
return false if raw.size != ID3::ID3v1tagSize
|
75
|
+
|
76
|
+
if (raw[ID3v1versionbyte] == 0)
|
77
|
+
@version = "1.0"
|
78
|
+
else
|
79
|
+
@version = "1.1"
|
80
|
+
end
|
81
|
+
|
82
|
+
self.clear # remove all entries from Hash, we don't want left-overs..
|
83
|
+
|
84
|
+
ID3::SUPPORTED_SYMBOLS[@version].each{ |key,val|
|
85
|
+
if val.class == Range
|
86
|
+
# self[key] = raw[val].squeeze(" \000").chomp(" ").chomp("\000")
|
87
|
+
self[key] = raw[val].strip
|
88
|
+
elsif val.class == Fixnum
|
89
|
+
self[key] = raw[val].to_s
|
90
|
+
else
|
91
|
+
# this can't happen the way we defined the hash..
|
92
|
+
# printf "unknown key/val : #{key} / #{val} ; val-type: %s\n", val.class
|
93
|
+
end
|
94
|
+
}
|
95
|
+
@raw = raw
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
# ----------------------------------------------------------------------
|
99
|
+
# dump version 1.1 ID3 Tag into a binary array
|
100
|
+
#
|
101
|
+
# although we provide this method, it's stongly discouraged to use it,
|
102
|
+
# because ID3 version 1.x tags are inferior to version 2.x tags, as entries
|
103
|
+
# are often truncated and hence ID3 v1 tags are often useless..
|
104
|
+
|
105
|
+
def dump
|
106
|
+
zeroes = ZEROBYTE * 32
|
107
|
+
raw = ZEROBYTE * ID3::ID3v1tagSize
|
108
|
+
raw[0..2] = 'TAG'
|
109
|
+
|
110
|
+
self.each{ |key,value|
|
111
|
+
|
112
|
+
range = ID3::Symbol2framename['1.1'][key]
|
113
|
+
|
114
|
+
if range.class == Range
|
115
|
+
length = range.last - range.first + 1
|
116
|
+
paddedstring = value + zeroes
|
117
|
+
raw[range] = paddedstring[0..length-1]
|
118
|
+
elsif range.class == Fixnum
|
119
|
+
raw[range] = value.to_i.chr # supposedly assigning a binary integer value to the location in the string
|
120
|
+
else
|
121
|
+
# this can't happen the way we defined the hash..
|
122
|
+
next
|
123
|
+
end
|
124
|
+
}
|
125
|
+
|
126
|
+
return raw
|
127
|
+
end
|
128
|
+
# ----------------------------------------------------------------------
|
129
|
+
end # of class Tag1
|
130
|
+
|
131
|
+
end
|
data/lib/id3/tag2.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
module ID3
|
2
|
+
|
3
|
+
# ==============================================================================
|
4
|
+
# Class Tag2 ID3 Version 2.x.y Tag
|
5
|
+
#
|
6
|
+
# parses ID3v2 tags from a binary array
|
7
|
+
# dumps ID3v2 tags into a binary array
|
8
|
+
# allows to modify tag's contents
|
9
|
+
#
|
10
|
+
# as per definition, the frames are in no fixed order
|
11
|
+
|
12
|
+
class Tag2 < GenericTag
|
13
|
+
attr_reader :rawflags, :flags
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
@rawflags = 0
|
18
|
+
@flags = {}
|
19
|
+
@version = '2.3.0' # default version
|
20
|
+
end
|
21
|
+
|
22
|
+
# this is obviously half-baked.. does not really work! We need SubClasses of ID3::Frame with specific handling
|
23
|
+
#
|
24
|
+
def []=(framename,val)
|
25
|
+
# if this is a valid frame of known type, we return it's total length and a struct
|
26
|
+
#
|
27
|
+
if ID3::SUPPORTED_SYMBOLS[@version].has_value?(framename)
|
28
|
+
frame = ID3::Frame.new( framename, @version)
|
29
|
+
self[ framename ] = frame
|
30
|
+
frame['text'] = val if frame.has_key?('text')
|
31
|
+
return frame
|
32
|
+
else
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_from_buffer(string)
|
38
|
+
has_tag = string =~ /^ID3/
|
39
|
+
if has_tag
|
40
|
+
major = string.getbyte(ID3::ID3v2major)
|
41
|
+
minor = string.getbyte(ID3::ID3v2minor)
|
42
|
+
@version = "2." + major.to_s + '.' + minor.to_s
|
43
|
+
@rawflags = string.getbyte(ID3::ID3v2flags)
|
44
|
+
size = ID3::ID3v2headerSize + ID3.unmungeSize( string[ID3::ID3v2tagSize..ID3::ID3v2tagSize+4] )
|
45
|
+
return false if string.size < size
|
46
|
+
@raw = string[0...size]
|
47
|
+
# parse the raw flags:
|
48
|
+
if (@rawflags & ID3::TAG_HEADER_FLAG_MASK[@version] != 0)
|
49
|
+
# in this case we need to skip parsing the frame... and skip to the next one...
|
50
|
+
wrong = @rawflags & ID3::TAG_HEADER_FLAG_MASK[@version]
|
51
|
+
error = printf "ID3 version %s header flags 0x%X contain invalid flags 0x%X !\n", @version, @rawflags, wrong
|
52
|
+
raise ArgumentError, error
|
53
|
+
end
|
54
|
+
|
55
|
+
@flags = Hash.new
|
56
|
+
|
57
|
+
ID3::TAG_HEADER_FLAGS[@version].each{ |key,val|
|
58
|
+
# only define the flags which are set..
|
59
|
+
@flags[key] = true if (@rawflags & val == 1)
|
60
|
+
}
|
61
|
+
else
|
62
|
+
@raw = nil
|
63
|
+
@version = nil
|
64
|
+
return false
|
65
|
+
end
|
66
|
+
#
|
67
|
+
# now parse all the frames
|
68
|
+
#
|
69
|
+
i = ID3::ID3v2headerSize; # we start parsing right after the ID3v2 header
|
70
|
+
|
71
|
+
while (i < @raw.size) && (@raw.getbyte(i) != 0)
|
72
|
+
len,frame = parse_frame_header(i) # this will create the correct frame
|
73
|
+
if len != 0
|
74
|
+
i += len
|
75
|
+
else
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
has_tag
|
81
|
+
end
|
82
|
+
|
83
|
+
def read_from_file(filename)
|
84
|
+
f = File.open(filename, 'rb:BINARY')
|
85
|
+
has_tag = (f.read(3) == "ID3")
|
86
|
+
if has_tag
|
87
|
+
major = f.get_byte
|
88
|
+
minor = f.get_byte
|
89
|
+
@version = "2." + major.to_s + '.' + minor.to_s
|
90
|
+
@rawflags = f.get_byte
|
91
|
+
size = ID3::ID3v2headerSize + unmungeSize(f.read(4)) # was read_bytes, which was a BUG!!
|
92
|
+
f.seek(0)
|
93
|
+
@raw = f.read(size)
|
94
|
+
|
95
|
+
# parse the raw flags:
|
96
|
+
if (@rawflags & ID3::TAG_HEADER_FLAG_MASK[@version] != 0)
|
97
|
+
# in this case we need to skip parsing the frame... and skip to the next one...
|
98
|
+
wrong = @rawflags & ID3::TAG_HEADER_FLAG_MASK[@version]
|
99
|
+
error = printf "ID3 version %s header flags 0x%X contain invalid flags 0x%X !\n", @version, @rawflags, wrong
|
100
|
+
raise ArgumentError, error
|
101
|
+
end
|
102
|
+
|
103
|
+
@flags = Hash.new
|
104
|
+
|
105
|
+
ID3::TAG_HEADER_FLAGS[@version].each{ |key,val|
|
106
|
+
# only define the flags which are set..
|
107
|
+
@flags[key] = true if (@rawflags & val == 1)
|
108
|
+
}
|
109
|
+
else
|
110
|
+
@raw = nil
|
111
|
+
@version = nil
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
f.close
|
115
|
+
#
|
116
|
+
# now parse all the frames
|
117
|
+
#
|
118
|
+
i = ID3::ID3v2headerSize; # we start parsing right after the ID3v2 header
|
119
|
+
|
120
|
+
while (i < @raw.size) && (@raw.getbyte(i) != 0)
|
121
|
+
len,frame = parse_frame_header(i) # this will create the correct frame
|
122
|
+
if len != 0
|
123
|
+
i += len
|
124
|
+
else
|
125
|
+
break
|
126
|
+
end
|
127
|
+
end
|
128
|
+
has_tag
|
129
|
+
end
|
130
|
+
alias read read_from_file
|
131
|
+
|
132
|
+
# ----------------------------------------------------------------------
|
133
|
+
# write
|
134
|
+
#
|
135
|
+
# writes and replaces existing ID3-v2-tag if one is present
|
136
|
+
# Careful, this does NOT merge or append, it overwrites!
|
137
|
+
|
138
|
+
# not yet implemented, because AudioFile.write does the job better
|
139
|
+
|
140
|
+
# def write(filename)
|
141
|
+
# check how long the old ID3-v2 tag is
|
142
|
+
|
143
|
+
# dump ID3-v2-tag
|
144
|
+
|
145
|
+
# append old audio to new tag
|
146
|
+
|
147
|
+
# end
|
148
|
+
|
149
|
+
# ----------------------------------------------------------------------------
|
150
|
+
# writeID3v2
|
151
|
+
# just writes the ID3v2 tag by itself into a file, no audio data is written
|
152
|
+
#
|
153
|
+
# for backing up ID3v2 tags and debugging only..
|
154
|
+
#
|
155
|
+
|
156
|
+
# def writeID3v2
|
157
|
+
|
158
|
+
# end
|
159
|
+
|
160
|
+
# ----------------------------------------------------------------------
|
161
|
+
# parse_frame_header
|
162
|
+
#
|
163
|
+
# each frame consists of a header of fixed length;
|
164
|
+
# depending on the ID3version, either 6 or 10 bytes.
|
165
|
+
# and of a data portion which is of variable length,
|
166
|
+
# and which contents might not be parsable by us
|
167
|
+
#
|
168
|
+
# INPUT: index to where in the @raw data the frame starts
|
169
|
+
# RETURNS: if successful parse:
|
170
|
+
# total size in bytes, ID3frame struct
|
171
|
+
# else:
|
172
|
+
# 0, nil
|
173
|
+
#
|
174
|
+
#
|
175
|
+
# Struct of type ID3frame which contains:
|
176
|
+
# the name, size (in bytes), headerX,
|
177
|
+
# dataStartX, dataEndX, flags
|
178
|
+
# the data indices point into the @raw data, so we can cut out
|
179
|
+
# and parse the data at a later point in time.
|
180
|
+
#
|
181
|
+
# total frame size = dataEndX - headerX
|
182
|
+
# total header size= dataStartX - headerX
|
183
|
+
# total data size = dataEndX - dataStartX
|
184
|
+
#
|
185
|
+
private
|
186
|
+
def parse_frame_header(x)
|
187
|
+
framename = ""; flags = nil
|
188
|
+
size = 0
|
189
|
+
|
190
|
+
if @version =~ /^2\.2\./
|
191
|
+
frameHeaderSize = 6 # 2.2.x Header Size is 6 bytes
|
192
|
+
header = @raw[x..x+frameHeaderSize-1]
|
193
|
+
|
194
|
+
framename = header[0..2]
|
195
|
+
size = (header.getbyte(3)*256**2)+(header.getbyte(4)*256)+header.getbyte(5)
|
196
|
+
flags = nil
|
197
|
+
# printf "frame: %s , size: %d\n", framename , size
|
198
|
+
|
199
|
+
elsif @version =~ /^2\.[34]\./
|
200
|
+
# for version 2.3.0 and 2.4.0 the header is 10 bytes long
|
201
|
+
frameHeaderSize = 10
|
202
|
+
header = @raw[x..x+frameHeaderSize-1]
|
203
|
+
|
204
|
+
# puts @raw.inspect
|
205
|
+
|
206
|
+
framename = header[0..3]
|
207
|
+
size = (header.getbyte(4)*256**3)+(header.getbyte(5)*256**2)+(header.getbyte(6)*256)+header.getbyte(7)
|
208
|
+
flags= header[8..9]
|
209
|
+
# printf "frame: %s , size: %d, flags: %s\n", framename , size, flags
|
210
|
+
|
211
|
+
else
|
212
|
+
# we can't parse higher versions
|
213
|
+
return 0, false
|
214
|
+
end
|
215
|
+
|
216
|
+
# if this is a valid frame of known type, we return it's total length and a struct
|
217
|
+
#
|
218
|
+
if ID3::SUPPORTED_SYMBOLS[@version].has_value?(framename)
|
219
|
+
frame = ID3::Frame.new( framename, @version , flags, self, x, x+frameHeaderSize , x+frameHeaderSize + size - 1 )
|
220
|
+
self[ ID3::Framename2symbol[@version][frame.name] ] = frame
|
221
|
+
return size+frameHeaderSize , frame
|
222
|
+
else
|
223
|
+
return 0, nil
|
224
|
+
end
|
225
|
+
end
|
226
|
+
# ----------------------------------------------------------------------
|
227
|
+
# dump a ID3-v2 tag into a binary array
|
228
|
+
#
|
229
|
+
# NOTE:
|
230
|
+
# when "dumping" an ID3-v2 tag, I would like to have more control about
|
231
|
+
# which frames get dumped first.. e.g. the most important frames (with the
|
232
|
+
# most important information) should be dumped first..
|
233
|
+
#
|
234
|
+
|
235
|
+
public
|
236
|
+
def dump
|
237
|
+
data = ""
|
238
|
+
|
239
|
+
# dump all the frames
|
240
|
+
self.each { |framename,framedata|
|
241
|
+
data << framedata.dump
|
242
|
+
}
|
243
|
+
# add some padding perhaps 32 bytes (should be defined by the user!)
|
244
|
+
# NOTE: I noticed that iTunes adds excessive amounts of padding
|
245
|
+
data << ZEROBYTE * 32
|
246
|
+
|
247
|
+
# calculate the complete length of the data-section
|
248
|
+
size = mungeSize(data.size)
|
249
|
+
|
250
|
+
major,minor = @version.sub(/^2\.([0-9])\.([0-9])/, '\1 \2').split
|
251
|
+
|
252
|
+
# prepend a valid ID3-v2.x header to the data block
|
253
|
+
header = "ID3" << major.to_i << minor.to_i << @rawflags << size[0] << size[1] << size[2] << size[3]
|
254
|
+
|
255
|
+
header + data
|
256
|
+
end
|
257
|
+
# ----------------------------------------------------------------------
|
258
|
+
|
259
|
+
end # of class Tag2
|
260
|
+
|
261
|
+
end
|