mediainfo 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog ADDED
@@ -0,0 +1,24 @@
1
+ v0.5.1 Packaging Fix
2
+ - Gemspec was referencing a missing file
3
+ - Behold: THREE DIGIT VERSION NUMBER. Serious business now!
4
+
5
+ v0.5 Raw, Son
6
+ - You can now initialize an instance from raw CLI output
7
+
8
+ v0.4 Mediainfo Respects Your Existing Customs
9
+ - Define String#shell_escape only if it is not already defined
10
+
11
+ v0.3 Mediainfo Is High Class
12
+ - Class instead of instance-level customization of mediainfo binary path.
13
+ Set Mediainfo.path = /path/to/binary if mediainfo is not in your shell
14
+ path.
15
+ - Instances retain the :last_command run. The API to rerun that command is
16
+ still private.
17
+ - Started defining custom Mediainfo exception classes and are now raising
18
+ Mediainfo::ExecutionError instead of RuntimeError if the system command
19
+ exits with anything other than 0.
20
+
21
+ v0.2 Escape from the Shell
22
+ - Added proper escaping for funky filenames. Thanks to Peter Vandenberk!
23
+
24
+ v0.1 Initial Release
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ This program is free software. It comes without any warranty, to
2
+ the extent permitted by applicable law. You can redistribute it
3
+ and/or modify it under the terms of the Do What The Fuck You Want
4
+ To Public License, Version 2, as published by Sam Hocevar. See
5
+ http://sam.zoy.org/wtfpl/COPYING for more details.
6
+
7
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
8
+ Version 2, December 2004
9
+
10
+ Copyright (C) 2009 Seth Thomas Rasmussen
11
+ Everyone is permitted to copy and distribute verbatim or modified
12
+ copies of this license document, and changing it is allowed as long
13
+ as the name is changed.
14
+
15
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
16
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
17
+
18
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/Manifest ADDED
@@ -0,0 +1,9 @@
1
+ Changelog
2
+ LICENSE
3
+ Manifest
4
+ README.markdown
5
+ Rakefile
6
+ lib/mediainfo.rb
7
+ lib/mediainfo/attr_readers.rb
8
+ lib/mediainfo/string.rb
9
+ mediainfo.gemspec
data/README.markdown ADDED
@@ -0,0 +1,40 @@
1
+ # Mediainfo
2
+
3
+ Mediainfo is a class wrapping [the mediainfo CLI](http://mediainfo.sourceforge.net).
4
+
5
+ ## Usage
6
+
7
+ info = Mediainfo.new "/path/to/file"
8
+
9
+ That will issue the system call to `mediainfo` and parse the output.
10
+ From there, you can call numerous methods to get a variety of information
11
+ about a file. Some attributes may be present for some files where others
12
+ are not.
13
+
14
+ For a list of all possible attributes supported:
15
+
16
+ Mediainfo.supported_attributes
17
+
18
+ In addition to the stock arguments provided by parsing `mediainfo` output,
19
+ some convenience methods and added behavior is added.
20
+
21
+ Mediainfo is inspired by RVideo::Inspector, part of the rvideo gem.
22
+ The rvideo inspector is based on output from ffmpeg which is not
23
+ intended to be machine parseable. I spent a little while chasing
24
+ the ffmpeg development team, and decided finally that perhaps other
25
+ tools were better. As such, some of the API for Mediainfo is straight
26
+ from RVideo::Inspector. Some is not. Just saying.
27
+
28
+ ## Requirements
29
+
30
+ This library is compatible with:
31
+
32
+ MediaInfo Command line,
33
+ MediaInfoLib - v0.7.11
34
+
35
+ to the extent that is documented in the tests.
36
+
37
+ ## Contributors
38
+
39
+ * Seth Thomas Rasmussen - [http://greatseth.com](http://greatseth.com)
40
+ * Peter Vandenberk - [http://github.com/pvdb](http://github.com/pvdb)
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require "rake/testtask"
2
+ load "Rakefile.cloud" if File.exist? "Rakefile.cloud"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/*_test.rb"]
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
12
+ namespace :mediainfo do
13
+ task :fixture do
14
+ unless file = ENV["file"]
15
+ puts "Usage: rake mediainfo:fixture file=/path/to/file"
16
+ exit
17
+ end
18
+ fixture = File.expand_path "./test/fixtures/#{File.basename file}.txt"
19
+ system "mediainfo #{file} > #{fixture}"
20
+ if File.exist? fixture
21
+ puts "Generated fixture #{fixture}."
22
+ else
23
+ puts "Error generating fixture. #{fixture} not created."
24
+ end
25
+ end
26
+ end
27
+
28
+ require "rubygems"
29
+ require "echoe"
30
+
31
+ Echoe.new "mediainfo" do |p|
32
+ p.description = "Mediainfo is a class wrapping the mediainfo CLI (http://mediainfo.sourceforge.net)"
33
+ p.author = "Seth Thomas Rasmussen"
34
+ p.email = "sethrasmussen@gmail.com"
35
+ p.url = "http://greatseth.com"
36
+ p.ignore_pattern = %w( test/**/* )
37
+ end
data/lib/mediainfo.rb ADDED
@@ -0,0 +1,260 @@
1
+ require "mediainfo/string"
2
+ require "mediainfo/attr_readers"
3
+
4
+ # Mediainfo is a class encapsulating the ability to run `mediainfo`
5
+ # on a file and expose the information it produces in a straightforward
6
+ # manner.
7
+ #
8
+ # Basic usage:
9
+ #
10
+ # info = Mediainfo.new "/path/to/file"
11
+ #
12
+ # That will issue the system call to `mediainfo` and parse the output.
13
+ # From there, you can call numerous methods to get a variety of information
14
+ # about a file. Some attributes may be present for some files where others
15
+ # are not.
16
+ #
17
+ # You may also initialize a Mediainfo instance using raw CLI output
18
+ # you have saved for some reason.
19
+ #
20
+ # info = Mediainfo.new
21
+ # info.raw_response = cli_output
22
+ #
23
+ # Setting the raw_response triggers the system call, and from that point on
24
+ # the object should behave the same as if you'd initialized it with the path
25
+ # to a file.
26
+ #
27
+ # For a list of all possible attributes supported:
28
+ #
29
+ # Mediainfo.supported_attributes
30
+ #
31
+ # In addition to the stock arguments provided by parsing `mediainfo` output,
32
+ # some convenience methods and added behavior is added.
33
+ class Mediainfo
34
+ extend AttrReaders
35
+
36
+ SECTIONS = %w( Audio Video Image ) # and General
37
+
38
+ ### GENERAL
39
+
40
+ mediainfo_attr_reader :codec_id, "Codec ID"
41
+
42
+ mediainfo_duration_reader :duration
43
+
44
+ mediainfo_attr_reader :format
45
+ mediainfo_attr_reader :format_profile
46
+ mediainfo_attr_reader :format_info
47
+ mediainfo_attr_reader :overall_bit_rate
48
+ mediainfo_attr_reader :writing_application
49
+ mediainfo_attr_reader :writing_library
50
+
51
+ def size; File.size(@full_filename); end
52
+
53
+ mediainfo_date_reader :mastered_date
54
+ mediainfo_date_reader :tagged_date
55
+ mediainfo_date_reader :encoded_date
56
+
57
+ ### VIDEO
58
+
59
+ mediainfo_section_query :video
60
+
61
+ # XXX this breaks from RVideo::Inspector which returns
62
+ # something like "#0.1" instead of "1"
63
+ mediainfo_attr_reader :video_stream_id, "ID"
64
+
65
+ mediainfo_duration_reader :video_duration
66
+
67
+ mediainfo_attr_reader :video_stream_size
68
+ mediainfo_attr_reader :video_bit_rate
69
+ mediainfo_attr_reader :video_nominal_bit_rate
70
+
71
+ mediainfo_attr_reader :video_bit_rate_mode
72
+ def cbr?; video? and "Constant" == video_bit_rate_mode; end
73
+ def vbr?; video? and not cbr?; end
74
+
75
+ mediainfo_attr_reader :video_scan_order
76
+ mediainfo_attr_reader :video_scan_type
77
+ def interlaced?; video? and "Interlaced" == video_scan_type; end
78
+ def progressive?; video? and not interlaced? end
79
+
80
+ mediainfo_int_reader :video_resolution
81
+
82
+ mediainfo_attr_reader :video_colorimetry
83
+ alias_method :video_colorspace, :video_colorimetry
84
+
85
+ mediainfo_attr_reader :video_format
86
+ mediainfo_attr_reader :video_format_profile
87
+ mediainfo_attr_reader :video_format_version
88
+ mediainfo_attr_reader :video_format_settings_cabac, "Format settings, CABAC"
89
+ mediainfo_attr_reader :video_format_settings_reframes, "Format settings, ReFrames"
90
+ mediainfo_attr_reader :video_format_settings_matrix, "Format settings, Matrix"
91
+ # Format settings, BVOP : Yes
92
+ # Format settings, QPel : No
93
+ # Format settings, GMC : No warppoints
94
+ # mediainfo_attr_reader :video_format_settings_qpel, "Format settings, QPel"
95
+
96
+ mediainfo_attr_reader :video_codec_id, "Codec ID"
97
+ mediainfo_attr_reader :video_codec_info, "Codec ID/Info"
98
+
99
+ mediainfo_attr_reader :video_frame_rate
100
+ def fps; video_frame_rate[/[\d.]+/].to_f if video?; end
101
+ alias_method :framerate, :fps
102
+
103
+ mediainfo_attr_reader :video_frame_rate_mode
104
+
105
+ mediainfo_attr_reader :video_display_aspect_ratio
106
+ alias_method :display_aspect_ratio, :video_display_aspect_ratio
107
+
108
+ mediainfo_attr_reader :video_bits_pixel_frame, "Bits/(Pixel*Frame)"
109
+
110
+ mediainfo_int_reader :video_width
111
+ mediainfo_int_reader :video_height
112
+
113
+ def resolution; "#{width}x#{height}" if video? or image?; end
114
+ def width; if video?; video_width; elsif image?; image_width; end; end
115
+ def height; if video?; video_height; elsif image?; image_height; end; end
116
+
117
+ mediainfo_date_reader :video_encoded_date
118
+ mediainfo_date_reader :video_tagged_date
119
+
120
+ ### AUDIO
121
+
122
+ mediainfo_section_query :audio
123
+
124
+ # XXX this breaks from RVideo::Inspector which returns
125
+ # something like "#0.1" instead of "1"
126
+ mediainfo_attr_reader :audio_stream_id, "ID"
127
+
128
+ mediainfo_duration_reader :audio_duration
129
+
130
+ mediainfo_attr_reader :audio_sampling_rate
131
+ def audio_sample_rate
132
+ return unless rate = audio_sampling_rate_before_type_cast
133
+ number = rate.gsub(/[^\d.]+/, "").to_f
134
+ number = case rate
135
+ when /KHz/ then number * 1000
136
+ when /Hz/ then number
137
+ else
138
+ raise "unhandled sample rate! please report bug!"
139
+ end
140
+ number.to_i
141
+ end
142
+ alias_method :audio_sampling_rate, :audio_sample_rate
143
+
144
+ mediainfo_attr_reader :audio_stream_size
145
+ mediainfo_attr_reader :audio_bit_rate
146
+ mediainfo_attr_reader :audio_bit_rate_mode
147
+ mediainfo_attr_reader :audio_interleave_duration, "Interleave, duration"
148
+
149
+ mediainfo_int_reader :audio_resolution
150
+ alias_method :audio_sample_bit_depth, :audio_resolution
151
+
152
+ mediainfo_attr_reader :audio_format
153
+ mediainfo_attr_reader :audio_format_info, "Format/Info"
154
+ mediainfo_attr_reader :audio_format_settings_endianness, "Format settings, Endianness"
155
+ mediainfo_attr_reader :audio_format_settings_sign, "Format settings, Sign"
156
+ mediainfo_attr_reader :audio_codec_id, "Codec ID"
157
+ mediainfo_attr_reader :audio_codec_info, "Codec ID/Info"
158
+ mediainfo_attr_reader :audio_channel_positions
159
+
160
+ # XXX this breaks from RVideo::Inspector which returns
161
+ # strings like "mono" or "stereo" for this method.
162
+ mediainfo_int_reader :audio_channels, "Channel(s)"
163
+ def stereo?; 2 == audio_channels; end
164
+ def mono?; 1 == audio_channels; end
165
+
166
+ mediainfo_date_reader :audio_encoded_date
167
+ mediainfo_date_reader :audio_tagged_date
168
+
169
+ ### IMAGE
170
+
171
+ mediainfo_section_query :image
172
+ mediainfo_attr_reader :image_resolution
173
+ mediainfo_attr_reader :image_format
174
+
175
+ mediainfo_int_reader :image_width
176
+ mediainfo_int_reader :image_height
177
+
178
+ ###
179
+
180
+ attr_reader :raw_response, :parsed_response,
181
+ :full_filename, :filename, :path, :escaped_full_filename
182
+
183
+ def initialize(full_filename = nil)
184
+ if full_filename
185
+ @full_filename = File.expand_path full_filename
186
+ @path = File.dirname @full_filename
187
+ @filename = File.basename @full_filename
188
+
189
+ raise ArgumentError, "need a path to a video file, got nil" unless @full_filename
190
+ raise ArgumentError, "need a path to a video file, #{@full_filename} does not exist" unless File.exist? @full_filename
191
+
192
+ @escaped_full_filename = @full_filename.shell_escape
193
+
194
+ self.raw_response = mediainfo!
195
+ end
196
+ end
197
+
198
+ def raw_response=(mediainfo_cli_output)
199
+ @raw_response = mediainfo_cli_output
200
+ parse!
201
+ @raw_response
202
+ end
203
+
204
+ class << self; attr_accessor :path; end
205
+ def path; self.class.path; end
206
+
207
+ def self.default_mediainfo_path!; self.path = "mediainfo"; end
208
+ default_mediainfo_path! unless path
209
+
210
+ def mediainfo_version
211
+ `#{path} --Version`[/v([\d.]+)/, 1]
212
+ end
213
+
214
+ attr_reader :last_command
215
+
216
+ class Error < StandardError; end
217
+ class ExecutionError < Error; end
218
+
219
+ def inspect
220
+ super.sub /@raw_response=".+?", @/, %{@raw_response="...", @}
221
+ end
222
+
223
+ private
224
+ def mediainfo!
225
+ # for bash, see: http://www.faqs.org/docs/bashman/bashref_12.html
226
+ # but appears to be working for other shells: sh, zsh, ksh, dash
227
+ @last_command = "#{path} #{@escaped_full_filename}"
228
+ run_last_command!
229
+ end
230
+
231
+ def run_last_command!
232
+ raw_response = `#{@last_command}`
233
+ unless $? == 0
234
+ raise ExecutionError, "Execution of `#{@last_command}` failed: #{raw_response.inspect}"
235
+ end
236
+ raw_response
237
+ end
238
+
239
+ def parse!
240
+ @parsed_response = {}
241
+ subsection = nil
242
+
243
+ @raw_response.to_s.split("\n").map { |x| x.strip }.each do |line|
244
+ next if line.empty? || line == "General"
245
+
246
+ if SECTIONS.include? line
247
+ subsection = line
248
+ @parsed_response[subsection] = {}
249
+ next
250
+ end
251
+
252
+ bucket = @parsed_response
253
+ bucket = bucket[subsection] if subsection
254
+
255
+ key, value = line.split(":", 2).map { |x| x.strip }
256
+
257
+ bucket[key] = value
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,76 @@
1
+ require "rubygems"
2
+ require "active_support" # TODO selective includes once they are released
3
+
4
+ class Mediainfo
5
+ module AttrReaders
6
+ def supported_attributes
7
+ @supported_attributes ||= []
8
+ end
9
+
10
+ def mediainfo_attr_reader(name, mediainfo_key = nil)
11
+ supported_attributes << name
12
+ attr_name = "#{name}_before_type_cast"
13
+ define_method attr_name do
14
+ if v = instance_variable_get("@#{attr_name}")
15
+ v
16
+ else
17
+ v = if md = name.to_s.match(/^(#{SECTIONS.map { |x| x.underscore } * "|"})_(.+)$/)
18
+ k = mediainfo_key ? mediainfo_key : md[2].humanize.capitalize
19
+ if subsection = @parsed_response[md[1].capitalize]
20
+ subsection[k]
21
+ end
22
+ else
23
+ k = mediainfo_key ? mediainfo_key : name.to_s.humanize.capitalize
24
+ @parsed_response[k]
25
+ end
26
+
27
+ instance_variable_set "@#{attr_name}", v
28
+ v
29
+ end
30
+ end
31
+
32
+ define_method name do
33
+ if v = instance_variable_get("@#{name}")
34
+ v
35
+ else
36
+ v = send "#{name}_before_type_cast"
37
+ v = yield v if v and block_given?
38
+
39
+ instance_variable_set "@#{name}", v
40
+ v
41
+ end
42
+ end
43
+ end
44
+
45
+ def mediainfo_duration_reader(*a)
46
+ mediainfo_attr_reader *a do |v|
47
+ t = 0
48
+ v.split(/\s+/).each do |tf|
49
+ case tf
50
+ # XXX haven't actually seen hot they represent hours yet
51
+ # but hopefully this is ok.. :\
52
+ when /\d+h/ then t += tf.to_i * 60 * 60 * 1000
53
+ when /\d+mn/ then t += tf.to_i * 60 * 1000
54
+ when /\d+ms/ then t += tf.to_i
55
+ when /\d+s/ then t += tf.to_i * 1000
56
+ else
57
+ raise "unexpected time fragment! please report bug!"
58
+ end
59
+ end
60
+ t
61
+ end
62
+ end
63
+
64
+ def mediainfo_date_reader(*a)
65
+ mediainfo_attr_reader(*a) { |v| Time.parse v }
66
+ end
67
+
68
+ def mediainfo_int_reader(*a)
69
+ mediainfo_attr_reader(*a) { |v| v.gsub(/\D+/, "").to_i }
70
+ end
71
+
72
+ def mediainfo_section_query(name)
73
+ define_method("#{name}?") { @parsed_response.key? name.to_s.capitalize }
74
+ end
75
+ end
76
+ end