streamio-ffmpeg 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b2f7f026d79edbb50b3cc2a727970b24ddc14e35
4
+ data.tar.gz: 1eb3016af722ac698e9ffa27515b515f7683bf7a
5
+ SHA512:
6
+ metadata.gz: bd2bde719ec750a394f4a9143d117355e615d7302d9da8c02d58300b59fed78c7e7e85c2d8b4821221cc21fc402c6bc94982596db37df85f121915aa3b889265
7
+ data.tar.gz: 448a6304a0c99eb13a1540bf8a99f45db9d8a08f12d7430a08a6f4b7a6a0a12746f9b1211587ff226a5c37184d81438ab3fd18780a393ba7439c9e30a44a472b
data/CHANGELOG CHANGED
@@ -1,3 +1,30 @@
1
+ == 1.0.0 2013-07-08
2
+
3
+ New:
4
+ * Bumped target ffmpeg version to 1.2.1
5
+
6
+ Improvements:
7
+ * Simpler implementation for timeouts.
8
+ Should be far less cpu and memory dependent (don't spawn a thread for every line of output)
9
+ Timeout spec now passes in Rubinius (using 1.9 mode)
10
+ * Give helpful error message for windows users lacking the win32-process gem (thanks casoetan)
11
+ * Add Movie#container (thanks vitalis)
12
+ * Support vprofile and preset encoding options (thanks vitalis)
13
+
14
+ Changes:
15
+ * Default timeout lowered to 30 seconds
16
+
17
+ Bugs:
18
+ * Avoid crash if asking for frame_rate of a video without video stream (thanks squidarth)
19
+ * Fix crash when doing audio transcoding on ffmpeg >= 1.0.1 (thanks vitalis)
20
+
21
+ Deprecations:
22
+ * Removed support for Ruby 1.8
23
+ * Removed support for ffmpeg 0.7
24
+
25
+ Refactorings:
26
+ * Quite a few, see commit history for details.
27
+
1
28
  == 0.9.0 2012-07-24
2
29
 
3
30
  New:
@@ -76,7 +103,7 @@ Refactorings:
76
103
 
77
104
  == 0.7.4 2010-12-07
78
105
 
79
- * Fixed broken duration on movies with start times over 0 by reducing duration with start-time
106
+ * Fixed broken duration on movies with start times over 0 by reducing duration with start-time
80
107
 
81
108
  == 0.7.3 2010-08-26
82
109
 
@@ -110,7 +137,7 @@ Refactorings:
110
137
 
111
138
  == 0.6.7 2010-06-10
112
139
 
113
- * Bugfix - aspect ratio preserver could suggest non even resolutions in certain circumstances
140
+ * Bugfix - aspect ratio preserver could suggest non even resolutions in certain circumstances
114
141
 
115
142
  == 0.6.6 2010-06-10
116
143
 
@@ -143,7 +170,7 @@ Refactorings:
143
170
 
144
171
  == 0.5.0 2010-04-28
145
172
 
146
- * Added logging capabilities
173
+ * Added logging capabilities
147
174
 
148
175
  == 0.4.3 2010-04-06
149
176
 
@@ -172,4 +199,4 @@ Refactorings:
172
199
 
173
200
  == 0.1.0 2010-02-05
174
201
 
175
- * Some basic parsing of metadata added
202
+ * Some basic parsing of metadata added
data/README.md CHANGED
@@ -12,7 +12,18 @@ Installation
12
12
 
13
13
  (sudo) gem install streamio-ffmpeg
14
14
 
15
- This version is tested against ffmpeg 0.11.1. So no guarantees with earlier (or much later) versions. Output and input standards have inconveniently changed rather a lot between versions of ffmpeg. My goal is to keep this library in sync with new versions of ffmpeg as they come along.
15
+ Compatibility
16
+ -------------
17
+
18
+ ### Ruby
19
+
20
+ Only guaranteed to work with MRI Ruby 1.9.3 or later.
21
+ Should work with rubinius head in 1.9 mode.
22
+ Will not work in jruby until they fix: http://goo.gl/Z4UcX (should work in the upcoming 1.7.5)
23
+
24
+ ### ffmpeg
25
+
26
+ The current gem is tested against ffmpeg 1.2.1. So no guarantees with earlier (or much later) versions. Output and input standards have inconveniently changed rather a lot between versions of ffmpeg. My goal is to keep this library in sync with new versions of ffmpeg as they come along.
16
27
 
17
28
  Usage
18
29
  -----
@@ -72,11 +83,12 @@ movie.transcode("movie.mp4", "-ac aac -vc libx264 -ac 2 ...")
72
83
  Use the EncodingOptions parser for humanly readable transcoding options. Below you'll find most of 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.
73
84
 
74
85
  ``` ruby
75
- options = {:video_codec => "libx264", :frame_rate => 10, :resolution => "320x240", :video_bitrate => 300, :video_bitrate_tolerance => 100,
76
- :aspect => 1.333333, :keyframe_interval => 90,
77
- :audio_codec => "libfaac", :audio_bitrate => 32, :audio_sample_rate => 22050, :audio_channels => 1,
78
- :threads => 2,
79
- :custom => "-vf crop=60:60:10:10"}
86
+ options = {video_codec: "libx264", frame_rate: 10, resolution: "320x240", video_bitrate: 300, video_bitrate_tolerance: 100,
87
+ aspect: 1.333333, keyframe_interval: 90,
88
+ x264_profile: "high", x264_preset: "slow",
89
+ audio_codec: "libfaac", audio_bitrate: 32, audio_sample_rate: 22050, audio_channels: 1,
90
+ threads: 2,
91
+ custom: "-vf crop=60:60:10:10"}
80
92
  movie.transcode("movie.mp4", options)
81
93
  ```
82
94
 
@@ -92,7 +104,7 @@ transcoded_movie.audio_codec # "mp3"
92
104
  Aspect ratio is added to encoding options automatically if none is specified.
93
105
 
94
106
  ``` ruby
95
- options = {:resolution => "320x180"} # Will add -aspect 1.77777777777778 to ffmpeg
107
+ options = { resolution: "320x180" } # Will add -aspect 1.77777777777778 to ffmpeg
96
108
  ```
97
109
 
98
110
  Preserve aspect ratio on width or height by using the preserve_aspect_ratio transcoder option.
@@ -100,19 +112,19 @@ Preserve aspect ratio on width or height by using the preserve_aspect_ratio tran
100
112
  ``` ruby
101
113
  widescreen_movie = FFMPEG::Movie.new("path/to/widescreen_movie.mov")
102
114
 
103
- options = {:resolution => "320x240"}
115
+ options = { resolution: "320x240" }
104
116
 
105
- transcoder_options = {:preserve_aspect_ratio => :width}
117
+ transcoder_options = { preserve_aspect_ratio: :width }
106
118
  widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 320x180
107
119
 
108
- transcoder_options = {:preserve_aspect_ratio => :height}
120
+ transcoder_options = { preserve_aspect_ratio: :height }
109
121
  widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 426x240
110
122
  ```
111
123
 
112
124
  For constant bitrate encoding use video_min_bitrate and video_max_bitrate with buffer_size.
113
125
 
114
126
  ``` ruby
115
- options = {:video_min_bitrate => 600, :video_max_bitrate => 600, :buffer_size => 2000}
127
+ options = {video_min_bitrate: 600, video_max_bitrate: 600, buffer_size: 2000}
116
128
  movie.transcode("movie.flv", options)
117
129
  ```
118
130
 
@@ -127,13 +139,13 @@ movie.screenshot("screenshot.jpg")
127
139
  The screenshot method has the very same API as transcode so the same options will work.
128
140
 
129
141
  ``` ruby
130
- movie.screenshot("screenshot.bmp", :seek_time => 5, :resolution => '320x240')
142
+ movie.screenshot("screenshot.bmp", seek_time: 5, resolution: '320x240')
131
143
  ```
132
144
 
133
145
  You can preserve aspect ratio the same way as when using transcode.
134
146
 
135
147
  ``` ruby
136
- movie.screenshot("screenshot.png", {:seek_time => 2, :resolution => '200x120'}, :preserve_aspect_ratio => :width)
148
+ movie.screenshot("screenshot.png", { seek_time: 2, resolution: '200x120' }, preserve_aspect_ratio: :width)
137
149
  ```
138
150
 
139
151
  Specify the path to ffmpeg
@@ -151,19 +163,34 @@ This will cause the same command to run as "/usr/local/bin/ffmpeg -i /path/to/in
151
163
  Automatically kill hung processes
152
164
  ---------------------------------
153
165
 
154
- By default, streamio will wait for 200 seconds between IO feedback from the FFMPEG process. After which an error is logged and the process killed.
166
+ By default, streamio will wait for 30 seconds between IO feedback from the FFMPEG process. After which an error is logged and the process killed.
155
167
  It is possible to modify this behaviour by setting a new default:
156
168
 
157
169
  ``` ruby
158
170
  # Change the timeout
159
- Transcoder.timeout = 30
171
+ Transcoder.timeout = 10
160
172
 
161
173
  # Disable the timeout altogether
162
174
  Transcoder.timeout = false
163
175
  ```
164
176
 
177
+ Disabling output file validation
178
+ ------------------------------
179
+
180
+ By default Transcoder validates the output file, in case you use FFMPEG for HLS
181
+ format that creates multiple outputs you can disable the validation by passing
182
+ `validate: false` to transcoder_options.
183
+
184
+ Note that transcode will not return the encoded movie object in this case since
185
+ attempting to open a (possibly) invalid output file might result in an error being raised.
186
+
187
+ ```ruby
188
+ transcoder_options = { validate: false }
189
+ movie.transcode("movie.mp4", options, transcoder_options) # returns nil
190
+ ```
191
+
165
192
 
166
193
  Copyright
167
194
  ---------
168
195
 
169
- Copyright (c) 2011 Streamio AB. See LICENSE for details.
196
+ Copyright (c) Streamio AB. See LICENSE for details.
@@ -3,119 +3,119 @@ module FFMPEG
3
3
  def initialize(options = {})
4
4
  merge!(options)
5
5
  end
6
-
6
+
7
7
  def to_s
8
8
  params = collect do |key, value|
9
9
  send("convert_#{key}", value) if value && supports_option?(key)
10
10
  end
11
-
11
+
12
12
  # codecs should go before the presets so that the files will be matched successfully
13
13
  # all other parameters go after so that we can override whatever is in the preset
14
14
  codecs = params.select { |p| p =~ /codec/ }
15
15
  presets = params.select { |p| p =~ /\-.pre/ }
16
16
  other = params - codecs - presets
17
17
  params = codecs + presets + other
18
-
18
+
19
19
  params_string = params.join(" ")
20
20
  params_string << " #{convert_aspect(calculate_aspect)}" if calculate_aspect?
21
21
  params_string
22
22
  end
23
-
23
+
24
24
  def width
25
25
  self[:resolution].split("x").first.to_i rescue nil
26
26
  end
27
-
27
+
28
28
  def height
29
29
  self[:resolution].split("x").last.to_i rescue nil
30
30
  end
31
-
31
+
32
32
  private
33
33
  def supports_option?(option)
34
34
  option = RUBY_VERSION < "1.9" ? "convert_#{option}" : "convert_#{option}".to_sym
35
35
  private_methods.include?(option)
36
36
  end
37
-
37
+
38
38
  def convert_aspect(value)
39
39
  "-aspect #{value}"
40
40
  end
41
-
41
+
42
42
  def calculate_aspect
43
43
  width, height = self[:resolution].split("x")
44
44
  width.to_f / height.to_f
45
45
  end
46
-
46
+
47
47
  def calculate_aspect?
48
48
  self[:aspect].nil? && self[:resolution]
49
49
  end
50
-
50
+
51
51
  def convert_video_codec(value)
52
52
  "-vcodec #{value}"
53
53
  end
54
-
54
+
55
55
  def convert_frame_rate(value)
56
56
  "-r #{value}"
57
57
  end
58
-
58
+
59
59
  def convert_resolution(value)
60
60
  "-s #{value}"
61
61
  end
62
-
62
+
63
63
  def convert_video_bitrate(value)
64
64
  "-b:v #{k_format(value)}"
65
65
  end
66
-
66
+
67
67
  def convert_audio_codec(value)
68
68
  "-acodec #{value}"
69
69
  end
70
-
70
+
71
71
  def convert_audio_bitrate(value)
72
72
  "-b:a #{k_format(value)}"
73
73
  end
74
-
74
+
75
75
  def convert_audio_sample_rate(value)
76
76
  "-ar #{value}"
77
77
  end
78
-
78
+
79
79
  def convert_audio_channels(value)
80
80
  "-ac #{value}"
81
81
  end
82
-
82
+
83
83
  def convert_video_max_bitrate(value)
84
84
  "-maxrate #{k_format(value)}"
85
85
  end
86
-
86
+
87
87
  def convert_video_min_bitrate(value)
88
88
  "-minrate #{k_format(value)}"
89
89
  end
90
-
90
+
91
91
  def convert_buffer_size(value)
92
92
  "-bufsize #{k_format(value)}"
93
93
  end
94
-
94
+
95
95
  def convert_video_bitrate_tolerance(value)
96
96
  "-bt #{k_format(value)}"
97
97
  end
98
-
98
+
99
99
  def convert_threads(value)
100
100
  "-threads #{value}"
101
101
  end
102
-
102
+
103
103
  def convert_duration(value)
104
104
  "-t #{value}"
105
105
  end
106
-
106
+
107
107
  def convert_video_preset(value)
108
108
  "-vpre #{value}"
109
109
  end
110
-
110
+
111
111
  def convert_audio_preset(value)
112
112
  "-apre #{value}"
113
113
  end
114
-
114
+
115
115
  def convert_file_preset(value)
116
116
  "-fpre #{value}"
117
117
  end
118
-
118
+
119
119
  def convert_keyframe_interval(value)
120
120
  "-g #{value}"
121
121
  end
@@ -123,15 +123,23 @@ module FFMPEG
123
123
  def convert_seek_time(value)
124
124
  "-ss #{value}"
125
125
  end
126
-
126
+
127
127
  def convert_screenshot(value)
128
128
  value ? "-vframes 1 -f image2" : ""
129
129
  end
130
-
130
+
131
+ def convert_x264_vprofile(value)
132
+ "-vprofile #{value}"
133
+ end
134
+
135
+ def convert_x264_preset(value)
136
+ "-preset #{value}"
137
+ end
138
+
131
139
  def convert_custom(value)
132
140
  value
133
141
  end
134
-
142
+
135
143
  def k_format(value)
136
144
  value.to_s.include?("k") ? value : "#{value}k"
137
145
  end
@@ -1,59 +1,42 @@
1
- if RUBY_VERSION =~ /1\.8/
2
- # Useful when `timeout.rb`, which, on M.R.I 1.8, relies on green threads, does not work consistently.
3
- begin
4
- require 'system_timer'
5
- FFMPEG::Timer = SystemTimer
6
- rescue LoadError
7
- require 'timeout'
8
- FFMPEG::Timer = Timeout
9
- end
10
- else
11
- require 'timeout'
12
- FFMPEG::Timer = Timeout
1
+ require 'timeout'
2
+ require 'thread'
3
+ if RUBY_PLATFORM =~ /(win|w)(32|64)$/
4
+ begin
5
+ require 'win32/process'
6
+ rescue LoadError
7
+ "Warning: streamio-ffmpeg is missing the win32-process gem to properly handle hung transcodings. Install the gem (in Gemfile if using bundler) to avoid errors."
8
+ end
13
9
  end
14
10
 
15
- require 'win32/process' if RUBY_PLATFORM =~ /(win|w)(32|64)$/
16
-
17
11
  #
18
12
  # Monkey Patch timeout support into the IO class
19
13
  #
20
14
  class IO
21
15
  def each_with_timeout(pid, seconds, sep_string=$/)
22
- sleeping_queue = Queue.new
23
- thread = nil
24
-
25
- timer_set = lambda do
26
- thread = new_thread(pid) { FFMPEG::Timer.timeout(seconds) { sleeping_queue.pop } }
27
- end
16
+ last_update = Time.now
28
17
 
29
- timer_cancel = lambda do
30
- thread.kill if thread rescue nil
31
- end
18
+ current_thread = Thread.current
19
+ check_update_thread = Thread.new do
20
+ loop do
21
+ sleep 0.1
22
+ if last_update - Time.now < -seconds
23
+ current_thread.raise Timeout::Error.new('output wait time expired')
24
+ end
25
+ end
26
+ end
32
27
 
33
- timer_set.call
34
- each(sep_string) do |buffer|
35
- timer_cancel.call
36
- yield buffer
37
- timer_set.call
38
- end
28
+ each(sep_string) do |buffer|
29
+ last_update = Time.now
30
+ yield buffer
31
+ end
32
+ rescue Timeout::Error
33
+ if RUBY_PLATFORM =~ /(win|w)(32|64)$/
34
+ Process.kill(1, pid)
35
+ else
36
+ Process.kill('SIGKILL', pid)
37
+ end
38
+ raise
39
39
  ensure
40
- timer_cancel.call
41
- end
42
-
43
- private
44
- def new_thread(pid, &block)
45
- current_thread = Thread.current
46
- Thread.new do
47
- begin
48
- block.call
49
- rescue Exception => e
50
- current_thread.raise e
51
- if RUBY_PLATFORM =~ /(win|w)(32|64)$/
52
- Process.kill(1, pid)
53
- else
54
- Process.kill('SIGKILL', pid)
55
- end
56
- end
57
- end
40
+ check_update_thread.kill
58
41
  end
59
42
  end
@@ -5,77 +5,81 @@ module FFMPEG
5
5
  attr_reader :path, :duration, :time, :bitrate, :rotation, :creation_time
6
6
  attr_reader :video_stream, :video_codec, :video_bitrate, :colorspace, :resolution, :dar
7
7
  attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate
8
-
8
+ attr_reader :container
9
+
9
10
  def initialize(path)
10
11
  raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exists?(path)
11
-
12
+
12
13
  @path = path
13
14
 
14
15
  # ffmpeg will output to stderr
15
16
  command = "#{FFMPEG.ffmpeg_binary} -i #{Shellwords.escape(path)}"
16
17
  output = Open3.popen3(command) { |stdin, stdout, stderr| stderr.read }
17
-
18
+
18
19
  fix_encoding(output)
19
-
20
+
21
+ output[/Input \#\d+\,\s*(\S+),\s*from/]
22
+ @container = $1
23
+
20
24
  output[/Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})/]
21
25
  @duration = ($1.to_i*60*60) + ($2.to_i*60) + $3.to_f
22
-
26
+
23
27
  output[/start: (\d*\.\d*)/]
24
28
  @time = $1 ? $1.to_f : 0.0
25
29
 
26
30
  output[/creation_time {1,}: {1,}(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/]
27
31
  @creation_time = $1 ? Time.parse("#{$1}") : nil
28
-
32
+
29
33
  output[/bitrate: (\d*)/]
30
34
  @bitrate = $1 ? $1.to_i : nil
31
-
35
+
32
36
  output[/rotate\ {1,}:\ {1,}(\d*)/]
33
37
  @rotation = $1 ? $1.to_i : nil
34
38
 
35
- output[/Video: (.*)/]
39
+ output[/Video:\ (.*)/]
36
40
  @video_stream = $1
37
-
38
- output[/Audio: (.*)/]
41
+
42
+ output[/Audio:\ (.*)/]
39
43
  @audio_stream = $1
40
-
44
+
41
45
  if video_stream
42
46
  @video_codec, @colorspace, resolution, video_bitrate = video_stream.split(/\s?,\s?/)
43
47
  @video_bitrate = video_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
44
48
  @resolution = resolution.split(" ").first rescue nil # get rid of [PAR 1:1 DAR 16:9]
45
49
  @dar = $1 if video_stream[/DAR (\d+:\d+)/]
46
50
  end
47
-
51
+
48
52
  if audio_stream
49
53
  @audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = audio_stream.split(/\s?,\s?/)
50
54
  @audio_bitrate = audio_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
51
55
  @audio_sample_rate = audio_sample_rate[/\d*/].to_i
52
56
  end
53
-
57
+
54
58
  @invalid = true if @video_stream.to_s.empty? && @audio_stream.to_s.empty?
55
59
  @invalid = true if output.include?("is not supported")
56
60
  @invalid = true if output.include?("could not find codec parameters")
57
61
  end
58
-
62
+
59
63
  def valid?
60
64
  not @invalid
61
65
  end
62
-
66
+
63
67
  def width
64
68
  resolution.split("x")[0].to_i rescue nil
65
69
  end
66
-
70
+
67
71
  def height
68
72
  resolution.split("x")[1].to_i rescue nil
69
73
  end
70
-
74
+
71
75
  def calculated_aspect_ratio
72
76
  aspect_from_dar || aspect_from_dimensions
73
77
  end
74
-
78
+
75
79
  def size
76
80
  File.size(@path)
77
81
  end
78
-
82
+
79
83
  def audio_channels
80
84
  return nil unless @audio_channels
81
85
  return @audio_channels[/\d*/].to_i if @audio_channels["channels"]
@@ -83,19 +87,20 @@ module FFMPEG
83
87
  return 2 if @audio_channels["stereo"]
84
88
  return 6 if @audio_channels["5.1"]
85
89
  end
86
-
90
+
87
91
  def frame_rate
92
+ return nil unless video_stream
88
93
  video_stream[/(\d*\.?\d*)\s?fps/] ? $1.to_f : nil
89
94
  end
90
-
95
+
91
96
  def transcode(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
92
97
  Transcoder.new(self, output_file, options, transcoder_options).run &block
93
98
  end
94
-
99
+
95
100
  def screenshot(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
96
- Transcoder.new(self, output_file, options.merge(:screenshot => true), transcoder_options).run &block
101
+ Transcoder.new(self, output_file, options.merge(screenshot: true), transcoder_options).run &block
97
102
  end
98
-
103
+
99
104
  protected
100
105
  def aspect_from_dar
101
106
  return nil unless dar
@@ -103,12 +108,12 @@ module FFMPEG
103
108
  aspect = w.to_f / h.to_f
104
109
  aspect.zero? ? nil : aspect
105
110
  end
106
-
111
+
107
112
  def aspect_from_dimensions
108
113
  aspect = width.to_f / height.to_f
109
114
  aspect.nan? ? nil : aspect
110
115
  end
111
-
116
+
112
117
  def fix_encoding(output)
113
118
  output[/test/] # Running a regexp on the string throws error if it's not UTF-8
114
119
  rescue ArgumentError
@@ -3,7 +3,7 @@ require 'shellwords'
3
3
 
4
4
  module FFMPEG
5
5
  class Transcoder
6
- @@timeout = 200
6
+ @@timeout = 30
7
7
 
8
8
  def self.timeout=(time)
9
9
  @@timeout = time
@@ -16,7 +16,7 @@ module FFMPEG
16
16
  def initialize(movie, output_file, options = EncodingOptions.new, transcoder_options = {})
17
17
  @movie = movie
18
18
  @output_file = output_file
19
-
19
+
20
20
  if options.is_a?(String) || options.is_a?(EncodingOptions)
21
21
  @raw_options = options
22
22
  elsif options.is_a?(Hash)
@@ -24,79 +24,85 @@ module FFMPEG
24
24
  else
25
25
  raise ArgumentError, "Unknown options format '#{options.class}', should be either EncodingOptions, Hash or String."
26
26
  end
27
-
27
+
28
28
  @transcoder_options = transcoder_options
29
29
  @errors = []
30
-
30
+
31
31
  apply_transcoder_options
32
32
  end
33
-
34
- # ffmpeg < 0.8: frame= 413 fps= 48 q=31.0 size= 2139kB time=16.52 bitrate=1060.6kbits/s
35
- # ffmpeg >= 0.8: frame= 4855 fps= 46 q=31.0 size= 45306kB time=00:02:42.28 bitrate=2287.0kbits/
36
- def run
37
- command = "#{FFMPEG.ffmpeg_binary} -y -i #{Shellwords.escape(@movie.path)} #{@raw_options} #{Shellwords.escape(@output_file)}"
38
- FFMPEG.logger.info("Running transcoding...\n#{command}\n")
39
- output = ""
40
- last_output = nil
41
- Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
33
+
34
+ def run(&block)
35
+ transcode_movie(&block)
36
+ if @transcoder_options[:validate]
37
+ validate_output_file(&block)
38
+ return encoded
39
+ else
40
+ return nil
41
+ end
42
+ end
43
+
44
+ def encoding_succeeded?
45
+ @errors << "no output file created" and return false unless File.exists?(@output_file)
46
+ @errors << "encoded file is invalid" and return false unless encoded.valid?
47
+ true
48
+ end
49
+
50
+ def encoded
51
+ @encoded ||= Movie.new(@output_file)
52
+ end
53
+
54
+ private
55
+ # frame= 4855 fps= 46 q=31.0 size= 45306kB time=00:02:42.28 bitrate=2287.0kbits/
56
+ def transcode_movie
57
+ @command = "#{FFMPEG.ffmpeg_binary} -y -i #{Shellwords.escape(@movie.path)} #{@raw_options} #{Shellwords.escape(@output_file)}"
58
+ FFMPEG.logger.info("Running transcoding...\n#{@command}\n")
59
+ @output = ""
60
+
61
+ Open3.popen3(@command) do |stdin, stdout, stderr, wait_thr|
42
62
  begin
43
63
  yield(0.0) if block_given?
44
64
  next_line = Proc.new do |line|
45
65
  fix_encoding(line)
46
- output << line
66
+ @output << line
47
67
  if line.include?("time=")
48
68
  if line =~ /time=(\d+):(\d+):(\d+.\d+)/ # ffmpeg 0.8 and above style
49
69
  time = ($1.to_i * 3600) + ($2.to_i * 60) + $3.to_f
50
- elsif line =~ /time=(\d+.\d+)/ # ffmpeg 0.7 and below style
51
- time = $1.to_f
52
70
  else # better make sure it wont blow up in case of unexpected output
53
71
  time = 0.0
54
72
  end
55
73
  progress = time / @movie.duration
56
74
  yield(progress) if block_given?
57
75
  end
58
- if line =~ /Unsupported codec/
59
- FFMPEG.logger.error "Failed encoding...\nCommand\n#{command}\nOutput\n#{output}\n"
60
- raise "Failed encoding: #{line}"
61
- end
62
76
  end
63
-
77
+
64
78
  if @@timeout
65
- stderr.each_with_timeout(wait_thr.pid, @@timeout, "r", &next_line)
79
+ stderr.each_with_timeout(wait_thr.pid, @@timeout, 'size=', &next_line)
66
80
  else
67
- stderr.each("r", &next_line)
81
+ stderr.each('size=', &next_line)
68
82
  end
69
-
83
+
70
84
  rescue Timeout::Error => e
71
- FFMPEG.logger.error "Process hung...\nCommand\n#{command}\nOutput\n#{output}\n"
72
- raise FFMPEG::Error, "Process hung. Full output: #{output}"
85
+ FFMPEG.logger.error "Process hung...\n@command\n#{@command}\nOutput\n#{@output}\n"
86
+ raise Error, "Process hung. Full output: #{@output}"
73
87
  end
74
88
  end
89
+ end
75
90
 
91
+ def validate_output_file(&block)
76
92
  if encoding_succeeded?
77
93
  yield(1.0) if block_given?
78
94
  FFMPEG.logger.info "Transcoding of #{@movie.path} to #{@output_file} succeeded\n"
79
95
  else
80
96
  errors = "Errors: #{@errors.join(", ")}. "
81
- FFMPEG.logger.error "Failed encoding...\n#{command}\n\n#{output}\n#{errors}\n"
82
- raise FFMPEG::Error, "Failed encoding.#{errors}Full output: #{output}"
97
+ FFMPEG.logger.error "Failed encoding...\n#{@command}\n\n#{@output}\n#{errors}\n"
98
+ raise Error, "Failed encoding.#{errors}Full output: #{@output}"
83
99
  end
84
-
85
- encoded
86
- end
87
-
88
- def encoding_succeeded?
89
- @errors << "no output file created" and return false unless File.exists?(@output_file)
90
- @errors << "encoded file is invalid" and return false unless encoded.valid?
91
- true
92
- end
93
-
94
- def encoded
95
- @encoded ||= Movie.new(@output_file)
96
100
  end
97
-
98
- private
101
+
99
102
  def apply_transcoder_options
103
+ # if true runs #validate_output_file
104
+ @transcoder_options[:validate] = @transcoder_options.fetch(:validate) { true }
105
+
100
106
  return if @movie.calculated_aspect_ratio.nil?
101
107
  case @transcoder_options[:preserve_aspect_ratio].to_s
102
108
  when "width"
@@ -111,7 +117,7 @@ module FFMPEG
111
117
  @raw_options[:resolution] = "#{new_width}x#{@raw_options.height}"
112
118
  end
113
119
  end
114
-
120
+
115
121
  def fix_encoding(output)
116
122
  output[/test/]
117
123
  rescue ArgumentError
@@ -1,3 +1,3 @@
1
1
  module FFMPEG
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -19,7 +19,7 @@ module FFMPEG
19
19
  def self.logger=(log)
20
20
  @logger = log
21
21
  end
22
-
22
+
23
23
  # Get FFMPEG logger.
24
24
  #
25
25
  # @return [Logger]
@@ -43,6 +43,6 @@ module FFMPEG
43
43
  #
44
44
  # @return [String] the path to the ffmpeg binary
45
45
  def self.ffmpeg_binary
46
- @ffmpeg_binary.nil? ? 'ffmpeg' : @ffmpeg_binary
46
+ @ffmpeg_binary || 'ffmpeg'
47
47
  end
48
48
  end
metadata CHANGED
@@ -1,52 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: streamio-ffmpeg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
5
- prerelease:
4
+ version: 1.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - David Backeus
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-07-24 00:00:00.000000000 Z
11
+ date: 2013-07-08 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
- version: '2.7'
19
+ version: '2.14'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
29
- version: '2.7'
26
+ version: '2.14'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rake
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
31
  - - ~>
36
32
  - !ruby/object:Gem::Version
37
- version: 0.9.2
33
+ version: '10.1'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
38
  - - ~>
44
39
  - !ruby/object:Gem::Version
45
- version: 0.9.2
46
- description: Simple yet powerful wrapper around ffmpeg to get metadata from movies
47
- and do transcoding.
40
+ version: '10.1'
41
+ description:
48
42
  email:
49
- - david@streamio.se
43
+ - david@streamio.com
50
44
  executables: []
51
45
  extensions: []
52
46
  extra_rdoc_files: []
@@ -63,32 +57,25 @@ files:
63
57
  - CHANGELOG
64
58
  homepage: http://github.com/streamio/streamio-ffmpeg
65
59
  licenses: []
60
+ metadata: {}
66
61
  post_install_message:
67
62
  rdoc_options: []
68
63
  require_paths:
69
64
  - lib
70
65
  required_ruby_version: !ruby/object:Gem::Requirement
71
- none: false
72
66
  requirements:
73
- - - ! '>='
67
+ - - '>='
74
68
  - !ruby/object:Gem::Version
75
69
  version: '0'
76
- segments:
77
- - 0
78
- hash: 190048295991981084
79
70
  required_rubygems_version: !ruby/object:Gem::Requirement
80
- none: false
81
71
  requirements:
82
- - - ! '>='
72
+ - - '>='
83
73
  - !ruby/object:Gem::Version
84
74
  version: '0'
85
- segments:
86
- - 0
87
- hash: 190048295991981084
88
75
  requirements: []
89
76
  rubyforge_project:
90
- rubygems_version: 1.8.24
77
+ rubygems_version: 2.0.3
91
78
  signing_key:
92
- specification_version: 3
93
- summary: Reads metadata and transcodes movies.
79
+ specification_version: 4
80
+ summary: Wraps ffmpeg to read metadata and transcodes videos.
94
81
  test_files: []