lib/mediainfo.rb in mediainfo-0.6.2 vs lib/mediainfo.rb in mediainfo-0.7.0
- old
+ new
@@ -1,5 +1,6 @@
+require "forwardable"
require "mediainfo/string"
require "mediainfo/attr_readers"
=begin
# Mediainfo
@@ -53,184 +54,289 @@
Previous versions of this gem(<= 0.5.1) worked against v0.7.11, which did not
generate XML output, and is no longer supported.
=end
class Mediainfo
+ extend Forwardable
extend AttrReaders
- SECTIONS = %w( audio video image ) # and General
+ class Error < StandardError; end
+ class ExecutionError < Error; end
+ class IncompatibleVersionError < Error; end
- ### GENERAL
+ def self.delegate(method_name, stream_type = nil)
+ if stream_type == :general
+ def_delegator :"@#{stream_type}_stream", method_name
+ else
+ def_delegator :"@#{stream_type}_stream", method_name, "#{stream_type}_#{method_name}"
+ end
+ end
- mediainfo_attr_reader :codec_id, "Codec ID"
+ def self.version
+ @version ||= `#{path} --Version`[/v([\d.]+)/, 1]
+ end
- mediainfo_duration_reader :duration
+ # AttrReaders depends on this.
+ def self.supported_attributes; @supported_attributes ||= []; end
- mediainfo_attr_reader :format
- mediainfo_attr_reader :format_profile
- mediainfo_attr_reader :format_info
- mediainfo_attr_reader :overall_bit_rate
- mediainfo_attr_reader :writing_application
- mediainfo_attr_reader :writing_library
+ SECTIONS = [:general, :video, :audio, :image]
+ NON_GENERAL_SECTIONS = SECTIONS - [:general]
+ attr_reader :streams
+
+ # Size of source file as reported by File.size.
+ # Returns nil if you haven't yet fired off the system command.
def size; File.size(@full_filename) if @full_filename; end
- mediainfo_date_reader :mastered_date
- mediainfo_date_reader :tagged_date
- mediainfo_date_reader :encoded_date
+ class StreamProxy
+ def initialize(mediainfo, stream_type)
+ unless Mediainfo::SECTIONS.include? stream_type
+ raise ArgumentError, "invalid stream_type: #{stream_type.inspect}"
+ end
+
+ @stream_type = stream_type
+ @mediainfo = mediainfo
+ @streams = @mediainfo.streams.select { |x| x.send("#{stream_type}?") }
+ end
+
+ def [](id); @streams[id]; end
+ def count; @streams.size; end
+ attr_reader :streams
+ attr_reader :stream_type
+
+ class SingleStreamAPIError < RuntimeError; end
+ class NoStreamsForProxyError < NoMethodError; end
+
+ def method_missing(m, *a, &b)
+ if streams.size > 1
+ raise SingleStreamAPIError, "You cannot use the single stream, convenience API on a multi-stream file."
+ else
+ if relevant_stream = streams.detect { |s| s.respond_to?(m) }
+ relevant_stream.send(m, *a, &b)
+ else
+ raise NoStreamsForProxyError, "there are no :#{stream_type} streams to send :#{m} to"
+ end
+ end
+ end
+ end
- ### VIDEO
+ class Stream
+ class InvalidStreamType < Mediainfo::Error; end
+
+ def self.inherited(stream_type)
+ stream_type.extend(AttrReaders)
+
+ def stream_type.method_added(method_name)
+ if stream_type = name[/[^:]+$/][/^(#{SECTIONS.map { |x| x.to_s.capitalize } * '|'})/]
+ stream_type.downcase!
+ stream_type = stream_type.to_sym
+ else
+ raise "could not determine stream type, please report bug!"
+ end
+
+ Mediainfo.delegate(method_name, stream_type)
+ end
+ end
+
+ def self.create(stream_type)
+ raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
+
+ stream_class_name = "#{stream_type}Stream"
+
+ if Mediainfo.const_defined?(stream_class_name)
+ Mediainfo.const_get(stream_class_name).new(stream_type)
+ else
+ raise InvalidStreamType, "bad stream type: #{stream_type.inspect}"
+ end
+ end
+
+ def initialize(stream_type)
+ raise ArgumentError, "need a stream_type, received #{stream_type.inspect}" if stream_type.nil?
+
+ @stream_type = stream_type.downcase.to_sym
+
+ # TODO @parsed_response is not the best name anymore, but I'm leaving it
+ # alone to focus on refactoring the interface to the streams
+ # before I refactor the attribute reader implementations.
+ @parsed_response = { @stream_type => {} }
+ end
+
+ def [](k); @parsed_response[@stream_type][k]; end
+ def []=(k,v); @parsed_response[@stream_type][k] = v; end
+
+ Mediainfo::SECTIONS.each { |t| define_method("#{t}?") { t == @stream_type } }
+ end
- mediainfo_section_query :video
+ class GeneralStream < Stream
+ mediainfo_attr_reader :codec_id, "Codec ID"
+
+ mediainfo_duration_reader :duration
+
+ mediainfo_attr_reader :format
+ mediainfo_attr_reader :format_profile
+ mediainfo_attr_reader :format_info
+ mediainfo_attr_reader :overall_bit_rate
+ mediainfo_attr_reader :writing_application
+ mediainfo_attr_reader :writing_library
+
+ mediainfo_date_reader :mastered_date
+ mediainfo_date_reader :tagged_date
+ mediainfo_date_reader :encoded_date
+ end
- mediainfo_attr_reader :video_stream_id, "ID"
+ class VideoStream < Stream
+ mediainfo_attr_reader :stream_id, "ID"
+
+ mediainfo_duration_reader :duration
+
+ mediainfo_attr_reader :stream_size
+ mediainfo_attr_reader :bit_rate
+ mediainfo_attr_reader :nominal_bit_rate
+
+ mediainfo_attr_reader :bit_rate_mode
+ def cbr?; video? and "Constant" == bit_rate_mode; end
+ def vbr?; video? and not cbr?; end
+
+ mediainfo_attr_reader :scan_order
+ mediainfo_attr_reader :scan_type
+ def interlaced?; video? and "Interlaced" == scan_type; end
+ def progressive?; video? and not interlaced? end
+
+ mediainfo_int_reader :resolution
+
+ mediainfo_attr_reader :colorimetry
+ alias_method :colorspace, :colorimetry
+
+ mediainfo_attr_reader :format
+ mediainfo_attr_reader :format_info
+ mediainfo_attr_reader :format_profile
+ mediainfo_attr_reader :format_version
+ mediainfo_attr_reader :format_settings_cabac, "Format settings, CABAC"
+ mediainfo_attr_reader :format_settings_reframes, "Format settings, ReFrames"
+ mediainfo_attr_reader :format_settings_matrix, "Format settings, Matrix"
+ # Format settings, BVOP : Yes
+ # Format settings, QPel : No
+ # Format settings, GMC : No warppoints
+ # mediainfo_attr_reader :format_settings_qpel, "Format settings, QPel"
+ mediainfo_attr_reader :color_primaries
+ mediainfo_attr_reader :transfer_characteristics
+ mediainfo_attr_reader :matrix_coefficients
+
+ mediainfo_attr_reader :codec_id, "Codec ID"
+ mediainfo_attr_reader :codec_info, "Codec ID/Info"
+ alias_method :codec_id_info, :codec_info
+
+ mediainfo_attr_reader :frame_rate
+ def fps; frame_rate[/[\d.]+/].to_f if frame_rate; end
+ alias_method :framerate, :fps
+
+ mediainfo_attr_reader :minimum_frame_rate
+ def min_fps; minimum_frame_rate[/[\d.]+/].to_f if video?; end
+ alias_method :min_framerate, :min_fps
+
+ mediainfo_attr_reader :maximum_frame_rate
+ def max_fps; maximum_frame_rate[/[\d.]+/].to_f if video?; end
+ alias_method :max_framerate, :max_fps
+
+ mediainfo_attr_reader :frame_rate_mode
+
+ mediainfo_attr_reader :display_aspect_ratio
+ # alias_method :display_aspect_ratio, :display_aspect_ratio
+
+ mediainfo_attr_reader :bits_pixel_frame, "Bits/(Pixel*Frame)"
+
+ mediainfo_int_reader :width
+ mediainfo_int_reader :height
+
+ def frame_size; "#{width}x#{height}" if width or height; end
+
+ mediainfo_date_reader :encoded_date
+ mediainfo_date_reader :tagged_date
+
+ mediainfo_attr_reader :standard
+ end
- mediainfo_duration_reader :video_duration
-
- mediainfo_attr_reader :video_stream_size
- mediainfo_attr_reader :video_bit_rate
- mediainfo_attr_reader :video_nominal_bit_rate
-
- mediainfo_attr_reader :video_bit_rate_mode
- def cbr?; video? and "Constant" == video_bit_rate_mode; end
- def vbr?; video? and not cbr?; end
-
- mediainfo_attr_reader :video_scan_order
- mediainfo_attr_reader :video_scan_type
- def interlaced?; video? and "Interlaced" == video_scan_type; end
- def progressive?; video? and not interlaced? end
-
- mediainfo_int_reader :video_resolution
-
- mediainfo_attr_reader :video_colorimetry
- alias_method :video_colorspace, :video_colorimetry
-
- mediainfo_attr_reader :video_format
- mediainfo_attr_reader :video_format_profile
- mediainfo_attr_reader :video_format_version
- mediainfo_attr_reader :video_format_settings_cabac, "Format settings, CABAC"
- mediainfo_attr_reader :video_format_settings_reframes, "Format settings, ReFrames"
- mediainfo_attr_reader :video_format_settings_matrix, "Format settings, Matrix"
- # Format settings, BVOP : Yes
- # Format settings, QPel : No
- # Format settings, GMC : No warppoints
- # mediainfo_attr_reader :video_format_settings_qpel, "Format settings, QPel"
- mediainfo_attr_reader :video_color_primaries
- mediainfo_attr_reader :video_transfer_characteristics
- mediainfo_attr_reader :video_matrix_coefficients
-
- mediainfo_attr_reader :video_codec_id, "Codec ID"
- mediainfo_attr_reader :video_codec_info, "Codec ID/Info"
-
- mediainfo_attr_reader :video_frame_rate
- def fps; video_frame_rate[/[\d.]+/].to_f if video?; end
- alias_method :framerate, :fps
-
- mediainfo_attr_reader :video_minimum_frame_rate
- def min_fps; video_minimum_frame_rate[/[\d.]+/].to_f if video?; end
- alias_method :min_framerate, :min_fps
-
- mediainfo_attr_reader :video_maximum_frame_rate
- def max_fps; video_maximum_frame_rate[/[\d.]+/].to_f if video?; end
- alias_method :max_framerate, :max_fps
-
- mediainfo_attr_reader :video_frame_rate_mode
-
- mediainfo_attr_reader :video_display_aspect_ratio
- alias_method :display_aspect_ratio, :video_display_aspect_ratio
-
- mediainfo_attr_reader :video_bits_pixel_frame, "Bits/(Pixel*Frame)"
-
- mediainfo_int_reader :video_width
- mediainfo_int_reader :video_height
-
- def resolution; "#{width}x#{height}" if video? or image?; end
- def width; if video?; video_width; elsif image?; image_width; end; end
- def height; if video?; video_height; elsif image?; image_height; end; end
-
- mediainfo_date_reader :video_encoded_date
- mediainfo_date_reader :video_tagged_date
-
- ### AUDIO
-
- mediainfo_section_query :audio
-
- mediainfo_attr_reader :audio_stream_id, "ID"
-
- mediainfo_duration_reader :audio_duration
-
- mediainfo_attr_reader :audio_sampling_rate
- def audio_sample_rate
- return unless rate = audio_sampling_rate_before_type_cast
- number = rate.gsub(/[^\d.]+/, "").to_f
- number = case rate
- when /KHz/ then number * 1000
- when /Hz/ then number
- else
- raise "unhandled sample rate! please report bug!"
+ class AudioStream < Stream
+ mediainfo_attr_reader :stream_id, "ID"
+
+ mediainfo_duration_reader :duration
+
+ mediainfo_attr_reader :sampling_rate
+ def sample_rate
+ return unless rate = sampling_rate_before_type_cast
+ number = rate.gsub(/[^\d.]+/, "").to_f
+ number = case rate
+ when /KHz/ then number * 1000
+ when /Hz/ then number
+ else
+ raise "unhandled sample rate! please report bug!"
+ end
+ number.to_i
end
- number.to_i
+ alias_method :sampling_rate, :sample_rate
+
+ mediainfo_attr_reader :stream_size
+ mediainfo_attr_reader :bit_rate
+ mediainfo_attr_reader :bit_rate_mode
+ mediainfo_attr_reader :interleave_duration, "Interleave, duration"
+
+ mediainfo_int_reader :resolution
+ alias_method :sample_bit_depth, :resolution
+
+ mediainfo_attr_reader :format
+ mediainfo_attr_reader :format_profile
+ mediainfo_attr_reader :format_version
+ mediainfo_attr_reader :format_info, "Format/Info"
+ mediainfo_attr_reader :format_settings_sbr, "Format settings, SBR"
+ mediainfo_attr_reader :format_settings_endianness, "Format settings, Endianness"
+ mediainfo_attr_reader :format_settings_sign, "Format settings, Sign"
+ mediainfo_attr_reader :codec_id, "Codec ID"
+ mediainfo_attr_reader :codec_info, "Codec ID/Info"
+ mediainfo_attr_reader :codec_id_hint
+ mediainfo_attr_reader :channel_positions
+
+ mediainfo_int_reader :channels, "Channel(s)"
+ def stereo?; 2 == channels; end
+ def mono?; 1 == channels; end
+
+ mediainfo_date_reader :encoded_date
+ mediainfo_date_reader :tagged_date
end
- alias_method :audio_sampling_rate, :audio_sample_rate
- mediainfo_attr_reader :audio_stream_size
- mediainfo_attr_reader :audio_bit_rate
- mediainfo_attr_reader :audio_bit_rate_mode
- mediainfo_attr_reader :audio_interleave_duration, "Interleave, duration"
+ class ImageStream < Stream
+ mediainfo_attr_reader :resolution
+ mediainfo_attr_reader :format
+
+ mediainfo_int_reader :width
+ mediainfo_int_reader :height
+
+ def frame_size; "#{width}x#{height}" if width or height; end
+ end
- mediainfo_int_reader :audio_resolution
- alias_method :audio_sample_bit_depth, :audio_resolution
+ Mediainfo::SECTIONS.each do |stream_type|
+ class_eval %{
+ def #{stream_type}; @#{stream_type}_proxy ||= StreamProxy.new(self, :#{stream_type}); end
+ def #{stream_type}?; streams.any? { |x| x.#{stream_type}? }; end
+ }, __FILE__, __LINE__
+ end
- mediainfo_attr_reader :audio_format
- mediainfo_attr_reader :audio_format_info, "Format/Info"
- mediainfo_attr_reader :audio_format_settings_endianness, "Format settings, Endianness"
- mediainfo_attr_reader :audio_format_settings_sign, "Format settings, Sign"
- mediainfo_attr_reader :audio_codec_id, "Codec ID"
- mediainfo_attr_reader :audio_codec_info, "Codec ID/Info"
- mediainfo_attr_reader :audio_codec_id_hint
- mediainfo_attr_reader :audio_channel_positions
-
- mediainfo_int_reader :audio_channels, "Channel(s)"
- def stereo?; 2 == audio_channels; end
- def mono?; 1 == audio_channels; end
-
- mediainfo_date_reader :audio_encoded_date
- mediainfo_date_reader :audio_tagged_date
-
- ### IMAGE
-
- mediainfo_section_query :image
- mediainfo_attr_reader :image_resolution
- mediainfo_attr_reader :image_format
-
- mediainfo_int_reader :image_width
- mediainfo_int_reader :image_height
-
###
attr_reader :raw_response, :parsed_response,
:full_filename, :filename, :path, :escaped_full_filename
###
- class Error < StandardError; end
- class ExecutionError < Error; end
- class IncompatibleVersionError < Error; end
-
- def self.version
- @version ||= `#{path} --Version`[/v([\d.]+)/, 1]
- end
-
- ###
-
def initialize(full_filename = nil)
if mediainfo_version < "0.7.25"
raise IncompatibleVersionError,
"Your version of mediainfo, #{mediainfo_version}, " +
"is not compatible with this gem. >= 0.7.25 required."
end
+ @streams = []
+
if full_filename
@full_filename = File.expand_path full_filename
@path = File.dirname @full_filename
@filename = File.basename @full_filename
@@ -278,18 +384,16 @@
def xml_parser; self.class.xml_parser; end
def self.default_mediainfo_path!; self.path = "mediainfo"; end
default_mediainfo_path! unless path
- def mediainfo_version
- self.class.version
- end
+ def mediainfo_version; self.class.version; end
attr_reader :last_command
def inspect
- super.sub /@raw_response=".+?", @/, %{@raw_response="...", @}
+ super.sub(/@raw_response=".+?", @/, %{@raw_response="...", @})
end
private
def mediainfo!
@last_command = "#{path} #{@escaped_full_filename} --Output=XML"
@@ -310,50 +414,46 @@
self.class.load_xml_parser!
else
require "rexml/document"
end
- @parsed_response = {}
-
case xml_parser
when "nokogiri"
Nokogiri::XML(@raw_response).xpath("//track").each { |t|
- bucket = bucket_for t['type']
-
+ s = Stream.create(t['type'])
t.xpath("*").each do |c|
- bucket[key_for(c)] = c.content.strip
+ s[key_for(c)] = c.content.strip
end
+ @streams << s
}
when "hpricot"
Hpricot::XML(@raw_response).search("track").each { |t|
- bucket = bucket_for t['type']
-
+ s = Stream.create(t['type'])
t.children.select { |n| n.is_a? Hpricot::Elem }.each do |c|
- bucket[key_for(c)] = c.inner_html.strip
+ s[key_for(c)] = c.inner_html.strip
end
+ @streams << s
}
else
REXML::Document.new(@raw_response).elements.each("/Mediainfo/File/track") { |t|
- bucket = bucket_for t.attributes['type']
-
+ s = Stream.create(t.attributes['type'])
t.children.select { |n| n.is_a? REXML::Element }.each do |c|
- bucket[key_for(c)] = c.text.strip
+ s[key_for(c)] = c.text.strip
end
+ @streams << s
}
end
+
+ SECTIONS.each do |section|
+ default_target_stream = if send("#{section}?")
+ send(section).streams.first
+ else
+ Mediainfo.const_get("#{section.to_s.capitalize}Stream").new(section.to_s.capitalize)
+ end
+ instance_variable_set "@#{section}_stream", default_target_stream
+ end
end
def key_for(attribute_node)
attribute_node.name.downcase.gsub(/_+/, "_").gsub(/_s(\W|$)/, "s").strip
- end
-
- def bucket_for(section)
- section = section.downcase if section
-
- if section == "general"
- @parsed_response
- else
- @parsed_response[section] ||= {}
- @parsed_response[section]
- end
end
end