streamio-ffmpeg 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.4.0 2009-02-10
2
+
3
+ * Transcoding API changed to make use of more humanly readable options (see README for examples)
4
+ * Fixed frame rate parsing for integer frame rates
5
+
1
6
  == 0.3.0 2009-02-07
2
7
 
3
8
  * Simple transcoding
data/README.rdoc CHANGED
@@ -34,21 +34,28 @@ Simplest possible wrapper around FFMPEG to get metadata from movie files and do
34
34
 
35
35
  === Transcoding
36
36
 
37
- Use :output_file option to specify the output file path.
37
+ First argument is the output file path.
38
38
 
39
- movie.transcode(:output_file => "tmp/movie.mp4") # Default ffmpeg settings for mp4 format
39
+ movie.transcode("tmp/movie.mp4") # Default ffmpeg settings for mp4 format
40
40
 
41
41
  Keep track of progress with an optional block.
42
42
 
43
- movie.transcode(:output_file => "movie.mp4") { |progress| puts progress } # 0.2 ... 0.5 ... 1.0
43
+ movie.transcode(movie.mp4") { |progress| puts progress } # 0.2 ... 0.5 ... 1.0
44
44
 
45
- Give custom command line options to ffmpeg through :raw_options.
45
+ Give custom command line options with a string.
46
+
47
+ movie.transcode("movie.mp4", "-ac aac -vc libx264 -ac 2 ...")
48
+
49
+ Use the EncodingOptions parser for humanly readable transcoding options. Below you'll find all the supported options. Note that the :custom key will be used as is without modification so use it for any tricky business you might need.
50
+
51
+ options = {:video_codec => "libx264", :frame_rate => 10, :resolution => "320x240", :video_bitrate => 300,
52
+ :audio_codec => "libfaac", :audio_bitrate => 32, :audio_sample_rate => 22050, :audio_channels => 1,
53
+ :custom => "-flags +loop -cmp +chroma -partitions +parti4x4+partp8x8 -flags2 +mixed_refs -me_method umh -subq 6 -refs 6 -rc_eq 'blurCplx^(1-qComp)' -coder 0 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -level 21"}
54
+ movie.transcode("movie.mp4", options)
46
55
 
47
- movie.transcode(:raw_options => "-ac aac -vc libx264 -ac 2 ...", :output_file => "movie.mp4")
48
-
49
56
  The transcode function returns a Movie object for the encoded file.
50
57
 
51
- transcoded_movie = movie.transcode(:output_file => "tmp/movie.flv")
58
+ transcoded_movie = movie.transcode("tmp/movie.flv")
52
59
 
53
60
  transcoded_movie.video_codec # "flv"
54
61
  transcoded_movie.audio_codec # "mp3"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
@@ -0,0 +1,60 @@
1
+ # Include support for -bt -maxrate -minrate -bufsize?
2
+
3
+ module FFMPEG
4
+ class EncodingOptions < Hash
5
+ def initialize(options = {})
6
+ merge!(options)
7
+ end
8
+
9
+ def to_s
10
+ collect do |key, value|
11
+ send("convert_#{key}", value) if supports_option?(key)
12
+ end.join(" ")
13
+ end
14
+
15
+ private
16
+ def supports_option?(option)
17
+ private_methods.include?("convert_#{option}")
18
+ end
19
+
20
+ def convert_video_codec(value)
21
+ "-vcodec #{value}"
22
+ end
23
+
24
+ def convert_frame_rate(value)
25
+ "-r #{value}"
26
+ end
27
+
28
+ def convert_resolution(value)
29
+ "-s #{value}"
30
+ end
31
+
32
+ def convert_video_bitrate(value)
33
+ "-b #{value}#{k_format(value)}"
34
+ end
35
+
36
+ def convert_audio_codec(value)
37
+ "-acodec #{value}"
38
+ end
39
+
40
+ def convert_audio_bitrate(value)
41
+ "-ab #{value}#{k_format(value)}"
42
+ end
43
+
44
+ def convert_audio_sample_rate(value)
45
+ "-ar #{value}"
46
+ end
47
+
48
+ def convert_audio_channels(value)
49
+ "-ac #{value}"
50
+ end
51
+
52
+ def convert_custom(value)
53
+ value
54
+ end
55
+
56
+ def k_format(value)
57
+ "k" unless value.to_s.include?("k")
58
+ end
59
+ end
60
+ end
data/lib/ffmpeg/movie.rb CHANGED
@@ -56,12 +56,11 @@ module FFMPEG
56
56
  end
57
57
 
58
58
  def frame_rate
59
- video_stream[/(\d*\.\d*)\s?fps/] ? $1.to_f : nil
59
+ video_stream[/(\d*\.?\d*)\s?fps/] ? $1.to_f : nil
60
60
  end
61
61
 
62
62
  def transcode(options, &block)
63
- transcoder = Transcoder.new(self, options)
64
- transcoder.run &block
63
+ Transcoder.new(self, options).run &block
65
64
  end
66
65
  end
67
66
  end
@@ -2,15 +2,23 @@ require 'open3'
2
2
 
3
3
  module FFMPEG
4
4
  class Transcoder
5
- def initialize(movie, options)
6
- raise ArgumentError, "you need to specify options[:output_file]" unless options[:output_file]
7
-
5
+ def initialize(movie, output_file, options = EncodingOptions.new)
8
6
  @movie = movie
9
- @options = options
7
+ @output_file = output_file
8
+
9
+ if options.is_a?(String) || options.is_a?(EncodingOptions)
10
+ @raw_options = options
11
+ elsif options.is_a?(Hash)
12
+ @raw_options = EncodingOptions.new(options)
13
+ else
14
+ raise ArgumentError, "Unknown options format '#{options.class}', should be either EncodingOptions, Hash or String."
15
+ end
16
+
17
+ @errors = []
10
18
  end
11
19
 
12
20
  def run
13
- command = "ffmpeg -y -i '#{@movie.path}' #{@options[:raw_options]} '#{@options[:output_file]}'"
21
+ command = "ffmpeg -y -i '#{@movie.path}' #{@raw_options} '#{@output_file}'"
14
22
  last_output = nil
15
23
  Open3.popen3(command) do |stdin, stdout, stderr|
16
24
  stderr.each("r") do |line|
@@ -24,24 +32,39 @@ module FFMPEG
24
32
  end
25
33
  last_output = line
26
34
  end
27
- end
35
+ end
28
36
 
29
37
  if encoding_succeeded?
30
38
  yield(1.0) if block_given?
31
39
  else
32
- raise "Failed encoding. Last output: #{last_output}. Original duration: #{@movie.duration}. Encoded duration: #{encoded.duration}."
40
+ raise "Failed encoding. Last output: #{last_output}. Errors: #{@errors.join(", ")}"
33
41
  end
34
42
 
35
43
  encoded
36
44
  end
37
45
 
38
46
  def encoding_succeeded?
47
+ unless File.exists?(@output_file)
48
+ @errors << "no output file created"
49
+ return false
50
+ end
51
+
52
+ unless encoded.valid?
53
+ @errors << "encoded file is invalid"
54
+ return false
55
+ end
56
+
39
57
  precision = 1.1
40
- encoded.valid? && !(encoded.duration >= (@movie.duration * precision) or encoded.duration <= (@movie.duration / precision))
58
+ unless !(encoded.duration >= (@movie.duration * precision) or encoded.duration <= (@movie.duration / precision))
59
+ @errors << "encoded file duration differed from original (original: #{@movie.duration}sec, encoded: #{encoded.duration}sec)"
60
+ return false
61
+ end
62
+
63
+ true
41
64
  end
42
65
 
43
66
  def encoded
44
- @encoded ||= Movie.new(@options[:output_file])
67
+ @encoded ||= Movie.new(@output_file)
45
68
  end
46
69
  end
47
70
  end
@@ -2,6 +2,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__)
2
2
 
3
3
  require 'ffmpeg/movie'
4
4
  require 'ffmpeg/transcoder'
5
+ require 'ffmpeg/encoding_options'
5
6
 
6
7
  module FFMPEG
7
8
  VERSION = '0.1.0'
@@ -0,0 +1,52 @@
1
+ require 'spec_helper.rb'
2
+
3
+ module FFMPEG
4
+ describe EncodingOptions do
5
+ describe "ffmpeg arguments conversion" do
6
+ it "should convert video codec" do
7
+ EncodingOptions.new(:video_codec => "libx264").to_s.should == "-vcodec libx264"
8
+ end
9
+
10
+ it "should convert frame rate" do
11
+ EncodingOptions.new(:frame_rate => 29.9).to_s.should == "-r 29.9"
12
+ end
13
+
14
+ it "should convert the resolution" do
15
+ EncodingOptions.new(:resolution => "640x480").to_s.should == "-s 640x480"
16
+ end
17
+
18
+ it "should convert video bitrate" do
19
+ EncodingOptions.new(:video_bitrate => "600k").to_s.should == "-b 600k"
20
+ end
21
+
22
+ it "should use k unit for video bitrate" do
23
+ EncodingOptions.new(:video_bitrate => 600).to_s.should == "-b 600k"
24
+ end
25
+
26
+ it "should convert audio codec" do
27
+ EncodingOptions.new(:audio_codec => "aac").to_s.should == "-acodec aac"
28
+ end
29
+
30
+ it "should convert audio bitrate" do
31
+ EncodingOptions.new(:audio_bitrate => "128k").to_s.should == "-ab 128k"
32
+ end
33
+
34
+ it "should use k unit for audio bitrate" do
35
+ EncodingOptions.new(:audio_bitrate => 128).to_s.should == "-ab 128k"
36
+ end
37
+
38
+ it "should convert audio sample rate" do
39
+ EncodingOptions.new(:audio_sample_rate => 44100).to_s.should == "-ar 44100"
40
+ end
41
+
42
+ it "should convert audio channels" do
43
+ EncodingOptions.new(:audio_channels => 2).to_s.should == "-ac 2"
44
+ end
45
+
46
+ it "should convert a lot of them simultaneously" do
47
+ converted = EncodingOptions.new(:video_codec => "libx264", :audio_codec => "aac", :video_bitrate => "1000k").to_s
48
+ converted.should match(/-acodec aac/)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -86,11 +86,13 @@ module FFMPEG
86
86
 
87
87
  describe "transcode" do
88
88
  it "should run the transcoder" do
89
- FileUtils.rm_f "#{tmp_path}/awesome.flv"
90
89
  movie = Movie.new("#{fixture_path}/movies/awesome.mov")
91
- encoded = movie.transcode(:output_file => "#{tmp_path}/awesome.flv")
92
- encoded.should be_valid
93
- File.exists?("#{tmp_path}/awesome.flv").should be_true
90
+
91
+ mockery = mock(Transcoder)
92
+ Transcoder.should_receive(:new).with(movie, "#{tmp_path}/awesome.flv").and_return(mockery)
93
+ mockery.should_receive(:run)
94
+
95
+ movie.transcode("#{tmp_path}/awesome.flv")
94
96
  end
95
97
  end
96
98
  end
@@ -2,21 +2,75 @@ require 'spec_helper.rb'
2
2
 
3
3
  module FFMPEG
4
4
  describe Transcoder do
5
- describe "initializing" do
6
- it "should require an output_file option" do
7
- lambda { Transcoder.new(nil, {}) }.should raise_error(ArgumentError, /output_file/)
5
+ describe "initialization" do
6
+ before(:each) do
7
+ @movie = Movie.new("#{fixture_path}/movies/awesome.mov")
8
+ @output_path = "#{tmp_path}/awesome.flv"
9
+ end
10
+
11
+ it "should accept EncodingOptions as options" do
12
+ lambda { Transcoder.new(@movie, @output_path, EncodingOptions.new) }.should_not raise_error(ArgumentError)
13
+ end
14
+
15
+ it "should accept Hash as options" do
16
+ lambda { Transcoder.new(@movie, @output_path, :video_codec => "libx264") }.should_not raise_error(ArgumentError)
17
+ end
18
+
19
+ it "should accept String as options" do
20
+ lambda { Transcoder.new(@movie, @output_path, "-vcodec libx264") }.should_not raise_error(ArgumentError)
21
+ end
22
+
23
+ it "should not accept anything else as options" do
24
+ lambda { Transcoder.new(@movie, @output_path, ["array?"]) }.should raise_error(ArgumentError, /Unknown options format/)
8
25
  end
9
26
  end
10
27
 
11
28
  describe "transcoding" do
12
- it "should transcode the movie" do
29
+ it "should transcode the movie with progress given an awesome movie" do
13
30
  FileUtils.rm_f "#{tmp_path}/awesome.flv"
31
+
14
32
  movie = Movie.new("#{fixture_path}/movies/awesome.mov")
15
- transcoder = Transcoder.new(movie, :output_file => "tmp/awesome.flv")
16
- transcoder.run
33
+
34
+ transcoder = Transcoder.new(movie, "#{tmp_path}/awesome.flv")
35
+ stored_progress = 0
36
+ transcoder.run { |progress| stored_progress = progress }
17
37
  transcoder.encoded.should be_valid
38
+ stored_progress.should == 1.0
18
39
  File.exists?("#{tmp_path}/awesome.flv").should be_true
19
40
  end
41
+
42
+ it "should transcode the movie with EncodingOptions" do
43
+ FileUtils.rm_f "#{tmp_path}/optionalized.mp4"
44
+
45
+ movie = Movie.new("#{fixture_path}/movies/awesome.mov")
46
+ options = {:video_codec => "libx264", :frame_rate => 10, :resolution => "320x240", :video_bitrate => 300,
47
+ :audio_codec => "libfaac", :audio_bitrate => 32, :audio_sample_rate => 22050, :audio_channels => 1,
48
+ :custom => "-flags +loop -cmp +chroma -partitions +parti4x4+partp8x8 -flags2 +mixed_refs -me_method umh -subq 6 -refs 6 -rc_eq 'blurCplx^(1-qComp)' -coder 0 -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -level 21"}
49
+
50
+ encoded = Transcoder.new(movie, "#{tmp_path}/optionalized.mp4", options).run
51
+ encoded.video_codec.should == "h264"
52
+ encoded.resolution.should == "320x240"
53
+ encoded.frame_rate.should == 10.0
54
+ encoded.audio_codec.should == "aac"
55
+ encoded.audio_sample_rate.should == 22050
56
+ encoded.audio_channels.should == 1
57
+ end
58
+
59
+ it "should transcode the movie with String options" do
60
+ FileUtils.rm_f "#{tmp_path}/string_optionalized.flv"
61
+
62
+ movie = Movie.new("#{fixture_path}/movies/awesome.mov")
63
+
64
+ encoded = Transcoder.new(movie, "#{tmp_path}/string_optionalized.flv", "-s 300x200 -ac 2").run
65
+ encoded.resolution.should == "300x200"
66
+ encoded.audio_channels.should == 2
67
+ end
68
+
69
+ it "should fail when given an invalid movie" do
70
+ movie = Movie.new(__FILE__)
71
+ transcoder = Transcoder.new(movie, "#{tmp_path}/fail.flv")
72
+ lambda { transcoder.run }.should raise_error(RuntimeError, /no output file created/)
73
+ end
20
74
  end
21
75
  end
22
76
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{streamio-ffmpeg}
8
- s.version = "0.3.0"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["David Backeus"]
12
- s.date = %q{2010-02-07}
12
+ s.date = %q{2010-02-10}
13
13
  s.description = %q{Simple wrapper around ffmpeg to get metadata from movies and do transcoding}
14
14
  s.email = %q{duztdruid@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -24,9 +24,11 @@ Gem::Specification.new do |s|
24
24
  "README.rdoc",
25
25
  "Rakefile",
26
26
  "VERSION",
27
+ "lib/ffmpeg/encoding_options.rb",
27
28
  "lib/ffmpeg/movie.rb",
28
29
  "lib/ffmpeg/transcoder.rb",
29
30
  "lib/streamio-ffmpeg.rb",
31
+ "spec/ffmpeg/encoding_options_spec.rb",
30
32
  "spec/ffmpeg/movie_spec.rb",
31
33
  "spec/ffmpeg/transcoder_spec.rb",
32
34
  "spec/fixtures/movies/awesome.mov",
@@ -40,7 +42,8 @@ Gem::Specification.new do |s|
40
42
  s.rubygems_version = %q{1.3.5}
41
43
  s.summary = %q{Simple wrapper around ffmpeg to get metadata from movies and do transcoding}
42
44
  s.test_files = [
43
- "spec/ffmpeg/movie_spec.rb",
45
+ "spec/ffmpeg/encoding_options_spec.rb",
46
+ "spec/ffmpeg/movie_spec.rb",
44
47
  "spec/ffmpeg/transcoder_spec.rb",
45
48
  "spec/spec_helper.rb"
46
49
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: streamio-ffmpeg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Backeus
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-07 00:00:00 +01:00
12
+ date: 2010-02-10 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -39,9 +39,11 @@ files:
39
39
  - README.rdoc
40
40
  - Rakefile
41
41
  - VERSION
42
+ - lib/ffmpeg/encoding_options.rb
42
43
  - lib/ffmpeg/movie.rb
43
44
  - lib/ffmpeg/transcoder.rb
44
45
  - lib/streamio-ffmpeg.rb
46
+ - spec/ffmpeg/encoding_options_spec.rb
45
47
  - spec/ffmpeg/movie_spec.rb
46
48
  - spec/ffmpeg/transcoder_spec.rb
47
49
  - spec/fixtures/movies/awesome.mov
@@ -77,6 +79,7 @@ signing_key:
77
79
  specification_version: 3
78
80
  summary: Simple wrapper around ffmpeg to get metadata from movies and do transcoding
79
81
  test_files:
82
+ - spec/ffmpeg/encoding_options_spec.rb
80
83
  - spec/ffmpeg/movie_spec.rb
81
84
  - spec/ffmpeg/transcoder_spec.rb
82
85
  - spec/spec_helper.rb