mediainfo 0.5.1
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 +24 -0
- data/LICENSE +18 -0
- data/Manifest +9 -0
- data/README.markdown +40 -0
- data/Rakefile +37 -0
- data/lib/mediainfo.rb +260 -0
- data/lib/mediainfo/attr_readers.rb +76 -0
- data/lib/mediainfo/string.rb +9 -0
- data/mediainfo.gemspec +31 -0
- data/test/mediainfo_awaywego_encoded_test.rb +271 -0
- data/test/mediainfo_awaywego_test.rb +275 -0
- data/test/mediainfo_dinner_test.rb +257 -0
- data/test/mediainfo_hats_test.rb +257 -0
- data/test/mediainfo_string_test.rb +12 -0
- data/test/mediainfo_test.rb +112 -0
- data/test/mediainfo_vimeo_test.rb +257 -0
- data/test/mediainfo_vimeoimage_test.rb +234 -0
- data/test/test_helper.rb +18 -0
- metadata +80 -0
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
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
|