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.
@@ -0,0 +1,178 @@
1
+ module ID3
2
+ # ==============================================================================
3
+ # Class Frame ID3 Version 2.x.y Frame
4
+ #
5
+ # parses ID3v2 frames from a binary array
6
+ # dumps ID3v2 frames into a binary array
7
+ # allows to modify frame's contents if the frame was decoded..
8
+ #
9
+ # NOTE: right now the class Frame is derived from Hash, which is wrong..
10
+ # It should really be derived from something like RestrictedOrderedHash
11
+ # ... a new class, which preserves the order of keys, and which does
12
+ # strict checking that all keys are present and reference correct values!
13
+ # e.g. frames["COMMENT"]
14
+ # ==> {"encoding"=>Byte, "language"=>Chars3, "text1"=>String, "text2"=>String}
15
+ #
16
+ # e.g. user should be able to create a new frame , like:
17
+ # tag2.frames["COMMENT"] = "right side"
18
+ #
19
+ # and the following checks should be done:
20
+ #
21
+ # 1) if "COMMENT" is a correct key for tag2
22
+ # 2) if the "right side" contains the correct keys
23
+ # 3) if the "right side" contains the correct value for each key
24
+ #
25
+ # In the simplest case, the "right side" might be just a string,
26
+ # but for most FrameTypes, it's a complex datastructure.. and we need
27
+ # to check it for correctness before doing the assignment..
28
+ #
29
+ # NOTE2: the class Tag2 should have hash-like accessor functions to let the user
30
+ # easily access frames and their contents..
31
+ #
32
+ # e.g. tag2[framename] would really access tag2.frames[framename]
33
+ #
34
+ # and if that works, we can make tag2.frames private and hidden!
35
+ #
36
+ # This means, that when we generate the parse and dump routines dynamically,
37
+ # we may want to create the corresponding accessor methods for Tag2 class
38
+ # as well...? or are generic ones enough?
39
+ #
40
+ #
41
+ # NOTE3:
42
+ #
43
+ # The old way to pack / unpack frames to encode / decode them, is working,
44
+ # but has the disadvantage that it's a little bit too close to the metal.
45
+ # e.g. encoding and textcontent are both accessible, but ideally only
46
+ # the textvalue should be accessible and settable, and the encoding should
47
+ # automatically be set correctly / accordingly...
48
+ #
49
+ # NOTE4:
50
+ # for frames like TXXX , WXXX , which can occur multiple times in a ID3v2 frame,
51
+ # we should manage those tags as an Array...
52
+ #
53
+
54
+ class Frame < RestrictedOrderedHash
55
+
56
+ attr_reader :name, :version
57
+ attr_reader :headerStartX, :dataStartX, :dataEndX, :rawdata, :rawheader # debugging only
58
+
59
+ # ----------------------------------------------------------------------
60
+ # return the complete raw frame
61
+
62
+ def raw
63
+ return @rawheader + @rawdata
64
+ end
65
+ # ----------------------------------------------------------------------
66
+
67
+ def initialize(name, version = '2.3.0', flags = 0, tag, headerStartX, dataStartX, dataEndX )
68
+ super
69
+
70
+ @name = name
71
+ @headerStartX = headerStartX if headerStartX
72
+ @dataStartX = dataStartX if dataStartX
73
+ @dataEndX = dataEndX if dataEndX
74
+
75
+ if tag
76
+ @rawdata = tag.raw[dataStartX..dataEndX]
77
+ @rawheader = tag.raw[headerStartX..dataStartX-1]
78
+ # parse the darn flags, if there are any..
79
+ @version = tag.version # caching..
80
+ else
81
+ @rawdata = ''
82
+ @rawheader= ''
83
+ @version = version
84
+ end
85
+
86
+ case @version
87
+ when /2\.2\.[0-9]/
88
+ # no flags, no extra attributes necessary
89
+
90
+ when /2\.[34]\.0/
91
+
92
+ # dynamically create attributes and reader functions for flags in ID3-frames:
93
+ # (not defined in earlier ID3 versions)
94
+ instance_eval <<-EOB
95
+ class << self
96
+ attr_reader :rawflags, :flags
97
+ end
98
+ EOB
99
+
100
+ @rawflags = flags.to_i # preserve the raw flags (for debugging only)
101
+
102
+ if (flags.to_i & FRAME_HEADER_FLAG_MASK[@version] != 0)
103
+ # in this case we need to skip parsing the frame... and skip to the next one...
104
+ wrong = flags.to_i & FRAME_HEADER_FLAG_MASK[@version]
105
+ error = printf "ID3 version %s frame header flags 0x%X contain invalid flags 0x%X !\n", @version, flags, wrong
106
+ raise ArgumentError, error
107
+ end
108
+
109
+ @flags = Hash.new
110
+
111
+ FRAME_HEADER_FLAGS[@version].each do |key,val|
112
+ # only define the flags which are set..
113
+ @flags[key] = true if (flags.to_i & val == 1)
114
+ end
115
+
116
+ else
117
+ raise ArgumentError, "ID3 version #{@version} not recognized when parsing frame header flags\n"
118
+ end # parsing flags
119
+
120
+ # generate methods for parsing data (low-level read support) and for dumping data out (low-level write-support)
121
+ #
122
+ # based on the particular ID3-version and the ID3-frame name, we basically obtain a string saying how to pack/unpack the data for that frame
123
+ # then we use that packing-string to define a parser and dump method for this particular frame
124
+
125
+ instance_eval <<-EOB
126
+ class << self
127
+
128
+ def parse
129
+ # here we GENERATE the code to parse, dump and verify methods
130
+
131
+ vars,packing = ID3::FRAME_PARSER[ ID3::FrameName2FrameType[ ID3::Framename2symbol[self.version][self.name]] ]
132
+
133
+ values = self.rawdata.unpack(packing)
134
+
135
+ vars.each do |key|
136
+ self[key] = values.shift
137
+ end
138
+ self.lock # lock the OrderedHash
139
+ end
140
+
141
+
142
+ def dump
143
+ vars,packing = ID3::FRAME_PARSER[ ID3::FrameName2FrameType[ ID3::Framename2symbol[self.version][self.name]] ]
144
+
145
+ data = self.values.pack(packing) # we depend on an OrderedHash, so the values are in the correct order!!!
146
+ header = self.name.dup # we want the value! not the reference!!
147
+ len = data.length
148
+ if self.version =~ /^2\.2\./
149
+ byte2,rest = len.divmod(256**2)
150
+ byte1,byte0 = rest.divmod(256)
151
+
152
+ header << byte2 << byte1 << byte0
153
+
154
+ elsif self.version =~ /^2\.[34]\./ # 10-byte header
155
+ byte3,rest = len.divmod(256**3)
156
+ byte2,rest = rest.divmod(256**2)
157
+ byte1,byte0 = rest.divmod(256)
158
+
159
+ flags1,flags0 = self.rawflags.divmod(256)
160
+
161
+ header << byte3 << byte2 << byte1 << byte0 << flags1 << flags0
162
+ end
163
+ header << data
164
+ end
165
+
166
+ end
167
+ EOB
168
+
169
+ self.parse # now we're using the just defined parsing routine
170
+
171
+ return self
172
+ end
173
+ # ----------------------------------------------------------------------
174
+
175
+ end # of class Frame
176
+ # ==============================================================================
177
+
178
+ end
@@ -0,0 +1,19 @@
1
+ module ID3
2
+ # ==============================================================================
3
+ # Class FrameArray
4
+ #
5
+ # basically nothing more than an Array, but it knows how to dump it's contents as ID3v2 frames
6
+ #
7
+ # this solves in part the problem of having multiple ID3v2 frames in one tag, e.g. TXXX , WXXX, APIC
8
+
9
+ class FrameArray < Array
10
+ def dump
11
+ result = ''
12
+ self.each do |element|
13
+ result << element.dump
14
+ end
15
+ return result
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,73 @@
1
+ module ID3
2
+
3
+ # ==============================================================================
4
+ # Class GenericTag
5
+ #
6
+ # Helper class for Tag1 and Tag2
7
+ #
8
+ # Checks that user uses a valid key, and adds methods for size computation
9
+ #
10
+ # as per ID3-definition, the frames are in no fixed order! that's why we can derive
11
+ # this class from Hash. But in the future we may want to write certain frames first
12
+ # into the ID3-tag and therefore may want to derive it from RestrictedOrderedHash
13
+
14
+ # BUG (4) : When an ID3frame is assigned a value, e.g. a String, then the Hash just stores the value right now.
15
+ # Whereas when you read the ID3v2 tag, the object for the frame is ID3::Frame
16
+
17
+ class GenericTag < RestrictedOrderedHash
18
+ attr_accessor :version
19
+ attr_reader :raw
20
+
21
+ # these definitions are to prevent users from inventing their own field names..
22
+ # but on the other hand, they should be able to create a new valid field, if
23
+ # it's not yet in the current tag, but it's valid for that ID3-version...
24
+
25
+ alias old_set []=
26
+ private :old_set
27
+
28
+ # ----------------------------------------------------------------------
29
+ def []=(key,val)
30
+ if @version == ""
31
+ raise ArgumentError, "undefined version of ID3-tag! - set version before accessing components!\n"
32
+ else
33
+ if ID3::SUPPORTED_SYMBOLS[@version].keys.include?(key)
34
+ old_set(key,val)
35
+ else
36
+ # exception
37
+ raise ArgumentError, "Incorrect ID3-field \"#{key}\" for ID3 version #{@version}\n" +
38
+ " valid ID3-fields are: " + SUPPORTED_SYMBOLS[@version].keys.join(",") +"\n"
39
+ end
40
+ end
41
+ end
42
+ # ----------------------------------------------------------------------
43
+ # convert the 4 bytes found in the id3v2 header and return the size
44
+ private
45
+ def unmungeSize(bytes)
46
+ size = 0
47
+ j = 0; i = 3
48
+ while i >= 0
49
+ size += 128**i * (bytes.getbyte(j) & 0x7f)
50
+ j += 1
51
+ i -= 1
52
+ end
53
+ return size
54
+ end
55
+ # ----------------------------------------------------------------------
56
+ # convert the size into 4 bytes to be written into an id3v2 header
57
+ private
58
+ def mungeSize(size)
59
+ bytes = Array.new(4,0)
60
+ j = 0; i = 3
61
+ while i >= 0
62
+ bytes[j],size = size.divmod(128**i)
63
+ j += 1
64
+ i -= 1
65
+ end
66
+
67
+ return bytes
68
+ end
69
+ # ----------------------------------------------------------------------------
70
+
71
+ end # of class GenericTag
72
+
73
+ end # of module ID3
@@ -0,0 +1,159 @@
1
+ ################################################################################
2
+ # id3.rb Ruby Module for handling the following ID3-tag versions:
3
+ # ID3v1.0 , ID3v1.1, ID3v2.2.0, ID3v2.3.0, ID3v2.4.0
4
+ #
5
+ # Copyright (C) 2002 .. 2011 by Tilo Sloboda <firstname.lastname@google_email>
6
+ #
7
+ # created: 12 Oct 2002
8
+ # updated: Time-stamp: <Fri, 21 Oct 2011, 11:54:26 PDT tilo>
9
+ #
10
+ # Docs: http://www.id3.org/id3v2-00.txt
11
+ # http://www.id3.org/id3v2.3.0.txt
12
+ # http://www.id3.org/id3v2.4.0-changes.txt
13
+ # http://www.id3.org/id3v2.4.0-structure.txt
14
+ # http://www.id3.org/id3v2.4.0-frames.txt
15
+ #
16
+ # different versions of ID3 tags, support different fields.
17
+ # See: http://www.unixgods.org/~tilo/Ruby/ID3/docs/ID3v2_frames_comparison.txt
18
+ # See: http://www.unixgods.org/~tilo/Ruby/ID3/docs/ID3_comparison2.html
19
+ #
20
+ # PLEASE HELP:
21
+ #
22
+ # >>> Please contact me and email me the extracted ID3v2 tags, if you:
23
+ # >>> - if you have tags with exotic character encodings (exotic for me, not for you, obviously ;-) )
24
+ # >>> - if you find need support for any ID3v2 tags which are not yet supported by this library
25
+ # >>> (e.g. they are currently just parsed 'raw' and you need them fully parsed)
26
+ # >>> - if something terribly breaks
27
+ # >>>
28
+ # >>> You can find a small helper program in the examples folder, which extracts a ID3v2 tag from a file,
29
+ # >>> and saves it separately, so you can email it to me without emailing the whole audio file.
30
+ # >>>
31
+ # >>> THANK YOU FOR YOUR HELP!
32
+ #
33
+ # Non-ASCII encoded Strings:
34
+ #
35
+ # This library's main purpose is to unify access across different ID3-tag versions. So you don't have to worry
36
+ # about the changing names of the frames in different ID3-versions, and e.g. just access "AUTHOR" symbolically
37
+ # no matter if it's ID3 v1.0 v2.1.0 or v2.4.0. Think of this as a low-level library in that sense.
38
+ #
39
+ # Non-ASCII encodings are currently not really dealt with. For Strings which can be encoded differntly,
40
+ # you will see attributes like 'encoding' and 'text', where 'encoding' is a number representing the encoding,
41
+ # and the other attribute, e.g. 'text' or 'description', is the raw uninterpreted String.
42
+ #
43
+ # If your code requires to assign values to a ID3v2-frame which are foreign encoded Strings, you will need to make
44
+ # a small wrapper class on top of ID3::Frame which detects the encoding and properly saves it as a number.
45
+ # I'd love to add this -- but I don't have enough examples of ID3-tags in foreign languages. See: PLEASE HELP
46
+ #
47
+ # Limitations:
48
+ #
49
+ # - this library currently does not support the ID3v2.4 feature of having ID3v2 tags at the end of the file
50
+ # IMHO this doesn't make much sense in the age of streaming, and I haven't found examples for ths in any MP3-files.
51
+ # I think this is just one of the many unused "features" in the ID3v2 specifications ;-)
52
+ #
53
+ # - ID3v2 Chapters are not supported (see: Wikipedia)
54
+ #
55
+ # - ID3v1 extended tags are currently not supported (see: Wikipedia)
56
+ #
57
+ # License:
58
+ # Freely available under the terms of the OpenSource "Artistic License"
59
+ # in combination with the Addendum A (below)
60
+ #
61
+ # In case you did not get a copy of the license along with the software,
62
+ # it is also available at: http://www.unixgods.org/~tilo/artistic-license.html
63
+ #
64
+ # Addendum A:
65
+ # THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU!
66
+ # SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
67
+ # REPAIR OR CORRECTION.
68
+ #
69
+ # IN NO EVENT WILL THE COPYRIGHT HOLDERS BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL,
70
+ # SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY
71
+ # TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
72
+ # INACCURATE OR USELESS OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
73
+ # TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF THE COPYRIGHT HOLDERS OR OTHER PARTY HAS BEEN
74
+ # ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
75
+ #
76
+ #
77
+ # Author's Rant:
78
+ # The author of this ID3-library for Ruby is not responsible in any way for
79
+ # the awkward definition of the ID3-standards..
80
+ #
81
+ # You're lucky though that you can use this little library, rather than having
82
+ # to parse ID3v2 tags yourself! Trust me! At the first glance it doesn't seem
83
+ # to be so complicated, but the ID3v2 definitions are so convoluted and
84
+ # unnecessarily complicated, with so many useless frame-types, it's a pain to
85
+ # read the documents describing the ID3 V2.x standards.. with tiny bits of information
86
+ # strewn all accross the documents here and there.. and even worse to implement them.
87
+ #
88
+ # I don't know what these people were thinking... can we make it any more
89
+ # complicated than that?? ID3 version 2.4.0 tops everything! If this flag
90
+ # is set and it's a full moon, and an even weekday number, then do this..
91
+ # Outch!!! I assume that's why I don't find any 2.4.0 tags in any of my
92
+ # MP3-files... seems like noone is writing 2.4.0 tags... iTunes writes 2.3.0
93
+ #
94
+ ################################################################################
95
+ # How does it work?
96
+ #
97
+ # Main concepts used:
98
+ #
99
+ # - Unification of ID3 Frames according to this nomenclature, using "pretty" names for frames:
100
+ # http://www.unixgods.org/~tilo/Ruby/ID3/docs/ID3_comparison2.html
101
+ #
102
+ # - String pack/unpack to parse and dump contents of ID3v2 frames;
103
+ # For each ID3v2 frame type, there is a specific list of attributes for that frame
104
+ # and a pack/unpack recipe associated with that frame type (see: FRAME_PARSER Hash)
105
+ #
106
+ # - if there is a ID3v2 frame that's not parsed yet, it's easy to add:
107
+ # - create a new entry for that frame's symbolic (pretty) name in FRAMETYPE2FRAMENAME Hash
108
+ # - make sure to delete that name from the "UNPARSED" category
109
+ # - add an entry to FRAME_PARSER Hash
110
+ # - note how these pre-defined Hashes are used in ID3::Frame class during parse() and dump()
111
+ #
112
+ # - Metaprogramming: when ID3v2 frames are instanciated when they are read,
113
+ # we define parse and dump methods individually, using above pack/unpack recipes
114
+ # (check the two lines which use ID3::FRAME_PARSER to better understand the internal mechanics)
115
+ #
116
+ # - After the ID3v2 frames are parsed, they are Hashes; the keys are the attributes defined in FRAME_PARSER,
117
+ # the values are the extracted data from the ID3v2 tag.
118
+ #
119
+ ################################################################################
120
+ #--
121
+ # TO DO:
122
+ #
123
+ # - haven't touched the code in a very long time..
124
+ # - I should write a general write-up and explanation on how to use the classes
125
+ # - I should write a general write-up to explain the metaprogramming ;)
126
+ #
127
+ # - they really changed all the IO calls in Ruby 1.9 -- how painful!!
128
+ # I need to make some wrappers, to handle this nicely in both Ruby 1.9 and 1.8
129
+ #
130
+ # - should probably use IO#sysopen , IO#sysseek , IO#sysread , IO#syswrite for low-level i/o
131
+ # - files should be opened with the 'b' option - to tell Ruby 1.9 to open them in binary mode
132
+ #
133
+ # - Note: the external representation for non-printable characters in strings is now hex, not octal
134
+ # - should use sha1 instead of md5
135
+ # - some functionality , like has_id3...tag? and is_mp3_file? should extend class File instead of being an ID3 module method
136
+ # - class AudioFile could extend class IO or File - hmm, not sure
137
+ # - class RestrictedOrderedHash vs OrderedHash vs Hash ...?? can we just do this with the ordered hash in 1.9?
138
+ # should probably at least inherit RestrictedOrderedHash < ActiveSupport::OrderedHash
139
+ #
140
+ # - tripple-check if the semantics of pack/unpack has changed between 1.8 and 1.9
141
+ # - hexdump definitely barfs in Ruby 1.9 -- needs fixing
142
+ #
143
+ # - check out ruby-uuid on how he manipulates raw bytes.. looks like it is Ruby 1.9 compatible.. .ord .char .bytes
144
+ #
145
+ # - this needs some serious refactoring..
146
+ #++
147
+
148
+
149
+ # ==============================================================================
150
+
151
+
152
+
153
+ # ==============================================================================
154
+
155
+ module ID3
156
+
157
+ # ... moved everything into separate files
158
+
159
+ end # of module ID3
@@ -0,0 +1,44 @@
1
+ #
2
+ # EXTENSIONS to Class IO (included in File)
3
+ #
4
+
5
+ # if you have a (partial) MP3-file stored in a File or IO object, you can check if it contains ID3 tags
6
+ # NOTE: file needs to be opened in binary mode! 'rb:binary'
7
+
8
+ class IO
9
+ def id3_versions
10
+ [ hasID3v1tag? ,hasID3v2tag? ].compact # returns an Array of version numbers
11
+ end
12
+
13
+ def hasID3tag?
14
+ hasID3v2tag? || hasID3v1tag? ? true : false # returns true or false
15
+ end
16
+
17
+ def hasID3v1tag?
18
+ seek(-ID3::ID3v1tagSize, IO::SEEK_END)
19
+ if (read(3) == 'TAG')
20
+ seek(-ID3::ID3v1tagSize + ID3::ID3v1versionbyte, IO::SEEK_END)
21
+ return get_byte == 0 ? "1.0" : "1.1"
22
+ else
23
+ return nil
24
+ end
25
+ end
26
+
27
+ def ID3v2_tag_size
28
+ rewind
29
+ return 0 if (read(3) != 'ID3')
30
+ read_bytes(3) # skip version and flags
31
+ return ID3::ID3v2headerSize + ID3.unmungeSize( read_bytes(4) )
32
+ end
33
+
34
+ def hasID3v2tag?
35
+ rewind
36
+ if (read(3) == "ID3")
37
+ major = get_byte
38
+ minor = get_byte
39
+ return version = "2." + major.to_s + '.' + minor.to_s
40
+ else
41
+ return nil
42
+ end
43
+ end
44
+ end