mediainfo 0.6.2 → 0.7.0

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/Changelog CHANGED
@@ -1,3 +1,8 @@
1
+ v0.7.0 Multiple Stream Support
2
+ - ***NOTE*** MAJOR API CHANGE ***NOTE***
3
+ - updated API to support files with multiple streams of one type,
4
+ e.g. a Quicktime with multiple video tracks
5
+
1
6
  v0.6.2 XML Configuration Examples
2
7
  - added examples on how to configure XML back end
3
8
 
data/Manifest CHANGED
@@ -3,6 +3,7 @@ LICENSE
3
3
  Manifest
4
4
  README.markdown
5
5
  Rakefile
6
+ index.html.template
6
7
  lib/mediainfo.rb
7
8
  lib/mediainfo/attr_readers.rb
8
9
  lib/mediainfo/string.rb
data/README.markdown CHANGED
@@ -54,3 +54,4 @@ generate XML output, and is no longer supported.
54
54
 
55
55
  * Seth Thomas Rasmussen - [http://greatseth.com](http://greatseth.com)
56
56
  * Peter Vandenberk - [http://github.com/pvdb](http://github.com/pvdb)
57
+ * Ned Campion - [http://github.com/nedcampion](http://github.com/nedcampion)
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ class Echoe
18
18
  end
19
19
 
20
20
  Echoe.new "mediainfo" do |p|
21
- p.description = "Mediainfo is a class wrapping the mediainfo CLI (http://mediainfo.sourceforge.net)"
21
+ p.description = p.summary = "Mediainfo is a class wrapping the mediainfo CLI (http://mediainfo.sourceforge.net)"
22
22
  p.author = "Seth Thomas Rasmussen"
23
23
  p.email = "sethrasmussen@gmail.com"
24
24
  p.url = "http://greatseth.github.com/mediainfo"
@@ -59,3 +59,5 @@ task :fixture do
59
59
  puts "Error generating fixture. #{fixture} not created."
60
60
  end
61
61
  end
62
+
63
+ # require 'github/pages/tasks'
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
6
+ <title>mediainfo</title>
7
+ </head>
8
+ <body>body</body>
9
+ </html>
data/lib/mediainfo.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "forwardable"
1
2
  require "mediainfo/string"
2
3
  require "mediainfo/attr_readers"
3
4
 
@@ -55,155 +56,268 @@ Previous versions of this gem(<= 0.5.1) worked against v0.7.11, which did not
55
56
  generate XML output, and is no longer supported.
56
57
  =end
57
58
  class Mediainfo
59
+ extend Forwardable
58
60
  extend AttrReaders
59
61
 
60
- SECTIONS = %w( audio video image ) # and General
61
-
62
- ### GENERAL
63
-
64
- mediainfo_attr_reader :codec_id, "Codec ID"
65
-
66
- mediainfo_duration_reader :duration
67
-
68
- mediainfo_attr_reader :format
69
- mediainfo_attr_reader :format_profile
70
- mediainfo_attr_reader :format_info
71
- mediainfo_attr_reader :overall_bit_rate
72
- mediainfo_attr_reader :writing_application
73
- mediainfo_attr_reader :writing_library
74
-
75
- def size; File.size(@full_filename) if @full_filename; end
76
-
77
- mediainfo_date_reader :mastered_date
78
- mediainfo_date_reader :tagged_date
79
- mediainfo_date_reader :encoded_date
80
-
81
- ### VIDEO
82
-
83
- mediainfo_section_query :video
84
-
85
- mediainfo_attr_reader :video_stream_id, "ID"
86
-
87
- mediainfo_duration_reader :video_duration
88
-
89
- mediainfo_attr_reader :video_stream_size
90
- mediainfo_attr_reader :video_bit_rate
91
- mediainfo_attr_reader :video_nominal_bit_rate
92
-
93
- mediainfo_attr_reader :video_bit_rate_mode
94
- def cbr?; video? and "Constant" == video_bit_rate_mode; end
95
- def vbr?; video? and not cbr?; end
96
-
97
- mediainfo_attr_reader :video_scan_order
98
- mediainfo_attr_reader :video_scan_type
99
- def interlaced?; video? and "Interlaced" == video_scan_type; end
100
- def progressive?; video? and not interlaced? end
101
-
102
- mediainfo_int_reader :video_resolution
103
-
104
- mediainfo_attr_reader :video_colorimetry
105
- alias_method :video_colorspace, :video_colorimetry
106
-
107
- mediainfo_attr_reader :video_format
108
- mediainfo_attr_reader :video_format_profile
109
- mediainfo_attr_reader :video_format_version
110
- mediainfo_attr_reader :video_format_settings_cabac, "Format settings, CABAC"
111
- mediainfo_attr_reader :video_format_settings_reframes, "Format settings, ReFrames"
112
- mediainfo_attr_reader :video_format_settings_matrix, "Format settings, Matrix"
113
- # Format settings, BVOP : Yes
114
- # Format settings, QPel : No
115
- # Format settings, GMC : No warppoints
116
- # mediainfo_attr_reader :video_format_settings_qpel, "Format settings, QPel"
117
- mediainfo_attr_reader :video_color_primaries
118
- mediainfo_attr_reader :video_transfer_characteristics
119
- mediainfo_attr_reader :video_matrix_coefficients
120
-
121
- mediainfo_attr_reader :video_codec_id, "Codec ID"
122
- mediainfo_attr_reader :video_codec_info, "Codec ID/Info"
123
-
124
- mediainfo_attr_reader :video_frame_rate
125
- def fps; video_frame_rate[/[\d.]+/].to_f if video?; end
126
- alias_method :framerate, :fps
127
-
128
- mediainfo_attr_reader :video_minimum_frame_rate
129
- def min_fps; video_minimum_frame_rate[/[\d.]+/].to_f if video?; end
130
- alias_method :min_framerate, :min_fps
131
-
132
- mediainfo_attr_reader :video_maximum_frame_rate
133
- def max_fps; video_maximum_frame_rate[/[\d.]+/].to_f if video?; end
134
- alias_method :max_framerate, :max_fps
135
-
136
- mediainfo_attr_reader :video_frame_rate_mode
137
-
138
- mediainfo_attr_reader :video_display_aspect_ratio
139
- alias_method :display_aspect_ratio, :video_display_aspect_ratio
140
-
141
- mediainfo_attr_reader :video_bits_pixel_frame, "Bits/(Pixel*Frame)"
142
-
143
- mediainfo_int_reader :video_width
144
- mediainfo_int_reader :video_height
62
+ class Error < StandardError; end
63
+ class ExecutionError < Error; end
64
+ class IncompatibleVersionError < Error; end
145
65
 
146
- def resolution; "#{width}x#{height}" if video? or image?; end
147
- def width; if video?; video_width; elsif image?; image_width; end; end
148
- def height; if video?; video_height; elsif image?; image_height; end; end
66
+ def self.delegate(method_name, stream_type = nil)
67
+ if stream_type == :general
68
+ def_delegator :"@#{stream_type}_stream", method_name
69
+ else
70
+ def_delegator :"@#{stream_type}_stream", method_name, "#{stream_type}_#{method_name}"
71
+ end
72
+ end
149
73
 
150
- mediainfo_date_reader :video_encoded_date
151
- mediainfo_date_reader :video_tagged_date
74
+ def self.version
75
+ @version ||= `#{path} --Version`[/v([\d.]+)/, 1]
76
+ end
152
77
 
153
- ### AUDIO
78
+ # AttrReaders depends on this.
79
+ def self.supported_attributes; @supported_attributes ||= []; end
154
80
 
155
- mediainfo_section_query :audio
81
+ SECTIONS = [:general, :video, :audio, :image]
82
+ NON_GENERAL_SECTIONS = SECTIONS - [:general]
156
83
 
157
- mediainfo_attr_reader :audio_stream_id, "ID"
84
+ attr_reader :streams
158
85
 
159
- mediainfo_duration_reader :audio_duration
86
+ # Size of source file as reported by File.size.
87
+ # Returns nil if you haven't yet fired off the system command.
88
+ def size; File.size(@full_filename) if @full_filename; end
160
89
 
161
- mediainfo_attr_reader :audio_sampling_rate
162
- def audio_sample_rate
163
- return unless rate = audio_sampling_rate_before_type_cast
164
- number = rate.gsub(/[^\d.]+/, "").to_f
165
- number = case rate
166
- when /KHz/ then number * 1000
167
- when /Hz/ then number
168
- else
169
- raise "unhandled sample rate! please report bug!"
90
+ class StreamProxy
91
+ def initialize(mediainfo, stream_type)
92
+ unless Mediainfo::SECTIONS.include? stream_type
93
+ raise ArgumentError, "invalid stream_type: #{stream_type.inspect}"
94
+ end
95
+
96
+ @stream_type = stream_type
97
+ @mediainfo = mediainfo
98
+ @streams = @mediainfo.streams.select { |x| x.send("#{stream_type}?") }
99
+ end
100
+
101
+ def [](id); @streams[id]; end
102
+ def count; @streams.size; end
103
+ attr_reader :streams
104
+ attr_reader :stream_type
105
+
106
+ class SingleStreamAPIError < RuntimeError; end
107
+ class NoStreamsForProxyError < NoMethodError; end
108
+
109
+ def method_missing(m, *a, &b)
110
+ if streams.size > 1
111
+ raise SingleStreamAPIError, "You cannot use the single stream, convenience API on a multi-stream file."
112
+ else
113
+ if relevant_stream = streams.detect { |s| s.respond_to?(m) }
114
+ relevant_stream.send(m, *a, &b)
115
+ else
116
+ raise NoStreamsForProxyError, "there are no :#{stream_type} streams to send :#{m} to"
117
+ end
118
+ end
170
119
  end
171
- number.to_i
172
120
  end
173
- alias_method :audio_sampling_rate, :audio_sample_rate
174
-
175
- mediainfo_attr_reader :audio_stream_size
176
- mediainfo_attr_reader :audio_bit_rate
177
- mediainfo_attr_reader :audio_bit_rate_mode
178
- mediainfo_attr_reader :audio_interleave_duration, "Interleave, duration"
179
121
 
180
- mediainfo_int_reader :audio_resolution
181
- alias_method :audio_sample_bit_depth, :audio_resolution
182
-
183
- mediainfo_attr_reader :audio_format
184
- mediainfo_attr_reader :audio_format_info, "Format/Info"
185
- mediainfo_attr_reader :audio_format_settings_endianness, "Format settings, Endianness"
186
- mediainfo_attr_reader :audio_format_settings_sign, "Format settings, Sign"
187
- mediainfo_attr_reader :audio_codec_id, "Codec ID"
188
- mediainfo_attr_reader :audio_codec_info, "Codec ID/Info"
189
- mediainfo_attr_reader :audio_codec_id_hint
190
- mediainfo_attr_reader :audio_channel_positions
122
+ class Stream
123
+ class InvalidStreamType < Mediainfo::Error; end
124
+
125
+ def self.inherited(stream_type)
126
+ stream_type.extend(AttrReaders)
127
+
128
+ def stream_type.method_added(method_name)
129
+ if stream_type = name[/[^:]+$/][/^(#{SECTIONS.map { |x| x.to_s.capitalize } * '|'})/]
130
+ stream_type.downcase!
131
+ stream_type = stream_type.to_sym
132
+ else
133
+ raise "could not determine stream type, please report bug!"
134
+ end
135
+
136
+ Mediainfo.delegate(method_name, stream_type)
137
+ end
138
+ end
139
+
140
+ def self.create(stream_type)
141
+ raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
142
+
143
+ stream_class_name = "#{stream_type}Stream"
144
+
145
+ if Mediainfo.const_defined?(stream_class_name)
146
+ Mediainfo.const_get(stream_class_name).new(stream_type)
147
+ else
148
+ raise InvalidStreamType, "bad stream type: #{stream_type.inspect}"
149
+ end
150
+ end
151
+
152
+ def initialize(stream_type)
153
+ raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
154
+
155
+ @stream_type = stream_type.downcase.to_sym
156
+
157
+ # TODO @parsed_response is not the best name anymore, but I'm leaving it
158
+ # alone to focus on refactoring the interface to the streams
159
+ # before I refactor the attribute reader implementations.
160
+ @parsed_response = { @stream_type => {} }
161
+ end
162
+
163
+ def [](k); @parsed_response[@stream_type][k]; end
164
+ def []=(k,v); @parsed_response[@stream_type][k] = v; end
165
+
166
+ Mediainfo::SECTIONS.each { |t| define_method("#{t}?") { t == @stream_type } }
167
+ end
191
168
 
192
- mediainfo_int_reader :audio_channels, "Channel(s)"
193
- def stereo?; 2 == audio_channels; end
194
- def mono?; 1 == audio_channels; end
169
+ class GeneralStream < Stream
170
+ mediainfo_attr_reader :codec_id, "Codec ID"
171
+
172
+ mediainfo_duration_reader :duration
173
+
174
+ mediainfo_attr_reader :format
175
+ mediainfo_attr_reader :format_profile
176
+ mediainfo_attr_reader :format_info
177
+ mediainfo_attr_reader :overall_bit_rate
178
+ mediainfo_attr_reader :writing_application
179
+ mediainfo_attr_reader :writing_library
180
+
181
+ mediainfo_date_reader :mastered_date
182
+ mediainfo_date_reader :tagged_date
183
+ mediainfo_date_reader :encoded_date
184
+ end
195
185
 
196
- mediainfo_date_reader :audio_encoded_date
197
- mediainfo_date_reader :audio_tagged_date
186
+ class VideoStream < Stream
187
+ mediainfo_attr_reader :stream_id, "ID"
188
+
189
+ mediainfo_duration_reader :duration
190
+
191
+ mediainfo_attr_reader :stream_size
192
+ mediainfo_attr_reader :bit_rate
193
+ mediainfo_attr_reader :nominal_bit_rate
194
+
195
+ mediainfo_attr_reader :bit_rate_mode
196
+ def cbr?; video? and "Constant" == bit_rate_mode; end
197
+ def vbr?; video? and not cbr?; end
198
+
199
+ mediainfo_attr_reader :scan_order
200
+ mediainfo_attr_reader :scan_type
201
+ def interlaced?; video? and "Interlaced" == scan_type; end
202
+ def progressive?; video? and not interlaced? end
203
+
204
+ mediainfo_int_reader :resolution
205
+
206
+ mediainfo_attr_reader :colorimetry
207
+ alias_method :colorspace, :colorimetry
208
+
209
+ mediainfo_attr_reader :format
210
+ mediainfo_attr_reader :format_info
211
+ mediainfo_attr_reader :format_profile
212
+ mediainfo_attr_reader :format_version
213
+ mediainfo_attr_reader :format_settings_cabac, "Format settings, CABAC"
214
+ mediainfo_attr_reader :format_settings_reframes, "Format settings, ReFrames"
215
+ mediainfo_attr_reader :format_settings_matrix, "Format settings, Matrix"
216
+ # Format settings, BVOP : Yes
217
+ # Format settings, QPel : No
218
+ # Format settings, GMC : No warppoints
219
+ # mediainfo_attr_reader :format_settings_qpel, "Format settings, QPel"
220
+ mediainfo_attr_reader :color_primaries
221
+ mediainfo_attr_reader :transfer_characteristics
222
+ mediainfo_attr_reader :matrix_coefficients
223
+
224
+ mediainfo_attr_reader :codec_id, "Codec ID"
225
+ mediainfo_attr_reader :codec_info, "Codec ID/Info"
226
+ alias_method :codec_id_info, :codec_info
227
+
228
+ mediainfo_attr_reader :frame_rate
229
+ def fps; frame_rate[/[\d.]+/].to_f if frame_rate; end
230
+ alias_method :framerate, :fps
231
+
232
+ mediainfo_attr_reader :minimum_frame_rate
233
+ def min_fps; minimum_frame_rate[/[\d.]+/].to_f if video?; end
234
+ alias_method :min_framerate, :min_fps
235
+
236
+ mediainfo_attr_reader :maximum_frame_rate
237
+ def max_fps; maximum_frame_rate[/[\d.]+/].to_f if video?; end
238
+ alias_method :max_framerate, :max_fps
239
+
240
+ mediainfo_attr_reader :frame_rate_mode
241
+
242
+ mediainfo_attr_reader :display_aspect_ratio
243
+ # alias_method :display_aspect_ratio, :display_aspect_ratio
244
+
245
+ mediainfo_attr_reader :bits_pixel_frame, "Bits/(Pixel*Frame)"
246
+
247
+ mediainfo_int_reader :width
248
+ mediainfo_int_reader :height
249
+
250
+ def frame_size; "#{width}x#{height}" if width or height; end
251
+
252
+ mediainfo_date_reader :encoded_date
253
+ mediainfo_date_reader :tagged_date
254
+
255
+ mediainfo_attr_reader :standard
256
+ end
198
257
 
199
- ### IMAGE
258
+ class AudioStream < Stream
259
+ mediainfo_attr_reader :stream_id, "ID"
260
+
261
+ mediainfo_duration_reader :duration
262
+
263
+ mediainfo_attr_reader :sampling_rate
264
+ def sample_rate
265
+ return unless rate = sampling_rate_before_type_cast
266
+ number = rate.gsub(/[^\d.]+/, "").to_f
267
+ number = case rate
268
+ when /KHz/ then number * 1000
269
+ when /Hz/ then number
270
+ else
271
+ raise "unhandled sample rate! please report bug!"
272
+ end
273
+ number.to_i
274
+ end
275
+ alias_method :sampling_rate, :sample_rate
276
+
277
+ mediainfo_attr_reader :stream_size
278
+ mediainfo_attr_reader :bit_rate
279
+ mediainfo_attr_reader :bit_rate_mode
280
+ mediainfo_attr_reader :interleave_duration, "Interleave, duration"
281
+
282
+ mediainfo_int_reader :resolution
283
+ alias_method :sample_bit_depth, :resolution
284
+
285
+ mediainfo_attr_reader :format
286
+ mediainfo_attr_reader :format_profile
287
+ mediainfo_attr_reader :format_version
288
+ mediainfo_attr_reader :format_info, "Format/Info"
289
+ mediainfo_attr_reader :format_settings_sbr, "Format settings, SBR"
290
+ mediainfo_attr_reader :format_settings_endianness, "Format settings, Endianness"
291
+ mediainfo_attr_reader :format_settings_sign, "Format settings, Sign"
292
+ mediainfo_attr_reader :codec_id, "Codec ID"
293
+ mediainfo_attr_reader :codec_info, "Codec ID/Info"
294
+ mediainfo_attr_reader :codec_id_hint
295
+ mediainfo_attr_reader :channel_positions
296
+
297
+ mediainfo_int_reader :channels, "Channel(s)"
298
+ def stereo?; 2 == channels; end
299
+ def mono?; 1 == channels; end
300
+
301
+ mediainfo_date_reader :encoded_date
302
+ mediainfo_date_reader :tagged_date
303
+ end
200
304
 
201
- mediainfo_section_query :image
202
- mediainfo_attr_reader :image_resolution
203
- mediainfo_attr_reader :image_format
305
+ class ImageStream < Stream
306
+ mediainfo_attr_reader :resolution
307
+ mediainfo_attr_reader :format
308
+
309
+ mediainfo_int_reader :width
310
+ mediainfo_int_reader :height
311
+
312
+ def frame_size; "#{width}x#{height}" if width or height; end
313
+ end
204
314
 
205
- mediainfo_int_reader :image_width
206
- mediainfo_int_reader :image_height
315
+ Mediainfo::SECTIONS.each do |stream_type|
316
+ class_eval %{
317
+ def #{stream_type}; @#{stream_type}_proxy ||= StreamProxy.new(self, :#{stream_type}); end
318
+ def #{stream_type}?; streams.any? { |x| x.#{stream_type}? }; end
319
+ }, __FILE__, __LINE__
320
+ end
207
321
 
208
322
  ###
209
323
 
@@ -212,16 +326,6 @@ class Mediainfo
212
326
 
213
327
  ###
214
328
 
215
- class Error < StandardError; end
216
- class ExecutionError < Error; end
217
- class IncompatibleVersionError < Error; end
218
-
219
- def self.version
220
- @version ||= `#{path} --Version`[/v([\d.]+)/, 1]
221
- end
222
-
223
- ###
224
-
225
329
  def initialize(full_filename = nil)
226
330
  if mediainfo_version < "0.7.25"
227
331
  raise IncompatibleVersionError,
@@ -229,6 +333,8 @@ class Mediainfo
229
333
  "is not compatible with this gem. >= 0.7.25 required."
230
334
  end
231
335
 
336
+ @streams = []
337
+
232
338
  if full_filename
233
339
  @full_filename = File.expand_path full_filename
234
340
  @path = File.dirname @full_filename
@@ -280,14 +386,12 @@ class Mediainfo
280
386
  def self.default_mediainfo_path!; self.path = "mediainfo"; end
281
387
  default_mediainfo_path! unless path
282
388
 
283
- def mediainfo_version
284
- self.class.version
285
- end
389
+ def mediainfo_version; self.class.version; end
286
390
 
287
391
  attr_reader :last_command
288
392
 
289
393
  def inspect
290
- super.sub /@raw_response=".+?", @/, %{@raw_response="...", @}
394
+ super.sub(/@raw_response=".+?", @/, %{@raw_response="...", @})
291
395
  end
292
396
 
293
397
  private
@@ -312,48 +416,44 @@ private
312
416
  require "rexml/document"
313
417
  end
314
418
 
315
- @parsed_response = {}
316
-
317
419
  case xml_parser
318
420
  when "nokogiri"
319
421
  Nokogiri::XML(@raw_response).xpath("//track").each { |t|
320
- bucket = bucket_for t['type']
321
-
422
+ s = Stream.create(t['type'])
322
423
  t.xpath("*").each do |c|
323
- bucket[key_for(c)] = c.content.strip
424
+ s[key_for(c)] = c.content.strip
324
425
  end
426
+ @streams << s
325
427
  }
326
428
  when "hpricot"
327
429
  Hpricot::XML(@raw_response).search("track").each { |t|
328
- bucket = bucket_for t['type']
329
-
430
+ s = Stream.create(t['type'])
330
431
  t.children.select { |n| n.is_a? Hpricot::Elem }.each do |c|
331
- bucket[key_for(c)] = c.inner_html.strip
432
+ s[key_for(c)] = c.inner_html.strip
332
433
  end
434
+ @streams << s
333
435
  }
334
436
  else
335
437
  REXML::Document.new(@raw_response).elements.each("/Mediainfo/File/track") { |t|
336
- bucket = bucket_for t.attributes['type']
337
-
438
+ s = Stream.create(t.attributes['type'])
338
439
  t.children.select { |n| n.is_a? REXML::Element }.each do |c|
339
- bucket[key_for(c)] = c.text.strip
440
+ s[key_for(c)] = c.text.strip
340
441
  end
442
+ @streams << s
341
443
  }
342
444
  end
445
+
446
+ SECTIONS.each do |section|
447
+ default_target_stream = if send("#{section}?")
448
+ send(section).streams.first
449
+ else
450
+ Mediainfo.const_get("#{section.to_s.capitalize}Stream").new(section.to_s.capitalize)
451
+ end
452
+ instance_variable_set "@#{section}_stream", default_target_stream
453
+ end
343
454
  end
344
455
 
345
456
  def key_for(attribute_node)
346
457
  attribute_node.name.downcase.gsub(/_+/, "_").gsub(/_s(\W|$)/, "s").strip
347
458
  end
348
-
349
- def bucket_for(section)
350
- section = section.downcase if section
351
-
352
- if section == "general"
353
- @parsed_response
354
- else
355
- @parsed_response[section] ||= {}
356
- @parsed_response[section]
357
- end
358
- end
359
459
  end