moumar-wmainfo-rb 0.7

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.
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: []