moumar-wmainfo-rb 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +43 -0
  2. data/lib/wmainfo.rb +420 -0
  3. metadata +64 -0
data/README ADDED
@@ -0,0 +1,43 @@
1
+ :: wmainfo-rb ::
2
+ Author: Darren Kirby
3
+ mailto:bulliver@badcomputer.org
4
+ License: Ruby
5
+
6
+ = Quick API docs =
7
+
8
+ == Initializing ==
9
+
10
+ require 'wmainfo'
11
+ foo = WmaInfo.new("someSong.wma")
12
+ ... or ...
13
+ foo = WmaInfo.new("someVideo.wmv", :encoding=>"UTF-16LE")
14
+ (default encoding is ASCII)
15
+ ... or ...
16
+ foo = WmaInfo.new("someVideo.wmv", :debug=>1)
17
+
18
+ == Public attributes ==
19
+
20
+ @drm :: 'true' if DRM present else 'false'
21
+ @tags :: dict of strings (id3 like data)
22
+ @info :: dict of variable types (non-id3 like data)
23
+ @ext_info :: dict of variable types (non-id3 like data) from ASF_Extended_Content_Description_Object
24
+ @headerObject :: dict of arrays (name, GUID, size and offset of ASF objects)
25
+ @stream :: dict of variable types (stream properties data)
26
+
27
+ == Public methods ==
28
+
29
+ print_objects :: pretty-print header objects
30
+ hasdrm? :: returns True if file has DRM
31
+ hastag?('str') :: returns True if @tags['str'] exists
32
+ print_tags :: pretty-print @tags dict
33
+ hasinfo?('str') :: returns True if @info['str'] exists
34
+ print_info :: pretty-print @info dict
35
+ print_stream :: pretty-print @stream dict
36
+
37
+ For more/different documentation see http://badcomputer.org/unix/code/wmainfo/
38
+
39
+ == Thanks/Contributors ==
40
+
41
+ Ilmari Heikkinen sent in a fix for uninitialized '@ext_info'.
42
+ Guillaume Pierronnet sent in a patch which improves character encoding handling.
43
+
data/lib/wmainfo.rb ADDED
@@ -0,0 +1,420 @@
1
+ # = Description
2
+ #
3
+ # wmainfo-ruby gives you access to low level information on wma/wmv/asf files.
4
+ # * It identifies all "ASF_..." objects and shows each objects size
5
+ # * It returns info such as bitrate, size, length, creation date etc
6
+ # * It returns meta-tags from ASF_Content_Description_Object
7
+ #
8
+ # = Note:
9
+ #
10
+ # I wrestled with the ASF spec (150 page .doc format!) with no joy for
11
+ # a while, then found Dan Sully's Audio-WMA Perl module:
12
+ # (http://cpants.perl.org/dist/Audio-WMA :: http://www.slimdevices.com/)
13
+ # This entire library is essentially a translation of (parts of) WMA.pm
14
+ # to Ruby. All credit for the hard work is owed to Dan...
15
+ #
16
+ # License:: Ruby
17
+ # Author:: Darren Kirby (mailto:bulliver@badcomputer.org)
18
+ # Website:: http://badcomputer.org/unix/code/wmainfo/
19
+
20
+ # Improved character encoding handling thanks to
21
+ # Guillaume Pierronnet <guillaume.pierronnet @nospam@ gmail.com>
22
+
23
+ require 'iconv'
24
+
25
+ # raised when errors occur parsing wma header
26
+ class WmaInfoError < StandardError
27
+ end
28
+
29
+ class WmaInfo
30
+ # WmaInfo.tags and WmaInfo.info are hashes of NAME=VALUE pairs
31
+ # WmaInfo.header_object is a hash of arrays
32
+ attr_reader :tags, :header_object, :info, :ext_info, :stream
33
+ def initialize(file, opts = {})
34
+ @drm = nil
35
+ @tags = {}
36
+ @header_object = {}
37
+ @info = {}
38
+ @ext_info = {}
39
+ @stream = {}
40
+ @file = file
41
+ @debug = opts[:debug]
42
+ @ic = Iconv.new(opts[:encoding] || "ASCII", "UTF-16LE")
43
+ parse_wma_header
44
+ end
45
+
46
+ # for ASF_Header_Object prints: "name: GUID size num_objects"
47
+ # for others, prints: "name: GUID size offset"
48
+ def print_objects
49
+ @header_object.each_pair do |key,val|
50
+ puts "#{key}: #{val[0]} #{val[1]} #{val[2]}"
51
+ end
52
+ end
53
+
54
+ # returns true if the file has DRM
55
+ # ie: if a "*Content_Encryption_Object" is found
56
+ def hasdrm?
57
+ @drm ? true : false
58
+ end
59
+
60
+ # returns true if tags["tag"] has a value
61
+ def hastag?(tag)
62
+ @tags[tag] ? true : false
63
+ end
64
+
65
+ # prettyprint WmaInfo.tags hash
66
+ def print_tags
67
+ @tags.each_pair { |key,val| puts "#{key}: #{val}" }
68
+ end
69
+
70
+ # returns true if info["field"] has a value
71
+ def hasinfo?(field)
72
+ @info[field] ? true : false
73
+ end
74
+
75
+ # prettyprint WmaInfo.info hash
76
+ def print_info
77
+ @info.each_pair { |key,val| puts "#{key}: #{val}" }
78
+ end
79
+
80
+ # prettyprint WmaInfo.stream hash
81
+ def print_stream
82
+ @stream.each_pair { |key,val| puts "#{key}: #{val}" }
83
+ end
84
+
85
+ # returns: "filename.wma :: Size: N bytes :: Bitrate: N kbps :: Duration: N seconds"
86
+ # this is useless
87
+ def to_s
88
+ "#{File.basename(@file)} :: Size: #{@size} bytes :: Bitrate: #{@info['bitrate']} kbps :: Duration: #{@info['playtime_seconds']} seconds"
89
+ end
90
+
91
+ #--
92
+ # This cleans up the output when using WmaInfo in irb
93
+ def inspect #:nodoc:
94
+ s = "#<#{self.class}:0x#{(self.object_id*2).to_s(16)} "
95
+ @header_object.each_pair do |k,v|
96
+ s += "(#{k.upcase} size=#{v[1]} offset=#{v[2]}) " unless k == "ASF_Header_Object"
97
+ end
98
+ s += "\b>"
99
+ end
100
+ #++
101
+
102
+ private
103
+ def parse_wma_header
104
+ @size = File.size(@file)
105
+ @fh = File.new(@file, "rb")
106
+ @offset = 0
107
+ @file_offset = 30
108
+ @guid_mapping = known_guids
109
+ @reverse_guid_mapping = @guid_mapping.invert
110
+
111
+ # read first 30 bytes and parse ASF_Header_Object
112
+ begin
113
+ object_id = byte_string_to_guid(@fh.read(16))
114
+ object_size = @fh.read(8).unpack("V")[0]
115
+ header_objects = @fh.read(4).unpack("V")[0]
116
+ reserved1 = @fh.read(1).unpack("b*")[0]
117
+ reserved2 = @fh.read(1).unpack("b*")[0]
118
+ object_id_name = @reverse_guid_mapping[object_id]
119
+ rescue
120
+ # not getting raised when fed a non-wma file
121
+ # object_size must be getting value because
122
+ # "Header size reported larger than file size"
123
+ # gets raised instead?
124
+ raise WmaInfoError, "Not a wma header", caller
125
+ end
126
+
127
+ if object_size > @size
128
+ raise WmaInfoError, "Header size reported larger than file size", caller
129
+ end
130
+
131
+ @header_object[object_id_name] = [object_id, object_size, header_objects, reserved1, reserved2]
132
+
133
+ if @debug
134
+ puts "object_id: #{object_id}"
135
+ puts "object_id_name: #{@reverse_guid_mapping[object_id]}"
136
+ puts "object_size: #{object_size}"
137
+ puts "header_objects: #{header_objects}"
138
+ puts "reserved1: #{reserved1}"
139
+ puts "reserved2: #{reserved2}"
140
+ end
141
+
142
+ @header_data = @fh.read(object_size - 30)
143
+ @fh.close
144
+ header_objects.times do
145
+ next_object = read_and_increment_offset(16)
146
+ next_object_text = byte_string_to_guid(next_object)
147
+ next_object_size = parse_64bit_string(read_and_increment_offset(8))
148
+ next_object_name = @reverse_guid_mapping[next_object_text];
149
+
150
+ @header_object[next_object_name] = [next_object_text, next_object_size, @file_offset]
151
+ @file_offset += next_object_size
152
+
153
+ if @debug
154
+ puts "next_objectGUID: #{next_object_text}"
155
+ puts "next_object_name: #{next_object_name}"
156
+ puts "next_object_size: #{next_object_size}"
157
+ end
158
+
159
+ # start looking at object contents
160
+ if next_object_name == 'ASF_File_Properties_Object'
161
+ parse_asf_file_properties_object
162
+ next
163
+ elsif next_object_name == 'ASF_Content_Description_Object'
164
+ parse_asf_content_description_object
165
+ next
166
+ elsif next_object_name == 'ASF_Extended_Content_Description_Object'
167
+ parse_asf_extended_content_description_object
168
+ next
169
+ elsif next_object_name == 'ASF_Stream_Properties_Object'
170
+ parse_asf_stream_properties_object
171
+ next
172
+ elsif next_object_name == 'ASF_Content_Encryption_Object' || next_object_name == 'ASF_Extended_Content_Encryption_Object'
173
+ parse_asf_content_encryption_object
174
+ end
175
+
176
+ # set our next object size
177
+ @offset += next_object_size - 24
178
+ end
179
+
180
+ # meta-tag like values go to 'tags' all others to 'info'
181
+ @ext_info.each do |k,v|
182
+ if k =~ /WM\/(TrackNumber|AlbumTitle|AlbumArtist|Genre|Year|Composer|Mood|Lyrics|BeatsPerMinute|Publisher)/
183
+ @tags[k.gsub(/WM\//, "")] = v # dump "WM/"
184
+ else
185
+ @info[k] = v
186
+ end
187
+ end
188
+
189
+ # dump empty tags
190
+ @tags.delete_if { |k,v| v == "" || v == nil }
191
+ end
192
+
193
+ def parse_asf_content_encryption_object
194
+ @drm = 1
195
+ end
196
+
197
+ def parse_asf_file_properties_object
198
+ fileid = read_and_increment_offset(16)
199
+ @info['fileid_guid'] = byte_string_to_guid(fileid)
200
+ @info['filesize'] = parse_64bit_string(read_and_increment_offset(8))
201
+ @info['creation_date'] = read_and_increment_offset(8).unpack("Q")[0]
202
+ @info['creation_date_unix'] = file_time_to_unix_time(@info['creation_date'])
203
+ @info['creation_string'] = Time.at(@info['creation_date_unix'].to_i)
204
+ @info['data_packets'] = read_and_increment_offset(8).unpack("V")[0]
205
+ @info['play_duration'] = parse_64bit_string(read_and_increment_offset(8))
206
+ @info['send_duration'] = parse_64bit_string(read_and_increment_offset(8))
207
+ @info['preroll'] = read_and_increment_offset(8).unpack("V")[0]
208
+ @info['playtime_seconds'] = (@info['play_duration'] / 10000000) - (@info['preroll'] / 1000)
209
+ flags_raw = read_and_increment_offset(4).unpack("V")[0]
210
+ if flags_raw & 0x0001 == 0
211
+ @info['broadcast'] = 0
212
+ else
213
+ @info['broadcast'] = 1
214
+ end
215
+ if flags_raw & 0x0002 == 0
216
+ @info['seekable'] = 0
217
+ else
218
+ @info['seekable'] = 1
219
+ end
220
+ @info['min_packet_size'] = read_and_increment_offset(4).unpack("V")[0]
221
+ @info['max_packet_size'] = read_and_increment_offset(4).unpack("V")[0]
222
+ @info['max_bitrate'] = read_and_increment_offset(4).unpack("V")[0]
223
+ @info['bitrate'] = @info['max_bitrate'] / 1000
224
+
225
+ if @debug
226
+ @info.each_pair { |key,val| puts "#{key}: #{val}" }
227
+ end
228
+
229
+ end
230
+
231
+ def parse_asf_content_description_object
232
+ lengths = {}
233
+ keys = %w/Title Author Copyright Description Rating/
234
+ keys.each do |key| # read the lengths of each key
235
+ lengths[key] = read_and_increment_offset(2).unpack("v")[0]
236
+ end
237
+ keys.each do |key| # now pull the data based on length
238
+ @tags[key] = decode_binary_string(read_and_increment_offset(lengths[key]))
239
+ end
240
+ end
241
+
242
+ def parse_asf_extended_content_description_object
243
+ @ext_info = {}
244
+ @ext_info['content_count'] = read_and_increment_offset(2).unpack("v")[0]
245
+ @ext_info['content_count'].times do |n|
246
+ ext = {}
247
+ ext['base_offset'] = @offset + 30
248
+ ext['name_length'] = read_and_increment_offset(2).unpack("v")[0]
249
+ ext['name'] = decode_binary_string(read_and_increment_offset(ext['name_length']))
250
+ ext['value_type'] = read_and_increment_offset(2).unpack("v")[0]
251
+ ext['value_length'] = read_and_increment_offset(2).unpack("v")[0]
252
+
253
+ value = read_and_increment_offset(ext['value_length'])
254
+ if ext['value_type'] <= 1
255
+ ext['value'] = decode_binary_string(value)
256
+ elsif ext['value_type'] == 4
257
+ ext['value'] = parse_64bit_string(value)
258
+ else
259
+ value_type_template = ["", "", "V", "V", "", "v"]
260
+ ext['value'] = value.unpack(value_type_template[ext['value_type']])[0]
261
+ end
262
+
263
+ if @debug
264
+ puts "base_offset: #{ext['base_offset']}"
265
+ puts "name length: #{ext['name_length']}"
266
+ puts "name: #{ext['name']}"
267
+ puts "value type: #{ext['value_type']}"
268
+ puts "value length: #{ext['value_length']}"
269
+ puts "value: #{ext['value']}"
270
+ end
271
+
272
+ @ext_info["#{ext['name']}"] = ext['value']
273
+ end
274
+ end
275
+
276
+ def parse_asf_stream_properties_object
277
+
278
+ streamType = read_and_increment_offset(16)
279
+ @stream['stream_type_guid'] = byte_string_to_guid(streamType)
280
+ @stream['stream_type_name'] = @reverse_guid_mapping[@stream['stream_type_guid']]
281
+ errorType = read_and_increment_offset(16)
282
+ @stream['error_correct_guid'] = byte_string_to_guid(errorType)
283
+ @stream['error_correct_name'] = @reverse_guid_mapping[@stream['error_correct_guid']]
284
+
285
+ @stream['time_offset'] = read_and_increment_offset(8).unpack("4v")[0]
286
+ @stream['type_data_length'] = read_and_increment_offset(4).unpack("2v")[0]
287
+ @stream['error_data_length'] = read_and_increment_offset(4).unpack("2v")[0]
288
+ flags_raw = read_and_increment_offset(2).unpack("v")[0]
289
+ @stream['stream_number'] = flags_raw & 0x007F
290
+ @stream['encrypted'] = flags_raw & 0x8000
291
+
292
+ # reserved - set to zero
293
+ read_and_increment_offset(4)
294
+
295
+ @stream['type_specific_data'] = read_and_increment_offset(@stream['type_data_length'])
296
+ @stream['error_correct_data'] = read_and_increment_offset(@stream['error_data_length'])
297
+
298
+ if @stream['stream_type_name'] == 'ASF_Audio_Media'
299
+ parse_asf_audio_media_object
300
+ end
301
+ end
302
+
303
+ def parse_asf_audio_media_object
304
+ data = @stream['type_specific_data'][0...16]
305
+ @stream['audio_channels'] = data[2...4].unpack("v")[0]
306
+ @stream['audio_sample_rate'] = data[4...8].unpack("2v")[0]
307
+ @stream['audio_bitrate'] = data[8...12].unpack("2v")[0] * 8
308
+ @stream['audio_bits_per_sample'] = data[14...16].unpack("v")[0]
309
+ end
310
+
311
+ # UTF16LE -> ASCII
312
+ def decode_binary_string(data)
313
+ @ic.iconv(data).strip
314
+ end
315
+
316
+ def read_and_increment_offset(size)
317
+ value = @header_data[@offset...(@offset + size)]
318
+ @offset += size
319
+ return value
320
+ end
321
+
322
+ def byte_string_to_guid(byteString)
323
+ if RUBY_VERSION[0..2] != "1.8"
324
+ byteString = byteString.bytes.to_a
325
+ end
326
+ guidString = sprintf("%02X", byteString[3])
327
+ guidString += sprintf("%02X", byteString[2])
328
+ guidString += sprintf("%02X", byteString[1])
329
+ guidString += sprintf("%02X", byteString[0])
330
+ guidString += '-'
331
+ guidString += sprintf("%02X", byteString[5])
332
+ guidString += sprintf("%02X", byteString[4])
333
+ guidString += '-'
334
+ guidString += sprintf("%02X", byteString[7])
335
+ guidString += sprintf("%02X", byteString[6])
336
+ guidString += '-'
337
+ guidString += sprintf("%02X", byteString[8])
338
+ guidString += sprintf("%02X", byteString[9])
339
+ guidString += '-'
340
+ guidString += sprintf("%02X", byteString[10])
341
+ guidString += sprintf("%02X", byteString[11])
342
+ guidString += sprintf("%02X", byteString[12])
343
+ guidString += sprintf("%02X", byteString[13])
344
+ guidString += sprintf("%02X", byteString[14])
345
+ guidString += sprintf("%02X", byteString[15])
346
+ end
347
+
348
+ def parse_64bit_string(data)
349
+ d = data.unpack('VV')
350
+ d[1] * 2 ** 32 + d[0]
351
+ end
352
+
353
+ def file_time_to_unix_time(time)
354
+ (time - 116444736000000000) / 10000000
355
+ end
356
+
357
+ def known_guids
358
+ guid_mapping = {
359
+ 'ASF_Extended_Stream_Properties_Object' => '14E6A5CB-C672-4332-8399-A96952065B5A',
360
+ 'ASF_Padding_Object' => '1806D474-CADF-4509-A4BA-9AABCB96AAE8',
361
+ 'ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio' => '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8',
362
+ 'ASF_Script_Command_Object' => '1EFB1A30-0B62-11D0-A39B-00A0C90348F6',
363
+ 'ASF_No_Error_Correction' => '20FB5700-5B55-11CF-A8FD-00805F5C442B',
364
+ 'ASF_Content_Branding_Object' => '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E',
365
+ 'ASF_Content_Encryption_Object' => '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E',
366
+ 'ASF_Digital_Signature_Object' => '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E',
367
+ 'ASF_Extended_Content_Encryption_Object' => '298AE614-2622-4C17-B935-DAE07EE9289C',
368
+ 'ASF_Simple_Index_Object' => '33000890-E5B1-11CF-89F4-00A0C90349CB',
369
+ 'ASF_Degradable_JPEG_Media' => '35907DE0-E415-11CF-A917-00805F5C442B',
370
+ 'ASF_Payload_Extension_System_Timecode' => '399595EC-8667-4E2D-8FDB-98814CE76C1E',
371
+ 'ASF_Binary_Media' => '3AFB65E2-47EF-40F2-AC2C-70A90D71D343',
372
+ 'ASF_Timecode_Index_Object' => '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C',
373
+ 'ASF_Metadata_Library_Object' => '44231C94-9498-49D1-A141-1D134E457054',
374
+ 'ASF_Reserved_3' => '4B1ACBE3-100B-11D0-A39B-00A0C90348F6',
375
+ 'ASF_Reserved_4' => '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB',
376
+ 'ASF_Command_Media' => '59DACFC0-59E6-11D0-A3AC-00A0C90348F6',
377
+ 'ASF_Header_Extension_Object' => '5FBF03B5-A92E-11CF-8EE3-00C00C205365',
378
+ 'ASF_Media_Object_Index_Parameters_Obj' => '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7',
379
+ 'ASF_Header_Object' => '75B22630-668E-11CF-A6D9-00AA0062CE6C',
380
+ 'ASF_Content_Description_Object' => '75B22633-668E-11CF-A6D9-00AA0062CE6C',
381
+ 'ASF_Error_Correction_Object' => '75B22635-668E-11CF-A6D9-00AA0062CE6C',
382
+ 'ASF_Data_Object' => '75B22636-668E-11CF-A6D9-00AA0062CE6C',
383
+ 'ASF_Web_Stream_Media_Subtype' => '776257D4-C627-41CB-8F81-7AC7FF1C40CC',
384
+ 'ASF_Stream_Bitrate_Properties_Object' => '7BF875CE-468D-11D1-8D82-006097C9A2B2',
385
+ 'ASF_Language_List_Object' => '7C4346A9-EFE0-4BFC-B229-393EDE415C85',
386
+ 'ASF_Codec_List_Object' => '86D15240-311D-11D0-A3A4-00A0C90348F6',
387
+ 'ASF_Reserved_2' => '86D15241-311D-11D0-A3A4-00A0C90348F6',
388
+ 'ASF_File_Properties_Object' => '8CABDCA1-A947-11CF-8EE4-00C00C205365',
389
+ 'ASF_File_Transfer_Media' => '91BD222C-F21C-497A-8B6D-5AA86BFC0185',
390
+ 'ASF_Advanced_Mutual_Exclusion_Object' => 'A08649CF-4775-4670-8A16-6E35357566CD',
391
+ 'ASF_Bandwidth_Sharing_Object' => 'A69609E6-517B-11D2-B6AF-00C04FD908E9',
392
+ 'ASF_Reserved_1' => 'ABD3D211-A9BA-11cf-8EE6-00C00C205365',
393
+ 'ASF_Bandwidth_Sharing_Exclusive' => 'AF6060AA-5197-11D2-B6AF-00C04FD908E9',
394
+ 'ASF_Bandwidth_Sharing_Partial' => 'AF6060AB-5197-11D2-B6AF-00C04FD908E9',
395
+ 'ASF_JFIF_Media' => 'B61BE100-5B4E-11CF-A8FD-00805F5C442B',
396
+ 'ASF_Stream_Properties_Object' => 'B7DC0791-A9B7-11CF-8EE6-00C00C205365',
397
+ 'ASF_Video_Media' => 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B',
398
+ 'ASF_Audio_Spread' => 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220',
399
+ 'ASF_Metadata_Object' => 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA',
400
+ 'ASF_Payload_Ext_Syst_Sample_Duration' => 'C6BD9450-867F-4907-83A3-C77921B733AD',
401
+ 'ASF_Group_Mutual_Exclusion_Object' => 'D1465A40-5A79-4338-B71B-E36B8FD6C249',
402
+ 'ASF_Extended_Content_Description_Object' => 'D2D0A440-E307-11D2-97F0-00A0C95EA850',
403
+ 'ASF_Stream_Prioritization_Object' => 'D4FED15B-88D3-454F-81F0-ED5C45999E24',
404
+ 'ASF_Payload_Ext_System_Content_Type' => 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC',
405
+ 'ASF_Index_Object' => 'D6E229D3-35DA-11D1-9034-00A0C90349BE',
406
+ 'ASF_Bitrate_Mutual_Exclusion_Object' => 'D6E229DC-35DA-11D1-9034-00A0C90349BE',
407
+ 'ASF_Index_Parameters_Object' => 'D6E229DF-35DA-11D1-9034-00A0C90349BE',
408
+ 'ASF_Mutex_Language' => 'D6E22A00-35DA-11D1-9034-00A0C90349BE',
409
+ 'ASF_Mutex_Bitrate' => 'D6E22A01-35DA-11D1-9034-00A0C90349BE',
410
+ 'ASF_Mutex_Unknown' => 'D6E22A02-35DA-11D1-9034-00A0C90349BE',
411
+ 'ASF_Web_Stream_Format' => 'DA1E6B13-8359-4050-B398-388E965BF00C',
412
+ 'ASF_Payload_Ext_System_File_Name' => 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B',
413
+ 'ASF_Marker_Object' => 'F487CD01-A951-11CF-8EE6-00C00C205365',
414
+ 'ASF_Timecode_Index_Parameters_Object' => 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24',
415
+ 'ASF_Audio_Media' => 'F8699E40-5B4D-11CF-A8FD-00805F5C442B',
416
+ 'ASF_Media_Object_Index_Object' => 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C',
417
+ 'ASF_Alt_Extended_Content_Encryption_Obj' => 'FF889EF1-ADEE-40DA-9E71-98704BB928CE',
418
+ }
419
+ end
420
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: moumar-wmainfo-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.7'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Darren Kirby
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-02 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! ":: wmainfo-rb ::\nAuthor: Darren Kirby\nmailto:bulliver@badcomputer.org\nLicense:
15
+ Ruby\n\n = Quick API docs =\n\n == Initializing ==\n\n require 'wmainfo'\n foo
16
+ = WmaInfo.new(\"someSong.wma\")\n ... or ...\n foo = WmaInfo.new(\"someVideo.wmv\",
17
+ :encoding=>\"UTF-16LE\") \n (default encoding is ASCII)\n ... or ...\n foo
18
+ = WmaInfo.new(\"someVideo.wmv\", :debug=>1)\n\n == Public attributes ==\n\n @drm
19
+ \ :: 'true' if DRM present else 'false'\n @tags :: dict of strings
20
+ (id3 like data)\n @info :: dict of variable types (non-id3 like data)\n
21
+ \ @ext_info :: dict of variable types (non-id3 like data) from ASF_Extended_Content_Description_Object\n
22
+ \ @headerObject :: dict of arrays (name, GUID, size and offset of ASF objects)\n
23
+ \ @stream :: dict of variable types (stream properties data)\n\n == Public
24
+ methods ==\n\n print_objects :: pretty-print header objects\n hasdrm? ::
25
+ returns True if file has DRM\n hastag?('str') :: returns True if @tags['str']
26
+ exists\n print_tags :: pretty-print @tags dict\n hasinfo?('str') :: returns
27
+ True if @info['str'] exists\n print_info :: pretty-print @info dict\n print_stream
28
+ \ :: pretty-print @stream dict\n\n For more/different documentation see http://badcomputer.org/unix/code/wmainfo/\n\n
29
+ \ == Thanks/Contributors == \n\n Ilmari Heikkinen sent in a fix for uninitialized
30
+ '@ext_info'.\n Guillaume Pierronnet sent in a patch which improves character encoding
31
+ handling.\n"
32
+ email: bulliver@badcomputer.org
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files:
36
+ - README
37
+ files:
38
+ - README
39
+ - lib/wmainfo.rb
40
+ homepage: http://badcomputer.org/unix/code/wmainfo/
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project: wmainfo-rb
60
+ rubygems_version: 1.8.17
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Pure Ruby lib for accessing info/tags from wma/wmv files
64
+ test_files: []