r7streamio_ffmpeg 2.0.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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjE2MjZlNzE1MTJiMTFmMDhlOGI3NDUwZWY4MjA5NTI2YWM3ZjE4Nw==
5
+ data.tar.gz: !binary |-
6
+ Njk0NDQ2YzZjZTFjYjBlOTJkZTU2ODc5MWZiNGRhYTg1ZjM0ZjgwYw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MzIxNTBhMDA2M2YyZjg5ZmNmZTk4ZWFmYjllNjA1NGIwMjcwYTUzMDdjNDU5
10
+ YTk0NjAwZDA2MzkxOTM1YjBhNmQ5OGVlYzliOWJjNWQxMDhjZDM4ZjA5MWZm
11
+ ZTBiZDBlMGEyNDBjMjRmNDIxOTU5MmJmOTYyOTg1NTU4NzJhNDU=
12
+ data.tar.gz: !binary |-
13
+ N2Q5NGJjOWQ1MDJjZmI1OTVmZDExMzAwZTU1NmUwYTg4MzY5OTUwNWQ1Zjdl
14
+ MTkzY2U5ZTZkNmViNzk2OTkyMGVmNDQxM2ZjMDIxOWViNTE0N2JjYmVmZjFm
15
+ NWNhZjEzMTY2OGQwNTQxZWIwMzFkNGNjZmM0NDM1ZTAxM2IzMGI=
data/CHANGELOG ADDED
@@ -0,0 +1,210 @@
1
+ == Master
2
+
3
+ New:
4
+ * Support watermarking (thanks smoothdvd)
5
+
6
+ Improvements:
7
+ * Allow parenthesis in colorspace (thanks walterdavis for initial code and rociiu for finding a bug with it)
8
+
9
+ == 1.0.0 2013-07-08
10
+
11
+ New:
12
+ * Bumped target ffmpeg version to 1.2.1
13
+
14
+ Improvements:
15
+ * Simpler implementation for timeouts.
16
+ Should be far less cpu and memory dependent (don't spawn a thread for every line of output)
17
+ Timeout spec now passes in Rubinius (using 1.9 mode)
18
+ * Give helpful error message for windows users lacking the win32-process gem (thanks casoetan)
19
+ * Add Movie#container (thanks vitalis)
20
+ * Support vprofile and preset encoding options (thanks vitalis)
21
+
22
+ Changes:
23
+ * Default timeout lowered to 30 seconds
24
+
25
+ Bugs:
26
+ * Avoid crash if asking for frame_rate of a video without video stream (thanks squidarth)
27
+ * Fix crash when doing audio transcoding on ffmpeg >= 1.0.1 (thanks vitalis)
28
+
29
+ Deprecations:
30
+ * Removed support for Ruby 1.8
31
+ * Removed support for ffmpeg 0.7
32
+
33
+ Refactorings:
34
+ * Quite a few, see commit history for details.
35
+
36
+ == 0.9.0 2012-07-24
37
+
38
+ New:
39
+ * Bumped target ffmpeg version to 0.11.1
40
+ * Add hung process detection with configurable timeout (thanks stakach)
41
+ * Raise FFMPEG::Error instead of generic RuntimeError on failed transcodings
42
+ * Movie#screenshot for more intuitive screenshotting (README has details)
43
+ * Movie#creation_time and Movie#rotation attributes when metadata is available (thanks Innonate)
44
+
45
+ Bugs:
46
+ * Fixed too many open files bug (thanks to akicho8)
47
+ * Fixed missing path escaping (thanks to mikesager)
48
+ * Fixed README typo (thanks to Linutux)
49
+ * Files outputing "could not find codec parameters" are now recognized as invalid
50
+
51
+ Deprecations:
52
+ * Removed Movie#uncertain_duration?
53
+ * Removed all the deprecated crop options (use :custom => '-vf crop=x:x:x:x' if you need it)
54
+
55
+ Refactorings:
56
+ * Removed the deprecated duration validation code
57
+ * Polish on the transcoder class
58
+ * Polish on the spec suite
59
+
60
+ == 0.8.5 2011-03-05
61
+
62
+ * If a clip has a DAR that doesn't make sense fall back to calculating aspect ratio from dimensions
63
+ * Allow filenames with single quote characters (thanks to youpy)
64
+
65
+ == 0.8.4 2011-11-30
66
+
67
+ * Duration now one decimal more accurate (thanks to Russel Brooks)
68
+ * Added encoding option seek_time (thanks to Misty De Meo)
69
+
70
+ == 0.8.3 2011-09-01
71
+
72
+ * Parameters now come in the order of codecs, presets, others so that we can override the presets
73
+ * Added encoding option keyframe_interval to set number of frames between i-frames (aka GOP size)
74
+ * Streamio (sponsor of this project) have launched new awesome pricing @ http://streamio.com
75
+
76
+ == 0.8.2 2011-08-19
77
+
78
+ * Path to ffmpeg binary can now be specified (thanks jonathandean)
79
+ * If ffmpeg output contains "is not supported" the Movie will be considered invalid
80
+
81
+ == 0.8.1 2011-07-28
82
+
83
+ * Fix progress yielding with ffmpeg 0.8
84
+ * Updated specs to pass with ffmpeg 0.8
85
+
86
+ == 0.8.0 2011-05-26
87
+
88
+ * Duration is now ALWAYS considered uncertain (we've noticed that ffmpeg is not always correct)
89
+ * This means that the duration check will normally never run (unless you manually hack @uncertain_duration to false)
90
+ * Movie#audio_channels now returns nil if there is no audio stream (instead of crashing)
91
+ * Development: Use Bundler
92
+ * Development: Update RSpec to 2.6
93
+
94
+ == 0.7.8 2011-04-04
95
+
96
+ * Fixed number of audio channels on files with 5.1 audio
97
+
98
+ == 0.7.7 2011-02-01
99
+
100
+ * Movies with starttime are now considered as having uncertain duration as its behavior is not consistent across formats
101
+ * Upgrade development environment to RSpec 2.4
102
+
103
+ == 0.7.6 2011-01-14
104
+
105
+ * Another ruby 1.9 encoding fix
106
+
107
+ == 0.7.5 2011-01-14
108
+
109
+ * Fixed some ruby 1.9 issues
110
+ * Added Movie#video_bitrate and Movie#audio_bitrate (thanks to mbj)
111
+
112
+ == 0.7.4 2010-12-07
113
+
114
+ * Fixed broken duration on movies with start times over 0 by reducing duration with start-time
115
+
116
+ == 0.7.3 2010-08-26
117
+
118
+ * Replaced Jewler with simple dynamic gemspec file
119
+ * Spec files now not in published gem to make it a lot smaller in size
120
+ * Full output from ffmpeg command in error raised during transcoding
121
+
122
+ == 0.7.2 2010-08-11
123
+
124
+ * Added encoding option duration
125
+ * Avoid crashing when ffmpeg can't find resolution of a movie
126
+
127
+ == 0.7.1 2010-07-08
128
+
129
+ * Make sure preset parameters are always put last to avoid them ending up before any codec assignments
130
+ * Testing against a fresh ffmpeg build (r24069)
131
+
132
+ == 0.7.0 2010-07-07
133
+
134
+ * Support for ffpresets through video_preset, audio_preset and file_preset encoding options
135
+ * Added encoding option video_bitrate_tolerance
136
+
137
+ == 0.6.8.1 2010-07-06
138
+
139
+ * Bugfix - aspect ratio was not calculated properly on movies with no DAR
140
+
141
+ == 0.6.8 2010-07-06
142
+
143
+ * Don't use encoding options with nil values
144
+ * Added encoding options video_max_bitrate, video_min_bitrate and buffer_size for constant bitrate encoding
145
+
146
+ == 0.6.7 2010-06-10
147
+
148
+ * Bugfix - aspect ratio preserver could suggest non even resolutions in certain circumstances
149
+
150
+ == 0.6.6 2010-06-10
151
+
152
+ * Transcodings to .jpg and .png will now work as they will skip duration validation
153
+
154
+ == 0.6.5 2010-05-19
155
+
156
+ * Movie#size method to get file size.
157
+
158
+ == 0.6.4 2010-05-12
159
+
160
+ * Ruby 1.9 compatibility fix for EncodingOptions (thanks michalf!)
161
+
162
+ == 0.6.3 2010-05-05
163
+
164
+ * Use DAR to calculate aspect ratio if available
165
+
166
+ == 0.6.2 2010-05-05
167
+
168
+ * Added Movie#uncertain_duration? which is true if ffmpeg is guessing duration from bitrate
169
+ * Skipping the transcoders duration validation if original file has uncertain duration
170
+ * Made sure aspect ratio preservation always rounds new size to an even number to avoid "not divisible by 2" errors
171
+ * Changed Movie#valid? logic to accept any movie with either a readable audio or video stream
172
+
173
+ == 0.6.0 2010-05-04
174
+
175
+ * Cropping options now handled by EncodingOptions (croptop, cropbottom, cropleft and cropright)
176
+ * Aspect ratio parameter calculated and added by default
177
+ * Added transcoder options to preserve original aspect ratio on width or height
178
+
179
+ == 0.5.0 2010-04-28
180
+
181
+ * Added logging capabilities
182
+
183
+ == 0.4.3 2010-04-06
184
+
185
+ * Correctly identify invalid movies on latest ffmpeg build (r22811)
186
+
187
+ == 0.4.2 2010-04-06
188
+
189
+ * Escape the path to handle spaces in filenames and avoid CLI injection attacks (thanks J. Weir!)
190
+
191
+ == 0.4.1 2010-02-10
192
+
193
+ * Forgot to change the transcoding shortcut from Movie
194
+
195
+ == 0.4.0 2010-02-10
196
+
197
+ * Transcoding API changed to make use of more humanly readable options (see README for examples)
198
+ * Fixed frame rate parsing for integer frame rates
199
+
200
+ == 0.3.0 2010-02-07
201
+
202
+ * Simple transcoding
203
+
204
+ == 0.2.0 2010-02-06
205
+
206
+ * Some more metadata parsing
207
+
208
+ == 0.1.0 2010-02-05
209
+
210
+ * Some basic parsing of metadata added
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Streamio AB
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,206 @@
1
+ Streamio FFMPEG
2
+ ===============
3
+
4
+ Simple yet powerful wrapper around the ffmpeg command for reading metadata and transcoding movies.
5
+
6
+ All work on this project is sponsored by the online video platform [Streamio](http://streamio.com).
7
+
8
+ [![Streamio](http://d253c4ja9jigvu.cloudfront.net/assets/small-logo.png)](http://streamio.com)
9
+
10
+ Installation
11
+ ------------
12
+
13
+ (sudo) gem install streamio-ffmpeg
14
+
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 2.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.
27
+
28
+ Usage
29
+ -----
30
+
31
+ ### Require the gem
32
+
33
+ ``` ruby
34
+ require 'rubygems'
35
+ require 'streamio-ffmpeg'
36
+ ```
37
+
38
+ ### Reading Metadata
39
+
40
+ ``` ruby
41
+ movie = FFMPEG::Movie.new("path/to/movie.mov")
42
+
43
+ movie.duration # 7.5 (duration of the movie in seconds)
44
+ movie.bitrate # 481 (bitrate in kb/s)
45
+ movie.size # 455546 (filesize in bytes)
46
+
47
+ movie.video_stream # "h264, yuv420p, 640x480 [PAR 1:1 DAR 4:3], 371 kb/s, 16.75 fps, 15 tbr, 600 tbn, 1200 tbc" (raw video stream info)
48
+ movie.video_codec # "h264"
49
+ movie.colorspace # "yuv420p"
50
+ movie.resolution # "640x480"
51
+ movie.width # 640 (width of the movie in pixels)
52
+ movie.height # 480 (height of the movie in pixels)
53
+ movie.frame_rate # 16.72 (frames per second)
54
+
55
+ movie.audio_stream # "aac, 44100 Hz, stereo, s16, 75 kb/s" (raw audio stream info)
56
+ movie.audio_codec # "aac"
57
+ movie.audio_sample_rate # 44100
58
+ movie.audio_channels # 2
59
+
60
+ movie.valid? # true (would be false if ffmpeg fails to read the movie)
61
+ ```
62
+
63
+ ### Transcoding
64
+
65
+ First argument is the output file path.
66
+
67
+ ``` ruby
68
+ movie.transcode("tmp/movie.mp4") # Default ffmpeg settings for mp4 format
69
+ ```
70
+
71
+ Keep track of progress with an optional block.
72
+
73
+ ``` ruby
74
+ movie.transcode("movie.mp4") { |progress| puts progress } # 0.2 ... 0.5 ... 1.0
75
+ ```
76
+
77
+ Give custom command line options with a string.
78
+
79
+ ``` ruby
80
+ movie.transcode("movie.mp4", "-ac aac -vc libx264 -ac 2 ...")
81
+ ```
82
+
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.
84
+
85
+ ``` ruby
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_vprofile: "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"}
92
+ movie.transcode("movie.mp4", options)
93
+ ```
94
+
95
+ The transcode function returns a Movie object for the encoded file.
96
+
97
+ ``` ruby
98
+ transcoded_movie = movie.transcode("tmp/movie.flv")
99
+
100
+ transcoded_movie.video_codec # "flv"
101
+ transcoded_movie.audio_codec # "mp3"
102
+ ```
103
+
104
+ Aspect ratio is added to encoding options automatically if none is specified.
105
+
106
+ ``` ruby
107
+ options = { resolution: "320x180" } # Will add -aspect 1.77777777777778 to ffmpeg
108
+ ```
109
+
110
+ Preserve aspect ratio on width or height by using the preserve_aspect_ratio transcoder option.
111
+
112
+ ``` ruby
113
+ widescreen_movie = FFMPEG::Movie.new("path/to/widescreen_movie.mov")
114
+
115
+ options = { resolution: "320x240" }
116
+
117
+ transcoder_options = { preserve_aspect_ratio: :width }
118
+ widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 320x180
119
+
120
+ transcoder_options = { preserve_aspect_ratio: :height }
121
+ widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 426x240
122
+ ```
123
+
124
+ For constant bitrate encoding use video_min_bitrate and video_max_bitrate with buffer_size.
125
+
126
+ ``` ruby
127
+ options = {video_min_bitrate: 600, video_max_bitrate: 600, buffer_size: 2000}
128
+ movie.transcode("movie.flv", options)
129
+ ```
130
+
131
+ Add watermark image on the video.
132
+
133
+ For example, you want to add a watermark on the video at right top corner with 10px padding.
134
+
135
+ ``` ruby
136
+ options = { watermark: "full_path_of_watermark.png", resolution: "640x360", watermark_filter: { position: "RT", padding_x: 10, padding_y: 10 } }
137
+ ```
138
+
139
+ Position can be "LT" (Left Top Corner), "RT" (Right Top Corner), "LB" (Left Bottom Corner), "RB" (Right Bottom Corner).
140
+
141
+ ### Taking Screenshots
142
+
143
+ You can use the screenshot method to make taking screenshots a bit simpler.
144
+
145
+ ``` ruby
146
+ movie.screenshot("screenshot.jpg")
147
+ ```
148
+
149
+ The screenshot method has the very same API as transcode so the same options will work.
150
+
151
+ ``` ruby
152
+ movie.screenshot("screenshot.bmp", seek_time: 5, resolution: '320x240')
153
+ ```
154
+
155
+ You can preserve aspect ratio the same way as when using transcode.
156
+
157
+ ``` ruby
158
+ movie.screenshot("screenshot.png", { seek_time: 2, resolution: '200x120' }, preserve_aspect_ratio: :width)
159
+ ```
160
+
161
+ Specify the path to ffmpeg
162
+ --------------------------
163
+
164
+ By default, streamio assumes that the ffmpeg binary is available in the execution path and named ffmpeg and so will run commands that look something like "ffmpeg -i /path/to/input.file ...". Use the FFMPEG.ffmpeg_binary setter to specify the full path to the binary if necessary:
165
+
166
+ ``` ruby
167
+ FFMPEG.ffmpeg_binary = '/usr/local/bin/ffmpeg'
168
+ ```
169
+
170
+ This will cause the same command to run as "/usr/local/bin/ffmpeg -i /path/to/input.file ..." instead.
171
+
172
+
173
+ Automatically kill hung processes
174
+ ---------------------------------
175
+
176
+ 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.
177
+ It is possible to modify this behaviour by setting a new default:
178
+
179
+ ``` ruby
180
+ # Change the timeout
181
+ Transcoder.timeout = 10
182
+
183
+ # Disable the timeout altogether
184
+ Transcoder.timeout = false
185
+ ```
186
+
187
+ Disabling output file validation
188
+ ------------------------------
189
+
190
+ By default Transcoder validates the output file, in case you use FFMPEG for HLS
191
+ format that creates multiple outputs you can disable the validation by passing
192
+ `validate: false` to transcoder_options.
193
+
194
+ Note that transcode will not return the encoded movie object in this case since
195
+ attempting to open a (possibly) invalid output file might result in an error being raised.
196
+
197
+ ```ruby
198
+ transcoder_options = { validate: false }
199
+ movie.transcode("movie.mp4", options, transcoder_options) # returns nil
200
+ ```
201
+
202
+
203
+ Copyright
204
+ ---------
205
+
206
+ Copyright (c) Streamio AB. See LICENSE for details.
@@ -0,0 +1,164 @@
1
+ module FFMPEG
2
+ class EncodingOptions < Hash
3
+ def initialize(options = {})
4
+ merge!(options)
5
+ end
6
+
7
+ def to_s
8
+ params = collect do |key, value|
9
+ send("convert_#{key}", value) if value && supports_option?(key)
10
+ end
11
+
12
+ # codecs should go before the presets so that the files will be matched successfully
13
+ # 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
+ other = params - codecs - presets
17
+ params = codecs + presets + other
18
+
19
+ params_string = params.join(" ")
20
+ params_string << " #{convert_aspect(calculate_aspect)}" if calculate_aspect?
21
+ params_string
22
+ end
23
+
24
+ def width
25
+ self[:resolution].split("x").first.to_i rescue nil
26
+ end
27
+
28
+ def height
29
+ self[:resolution].split("x").last.to_i rescue nil
30
+ end
31
+
32
+ private
33
+ def supports_option?(option)
34
+ option = RUBY_VERSION < "1.9" ? "convert_#{option}" : "convert_#{option}".to_sym
35
+ private_methods.include?(option)
36
+ end
37
+
38
+ def convert_aspect(value)
39
+ "-aspect #{value}"
40
+ end
41
+
42
+ def calculate_aspect
43
+ width, height = self[:resolution].split("x")
44
+ width.to_f / height.to_f
45
+ end
46
+
47
+ def calculate_aspect?
48
+ self[:aspect].nil? && self[:resolution]
49
+ end
50
+
51
+ def convert_video_codec(value)
52
+ "-vcodec #{value}"
53
+ end
54
+
55
+ def convert_frame_rate(value)
56
+ "-r #{value}"
57
+ end
58
+
59
+ def convert_resolution(value)
60
+ "-s #{value}"
61
+ end
62
+
63
+ def convert_video_bitrate(value)
64
+ "-b:v #{k_format(value)}"
65
+ end
66
+
67
+ def convert_audio_codec(value)
68
+ "-acodec #{value}"
69
+ end
70
+
71
+ def convert_audio_bitrate(value)
72
+ "-b:a #{k_format(value)}"
73
+ end
74
+
75
+ def convert_audio_sample_rate(value)
76
+ "-ar #{value}"
77
+ end
78
+
79
+ def convert_audio_channels(value)
80
+ "-ac #{value}"
81
+ end
82
+
83
+ def convert_video_max_bitrate(value)
84
+ "-maxrate #{k_format(value)}"
85
+ end
86
+
87
+ def convert_video_min_bitrate(value)
88
+ "-minrate #{k_format(value)}"
89
+ end
90
+
91
+ def convert_buffer_size(value)
92
+ "-bufsize #{k_format(value)}"
93
+ end
94
+
95
+ def convert_video_bitrate_tolerance(value)
96
+ "-bt #{k_format(value)}"
97
+ end
98
+
99
+ def convert_threads(value)
100
+ "-threads #{value}"
101
+ end
102
+
103
+ def convert_duration(value)
104
+ "-t #{value}"
105
+ end
106
+
107
+ def convert_video_preset(value)
108
+ "-vpre #{value}"
109
+ end
110
+
111
+ def convert_audio_preset(value)
112
+ "-apre #{value}"
113
+ end
114
+
115
+ def convert_file_preset(value)
116
+ "-fpre #{value}"
117
+ end
118
+
119
+ def convert_keyframe_interval(value)
120
+ "-g #{value}"
121
+ end
122
+
123
+ def convert_seek_time(value)
124
+ "-ss #{value}"
125
+ end
126
+
127
+ def convert_screenshot(value)
128
+ value ? "-vframes 1 -f image2" : ""
129
+ end
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
+
139
+ def convert_watermark(value)
140
+ "-i #{value}"
141
+ end
142
+
143
+ def convert_watermark_filter(value)
144
+ case value[:position].to_s
145
+ when "LT"
146
+ "-filter_complex 'scale=#{self[:resolution]},overlay=x=#{value[:padding_x]}:y=#{value[:padding_y]}'"
147
+ when "RT"
148
+ "-filter_complex 'scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{value[:padding_x]}:y=#{value[:padding_y]}'"
149
+ when "LB"
150
+ "-filter_complex 'scale=#{self[:resolution]},overlay=x=#{value[:padding_x]}:y=main_h-overlay_h-#{value[:padding_y]}'"
151
+ when "RB"
152
+ "-filter_complex 'scale=#{self[:resolution]},overlay=x=main_w-overlay_w-#{value[:padding_x]}:y=main_h-overlay_h-#{value[:padding_y]}'"
153
+ end
154
+ end
155
+
156
+ def convert_custom(value)
157
+ value
158
+ end
159
+
160
+ def k_format(value)
161
+ value.to_s.include?("k") ? value : "#{value}k"
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,4 @@
1
+ module FFMPEG
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,42 @@
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
9
+ end
10
+
11
+ #
12
+ # Monkey Patch timeout support into the IO class
13
+ #
14
+ class IO
15
+ def each_with_timeout(pid, seconds, sep_string=$/)
16
+ last_update = Time.now
17
+
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
27
+
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
+ ensure
40
+ check_update_thread.kill
41
+ end
42
+ end
@@ -0,0 +1,136 @@
1
+ require 'time'
2
+
3
+ module FFMPEG
4
+ class Movie
5
+ attr_reader :path, :duration, :time, :bitrate, :rotation, :creation_time
6
+ attr_reader :video_stream, :video_codec, :video_bitrate, :colorspace, :resolution, :sar, :dar
7
+ attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate
8
+ attr_reader :container
9
+
10
+ def initialize(path)
11
+ raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exists?(path)
12
+
13
+ @path = path
14
+
15
+ # ffmpeg will output to stderr
16
+ command = "#{FFMPEG.ffmpeg_binary} -i #{Shellwords.escape(path)}"
17
+ output = Open3.popen3(command) { |stdin, stdout, stderr| stderr.read }
18
+
19
+ fix_encoding(output)
20
+
21
+ output[/Input \#\d+\,\s*(\S+),\s*from/]
22
+ @container = $1
23
+
24
+ output[/Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})/]
25
+ @duration = ($1.to_i*60*60) + ($2.to_i*60) + $3.to_f
26
+
27
+ output[/start: (\d*\.\d*)/]
28
+ @time = $1 ? $1.to_f : 0.0
29
+
30
+ output[/creation_time {1,}: {1,}(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/]
31
+ @creation_time = $1 ? Time.parse("#{$1}") : nil
32
+
33
+ output[/bitrate: (\d*)/]
34
+ @bitrate = $1 ? $1.to_i : nil
35
+
36
+ output[/rotate\ {1,}:\ {1,}(\d*)/]
37
+ @rotation = $1 ? $1.to_i : nil
38
+
39
+ output[/Video:\ (.*) (|(default))/]
40
+ @video_stream = $1
41
+
42
+ output[/Audio:\ (.*) (|(default))/]
43
+ @audio_stream = $1
44
+
45
+ if @video_stream
46
+ commas_except_in_parenthesis = /(?:\([^()]*\)|[^,])+/ # regexp to handle "yuv420p(tv, bt709)" colorspace etc from http://goo.gl/6oi645
47
+ @video_codec, @colorspace, resolution, video_bitrate = @video_stream.scan(commas_except_in_parenthesis).map(&:strip)
48
+ @video_bitrate = video_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
49
+ @resolution = resolution.split(" ").first rescue nil # get rid of [PAR 1:1 DAR 16:9]
50
+ @sar = $1 if @video_stream[/SAR (\d+:\d+)/]
51
+ @dar = $1 if @video_stream[/DAR (\d+:\d+)/]
52
+ end
53
+
54
+ if @audio_stream
55
+ @audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = @audio_stream.split(/\s?,\s?/)
56
+ @audio_bitrate = audio_bitrate =~ %r(\A(\d+)) ? $1.to_i : nil
57
+ @audio_sample_rate = audio_sample_rate[/\d*/].to_i
58
+ end
59
+
60
+ @invalid = true if @video_stream.to_s.empty? && @audio_stream.to_s.empty?
61
+ @invalid = true if output.include?("is not supported")
62
+ @invalid = true if output.include?("could not find codec parameters")
63
+ end
64
+
65
+ def valid?
66
+ not @invalid
67
+ end
68
+
69
+ def width
70
+ resolution.split("x")[0].to_i rescue nil
71
+ end
72
+
73
+ def height
74
+ resolution.split("x")[1].to_i rescue nil
75
+ end
76
+
77
+ def calculated_aspect_ratio
78
+ aspect_from_dar || aspect_from_dimensions
79
+ end
80
+
81
+ def calculated_pixel_aspect_ratio
82
+ aspect_from_sar || 1
83
+ end
84
+
85
+ def size
86
+ File.size(@path)
87
+ end
88
+
89
+ def audio_channels
90
+ return nil unless @audio_channels
91
+ return @audio_channels[/\d*/].to_i if @audio_channels["channels"]
92
+ return 1 if @audio_channels["mono"]
93
+ return 2 if @audio_channels["stereo"]
94
+ return 6 if @audio_channels["5.1"]
95
+ end
96
+
97
+ def frame_rate
98
+ return nil unless video_stream
99
+ video_stream[/(\d*\.?\d*)\s?fps/] ? $1.to_f : nil
100
+ end
101
+
102
+ def transcode(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
103
+ Transcoder.new(self, output_file, options, transcoder_options).run &block
104
+ end
105
+
106
+ def screenshot(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
107
+ Transcoder.new(self, output_file, options.merge(screenshot: true), transcoder_options).run &block
108
+ end
109
+
110
+ protected
111
+ def aspect_from_dar
112
+ return nil unless dar
113
+ w, h = dar.split(":")
114
+ aspect = w.to_f / h.to_f
115
+ aspect.zero? ? nil : aspect
116
+ end
117
+
118
+ def aspect_from_sar
119
+ return nil unless sar
120
+ w, h = sar.split(":")
121
+ aspect = w.to_f / h.to_f
122
+ aspect.zero? ? nil : aspect
123
+ end
124
+
125
+ def aspect_from_dimensions
126
+ aspect = width.to_f / height.to_f
127
+ aspect.nan? ? nil : aspect
128
+ end
129
+
130
+ def fix_encoding(output)
131
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
132
+ rescue ArgumentError
133
+ output.force_encoding("ISO-8859-1")
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,127 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+
4
+ module FFMPEG
5
+ class Transcoder
6
+ @@timeout = 30
7
+
8
+ def self.timeout=(time)
9
+ @@timeout = time
10
+ end
11
+
12
+ def self.timeout
13
+ @@timeout
14
+ end
15
+
16
+ def initialize(movie, output_file, options = EncodingOptions.new, transcoder_options = {})
17
+ @movie = movie
18
+ @output_file = output_file
19
+
20
+ if options.is_a?(String) || options.is_a?(EncodingOptions)
21
+ @raw_options = options
22
+ elsif options.is_a?(Hash)
23
+ @raw_options = EncodingOptions.new(options)
24
+ else
25
+ raise ArgumentError, "Unknown options format '#{options.class}', should be either EncodingOptions, Hash or String."
26
+ end
27
+
28
+ @transcoder_options = transcoder_options
29
+ @errors = []
30
+
31
+ apply_transcoder_options
32
+ end
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|
62
+ begin
63
+ yield(0.0) if block_given?
64
+ next_line = Proc.new do |line|
65
+ fix_encoding(line)
66
+ @output << line
67
+ if line.include?("time=")
68
+ if line =~ /time=(\d+):(\d+):(\d+.\d+)/ # ffmpeg 0.8 and above style
69
+ time = ($1.to_i * 3600) + ($2.to_i * 60) + $3.to_f
70
+ else # better make sure it wont blow up in case of unexpected output
71
+ time = 0.0
72
+ end
73
+ progress = time / @movie.duration
74
+ yield(progress) if block_given?
75
+ end
76
+ end
77
+
78
+ if @@timeout
79
+ stderr.each_with_timeout(wait_thr.pid, @@timeout, 'size=', &next_line)
80
+ else
81
+ stderr.each('size=', &next_line)
82
+ end
83
+
84
+ rescue Timeout::Error => e
85
+ FFMPEG.logger.error "Process hung...\n@command\n#{@command}\nOutput\n#{@output}\n"
86
+ raise Error, "Process hung. Full output: #{@output}"
87
+ end
88
+ end
89
+ end
90
+
91
+ def validate_output_file(&block)
92
+ if encoding_succeeded?
93
+ yield(1.0) if block_given?
94
+ FFMPEG.logger.info "Transcoding of #{@movie.path} to #{@output_file} succeeded\n"
95
+ else
96
+ errors = "Errors: #{@errors.join(", ")}. "
97
+ FFMPEG.logger.error "Failed encoding...\n#{@command}\n\n#{@output}\n#{errors}\n"
98
+ raise Error, "Failed encoding.#{errors}Full output: #{@output}"
99
+ end
100
+ end
101
+
102
+ def apply_transcoder_options
103
+ # if true runs #validate_output_file
104
+ @transcoder_options[:validate] = @transcoder_options.fetch(:validate) { true }
105
+
106
+ return if @movie.calculated_aspect_ratio.nil?
107
+ case @transcoder_options[:preserve_aspect_ratio].to_s
108
+ when "width"
109
+ new_height = @raw_options.width / @movie.calculated_aspect_ratio
110
+ new_height = new_height.ceil.even? ? new_height.ceil : new_height.floor
111
+ new_height += 1 if new_height.odd? # needed if new_height ended up with no decimals in the first place
112
+ @raw_options[:resolution] = "#{@raw_options.width}x#{new_height}"
113
+ when "height"
114
+ new_width = @raw_options.height * @movie.calculated_aspect_ratio
115
+ new_width = new_width.ceil.even? ? new_width.ceil : new_width.floor
116
+ new_width += 1 if new_width.odd?
117
+ @raw_options[:resolution] = "#{new_width}x#{@raw_options.height}"
118
+ end
119
+ end
120
+
121
+ def fix_encoding(output)
122
+ output[/test/]
123
+ rescue ArgumentError
124
+ output.force_encoding("ISO-8859-1")
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ module FFMPEG
2
+ VERSION = "2.0.0"
3
+ end
@@ -0,0 +1,48 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'logger'
4
+ require 'stringio'
5
+
6
+ require 'ffmpeg/version'
7
+ require 'ffmpeg/errors'
8
+ require 'ffmpeg/movie'
9
+ require 'ffmpeg/io_monkey'
10
+ require 'ffmpeg/transcoder'
11
+ require 'ffmpeg/encoding_options'
12
+
13
+ module FFMPEG
14
+ # FFMPEG logs information about its progress when it's transcoding.
15
+ # Jack in your own logger through this method if you wish to.
16
+ #
17
+ # @param [Logger] log your own logger
18
+ # @return [Logger] the logger you set
19
+ def self.logger=(log)
20
+ @logger = log
21
+ end
22
+
23
+ # Get FFMPEG logger.
24
+ #
25
+ # @return [Logger]
26
+ def self.logger
27
+ return @logger if @logger
28
+ logger = Logger.new(STDOUT)
29
+ logger.level = Logger::INFO
30
+ @logger = logger
31
+ end
32
+
33
+ # Set the path of the ffmpeg binary.
34
+ # Can be useful if you need to specify a path such as /usr/local/bin/ffmpeg
35
+ #
36
+ # @param [String] path to the ffmpeg binary
37
+ # @return [String] the path you set
38
+ def self.ffmpeg_binary=(bin)
39
+ @ffmpeg_binary = bin
40
+ end
41
+
42
+ # Get the path to the ffmpeg binary, defaulting to 'ffmpeg'
43
+ #
44
+ # @return [String] the path to the ffmpeg binary
45
+ def self.ffmpeg_binary
46
+ @ffmpeg_binary || 'ffmpeg'
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: r7streamio_ffmpeg
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Martins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.1'
41
+ description:
42
+ email:
43
+ - rodrigo@rrmartins.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG
49
+ - LICENSE
50
+ - README.md
51
+ - lib/ffmpeg/encoding_options.rb
52
+ - lib/ffmpeg/errors.rb
53
+ - lib/ffmpeg/io_monkey.rb
54
+ - lib/ffmpeg/movie.rb
55
+ - lib/ffmpeg/transcoder.rb
56
+ - lib/ffmpeg/version.rb
57
+ - lib/streamio-ffmpeg.rb
58
+ homepage: http://github.com/r7com/streamio-ffmpeg
59
+ licenses: []
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.9.3
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.2.2
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Wraps ffmpeg to read metadata and transcodes videos. Copy with changes on
81
+ github.com/streamio/streamio-ffmpeg
82
+ test_files: []