id3 0.5.0 → 1.0.0.pre4
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.
- 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
|