axtro-rvideo 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +70 -0
  2. data/ENV +100 -0
  3. data/ENV2 +129 -0
  4. data/LICENSE +20 -0
  5. data/Manifest +67 -0
  6. data/README +91 -0
  7. data/RULES +11 -0
  8. data/Rakefile +63 -0
  9. data/axtro-rvideo.gemspec +36 -0
  10. data/config/boot.rb +25 -0
  11. data/lib/rvideo.rb +44 -0
  12. data/lib/rvideo/errors.rb +24 -0
  13. data/lib/rvideo/float.rb +7 -0
  14. data/lib/rvideo/frame_capturer.rb +129 -0
  15. data/lib/rvideo/inspector.rb +483 -0
  16. data/lib/rvideo/reporter.rb +176 -0
  17. data/lib/rvideo/reporter/views/index.html.erb +27 -0
  18. data/lib/rvideo/reporter/views/report.css +27 -0
  19. data/lib/rvideo/reporter/views/report.html.erb +81 -0
  20. data/lib/rvideo/reporter/views/report.js +9 -0
  21. data/lib/rvideo/string.rb +5 -0
  22. data/lib/rvideo/tools/abstract_tool.rb +414 -0
  23. data/lib/rvideo/tools/ffmpeg.rb +286 -0
  24. data/lib/rvideo/tools/ffmpeg2theora.rb +42 -0
  25. data/lib/rvideo/tools/flvtool2.rb +50 -0
  26. data/lib/rvideo/tools/mencoder.rb +103 -0
  27. data/lib/rvideo/tools/mp4box.rb +21 -0
  28. data/lib/rvideo/tools/mp4creator.rb +35 -0
  29. data/lib/rvideo/tools/mplayer.rb +31 -0
  30. data/lib/rvideo/tools/qtfaststart.rb +37 -0
  31. data/lib/rvideo/tools/yamdi.rb +44 -0
  32. data/lib/rvideo/transcoder.rb +120 -0
  33. data/lib/rvideo/version.rb +9 -0
  34. data/rvideo.gemspec +36 -0
  35. data/scripts/txt2html +67 -0
  36. data/setup.rb +1585 -0
  37. data/spec/files/boat.avi +0 -0
  38. data/spec/files/kites.mp4 +0 -0
  39. data/spec/fixtures/ffmpeg_builds.yml +28 -0
  40. data/spec/fixtures/ffmpeg_results.yml +608 -0
  41. data/spec/fixtures/files.yml +398 -0
  42. data/spec/fixtures/recipes.yml +58 -0
  43. data/spec/integrations/formats_spec.rb +315 -0
  44. data/spec/integrations/frame_capturer_spec.rb +26 -0
  45. data/spec/integrations/inspection_spec.rb +112 -0
  46. data/spec/integrations/recipes_spec.rb +0 -0
  47. data/spec/integrations/rvideo_spec.rb +17 -0
  48. data/spec/integrations/transcoder_integration_spec.rb +29 -0
  49. data/spec/integrations/transcoding_spec.rb +9 -0
  50. data/spec/spec.opts +1 -0
  51. data/spec/spec_helper.rb +16 -0
  52. data/spec/support.rb +36 -0
  53. data/spec/units/abstract_tool_spec.rb +123 -0
  54. data/spec/units/ffmpeg_spec.rb +327 -0
  55. data/spec/units/flvtool2_spec.rb +324 -0
  56. data/spec/units/frame_capturer_spec.rb +72 -0
  57. data/spec/units/inspector_spec.rb +59 -0
  58. data/spec/units/mencoder_spec.rb +4994 -0
  59. data/spec/units/mp4box_spec.rb +34 -0
  60. data/spec/units/mp4creator_spec.rb +34 -0
  61. data/spec/units/mplayer_spec.rb +34 -0
  62. data/spec/units/qtfaststart_spec.rb +35 -0
  63. data/spec/units/string_spec.rb +8 -0
  64. data/spec/units/transcoder_spec.rb +156 -0
  65. data/tasks/deployment.rake +5 -0
  66. data/tasks/testing.rake +27 -0
  67. data/tasks/transcoding.rake +40 -0
  68. data/tasks/website.rake +8 -0
  69. metadata +178 -0
data/RULES ADDED
@@ -0,0 +1,11 @@
1
+ Collection of transcoding edge cases and rules
2
+ ------------------------
3
+
4
+ * mpeg4 output errors out if frame rate is not supplied
5
+
6
+ [mpeg4 @ 0x149e810]timebase not supported by mpeg 4 standard
7
+ Error while opening codec for output stream #0.0 - maybe incorrect parameters such as bit_rate, rate, width or height
8
+
9
+ Solution: provide a frame rate with -r (any frame rate will do)
10
+
11
+
@@ -0,0 +1,63 @@
1
+ require "rubygems"
2
+ require "fileutils"
3
+ require "echoe"
4
+
5
+ __HERE__ = File.dirname(__FILE__)
6
+ require File.join(__HERE__, 'lib', 'rvideo', 'version')
7
+ require File.join(__HERE__, 'lib', 'rvideo')
8
+
9
+ ###
10
+
11
+ AUTHOR = [
12
+ "Peter Boling",
13
+ "Jonathan Dahl (Slantwise Design)",
14
+ "Seth Thomas Rasmussen"
15
+ ]
16
+ EMAIL = "sethrasmussen@gmail.com"
17
+ DESCRIPTION = "Inspect and transcode video and audio files."
18
+
19
+ NAME = "axtro-rvideo"
20
+
21
+ REV = `git log -n1 --pretty=oneline | cut -d' ' -f1`.strip
22
+ BRANCH = `git branch | grep '*' | cut -d' ' -f2`.strip
23
+
24
+ # This is not the version used for the gem.
25
+ # That is parsed from the CHANGELOG by Echoe.
26
+ VERS = "#{RVideo::VERSION::STRING} (#{BRANCH} @ #{REV})"
27
+
28
+ Echoe.new NAME do |p|
29
+ p.author = AUTHOR
30
+ p.description = DESCRIPTION
31
+ p.email = EMAIL
32
+ p.summary = DESCRIPTION
33
+ p.url = "http://github.com/greatseth/rvideo"
34
+
35
+ p.runtime_dependencies = ["activesupport"]
36
+ p.development_dependencies = ["rspec"]
37
+
38
+ p.ignore_pattern = [
39
+ "spec/files/boat.mpg",
40
+ "spec/files/dinner.3g2",
41
+ "spec/files/foo $ bar & baz",
42
+ "spec/files/hats.3gp",
43
+ "spec/files/quads.wmv",
44
+ "spec/files/sword.3gp",
45
+ "website/**/*",
46
+ "tmp/**/*",
47
+ "scripts/test_progress.rb"
48
+ ]
49
+
50
+ p.rdoc_options = [
51
+ "--quiet",
52
+ "--title", "rvideo documentation",
53
+ "--opname", "index.html",
54
+ "--line-numbers",
55
+ "--main", "README",
56
+ "--inline-source"
57
+ ]
58
+ end
59
+
60
+ # Load supporting Rake files
61
+ Dir[File.join(__HERE__, "tasks", "*.rake")].each { |t| load t }
62
+
63
+ puts "#{NAME} #{VERS}"
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{axtro-rvideo}
5
+ s.version = "0.9.6"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Peter Boling, Jonathan Dahl (Slantwise Design), Seth Thomas Rasmussen"]
9
+ s.date = %q{2010-09-24}
10
+ s.description = %q{Inspect and transcode video and audio files.}
11
+ s.email = %q{sethrasmussen@gmail.com}
12
+ s.extra_rdoc_files = ["CHANGELOG", "lib/rvideo/errors.rb", "lib/rvideo/float.rb", "lib/rvideo/frame_capturer.rb", "lib/rvideo/inspector.rb", "lib/rvideo/reporter/views/index.html.erb", "lib/rvideo/reporter/views/report.css", "lib/rvideo/reporter/views/report.html.erb", "lib/rvideo/reporter/views/report.js", "lib/rvideo/reporter.rb", "lib/rvideo/string.rb", "lib/rvideo/tools/abstract_tool.rb", "lib/rvideo/tools/ffmpeg.rb", "lib/rvideo/tools/ffmpeg2theora.rb", "lib/rvideo/tools/flvtool2.rb", "lib/rvideo/tools/mencoder.rb", "lib/rvideo/tools/mp4box.rb", "lib/rvideo/tools/mp4creator.rb", "lib/rvideo/tools/mplayer.rb", "lib/rvideo/tools/qtfaststart.rb", "lib/rvideo/tools/yamdi.rb", "lib/rvideo/transcoder.rb", "lib/rvideo/version.rb", "lib/rvideo.rb", "LICENSE", "README", "tasks/deployment.rake", "tasks/testing.rake", "tasks/transcoding.rake", "tasks/website.rake"]
13
+ s.files = ["CHANGELOG", "config/boot.rb", "ENV", "ENV2", "lib/rvideo/errors.rb", "lib/rvideo/float.rb", "lib/rvideo/frame_capturer.rb", "lib/rvideo/inspector.rb", "lib/rvideo/reporter/views/index.html.erb", "lib/rvideo/reporter/views/report.css", "lib/rvideo/reporter/views/report.html.erb", "lib/rvideo/reporter/views/report.js", "lib/rvideo/reporter.rb", "lib/rvideo/string.rb", "lib/rvideo/tools/abstract_tool.rb", "lib/rvideo/tools/ffmpeg.rb", "lib/rvideo/tools/ffmpeg2theora.rb", "lib/rvideo/tools/flvtool2.rb", "lib/rvideo/tools/mencoder.rb", "lib/rvideo/tools/mp4box.rb", "lib/rvideo/tools/mp4creator.rb", "lib/rvideo/tools/mplayer.rb", "lib/rvideo/tools/qtfaststart.rb", "lib/rvideo/tools/yamdi.rb", "lib/rvideo/transcoder.rb", "lib/rvideo/version.rb", "lib/rvideo.rb", "LICENSE", "Manifest", "Rakefile", "README", "RULES", "rvideo.gemspec", "scripts/txt2html", "setup.rb", "spec/files/boat.avi", "spec/files/kites.mp4", "spec/fixtures/ffmpeg_builds.yml", "spec/fixtures/ffmpeg_results.yml", "spec/fixtures/files.yml", "spec/fixtures/recipes.yml", "spec/integrations/formats_spec.rb", "spec/integrations/frame_capturer_spec.rb", "spec/integrations/inspection_spec.rb", "spec/integrations/recipes_spec.rb", "spec/integrations/rvideo_spec.rb", "spec/integrations/transcoder_integration_spec.rb", "spec/integrations/transcoding_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/support.rb", "spec/units/abstract_tool_spec.rb", "spec/units/ffmpeg_spec.rb", "spec/units/flvtool2_spec.rb", "spec/units/frame_capturer_spec.rb", "spec/units/inspector_spec.rb", "spec/units/mencoder_spec.rb", "spec/units/mp4box_spec.rb", "spec/units/mp4creator_spec.rb", "spec/units/mplayer_spec.rb", "spec/units/qtfaststart_spec.rb", "spec/units/string_spec.rb", "spec/units/transcoder_spec.rb", "tasks/deployment.rake", "tasks/testing.rake", "tasks/transcoding.rake", "tasks/website.rake", "axtro-rvideo.gemspec"]
14
+ s.homepage = %q{http://github.com/greatseth/rvideo}
15
+ s.rdoc_options = ["--quiet", "--title", "rvideo documentation", "--opname", "index.html", "--line-numbers", "--main", "README", "--inline-source"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{axtro-rvideo}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Inspect and transcode video and audio files.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ s.add_runtime_dependency(%q<activesupport>, [">= 0"])
27
+ s.add_development_dependency(%q<rspec>, [">= 0"])
28
+ else
29
+ s.add_dependency(%q<activesupport>, [">= 0"])
30
+ s.add_dependency(%q<rspec>, [">= 0"])
31
+ end
32
+ else
33
+ s.add_dependency(%q<activesupport>, [">= 0"])
34
+ s.add_dependency(%q<rspec>, [">= 0"])
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # I don't know what this file was intended for originally, but I've been
2
+ # using it to setup RVideo to play with in an IRB session. - Seth
3
+
4
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '..'))
5
+ require "lib/rvideo"
6
+ require "spec/support"
7
+
8
+ include RVideo
9
+
10
+ ###
11
+
12
+ class Inspector
13
+ public :video_match
14
+ public :audio_match
15
+ end
16
+
17
+ def inspector(filename)
18
+ options = if filename.is_a? Symbol
19
+ { :raw_response => files(filename) }
20
+ else
21
+ { :file => spec_file(filename) }
22
+ end
23
+
24
+ Inspector.new options
25
+ end
@@ -0,0 +1,44 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ # core extensions
4
+ require 'rvideo/float'
5
+ require 'rvideo/string'
6
+
7
+ # gems
8
+ require 'rubygems'
9
+ require 'active_support'
10
+
11
+ # rvideo
12
+ require 'rvideo/inspector'
13
+ require 'rvideo/frame_capturer'
14
+ require 'rvideo/errors'
15
+ require 'rvideo/transcoder'
16
+ require 'rvideo/tools/abstract_tool'
17
+ require 'rvideo/tools/ffmpeg'
18
+ require 'rvideo/tools/mencoder'
19
+ require 'rvideo/tools/flvtool2'
20
+ require 'rvideo/tools/mp4box'
21
+ require 'rvideo/tools/mplayer'
22
+ require 'rvideo/tools/mp4creator'
23
+ require 'rvideo/tools/ffmpeg2theora'
24
+ require 'rvideo/tools/yamdi'
25
+ require 'rvideo/tools/qtfaststart'
26
+
27
+ TEMP_PATH = File.expand_path(File.dirname(__FILE__) + '/../tmp')
28
+ REPORT_PATH = File.expand_path(File.dirname(__FILE__) + '/../report')
29
+
30
+ module RVideo
31
+ # Configure logging. Assumes that the logger object has an
32
+ # interface similar to stdlib's Logger class.
33
+ #
34
+ # RVideo.logger = Logger.new(STDOUT)
35
+ #
36
+ def self.logger=(logger)
37
+ @logger = logger
38
+ end
39
+
40
+ def self.logger
41
+ @logger = Logger.new("/dev/null") unless @logger
42
+ @logger
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ module RVideo
2
+ class TranscoderError < RuntimeError
3
+ class InvalidCommand < TranscoderError
4
+ end
5
+
6
+ class InvalidFile < TranscoderError
7
+ end
8
+
9
+ class InputFileNotFound < TranscoderError
10
+ end
11
+
12
+ class UnexpectedResult < TranscoderError
13
+ end
14
+
15
+ class ParameterError < TranscoderError
16
+ end
17
+
18
+ class UnknownError < TranscoderError
19
+ end
20
+
21
+ class UnknownTool < TranscoderError
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # Add a rounding method to the Float class.
2
+ class Float
3
+ def round_to(x)
4
+ (self * 10**x).round.to_f / 10**x
5
+ end
6
+ end
7
+
@@ -0,0 +1,129 @@
1
+ module RVideo
2
+ # FrameCapturer uses ffmpeg to capture frames from a movie in JPEG format.
3
+ #
4
+ # You can capture one or many frames in a variety of ways:
5
+ #
6
+ # - one frame at a given offset
7
+ # - multiple frames every n seconds from a given offset
8
+ #
9
+ # TODO
10
+ #
11
+ # - n frames total, evenly distributed across the duration of the movie
12
+ #
13
+ # For the offset options, three types of values are accepted:
14
+ # - percentage e.g. '37%'
15
+ # - seconds e.g. '37s' or simply '37'
16
+ # - frame e.g. '37f'
17
+ #
18
+ # If a time is outside of the duration of the file, it will choose a frame at the
19
+ # 99% mark.
20
+ #
21
+ # Example:
22
+ #
23
+ # RVideo::FrameCapturer.capture! :input => 'path/to/input.mp4', :offset => '10%'
24
+ # # => ['/path/to/screenshot/input-10p.jpg']
25
+ #
26
+ # In the case where you specify an :interval, e.g. :interval => 5 for a frame every
27
+ # 5 seconds, you will generally get a few more images that you might expect.
28
+ # Typically there will be at least two extra, one for the very start and one for
29
+ # the very end of the video. Then, depending on how close to a simple integer of
30
+ # seconds the duration of the video is, you may get one or two more.
31
+ #
32
+ # # Assuming input.mp4 is 19.6 seconds long..
33
+ # RVideo::FrameCapturer.capture! :input => 'path/to/input.mp4', :interval => 5
34
+ # # => ['/path/to/input-1.jpg','/path/to/input-2.jpg','/path/to/input-3.jpg',
35
+ # '/path/to/input-4.jpg','/path/to/input-5.jpg','/path/to/input-6.jpg']
36
+ #
37
+ # For more precision, you can try multiple capture commands, each getting
38
+ # a single frame but with increasing offsets.
39
+ class FrameCapturer
40
+ attr_reader :input, :output, :offset, :rate, :limit, :inspector, :command
41
+
42
+ def self.capture!(options)
43
+ new(options).capture!
44
+ end
45
+
46
+ def initialize(options)
47
+ @input = options[:input] || raise(ArgumentError, "need :input => /path/to/movie")
48
+
49
+ @inspector = Inspector.new :file => @input
50
+
51
+ @offset, @rate, @limit, @output = parse_options options
52
+
53
+ @command = "ffmpeg -i #{@input.shell_quoted} -ss #{@offset} -r #{@rate} -f image2 -vframes 1 #{@output.shell_quoted}"
54
+ end
55
+
56
+ def capture!
57
+ RVideo.logger.info("\nCreating Screenshot: #{@command}\n")
58
+ frame_result = `#{@command} 2>&1`
59
+ RVideo.logger.info("\nScreenshot results: #{frame_result}")
60
+
61
+ # TODO: Fix
62
+ #Dir[File.expand_path(@output).sub("%d", "*")].entries
63
+ [@output]
64
+ end
65
+
66
+ VALID_TIMECODE_FORMAT = /\A([0-9.,]*)(s|f|%)?\Z/
67
+
68
+ # TODO This method should not be public, but I'm too lazy to update the specs right now..
69
+ def calculate_time(timecode)
70
+ m = VALID_TIMECODE_FORMAT.match(timecode.to_s)
71
+ if m.nil? or m[1].nil? or m[1].empty?
72
+ raise TranscoderError::ParameterError,
73
+ "Invalid timecode for frame capture: #{timecode}. " <<
74
+ "Must be a number, optionally followed by s, f, or %."
75
+ end
76
+
77
+ case m[2]
78
+ when "s", nil
79
+ t = m[1].to_f
80
+ when "f"
81
+ t = m[1].to_f / @inspector.fps.to_f
82
+ when "%"
83
+ # milliseconds / 1000 * percent / 100
84
+ t = (@inspector.duration.to_i / 1000.0) * (m[1].to_f / 100.0)
85
+ else
86
+ raise TranscoderError::ParameterError,
87
+ "Invalid timecode for frame capture: #{timecode}. " <<
88
+ "Must be a number, optionally followed by s, f, or p."
89
+ end
90
+
91
+ if (t * 1000) > @inspector.duration
92
+ calculate_time("99%")
93
+ else
94
+ t
95
+ end
96
+ end
97
+
98
+ private
99
+ def parse_options(options)
100
+ offset = options[:offset] ? calculate_time(options[:offset]) : 0
101
+ rate = options[:interval] ? (1 / options[:interval].to_f) : 1
102
+
103
+ limit = nil
104
+ # if options[:limit]
105
+ # options[:limit]
106
+ # elsif not options[:interval]
107
+ # 1
108
+ # end
109
+
110
+ output = if options[:output]
111
+ options[:output]
112
+ else
113
+ path = File.dirname File.expand_path(options[:input])
114
+
115
+ name = File.basename(options[:input], ".*")
116
+ if options[:interval]
117
+ name << "-%d"
118
+ else
119
+ name << "-#{offset}"
120
+ end
121
+ name << ".jpg"
122
+
123
+ File.join path, name
124
+ end
125
+
126
+ [offset, rate, limit, output]
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,483 @@
1
+ module RVideo # :nodoc:
2
+ # To inspect a video or audio file, initialize an Inspector object.
3
+ #
4
+ # file = RVideo::Inspector.new(options_hash)
5
+ #
6
+ # Inspector accepts three options: file, raw_response, and ffmpeg_binary.
7
+ # Either raw_response or file is required; ffmpeg binary is optional.
8
+ #
9
+ # :file is a path to a file to be inspected.
10
+ #
11
+ # :raw_response is the full output of "ffmpeg -i [file]". If the
12
+ # :raw_response option is used, RVideo will not actually inspect a file;
13
+ # it will simply parse the provided response. This is useful if your
14
+ # application has already collected the ffmpeg -i response, and you don't
15
+ # want to call it again.
16
+ #
17
+ # :ffmpeg_binary is an optional argument that specifies the path to the
18
+ # ffmpeg binary to be used. If a path is not explicitly declared, RVideo
19
+ # will assume that ffmpeg exists in the Unix path. Type "which ffmpeg" to
20
+ # check if ffmpeg is installed and exists in your operating system's path.
21
+ class Inspector
22
+ attr_reader :filename, :path, :full_filename, :raw_response, :raw_metadata
23
+
24
+ attr_accessor :ffmpeg_binary
25
+
26
+ def initialize(options = {})
27
+ if not (options[:raw_response] or options[:file])
28
+ raise ArgumentError, "Must supply either an input file or a pregenerated response"
29
+ end
30
+
31
+ if options[:raw_response]
32
+ initialize_with_raw_response(options[:raw_response])
33
+ elsif options[:file]
34
+ initialize_with_file(options[:file], options[:ffmpeg_binary])
35
+ end
36
+
37
+ metadata = /(Input \#.*)\n.+\n\Z/m.match(@raw_response)
38
+
39
+ if /Unknown format/i.match(@raw_response) || metadata.nil?
40
+ @unknown_format = true
41
+ elsif /Duration: N\/A/im.match(@raw_response)
42
+ # in this case, we can at least still get the container type
43
+ @unreadable_file = true
44
+ @raw_metadata = metadata[1]
45
+ else
46
+ @raw_metadata = metadata[1]
47
+ end
48
+ end
49
+
50
+ def initialize_with_raw_response(raw_response)
51
+ @raw_response = raw_response
52
+ end
53
+
54
+ def initialize_with_file(file, ffmpeg_binary = nil)
55
+ if ffmpeg_binary
56
+ @ffmpeg_binary = ffmpeg_binary
57
+ if not FileTest.exist?(@ffmpeg_binary)
58
+ raise "ffmpeg could not be found (trying #{@ffmpeg_binary})"
59
+ end
60
+ else
61
+ # assume it is in the unix path
62
+ if not FileTest.exist?(`which ffmpeg`.chomp)
63
+ raise "ffmpeg could not be found (expected ffmpeg to be found in the Unix path)"
64
+ end
65
+ @ffmpeg_binary = "ffmpeg"
66
+ end
67
+
68
+ if not FileTest.exist?(file.gsub('"',''))
69
+ raise TranscoderError::InputFileNotFound, "File not found (#{file})"
70
+ end
71
+
72
+ @full_filename = file
73
+ @filename = File.basename(@full_filename)
74
+ @path = File.dirname(@full_filename)
75
+
76
+ @raw_response = `#{@ffmpeg_binary} -i #{@full_filename.shell_quoted} 2>&1`
77
+ end
78
+
79
+ # Returns true if the file can be read successfully. Returns false otherwise.
80
+ def valid?
81
+ not invalid?
82
+ end
83
+
84
+ # Returns false if the file can be read successfully. Returns false otherwise.
85
+ def invalid?
86
+ @unknown_format or @unreadable_file
87
+ end
88
+
89
+ # True if the format is not understood ("Unknown Format")
90
+ def unknown_format?
91
+ @unknown_format ? true : false
92
+ end
93
+
94
+ # True if the file is not readable ("Duration: N/A, bitrate: N/A")
95
+ def unreadable_file?
96
+ @unreadable_file ? true : false
97
+ end
98
+
99
+ # Does the file have an audio stream?
100
+ def audio?
101
+ not audio_match.nil?
102
+ end
103
+
104
+ # Does the file have a video stream?
105
+ def video?
106
+ not video_match.nil?
107
+ end
108
+
109
+ # Returns the version of ffmpeg used, In practice, this may or may not be
110
+ # useful.
111
+ #
112
+ # Examples:
113
+ #
114
+ # SVN-r6399
115
+ # CVS
116
+ #
117
+ def ffmpeg_version
118
+ @ffmpeg_version = @raw_response.split("\n").first.split("version").last.split(",").first.strip
119
+ end
120
+
121
+ # Returns the configuration options used to build ffmpeg.
122
+ #
123
+ # Example:
124
+ #
125
+ # --enable-mp3lame --enable-gpl --disable-ffplay --disable-ffserver
126
+ # --enable-a52 --enable-xvid
127
+ #
128
+ def ffmpeg_configuration
129
+ /(\s*configuration:)(.*)\n/.match(@raw_response)[2].strip
130
+ end
131
+
132
+ # Returns the versions of libavutil, libavcodec, and libavformat used by
133
+ # ffmpeg.
134
+ #
135
+ # Example:
136
+ #
137
+ # libavutil version: 49.0.0
138
+ # libavcodec version: 51.9.0
139
+ # libavformat version: 50.4.0
140
+ #
141
+ def ffmpeg_libav
142
+ /^(\s*lib.*\n)+/.match(@raw_response)[0].split("\n").each {|l| l.strip! }
143
+ end
144
+
145
+ # Returns the build description for ffmpeg.
146
+ #
147
+ # Example:
148
+ #
149
+ # built on Apr 15 2006 04:58:19, gcc: 4.0.1 (Apple Computer, Inc. build
150
+ # 5250)
151
+ #
152
+ def ffmpeg_build
153
+ /(\n\s*)(built on.*)(\n)/.match(@raw_response)[2]
154
+ end
155
+
156
+ # Returns the container format for the file. Instead of returning a single
157
+ # format, this may return a string of related formats.
158
+ #
159
+ # Examples:
160
+ #
161
+ # "avi"
162
+ #
163
+ # "mov,mp4,m4a,3gp,3g2,mj2"
164
+ #
165
+ def container
166
+ return nil if @unknown_format
167
+ /Input \#\d+\,\s*(\S+),\s*from/.match(@raw_metadata)[1]
168
+ end
169
+
170
+ # The duration of the movie, as a string.
171
+ #
172
+ # Example:
173
+ #
174
+ # "00:00:24.4" # 24.4 seconds
175
+ #
176
+ def raw_duration
177
+ return nil unless valid?
178
+ /Duration:\s*([0-9\:\.]+),/.match(@raw_metadata)[1]
179
+ end
180
+
181
+ # The duration of the movie in milliseconds, as an integer.
182
+ #
183
+ # Example:
184
+ #
185
+ # 24400 # 24.4 seconds
186
+ #
187
+ # Note that the precision of the duration is in tenths of a second, not
188
+ # thousandths, but milliseconds are a more standard unit of time than
189
+ # deciseconds.
190
+ #
191
+ def duration
192
+ return nil unless valid?
193
+
194
+ units = raw_duration.split(":")
195
+ (units[0].to_i * 60 * 60 * 1000) + (units[1].to_i * 60 * 1000) + (units[2].to_f * 1000).to_i
196
+ end
197
+
198
+ # The bitrate of the movie.
199
+ #
200
+ # Example:
201
+ #
202
+ # 3132
203
+ #
204
+ def bitrate
205
+ return nil unless valid?
206
+ bitrate_match[1].to_i
207
+ end
208
+
209
+ # The bitrate units used. In practice, this may always be kb/s.
210
+ #
211
+ # Example:
212
+ #
213
+ # "kb/s"
214
+ #
215
+ def bitrate_units
216
+ return nil unless valid?
217
+ bitrate_match[2]
218
+ end
219
+
220
+ def bitrate_with_units
221
+ "#{bitrate} #{bitrate_units}"
222
+ end
223
+
224
+ def audio_bit_rate
225
+ return nil unless audio?
226
+ audio_match[7].to_i
227
+ end
228
+
229
+ def audio_bit_rate_units
230
+ return nil unless audio?
231
+ audio_match[8]
232
+ end
233
+
234
+ def audio_bit_rate_with_units
235
+ "#{audio_bit_rate} #{audio_bit_rate_units}"
236
+ end
237
+
238
+ def audio_stream
239
+ return nil unless valid?
240
+
241
+ match = /\n\s*Stream.*Audio:.*\n/.match(@raw_response)
242
+ match[0].strip if match
243
+ end
244
+
245
+ # The audio codec used.
246
+ #
247
+ # Example:
248
+ #
249
+ # "aac"
250
+ #
251
+ def audio_codec
252
+ return nil unless audio?
253
+ audio_match[2]
254
+ end
255
+
256
+ # The sampling rate of the audio stream.
257
+ #
258
+ # Example:
259
+ #
260
+ # 44100
261
+ #
262
+ def audio_sample_rate
263
+ return nil unless audio?
264
+ audio_match[3].to_i
265
+ end
266
+
267
+ # The units used for the sampling rate. May always be Hz.
268
+ #
269
+ # Example:
270
+ #
271
+ # "Hz"
272
+ #
273
+ def audio_sample_rate_units
274
+ return nil unless audio?
275
+ audio_match[4]
276
+ end
277
+ alias_method :audio_sample_units, :audio_sample_rate_units
278
+
279
+ def audio_sample_rate_with_units
280
+ "#{audio_sample_rate} #{audio_sample_rate_units}"
281
+ end
282
+
283
+ # The channels used in the audio stream.
284
+ #
285
+ # Examples:
286
+ # "stereo"
287
+ # "mono"
288
+ # "5:1"
289
+ #
290
+ def audio_channels_string
291
+ return nil unless audio?
292
+ audio_match[5]
293
+ end
294
+
295
+ def audio_channels
296
+ return nil unless audio?
297
+
298
+ case audio_match[5]
299
+ when "mono" then 1
300
+ when "stereo" then 2
301
+ else
302
+ raise RuntimeError, "Unknown number of channels: #{audio_channels}"
303
+ end
304
+ end
305
+
306
+ # This should almost always return 16,
307
+ # as the vast majority of audio is 16 bit.
308
+ def audio_sample_bit_depth
309
+ return nil unless audio?
310
+ audio_match[6].to_i
311
+ end
312
+
313
+ # The ID of the audio stream (useful for troubleshooting).
314
+ #
315
+ # Example:
316
+ # #0.1
317
+ #
318
+ def audio_stream_id
319
+ return nil unless audio?
320
+ audio_match[1]
321
+ end
322
+
323
+ def video_stream
324
+ return nil unless valid?
325
+
326
+ match = /\n\s*Stream.*Video:.*\n/.match(@raw_response)
327
+ match[0].strip unless match.nil?
328
+ end
329
+
330
+ # The ID of the video stream (useful for troubleshooting).
331
+ #
332
+ # Example:
333
+ # #0.0
334
+ #
335
+ def video_stream_id
336
+ return nil unless video?
337
+ video_match[1]
338
+ end
339
+
340
+ # The video codec used.
341
+ #
342
+ # Example:
343
+ #
344
+ # "mpeg4"
345
+ #
346
+ def video_codec
347
+ return nil unless video?
348
+ video_match[3]
349
+ end
350
+
351
+ # The colorspace of the video stream.
352
+ #
353
+ # Example:
354
+ #
355
+ # "yuv420p"
356
+ #
357
+ def video_colorspace
358
+ return nil unless video?
359
+ video_match[4]
360
+ end
361
+
362
+ # The width of the video in pixels.
363
+ def width
364
+ return nil unless video?
365
+ video_match[5].to_i
366
+ end
367
+
368
+ # The height of the video in pixels.
369
+ def height
370
+ return nil unless video?
371
+ video_match[6].to_i
372
+ end
373
+
374
+ # width x height, as a string.
375
+ #
376
+ # Examples:
377
+ # 320x240
378
+ # 1280x720
379
+ #
380
+ def resolution
381
+ return nil unless video?
382
+ "#{width}x#{height}"
383
+ end
384
+
385
+ def pixel_aspect_ratio
386
+ return nil unless video?
387
+ video_match[7]
388
+ end
389
+
390
+ def display_aspect_ratio
391
+ return nil unless video?
392
+ video_match[8]
393
+ end
394
+
395
+ # The portion of the overall bitrate the video is responsible for.
396
+ def video_bit_rate
397
+ return nil unless video?
398
+ video_match[9]
399
+ end
400
+
401
+ def video_bit_rate_units
402
+ return nil unless video?
403
+ video_match[10]
404
+ end
405
+
406
+ # The frame rate of the video in frames per second
407
+ #
408
+ # Example:
409
+ #
410
+ # "29.97"
411
+ #
412
+ def fps
413
+ return nil unless video?
414
+ video_match[2] or video_match[11]
415
+ end
416
+ alias_method :framerate, :fps
417
+
418
+ def time_base
419
+ return nil unless video?
420
+ video_match[12]
421
+ end
422
+
423
+ def codec_time_base
424
+ return nil unless video?
425
+ video_match[13]
426
+ end
427
+
428
+ private
429
+
430
+ def bitrate_match
431
+ /bitrate: ([0-9\.]+)\s*(.*)\s+/.match(@raw_metadata)
432
+ end
433
+
434
+ ###
435
+ # I am wondering how reliable it would be to simplify a lot
436
+ # of this regexp parsery by using split(/\s*,\s*/) - Seth
437
+
438
+ SEP = '(?:,\s*)'
439
+ VAL = '([^,]+)'
440
+
441
+ RATE = '([\d.]+k?)'
442
+
443
+ AUDIO_MATCH_PATTERN = /
444
+ Stream\s+(.*?)[,:\(\[].*?\s*
445
+ Audio:\s+
446
+ #{VAL}#{SEP} # codec
447
+ #{RATE}\s+(\w*)#{SEP}? # sample rate
448
+ ([a-zA-Z:]*)#{SEP}? # channels
449
+ (?:s(\d+)#{SEP}?)? # audio sample bit depth
450
+ (?:(\d+)\s+(\S+))? # audio bit rate
451
+ /x
452
+
453
+ def audio_match
454
+ return nil unless valid?
455
+ AUDIO_MATCH_PATTERN.match(audio_stream)
456
+ end
457
+
458
+ FPS = 'fps(?:\(r\))?'
459
+
460
+ # FIXME tbn/tbc may need braced alternatives, too: tb(n) / tb(c) - not tested
461
+ # not fixing as of yet because it renumbers backrefs
462
+ VIDEO_MATCH_PATTERN = /
463
+ Stream\s*(\#[\d.]+)(?:[\(\[].+?[\)\]])?\s* # stream id
464
+ [,:]\s*
465
+ (?:#{RATE}\s*#{FPS}[,:]\s*)? # frame rate, older builds
466
+ Video:\s*
467
+ #{VAL}#{SEP} # codec
468
+ (?:#{VAL}#{SEP})? # color space
469
+ (\d+)x(\d+)#{SEP}? # resolution
470
+ (?:\s*\[?(?:PAR\s*(\d+:\d+))?\s*(?:DAR\s*(\d+:\d+))?\]?)? # pixel and display aspect ratios
471
+ #{SEP}?
472
+ (?:#{RATE}\s*(kb\/s)#{SEP}?)? # video bit rate
473
+ (?:#{RATE}\s*(?:tb\(?r\)?|#{FPS})#{SEP}?)? # frame rate
474
+ (?:#{RATE}\s*tbn#{SEP}?)? # time base
475
+ (?:#{RATE}\s*tbc#{SEP}?)? # codec time base
476
+ /x
477
+
478
+ def video_match
479
+ return nil unless valid?
480
+ VIDEO_MATCH_PATTERN.match(video_stream)
481
+ end
482
+ end
483
+ end