streamio-ffmpeg 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.