streamio-ffmpeg 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f7d05b3e4d5f1e43f04cea131e8f5f7e3772dec
4
- data.tar.gz: 645f8fcfd8691bb62c27e4182374316512b78de9
3
+ metadata.gz: 7a9cde12ae96942f93cff233ac586afa9dcb804d
4
+ data.tar.gz: 9fc3c59bbd8ebdcf0aa00dac17a0314c8490e877
5
5
  SHA512:
6
- metadata.gz: 5ec0c612684983472f87288d1f8216c1762969ab4271f53a092d5e34141d975ec3121e9f54667467fc5e11364517d54fef17a9d858c04b1c042c8494b0b727b6
7
- data.tar.gz: f362e03fcc8c05558e46cda6b6f4ab62ab3b9aadb5f67e030806403a29f6ffca3a0d5e58dc6f1dea175005e42094f20e2a1a7fc4fcfde136d1ecc17b7ac332b9
6
+ metadata.gz: 02226d4978a610a81cf08c154a99aeb18782a85e0e1071d836e9da1c57e0920780758f210490e7003ece9e551c2f2fc0247648be8ce8f0a4abebeb3a6ce61969
7
+ data.tar.gz: c31ab9b785ea8f1b58e009c2770c91caf9a4071bab81cadac187bb74dfa34e049f30269b7514800e7c1578169537949ce33551523f7226c34e8d4122fdd364e5
data/CHANGELOG CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  Current with 2.0.0
4
4
 
5
+ == 2.1.0 2016-08-08
6
+
7
+ New:
8
+ * Issue #135, Support creating slideshows from stills
9
+ * Issue #130, Adds support for a URL as an input
10
+ * Support for specifying options to apply to the input file
11
+ * Improved support for calling FFMpeg with popen3, improving compatibility with various platforms, including windows
12
+ * Issue #111. uses attr_accessor for the timeout attribute
13
+ * Exposes the movie's entire meta data and format tags as movie attributes
14
+ * Issue #122, adds encoding fix to stderr
15
+ * All specs pass and brought current with Rspec V3
16
+ * Issue #123, #131 improved stream validation, ignoring additional invalid streams if a valid stream is recognized
17
+ * Issue #124, improved parsing for moving creation_time
18
+ * SAR and DAR now adapt after rotation, DAR is ignored when calculating dimensions
19
+
20
+ Improvements:
21
+ * Allow parenthesis in colorspace (thanks walterdavis for initial code and rociiu for finding a bug with it)
22
+ * Width and height now switched if video is filmed in portrait mode. ffmpeg 2.7 or later now automatically rotates output
23
+ * Movie metadata now provided with ffprobe (#114). Thanks to Ryan Lovelett for the contributions!
24
+ * Ability to create multiple screenshots at consistent intervals in one pass (#113)
25
+
5
26
  == 2.0.0 2016-01-14
6
27
 
7
28
  New:
data/README.md CHANGED
@@ -130,6 +130,38 @@ options = {video_min_bitrate: 600, video_max_bitrate: 600, buffer_size: 2000}
130
130
  movie.transcode("movie.flv", options)
131
131
  ```
132
132
 
133
+ ### Specifying Input Options
134
+
135
+ To specify which options apply the input, such as changing the input framerate, use `input_options` hash
136
+ in the transcoder_options.
137
+
138
+ ``` ruby
139
+ movie = FFMPEG::Movie.new("path/to/movie.mov")
140
+
141
+ transcoder_options = { input_options: { framerate: '1/5' } }
142
+ movie.transcode("movie.mp4", {}, transcoder_options)
143
+
144
+ # FFMPEG Command will look like this:
145
+ # ffmpeg -y -framerate 1/5 -i path/to/movie.mov movie.mp4
146
+ ```
147
+
148
+ ### Overriding the Input Path
149
+
150
+ If FFMPEG's input path needs to specify a sequence of files, rather than a path to a single movie, transcoding_options
151
+ `input` can be set. If this option is present, the path of the original movie will not be used.
152
+
153
+ ``` ruby
154
+ movie = FFMPEG::Movie.new("path/to/movie.mov")
155
+
156
+ transcoder_options = { input: 'img_%03d.png' }
157
+ movie.transcode("movie.mp4", {}, transcoder_options)
158
+
159
+ # FFMPEG Command will look like this:
160
+ # ffmpeg -y -i img_%03d.png movie.mp4
161
+ ```
162
+
163
+ ### Watermarking
164
+
133
165
  Add watermark image on the video.
134
166
 
135
167
  For example, you want to add a watermark on the video at right top corner with 10px padding.
@@ -139,6 +171,8 @@ options = { watermark: "full_path_of_watermark.png", resolution: "640x360", wate
139
171
  ```
140
172
 
141
173
  Position can be "LT" (Left Top Corner), "RT" (Right Top Corner), "LB" (Left Bottom Corner), "RB" (Right Bottom Corner).
174
+ The watermark will not appear unless `watermark_options` specifies the position. `padding_x` and `padding_y` default to
175
+ `10`.
142
176
 
143
177
  ### Taking Screenshots
144
178
 
@@ -154,18 +188,18 @@ The screenshot method has the very same API as transcode so the same options wil
154
188
  movie.screenshot("screenshot.bmp", seek_time: 5, resolution: '320x240')
155
189
  ```
156
190
 
157
- To generate multiple screenshots in a single pass, specify `vframes`. The following code
158
- generates up to 20 screenshots every 10 seconds:
191
+ To generate multiple screenshots in a single pass, specify `vframes` and a wildcard filename. Make
192
+ sure to disable output file validation. The following code generates up to 20 screenshots every 10 seconds:
159
193
 
160
194
  ``` ruby
161
- movie.screenshot("screenshot.jpg", vframes: 20, frame_rate: '1/6')
195
+ movie.screenshot("screenshot_%d.jpg", {vframes: 20, frame_rate: '1/6'}, validate: false)
162
196
  ```
163
197
 
164
198
  To specify the quality when generating compressed screenshots (.jpg), use `quality` which specifies
165
199
  ffmpeg `-v:q` option. Quality is an integer between 1 and 31, where lower is better quality:
166
200
 
167
201
  ``` ruby
168
- movie.screenshot("screenshot.jpg", quality: 3)
202
+ movie.screenshot("screenshot_%d.jpg", quality: 3)
169
203
  ```
170
204
 
171
205
  You can preserve aspect ratio the same way as when using transcode.
@@ -174,6 +208,18 @@ You can preserve aspect ratio the same way as when using transcode.
174
208
  movie.screenshot("screenshot.png", { seek_time: 2, resolution: '200x120' }, preserve_aspect_ratio: :width)
175
209
  ```
176
210
 
211
+ ### Create a Slideshow from Stills
212
+ Creating a slideshow from stills uses named sequences of files and stiches the result together in a slideshow
213
+ video.
214
+
215
+ Since there is not movie to transcode, the Transcoder class needs to be used. The input and input_options are
216
+ provided through transcoder options.
217
+
218
+ ``` ruby
219
+ slideshow_transcoder = FFMPEG::Transcoder.new('', 'slideshow.mp4', {resolution: "320x240"}, input: 'img_%03d.jpeg', input_options: {framerate: '1/5' })
220
+ slideshow = slideshow_transcoder.run
221
+ ```
222
+
177
223
  Specify the path to ffmpeg
178
224
  --------------------------
179
225
 
@@ -4,22 +4,34 @@ module FFMPEG
4
4
  merge!(options)
5
5
  end
6
6
 
7
- def to_s
8
- params = collect do |key, value|
9
- send("convert_#{key}", value) if value && supports_option?(key)
7
+ def params_order(k)
8
+ if k =~ /watermark$/
9
+ 0
10
+ elsif k =~ /watermark/
11
+ 1
12
+ elsif k =~ /codec/
13
+ 2
14
+ elsif k =~ /preset/
15
+ 3
16
+ else
17
+ 4
10
18
  end
19
+ end
20
+
21
+ def to_a
22
+ params = []
11
23
 
12
24
  # codecs should go before the presets so that the files will be matched successfully
13
25
  # all other parameters go after so that we can override whatever is in the preset
14
- codecs = params.select { |p| p =~ /codec/ }
15
- presets = params.select { |p| p =~ /\-.pre/ }
16
- watermarkoptions = params.select { |p| p =~ /i / || p=~ /filter_complex/ }
17
- other = params - codecs - presets - watermarkoptions
18
- params = watermarkoptions + codecs + presets + other
26
+ keys.sort_by{|k| params_order(k) }.each do |key|
27
+
28
+ value = self[key]
29
+ a = send("convert_#{key}", value) if value && supports_option?(key)
30
+ params += a unless a.nil?
31
+ end
19
32
 
20
- params_string = params.join(" ")
21
- params_string << " #{convert_aspect(calculate_aspect)}" if calculate_aspect?
22
- params_string
33
+ params += convert_aspect(calculate_aspect) if calculate_aspect?
34
+ params.map(&:to_s)
23
35
  end
24
36
 
25
37
  def width
@@ -37,7 +49,7 @@ module FFMPEG
37
49
  end
38
50
 
39
51
  def convert_aspect(value)
40
- "-aspect #{value}"
52
+ ["-aspect", value]
41
53
  end
42
54
 
43
55
  def calculate_aspect
@@ -50,121 +62,130 @@ module FFMPEG
50
62
  end
51
63
 
52
64
  def convert_video_codec(value)
53
- "-vcodec #{value}"
65
+ ["-vcodec", value]
54
66
  end
55
67
 
56
68
  def convert_frame_rate(value)
57
- "-r #{value}"
69
+ ["-r", value]
58
70
  end
59
71
 
60
72
  def convert_resolution(value)
61
- "-s #{value}"
73
+ ["-s", value]
62
74
  end
63
75
 
64
76
  def convert_video_bitrate(value)
65
- "-b:v #{k_format(value)}"
77
+ ["-b:v", k_format(value)]
66
78
  end
67
79
 
68
80
  def convert_audio_codec(value)
69
- "-acodec #{value}"
81
+ ["-acodec", value]
70
82
  end
71
83
 
72
84
  def convert_audio_bitrate(value)
73
- "-b:a #{k_format(value)}"
85
+ ["-b:a", k_format(value)]
74
86
  end
75
87
 
76
88
  def convert_audio_sample_rate(value)
77
- "-ar #{value}"
89
+ ["-ar", value]
78
90
  end
79
91
 
80
92
  def convert_audio_channels(value)
81
- "-ac #{value}"
93
+ ["-ac", value]
82
94
  end
83
95
 
84
96
  def convert_video_max_bitrate(value)
85
- "-maxrate #{k_format(value)}"
97
+ ["-maxrate", k_format(value)]
86
98
  end
87
99
 
88
100
  def convert_video_min_bitrate(value)
89
- "-minrate #{k_format(value)}"
101
+ ["-minrate", k_format(value)]
90
102
  end
91
103
 
92
104
  def convert_buffer_size(value)
93
- "-bufsize #{k_format(value)}"
105
+ ["-bufsize", k_format(value)]
94
106
  end
95
107
 
96
108
  def convert_video_bitrate_tolerance(value)
97
- "-bt #{k_format(value)}"
109
+ ["-bt", k_format(value)]
98
110
  end
99
111
 
100
112
  def convert_threads(value)
101
- "-threads #{value}"
113
+ ["-threads", value]
102
114
  end
103
115
 
104
116
  def convert_target(value)
105
- "-target #{value}"
117
+ ['-target', value]
106
118
  end
107
119
 
108
120
  def convert_duration(value)
109
- "-t #{value}"
121
+ ["-t", value]
110
122
  end
111
123
 
112
124
  def convert_video_preset(value)
113
- "-vpre #{value}"
125
+ ["-vpre", value]
114
126
  end
115
127
 
116
128
  def convert_audio_preset(value)
117
- "-apre #{value}"
129
+ ["-apre", value]
118
130
  end
119
131
 
120
132
  def convert_file_preset(value)
121
- "-fpre #{value}"
133
+ ["-fpre", value]
122
134
  end
123
135
 
124
136
  def convert_keyframe_interval(value)
125
- "-g #{value}"
137
+ ["-g", value]
126
138
  end
127
139
 
128
140
  def convert_seek_time(value)
129
- "-ss #{value}"
141
+ ["-ss", value]
130
142
  end
131
143
 
132
144
  def convert_screenshot(value)
133
- vframes = '-vframes 1 ' unless self[:vframes]
134
- value ? "#{vframes}-f image2" : ""
145
+ result = []
146
+ unless self[:vframes]
147
+ result << '-vframes'
148
+ result << 1
149
+ end
150
+ result << '-f'
151
+ result << 'image2'
152
+ value ? result : []
135
153
  end
136
154
 
137
155
  def convert_quality(value)
138
- "-q:v #{value}"
156
+ ['-q:v', value]
139
157
  end
140
158
 
141
159
  def convert_vframes(value)
142
- "-vframes #{value}"
160
+ ['-vframes', value]
143
161
  end
144
162
 
145
163
  def convert_x264_vprofile(value)
146
- "-vprofile #{value}"
164
+ ["-vprofile", value]
147
165
  end
148
166
 
149
167
  def convert_x264_preset(value)
150
- "-preset #{value}"
168
+ ["-preset", value]
151
169
  end
152
170
 
153
171
  def convert_watermark(value)
154
- "-i #{value}"
172
+ ["-i", value]
155
173
  end
156
174
 
157
175
  def convert_watermark_filter(value)
158
- case value[:position].to_s
159
- when "LT"
160
- "-filter_complex 'scale=#{self[:resolution]},overlay=x=#{value[:padding_x]}:y=#{value[:padding_y]}'"
161
- when "RT"
162
- "-filter_complex 'scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{value[:padding_x]}:y=#{value[:padding_y]}'"
163
- when "LB"
164
- "-filter_complex 'scale=#{self[:resolution]},overlay=x=#{value[:padding_x]}:y=main_h-overlay_h-#{value[:padding_y]}'"
165
- when "RB"
166
- "-filter_complex 'scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{value[:padding_x]}:y=main_h-overlay_h-#{value[:padding_y]}'"
167
- end
176
+ position = value[:position]
177
+ padding_x = value[:padding_x] || 10
178
+ padding_y = value[:padding_y] || 10
179
+ case position.to_s
180
+ when "LT"
181
+ ["-filter_complex", "scale=#{self[:resolution]},overlay=x=#{padding_x}:y=#{padding_y}"]
182
+ when "RT"
183
+ ["-filter_complex", "scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{padding_x}:y=#{padding_y}"]
184
+ when "LB"
185
+ ["-filter_complex", "scale=#{self[:resolution]},overlay=x=#{padding_x}:y=main_h-overlay_h-#{padding_y}"]
186
+ when "RB"
187
+ ["-filter_complex", "scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{padding_x}:y=main_h-overlay_h-#{padding_y}"]
188
+ end
168
189
  end
169
190
 
170
191
  def convert_custom(value)
@@ -1,5 +1,6 @@
1
1
  require 'time'
2
2
  require 'multi_json'
3
+ require 'uri'
3
4
 
4
5
  module FFMPEG
5
6
  class Movie
@@ -7,52 +8,72 @@ module FFMPEG
7
8
  attr_reader :video_stream, :video_codec, :video_bitrate, :colorspace, :width, :height, :sar, :dar, :frame_rate
8
9
  attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate, :audio_channels
9
10
  attr_reader :container
11
+ attr_reader :metadata, :format_tags
12
+
13
+ UNSUPPORTED_CODEC_PATTERN = /^Unsupported codec with id (\d+) for input stream (\d+)$/
10
14
 
11
15
  def initialize(path)
12
- raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exist?(path)
16
+ @path = path
17
+
18
+ if remote?
19
+ @head = head
20
+ raise Errno::ENOENT, "the URL '#{path}' does not exist" if @head.nil? || @head.code.to_i != 200
21
+ else
22
+ raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exist?(path)
23
+ end
13
24
 
14
25
  @path = path
15
26
 
16
27
  # ffmpeg will output to stderr
17
- command = "#{FFMPEG.ffprobe_binary} -i #{Shellwords.escape(path)} -print_format json -show_format -show_streams -show_error"
28
+ command = [FFMPEG.ffprobe_binary, '-i', path, *['-print_format', 'json', '-show_format', '-show_streams', '-show_error']]
18
29
  std_output = ''
19
30
  std_error = ''
20
31
 
21
- Open3.popen3(command) do |stdin, stdout, stderr|
32
+ Open3.popen3(*command) do |stdin, stdout, stderr|
22
33
  std_output = stdout.read unless stdout.nil?
23
34
  std_error = stderr.read unless stderr.nil?
24
35
  end
25
36
 
26
37
  fix_encoding(std_output)
38
+ fix_encoding(std_error)
27
39
 
28
- metadata = MultiJson.load(std_output, symbolize_keys: true)
40
+ begin
41
+ @metadata = MultiJson.load(std_output, symbolize_keys: true)
42
+ rescue MultiJson::ParseError
43
+ raise "Could not parse output from FFProbe:\n#{ std_output }"
44
+ end
29
45
 
30
- if metadata.key?(:error)
46
+ if @metadata.key?(:error)
31
47
 
32
48
  @duration = 0
33
49
 
34
50
  else
51
+ video_streams = @metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'video' }
52
+ audio_streams = @metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'audio' }
35
53
 
36
- video_streams = metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'video' }
37
- audio_streams = metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'audio' }
54
+ @container = @metadata[:format][:format_name]
38
55
 
39
- @container = metadata[:format][:format_name]
56
+ @duration = @metadata[:format][:duration].to_f
40
57
 
41
- @duration = metadata[:format][:duration].to_f
58
+ @time = @metadata[:format][:start_time].to_f
42
59
 
43
- @time = metadata[:format][:start_time].to_f
60
+ @format_tags = @metadata[:format][:tags]
44
61
 
45
- @creation_time = if metadata[:format].key?(:tags) and metadata[:format][:tags].key?(:creation_time)
46
- Time.parse(metadata[:format][:tags][:creation_time])
62
+ @creation_time = if @format_tags and @format_tags.key?(:creation_time)
63
+ begin
64
+ Time.parse(@format_tags[:creation_time])
65
+ rescue ArgumentError
66
+ nil
67
+ end
47
68
  else
48
69
  nil
49
70
  end
50
71
 
51
- @bitrate = metadata[:format][:bit_rate].to_i
72
+ @bitrate = @metadata[:format][:bit_rate].to_i
52
73
 
53
- unless video_streams.empty?
54
- # TODO: Handle multiple video codecs (is that possible?)
55
- video_stream = video_streams.first
74
+ # TODO: Handle multiple video codecs (is that possible?)
75
+ video_stream = video_streams.first
76
+ unless video_stream.nil?
56
77
  @video_codec = video_stream[:codec_name]
57
78
  @colorspace = video_stream[:pix_fmt]
58
79
  @width = video_stream[:width]
@@ -76,9 +97,9 @@ module FFMPEG
76
97
  end
77
98
  end
78
99
 
79
- unless audio_streams.empty?
80
- # TODO: Handle multiple audio codecs
81
- audio_stream = audio_streams.first
100
+ # TODO: Handle multiple audio codecs
101
+ audio_stream = audio_streams.first
102
+ unless audio_stream.nil?
82
103
  @audio_channels = audio_stream[:channels].to_i
83
104
  @audio_codec = audio_stream[:codec_name]
84
105
  @audio_sample_rate = audio_stream[:sample_rate].to_i
@@ -89,16 +110,35 @@ module FFMPEG
89
110
 
90
111
  end
91
112
 
92
- @invalid = true if metadata.key?(:error)
93
- @invalid = true if std_error.include?("Unsupported codec")
94
- @invalid = true if std_error.include?("is not supported")
113
+ unsupported_stream_ids = unsupported_streams(std_error)
114
+ nil_or_unsupported = -> (stream) { stream.nil? || unsupported_stream_ids.include?(stream[:index]) }
115
+
116
+ @invalid = true if nil_or_unsupported.(video_stream) && nil_or_unsupported.(audio_stream)
117
+ @invalid = true if @metadata.key?(:error)
95
118
  @invalid = true if std_error.include?("could not find codec parameters")
96
119
  end
97
120
 
121
+ def unsupported_streams(std_error)
122
+ [].tap do |stream_indices|
123
+ std_error.each_line do |line|
124
+ match = line.match(UNSUPPORTED_CODEC_PATTERN)
125
+ stream_indices << match[2].to_i if match
126
+ end
127
+ end
128
+ end
129
+
98
130
  def valid?
99
131
  not @invalid
100
132
  end
101
133
 
134
+ def remote?
135
+ @path =~ URI::regexp
136
+ end
137
+
138
+ def local?
139
+ not remote?
140
+ end
141
+
102
142
  def width
103
143
  rotation.nil? || rotation == 180 ? @width : @height;
104
144
  end
@@ -122,7 +162,11 @@ module FFMPEG
122
162
  end
123
163
 
124
164
  def size
125
- File.size(@path)
165
+ if local?
166
+ File.size(@path)
167
+ else
168
+ @head.content_length
169
+ end
126
170
  end
127
171
 
128
172
  def audio_channel_layout
@@ -151,14 +195,14 @@ module FFMPEG
151
195
  def aspect_from_dar
152
196
  return nil unless dar
153
197
  w, h = dar.split(":")
154
- aspect = w.to_f / h.to_f
198
+ aspect = (@rotation==nil) || (@rotation==180)? (w.to_f / h.to_f):(h.to_f / w.to_f)
155
199
  aspect.zero? ? nil : aspect
156
200
  end
157
201
 
158
202
  def aspect_from_sar
159
203
  return nil unless sar
160
204
  w, h = sar.split(":")
161
- aspect = w.to_f / h.to_f
205
+ aspect = (@rotation==nil) || (@rotation==180)? (w.to_f / h.to_f):(h.to_f / w.to_f)
162
206
  aspect.zero? ? nil : aspect
163
207
  end
164
208
 
@@ -172,5 +216,16 @@ module FFMPEG
172
216
  rescue ArgumentError
173
217
  output.force_encoding("ISO-8859-1")
174
218
  end
219
+
220
+ def head
221
+ url = URI(@path)
222
+ return unless url.path
223
+
224
+ http = Net::HTTP.new(url.host, url.port)
225
+ http.use_ssl = url.port == 443
226
+ http.request_head(url.path)
227
+ rescue SocketError, Errno::ECONNREFUSED
228
+ nil
229
+ end
175
230
  end
176
231
  end
@@ -1,23 +1,23 @@
1
1
  require 'open3'
2
- require 'shellwords'
3
2
 
4
3
  module FFMPEG
5
4
  class Transcoder
6
- @@timeout = 30
5
+ attr_reader :command, :input
7
6
 
8
- def self.timeout=(time)
9
- @@timeout = time
10
- end
7
+ @@timeout = 30
11
8
 
12
- def self.timeout
13
- @@timeout
9
+ class << self
10
+ attr_accessor :timeout
14
11
  end
15
12
 
16
- def initialize(movie, output_file, options = EncodingOptions.new, transcoder_options = {})
17
- @movie = movie
13
+ def initialize(input, output_file, options = EncodingOptions.new, transcoder_options = {})
14
+ if input.is_a?(FFMPEG::Movie)
15
+ @movie = input
16
+ @input = input.path
17
+ end
18
18
  @output_file = output_file
19
19
 
20
- if options.is_a?(String) || options.is_a?(EncodingOptions)
20
+ if options.is_a?(Array) || options.is_a?(EncodingOptions)
21
21
  @raw_options = options
22
22
  elsif options.is_a?(Hash)
23
23
  @raw_options = EncodingOptions.new(options)
@@ -29,6 +29,14 @@ module FFMPEG
29
29
  @errors = []
30
30
 
31
31
  apply_transcoder_options
32
+
33
+ @input = @transcoder_options[:input] unless @transcoder_options[:input].nil?
34
+
35
+ input_options = @transcoder_options[:input_options] || []
36
+ iopts = []
37
+ input_options.each { |k, v| iopts += ['-' + k.to_s, v] }
38
+
39
+ @command = [FFMPEG.ffmpeg_binary, '-y', *iopts, '-i', @input, *@raw_options.to_a, @output_file]
32
40
  end
33
41
 
34
42
  def run(&block)
@@ -51,14 +59,17 @@ module FFMPEG
51
59
  @encoded ||= Movie.new(@output_file)
52
60
  end
53
61
 
62
+ def timeout
63
+ self.class.timeout
64
+ end
65
+
54
66
  private
55
67
  # frame= 4855 fps= 46 q=31.0 size= 45306kB time=00:02:42.28 bitrate=2287.0kbits/
56
68
  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")
69
+ FFMPEG.logger.info("Running transcoding...\n#{command}\n")
59
70
  @output = ""
60
71
 
61
- Open3.popen3(@command) do |_stdin, _stdout, stderr, wait_thr|
72
+ Open3.popen3(*command) do |_stdin, _stdout, stderr, wait_thr|
62
73
  begin
63
74
  yield(0.0) if block_given?
64
75
  next_line = Proc.new do |line|
@@ -75,14 +86,14 @@ module FFMPEG
75
86
  end
76
87
  end
77
88
 
78
- if @@timeout
79
- stderr.each_with_timeout(wait_thr.pid, @@timeout, 'size=', &next_line)
89
+ if timeout
90
+ stderr.each_with_timeout(wait_thr.pid, timeout, 'size=', &next_line)
80
91
  else
81
92
  stderr.each('size=', &next_line)
82
93
  end
83
94
 
84
95
  rescue Timeout::Error => e
85
- FFMPEG.logger.error "Process hung...\n@command\n#{@command}\nOutput\n#{@output}\n"
96
+ FFMPEG.logger.error "Process hung...\n@command\n#{command}\nOutput\n#{@output}\n"
86
97
  raise Error, "Process hung. Full output: #{@output}"
87
98
  end
88
99
  end
@@ -91,10 +102,10 @@ module FFMPEG
91
102
  def validate_output_file(&block)
92
103
  if encoding_succeeded?
93
104
  yield(1.0) if block_given?
94
- FFMPEG.logger.info "Transcoding of #{@movie.path} to #{@output_file} succeeded\n"
105
+ FFMPEG.logger.info "Transcoding of #{input} to #{@output_file} succeeded\n"
95
106
  else
96
107
  errors = "Errors: #{@errors.join(", ")}. "
97
- FFMPEG.logger.error "Failed encoding...\n#{@command}\n\n#{@output}\n#{errors}\n"
108
+ FFMPEG.logger.error "Failed encoding...\n#{command}\n\n#{@output}\n#{errors}\n"
98
109
  raise Error, "Failed encoding.#{errors}Full output: #{@output}"
99
110
  end
100
111
  end
@@ -103,7 +114,7 @@ module FFMPEG
103
114
  # if true runs #validate_output_file
104
115
  @transcoder_options[:validate] = @transcoder_options.fetch(:validate) { true }
105
116
 
106
- return if @movie.calculated_aspect_ratio.nil?
117
+ return if @movie.nil? || @movie.calculated_aspect_ratio.nil?
107
118
  case @transcoder_options[:preserve_aspect_ratio].to_s
108
119
  when "width"
109
120
  new_height = @raw_options.width / @movie.calculated_aspect_ratio
@@ -1,3 +1,3 @@
1
1
  module FFMPEG
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: streamio-ffmpeg
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rackfish AB
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-19 00:00:00.000000000 Z
11
+ date: 2016-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.14'
33
+ version: '3'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.14'
40
+ version: '3'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  version: '0'
90
90
  requirements: []
91
91
  rubyforge_project:
92
- rubygems_version: 2.4.3
92
+ rubygems_version: 2.6.6
93
93
  signing_key:
94
94
  specification_version: 4
95
95
  summary: Wraps ffmpeg to read metadata and transcodes videos.