mediainfo 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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