lib/mediainfo.rb in mediainfo-0.5.1 vs lib/mediainfo.rb in mediainfo-0.6.0
- old
+ new
@@ -31,11 +31,11 @@
# In addition to the stock arguments provided by parsing `mediainfo` output,
# some convenience methods and added behavior is added.
class Mediainfo
extend AttrReaders
- SECTIONS = %w( Audio Video Image ) # and General
+ SECTIONS = %w( audio video image ) # and General
### GENERAL
mediainfo_attr_reader :codec_id, "Codec ID"
@@ -46,22 +46,20 @@
mediainfo_attr_reader :format_info
mediainfo_attr_reader :overall_bit_rate
mediainfo_attr_reader :writing_application
mediainfo_attr_reader :writing_library
- def size; File.size(@full_filename); end
+ 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
### VIDEO
mediainfo_section_query :video
- # XXX this breaks from RVideo::Inspector which returns
- # something like "#0.1" instead of "1"
mediainfo_attr_reader :video_stream_id, "ID"
mediainfo_duration_reader :video_duration
mediainfo_attr_reader :video_stream_size
@@ -90,18 +88,29 @@
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
@@ -119,12 +128,10 @@
### AUDIO
mediainfo_section_query :audio
- # XXX this breaks from RVideo::Inspector which returns
- # something like "#0.1" instead of "1"
mediainfo_attr_reader :audio_stream_id, "ID"
mediainfo_duration_reader :audio_duration
mediainfo_attr_reader :audio_sampling_rate
@@ -153,14 +160,13 @@
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
- # XXX this breaks from RVideo::Inspector which returns
- # strings like "mono" or "stereo" for this method.
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
@@ -178,83 +184,152 @@
###
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
+
if full_filename
@full_filename = File.expand_path full_filename
@path = File.dirname @full_filename
@filename = File.basename @full_filename
-
+
raise ArgumentError, "need a path to a video file, got nil" unless @full_filename
raise ArgumentError, "need a path to a video file, #{@full_filename} does not exist" unless File.exist? @full_filename
-
+
@escaped_full_filename = @full_filename.shell_escape
-
+
self.raw_response = mediainfo!
end
end
-
- def raw_response=(mediainfo_cli_output)
- @raw_response = mediainfo_cli_output
+
+ def raw_response=(response)
+ raise ArgumentError, "raw response is nil" if response.nil?
+ @raw_response = response
parse!
@raw_response
end
- class << self; attr_accessor :path; end
+ class << self
+ attr_accessor :path
+
+ def load_xml_parser!(parser = xml_parser)
+ begin
+ gem parser
+ require parser
+ rescue Gem::LoadError => e
+ raise Gem::LoadError,
+ "your specified XML parser, #{parser.inspect}, could not be loaded: #{e}"
+ end
+ end
+
+ attr_reader :xml_parser
+ def xml_parser=(parser)
+ load_xml_parser! parser
+ @xml_parser = parser
+ end
+ end
+
+ unless ENV["MEDIAINFO_XML_PARSER"].to_s.strip.empty?
+ self.xml_parser = ENV["MEDIAINFO_XML_PARSER"]
+ end
+
def path; self.class.path; end
+ 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
- `#{path} --Version`[/v([\d.]+)/, 1]
+ self.class.version
end
attr_reader :last_command
- class Error < StandardError; end
- class ExecutionError < Error; end
-
def inspect
super.sub /@raw_response=".+?", @/, %{@raw_response="...", @}
end
private
def mediainfo!
- # for bash, see: http://www.faqs.org/docs/bashman/bashref_12.html
- # but appears to be working for other shells: sh, zsh, ksh, dash
- @last_command = "#{path} #{@escaped_full_filename}"
- run_last_command!
+ @last_command = "#{path} #{@escaped_full_filename} --Output=XML"
+ run_command!
end
- def run_last_command!
- raw_response = `#{@last_command}`
+ def run_command!
+ raw_response = `#{@last_command} 2>&1`
unless $? == 0
- raise ExecutionError, "Execution of `#{@last_command}` failed: #{raw_response.inspect}"
+ raise ExecutionError,
+ "Execution of `#{@last_command}` failed: #{raw_response.inspect}"
end
raw_response
end
def parse!
+ if xml_parser
+ self.class.load_xml_parser!
+ else
+ require "rexml/document"
+ end
+
@parsed_response = {}
- subsection = nil
- @raw_response.to_s.split("\n").map { |x| x.strip }.each do |line|
- next if line.empty? || line == "General"
-
- if SECTIONS.include? line
- subsection = line
- @parsed_response[subsection] = {}
- next
- end
-
- bucket = @parsed_response
- bucket = bucket[subsection] if subsection
-
- key, value = line.split(":", 2).map { |x| x.strip }
-
- bucket[key] = value
+ case xml_parser
+ when "nokogiri"
+ Nokogiri::XML(@raw_response).xpath("//track").each { |t|
+ bucket = bucket_for t['type']
+
+ t.xpath("*").each do |c|
+ bucket[key_for(c)] = c.content.strip
+ end
+ }
+ when "hpricot"
+ Hpricot::XML(@raw_response).search("track").each { |t|
+ bucket = bucket_for t['type']
+
+ t.children.select { |n| n.is_a? Hpricot::Elem }.each do |c|
+ bucket[key_for(c)] = c.inner_html.strip
+ end
+ }
+ else
+ REXML::Document.new(@raw_response).elements.each("/Mediainfo/File/track") { |t|
+ bucket = bucket_for t.attributes['type']
+
+ t.children.select { |n| n.is_a? REXML::Element }.each do |c|
+ bucket[key_for(c)] = c.text.strip
+ end
+ }
+ 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