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 +4 -4
- data/CHANGELOG +21 -0
- data/README.md +50 -4
- data/lib/ffmpeg/encoding_options.rb +70 -49
- data/lib/ffmpeg/movie.rb +80 -25
- data/lib/ffmpeg/transcoder.rb +30 -19
- data/lib/ffmpeg/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a9cde12ae96942f93cff233ac586afa9dcb804d
|
4
|
+
data.tar.gz: 9fc3c59bbd8ebdcf0aa00dac17a0314c8490e877
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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("
|
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("
|
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
|
8
|
-
|
9
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
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
|
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
|
65
|
+
["-vcodec", value]
|
54
66
|
end
|
55
67
|
|
56
68
|
def convert_frame_rate(value)
|
57
|
-
"-r
|
69
|
+
["-r", value]
|
58
70
|
end
|
59
71
|
|
60
72
|
def convert_resolution(value)
|
61
|
-
"-s
|
73
|
+
["-s", value]
|
62
74
|
end
|
63
75
|
|
64
76
|
def convert_video_bitrate(value)
|
65
|
-
"-b:v
|
77
|
+
["-b:v", k_format(value)]
|
66
78
|
end
|
67
79
|
|
68
80
|
def convert_audio_codec(value)
|
69
|
-
"-acodec
|
81
|
+
["-acodec", value]
|
70
82
|
end
|
71
83
|
|
72
84
|
def convert_audio_bitrate(value)
|
73
|
-
"-b:a
|
85
|
+
["-b:a", k_format(value)]
|
74
86
|
end
|
75
87
|
|
76
88
|
def convert_audio_sample_rate(value)
|
77
|
-
"-ar
|
89
|
+
["-ar", value]
|
78
90
|
end
|
79
91
|
|
80
92
|
def convert_audio_channels(value)
|
81
|
-
"-ac
|
93
|
+
["-ac", value]
|
82
94
|
end
|
83
95
|
|
84
96
|
def convert_video_max_bitrate(value)
|
85
|
-
"-maxrate
|
97
|
+
["-maxrate", k_format(value)]
|
86
98
|
end
|
87
99
|
|
88
100
|
def convert_video_min_bitrate(value)
|
89
|
-
"-minrate
|
101
|
+
["-minrate", k_format(value)]
|
90
102
|
end
|
91
103
|
|
92
104
|
def convert_buffer_size(value)
|
93
|
-
"-bufsize
|
105
|
+
["-bufsize", k_format(value)]
|
94
106
|
end
|
95
107
|
|
96
108
|
def convert_video_bitrate_tolerance(value)
|
97
|
-
"-bt
|
109
|
+
["-bt", k_format(value)]
|
98
110
|
end
|
99
111
|
|
100
112
|
def convert_threads(value)
|
101
|
-
"-threads
|
113
|
+
["-threads", value]
|
102
114
|
end
|
103
115
|
|
104
116
|
def convert_target(value)
|
105
|
-
|
117
|
+
['-target', value]
|
106
118
|
end
|
107
119
|
|
108
120
|
def convert_duration(value)
|
109
|
-
"-t
|
121
|
+
["-t", value]
|
110
122
|
end
|
111
123
|
|
112
124
|
def convert_video_preset(value)
|
113
|
-
"-vpre
|
125
|
+
["-vpre", value]
|
114
126
|
end
|
115
127
|
|
116
128
|
def convert_audio_preset(value)
|
117
|
-
"-apre
|
129
|
+
["-apre", value]
|
118
130
|
end
|
119
131
|
|
120
132
|
def convert_file_preset(value)
|
121
|
-
"-fpre
|
133
|
+
["-fpre", value]
|
122
134
|
end
|
123
135
|
|
124
136
|
def convert_keyframe_interval(value)
|
125
|
-
"-g
|
137
|
+
["-g", value]
|
126
138
|
end
|
127
139
|
|
128
140
|
def convert_seek_time(value)
|
129
|
-
"-ss
|
141
|
+
["-ss", value]
|
130
142
|
end
|
131
143
|
|
132
144
|
def convert_screenshot(value)
|
133
|
-
|
134
|
-
|
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
|
-
|
156
|
+
['-q:v', value]
|
139
157
|
end
|
140
158
|
|
141
159
|
def convert_vframes(value)
|
142
|
-
|
160
|
+
['-vframes', value]
|
143
161
|
end
|
144
162
|
|
145
163
|
def convert_x264_vprofile(value)
|
146
|
-
"-vprofile
|
164
|
+
["-vprofile", value]
|
147
165
|
end
|
148
166
|
|
149
167
|
def convert_x264_preset(value)
|
150
|
-
"-preset
|
168
|
+
["-preset", value]
|
151
169
|
end
|
152
170
|
|
153
171
|
def convert_watermark(value)
|
154
|
-
"-i
|
172
|
+
["-i", value]
|
155
173
|
end
|
156
174
|
|
157
175
|
def convert_watermark_filter(value)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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)
|
data/lib/ffmpeg/movie.rb
CHANGED
@@ -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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
@
|
56
|
+
@duration = @metadata[:format][:duration].to_f
|
40
57
|
|
41
|
-
@
|
58
|
+
@time = @metadata[:format][:start_time].to_f
|
42
59
|
|
43
|
-
@
|
60
|
+
@format_tags = @metadata[:format][:tags]
|
44
61
|
|
45
|
-
@creation_time = if
|
46
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
data/lib/ffmpeg/transcoder.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
require 'open3'
|
2
|
-
require 'shellwords'
|
3
2
|
|
4
3
|
module FFMPEG
|
5
4
|
class Transcoder
|
6
|
-
|
5
|
+
attr_reader :command, :input
|
7
6
|
|
8
|
-
|
9
|
-
@@timeout = time
|
10
|
-
end
|
7
|
+
@@timeout = 30
|
11
8
|
|
12
|
-
|
13
|
-
|
9
|
+
class << self
|
10
|
+
attr_accessor :timeout
|
14
11
|
end
|
15
12
|
|
16
|
-
def initialize(
|
17
|
-
|
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?(
|
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
|
-
|
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(
|
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
|
79
|
-
stderr.each_with_timeout(wait_thr.pid,
|
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#{
|
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 #{
|
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#{
|
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
|
data/lib/ffmpeg/version.rb
CHANGED
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.
|
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-
|
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: '
|
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: '
|
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.
|
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.
|