brainsome_streamio-ffmpeg 0.9.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 945f3f21932ccb39e121d05fa8630766c5738a89
4
+ data.tar.gz: 76469c827294b76473ad6a6fd03a5ef434e7a481
5
+ SHA512:
6
+ metadata.gz: 9010b5c343f7c56274c5d5dc5d5f369a3707c861e6e186bd0ff658b3682dfe725148d233db7ee06135588720baa6387a90785ff2797a7da498192f741f06359b
7
+ data.tar.gz: 94e9f48df5191c512f200e568f8e7f8265706201251b32cf956e46b6e17eec399ff7f4106aa563c6d023e81ef1580d5ad56f61a9d0b908b48f64939a683c3021
@@ -0,0 +1,175 @@
1
+ == 0.9.0 2012-07-24
2
+
3
+ New:
4
+ * Bumped target ffmpeg version to 0.11.1
5
+ * Add hung process detection with configurable timeout (thanks stakach)
6
+ * Raise FFMPEG::Error instead of generic RuntimeError on failed transcodings
7
+ * Movie#screenshot for more intuitive screenshotting (README has details)
8
+ * Movie#creation_time and Movie#rotation attributes when metadata is available (thanks Innonate)
9
+
10
+ Bugs:
11
+ * Fixed too many open files bug (thanks to akicho8)
12
+ * Fixed missing path escaping (thanks to mikesager)
13
+ * Fixed README typo (thanks to Linutux)
14
+ * Files outputing "could not find codec parameters" are now recognized as invalid
15
+
16
+ Deprecations:
17
+ * Removed Movie#uncertain_duration?
18
+ * Removed all the deprecated crop options (use :custom => '-vf crop=x:x:x:x' if you need it)
19
+
20
+ Refactorings:
21
+ * Removed the deprecated duration validation code
22
+ * Polish on the transcoder class
23
+ * Polish on the spec suite
24
+
25
+ == 0.8.5 2011-03-05
26
+
27
+ * If a clip has a DAR that doesn't make sense fall back to calculating aspect ratio from dimensions
28
+ * Allow filenames with single quote characters (thanks to youpy)
29
+
30
+ == 0.8.4 2011-11-30
31
+
32
+ * Duration now one decimal more accurate (thanks to Russel Brooks)
33
+ * Added encoding option seek_time (thanks to Misty De Meo)
34
+
35
+ == 0.8.3 2011-09-01
36
+
37
+ * Parameters now come in the order of codecs, presets, others so that we can override the presets
38
+ * Added encoding option keyframe_interval to set number of frames between i-frames (aka GOP size)
39
+ * Streamio (sponsor of this project) have launched new awesome pricing @ http://streamio.com
40
+
41
+ == 0.8.2 2011-08-19
42
+
43
+ * Path to ffmpeg binary can now be specified (thanks jonathandean)
44
+ * If ffmpeg output contains "is not supported" the Movie will be considered invalid
45
+
46
+ == 0.8.1 2011-07-28
47
+
48
+ * Fix progress yielding with ffmpeg 0.8
49
+ * Updated specs to pass with ffmpeg 0.8
50
+
51
+ == 0.8.0 2011-05-26
52
+
53
+ * Duration is now ALWAYS considered uncertain (we've noticed that ffmpeg is not always correct)
54
+ * This means that the duration check will normally never run (unless you manually hack @uncertain_duration to false)
55
+ * Movie#audio_channels now returns nil if there is no audio stream (instead of crashing)
56
+ * Development: Use Bundler
57
+ * Development: Update RSpec to 2.6
58
+
59
+ == 0.7.8 2011-04-04
60
+
61
+ * Fixed number of audio channels on files with 5.1 audio
62
+
63
+ == 0.7.7 2011-02-01
64
+
65
+ * Movies with starttime are now considered as having uncertain duration as its behavior is not consistent across formats
66
+ * Upgrade development environment to RSpec 2.4
67
+
68
+ == 0.7.6 2011-01-14
69
+
70
+ * Another ruby 1.9 encoding fix
71
+
72
+ == 0.7.5 2011-01-14
73
+
74
+ * Fixed some ruby 1.9 issues
75
+ * Added Movie#video_bitrate and Movie#audio_bitrate (thanks to mbj)
76
+
77
+ == 0.7.4 2010-12-07
78
+
79
+ * Fixed broken duration on movies with start times over 0 by reducing duration with start-time
80
+
81
+ == 0.7.3 2010-08-26
82
+
83
+ * Replaced Jewler with simple dynamic gemspec file
84
+ * Spec files now not in published gem to make it a lot smaller in size
85
+ * Full output from ffmpeg command in error raised during transcoding
86
+
87
+ == 0.7.2 2010-08-11
88
+
89
+ * Added encoding option duration
90
+ * Avoid crashing when ffmpeg can't find resolution of a movie
91
+
92
+ == 0.7.1 2010-07-08
93
+
94
+ * Make sure preset parameters are always put last to avoid them ending up before any codec assignments
95
+ * Testing against a fresh ffmpeg build (r24069)
96
+
97
+ == 0.7.0 2010-07-07
98
+
99
+ * Support for ffpresets through video_preset, audio_preset and file_preset encoding options
100
+ * Added encoding option video_bitrate_tolerance
101
+
102
+ == 0.6.8.1 2010-07-06
103
+
104
+ * Bugfix - aspect ratio was not calculated properly on movies with no DAR
105
+
106
+ == 0.6.8 2010-07-06
107
+
108
+ * Don't use encoding options with nil values
109
+ * Added encoding options video_max_bitrate, video_min_bitrate and buffer_size for constant bitrate encoding
110
+
111
+ == 0.6.7 2010-06-10
112
+
113
+ * Bugfix - aspect ratio preserver could suggest non even resolutions in certain circumstances
114
+
115
+ == 0.6.6 2010-06-10
116
+
117
+ * Transcodings to .jpg and .png will now work as they will skip duration validation
118
+
119
+ == 0.6.5 2010-05-19
120
+
121
+ * Movie#size method to get file size.
122
+
123
+ == 0.6.4 2010-05-12
124
+
125
+ * Ruby 1.9 compatibility fix for EncodingOptions (thanks michalf!)
126
+
127
+ == 0.6.3 2010-05-05
128
+
129
+ * Use DAR to calculate aspect ratio if available
130
+
131
+ == 0.6.2 2010-05-05
132
+
133
+ * Added Movie#uncertain_duration? which is true if ffmpeg is guessing duration from bitrate
134
+ * Skipping the transcoders duration validation if original file has uncertain duration
135
+ * Made sure aspect ratio preservation always rounds new size to an even number to avoid "not divisible by 2" errors
136
+ * Changed Movie#valid? logic to accept any movie with either a readable audio or video stream
137
+
138
+ == 0.6.0 2010-05-04
139
+
140
+ * Cropping options now handled by EncodingOptions (croptop, cropbottom, cropleft and cropright)
141
+ * Aspect ratio parameter calculated and added by default
142
+ * Added transcoder options to preserve original aspect ratio on width or height
143
+
144
+ == 0.5.0 2010-04-28
145
+
146
+ * Added logging capabilities
147
+
148
+ == 0.4.3 2010-04-06
149
+
150
+ * Correctly identify invalid movies on latest ffmpeg build (r22811)
151
+
152
+ == 0.4.2 2010-04-06
153
+
154
+ * Escape the path to handle spaces in filenames and avoid CLI injection attacks (thanks J. Weir!)
155
+
156
+ == 0.4.1 2010-02-10
157
+
158
+ * Forgot to change the transcoding shortcut from Movie
159
+
160
+ == 0.4.0 2010-02-10
161
+
162
+ * Transcoding API changed to make use of more humanly readable options (see README for examples)
163
+ * Fixed frame rate parsing for integer frame rates
164
+
165
+ == 0.3.0 2010-02-07
166
+
167
+ * Simple transcoding
168
+
169
+ == 0.2.0 2010-02-06
170
+
171
+ * Some more metadata parsing
172
+
173
+ == 0.1.0 2010-02-05
174
+
175
+ * 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.
@@ -0,0 +1,191 @@
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
+ This version is tested against ffmpeg 1.0. So no guarantees with earlier (or much later) versions. Output and input standards have inconveniently changed rather a lot between versions of ffmpeg. My goal is to keep this library in sync with new versions of ffmpeg as they come along.
16
+
17
+ Usage
18
+ -----
19
+
20
+ ### Require the gem
21
+
22
+ ``` ruby
23
+ require 'rubygems'
24
+ require 'streamio-ffmpeg'
25
+ ```
26
+
27
+ ### Reading Metadata
28
+
29
+ ``` ruby
30
+ movie = FFMPEG::Movie.new("path/to/movie.mov")
31
+
32
+ movie.duration # 7.5 (duration of the movie in seconds)
33
+ movie.bitrate # 481 (bitrate in kb/s)
34
+ movie.size # 455546 (filesize in bytes)
35
+
36
+ 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)
37
+ movie.video_codec # "h264"
38
+ movie.colorspace # "yuv420p"
39
+ movie.resolution # "640x480"
40
+ movie.width # 640 (width of the movie in pixels)
41
+ movie.height # 480 (height of the movie in pixels)
42
+ movie.frame_rate # 16.72 (frames per second)
43
+
44
+ movie.audio_stream # "aac, 44100 Hz, stereo, s16, 75 kb/s" (raw audio stream info)
45
+ movie.audio_codec # "aac"
46
+ movie.audio_sample_rate # 44100
47
+ movie.audio_channels # 2
48
+
49
+ movie.valid? # true (would be false if ffmpeg fails to read the movie)
50
+ ```
51
+
52
+ ### Transcoding
53
+
54
+ First argument is the output file path.
55
+
56
+ ``` ruby
57
+ movie.transcode("tmp/movie.mp4") # Default ffmpeg settings for mp4 format
58
+ ```
59
+
60
+ Keep track of progress with an optional block.
61
+
62
+ ``` ruby
63
+ movie.transcode("movie.mp4") { |progress| puts progress } # 0.2 ... 0.5 ... 1.0
64
+ ```
65
+
66
+ Give custom command line options with a string.
67
+
68
+ ``` ruby
69
+ movie.transcode("movie.mp4", "-ac aac -vc libx264 -ac 2 ...")
70
+ ```
71
+
72
+ Use the EncodingOptions parser for humanly readable transcoding options. Below you'll find most of the supported options. Note that the :custom key will be used as is without modification so use it for any tricky business you might need.
73
+
74
+ ``` ruby
75
+ options = {:video_codec => "libx264", :frame_rate => 10, :resolution => "320x240", :video_bitrate => 300, :video_bitrate_tolerance => 100,
76
+ :aspect => 1.333333, :keyframe_interval => 90,
77
+ :audio_codec => "libfaac", :audio_bitrate => 32, :audio_sample_rate => 22050, :audio_channels => 1,
78
+ :threads => 2,
79
+ :custom => "-vf crop=60:60:10:10"}
80
+ movie.transcode("movie.mp4", options)
81
+ ```
82
+
83
+ The transcode function returns a Movie object for the encoded file.
84
+
85
+ ``` ruby
86
+ transcoded_movie = movie.transcode("tmp/movie.flv")
87
+
88
+ transcoded_movie.video_codec # "flv"
89
+ transcoded_movie.audio_codec # "mp3"
90
+ ```
91
+
92
+ Aspect ratio is added to encoding options automatically if none is specified.
93
+
94
+ ``` ruby
95
+ options = {:resolution => "320x180"} # Will add -aspect 1.77777777777778 to ffmpeg
96
+ ```
97
+
98
+ Preserve aspect ratio on width or height by using the preserve_aspect_ratio transcoder option.
99
+
100
+ ``` ruby
101
+ widescreen_movie = FFMPEG::Movie.new("path/to/widescreen_movie.mov")
102
+
103
+ options = {:resolution => "320x240"}
104
+
105
+ transcoder_options = {:preserve_aspect_ratio => :width}
106
+ widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 320x180
107
+
108
+ transcoder_options = {:preserve_aspect_ratio => :height}
109
+ widescreen_movie.transcode("movie.mp4", options, transcoder_options) # Output resolution will be 426x240
110
+ ```
111
+
112
+ Disable the enlarge transcoding option to avoid lossy scaling.
113
+
114
+ ``` ruby
115
+ # With original resolution "640x480"
116
+ encoding_options = {:resolution => "1080x2"}
117
+ transcoder_options = {:preserve_aspect_ratio => :width, :enlarge => false}
118
+ # The movie will not be scaled up
119
+
120
+ encoding_options = {:resolution => "320x2"}
121
+ transcoder_options = {:preserve_aspect_ratio => :width, :enlarge => false}
122
+ # The movie will be scaled down to output resolution "320x240"
123
+ ```
124
+
125
+ With the transcoder option "autorotate" set to true, videos that contain rotation information
126
+ (usually videos from mobile phones) and would normally play "sideways" are automatically oriented correctly.
127
+
128
+ ``` ruby
129
+ # With original resolution "640x480", rotation "90"
130
+ transcoder_options = {:autoroate => true}
131
+ # The movie is rotated 90 degrees counterclockwise to output resolution "480x640"
132
+ ```
133
+
134
+ For constant bitrate encoding use video_min_bitrate and video_max_bitrate with buffer_size.
135
+
136
+ ``` ruby
137
+ options = {:video_min_bitrate => 600, :video_max_bitrate => 600, :buffer_size => 2000}
138
+ movie.transcode("movie.flv", options)
139
+ ```
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 200 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 = 30
182
+
183
+ # Disable the timeout altogether
184
+ Transcoder.timeout = false
185
+ ```
186
+
187
+
188
+ Copyright
189
+ ---------
190
+
191
+ Copyright (c) 2011 Streamio AB. See LICENSE for details.
@@ -0,0 +1,147 @@
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_custom(value)
132
+ value
133
+ end
134
+
135
+ def convert_metadata(value)
136
+ "-metadata:#{value}"
137
+ end
138
+
139
+ def convert_video_filter(value)
140
+ "-vf #{value}"
141
+ end
142
+
143
+ def k_format(value)
144
+ value.to_s.include?("k") ? value : "#{value}k"
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,4 @@
1
+ module FFMPEG
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,59 @@
1
+ if RUBY_VERSION =~ /1\.8/
2
+ # Useful when `timeout.rb`, which, on M.R.I 1.8, relies on green threads, does not work consistently.
3
+ begin
4
+ require 'system_timer'
5
+ FFMPEG::Timer = SystemTimer
6
+ rescue LoadError
7
+ require 'timeout'
8
+ FFMPEG::Timer = Timeout
9
+ end
10
+ else
11
+ require 'timeout'
12
+ FFMPEG::Timer = Timeout
13
+ end
14
+
15
+ require 'win32/process' if RUBY_PLATFORM =~ /(win|w)(32|64)$/
16
+
17
+ #
18
+ # Monkey Patch timeout support into the IO class
19
+ #
20
+ class IO
21
+ def each_with_timeout(pid, seconds, sep_string=$/)
22
+ sleeping_queue = Queue.new
23
+ thread = nil
24
+
25
+ timer_set = lambda do
26
+ thread = new_thread(pid) { FFMPEG::Timer.timeout(seconds) { sleeping_queue.pop } }
27
+ end
28
+
29
+ timer_cancel = lambda do
30
+ thread.kill if thread rescue nil
31
+ end
32
+
33
+ timer_set.call
34
+ each(sep_string) do |buffer|
35
+ timer_cancel.call
36
+ yield buffer
37
+ timer_set.call
38
+ end
39
+ ensure
40
+ timer_cancel.call
41
+ end
42
+
43
+ private
44
+ def new_thread(pid, &block)
45
+ current_thread = Thread.current
46
+ Thread.new do
47
+ begin
48
+ block.call
49
+ rescue Exception => e
50
+ current_thread.raise e
51
+ if RUBY_PLATFORM =~ /(win|w)(32|64)$/
52
+ Process.kill(1, pid)
53
+ else
54
+ Process.kill('SIGKILL', pid)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,119 @@
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, :dar
7
+ attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate
8
+
9
+ def initialize(path)
10
+ raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exists?(path)
11
+
12
+ @path = path
13
+
14
+ # ffmpeg will output to stderr
15
+ command = "#{FFMPEG.ffmpeg_binary} -i #{Shellwords.escape(path)}"
16
+ output = Open3.popen3(command) { |stdin, stdout, stderr| stderr.read }
17
+
18
+ fix_encoding(output)
19
+
20
+ output[/Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})/]
21
+ @duration = ($1.to_i*60*60) + ($2.to_i*60) + $3.to_f
22
+
23
+ output[/start: (\d*\.\d*)/]
24
+ @time = $1 ? $1.to_f : 0.0
25
+
26
+ output[/creation_time {1,}: {1,}(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/]
27
+ @creation_time = $1 ? Time.parse("#{$1}") : nil
28
+
29
+ output[/bitrate: (\d*)/]
30
+ @bitrate = $1 ? $1.to_i : nil
31
+
32
+ output[/rotate\ {1,}:\ {1,}(\d*)/]
33
+ @rotation = $1 ? $1.to_i : nil
34
+
35
+ output[/Video: (.*)/]
36
+ @video_stream = $1
37
+
38
+ output[/Audio: (.*)/]
39
+ @audio_stream = $1
40
+
41
+ if video_stream
42
+ @video_codec, @colorspace, resolution, video_bitrate = video_stream.split(/\s?,\s?/)
43
+ @video_bitrate = video_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
44
+ @resolution = resolution.split(" ").first rescue nil # get rid of [PAR 1:1 DAR 16:9]
45
+ @dar = $1 if video_stream[/DAR (\d+:\d+)/]
46
+ end
47
+
48
+ if audio_stream
49
+ @audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = audio_stream.split(/\s?,\s?/)
50
+ @audio_bitrate = audio_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
51
+ @audio_sample_rate = audio_sample_rate[/\d*/].to_i
52
+ end
53
+
54
+ @invalid = true if @video_stream.to_s.empty? && @audio_stream.to_s.empty?
55
+ @invalid = true if output.include?("is not supported")
56
+ @invalid = true if output.include?("could not find codec parameters")
57
+ end
58
+
59
+ def valid?
60
+ not @invalid
61
+ end
62
+
63
+ def width
64
+ resolution.split("x")[0].to_i rescue nil
65
+ end
66
+
67
+ def height
68
+ resolution.split("x")[1].to_i rescue nil
69
+ end
70
+
71
+ def calculated_aspect_ratio
72
+ aspect_from_dar || aspect_from_dimensions
73
+ end
74
+
75
+ def size
76
+ File.size(@path)
77
+ end
78
+
79
+ def audio_channels
80
+ return nil unless @audio_channels
81
+ return @audio_channels[/\d*/].to_i if @audio_channels["channels"]
82
+ return 1 if @audio_channels["mono"]
83
+ return 2 if @audio_channels["stereo"]
84
+ return 6 if @audio_channels["5.1"]
85
+ end
86
+
87
+ def frame_rate
88
+ return nil if video_stream.nil?
89
+ video_stream[/(\d*\.?\d*)\s?fps/] ? $1.to_f : nil
90
+ end
91
+
92
+ def transcode(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
93
+ Transcoder.new(self, output_file, options, transcoder_options).run &block
94
+ end
95
+
96
+ def screenshot(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
97
+ Transcoder.new(self, output_file, options.merge(:screenshot => true), transcoder_options).run &block
98
+ end
99
+
100
+ protected
101
+ def aspect_from_dar
102
+ return nil unless dar
103
+ w, h = dar.split(":")
104
+ aspect = w.to_f / h.to_f
105
+ aspect.zero? ? nil : aspect
106
+ end
107
+
108
+ def aspect_from_dimensions
109
+ aspect = width.to_f / height.to_f
110
+ aspect.nan? ? nil : aspect
111
+ end
112
+
113
+ def fix_encoding(output)
114
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
115
+ rescue ArgumentError
116
+ output.force_encoding("ISO-8859-1")
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,124 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+ require 'ffmpeg/transcoders/autorotator'
4
+ require 'ffmpeg/transcoders/scaler'
5
+
6
+ module FFMPEG
7
+
8
+ # transcoder options:
9
+ # - preserve_aspect_ration: [:width|:height]
10
+ # - scale_and_enlarge: boolean, default: true
11
+ # - autorotate: boolean
12
+ class Transcoder
13
+
14
+ include FFMPEG::Transcoders::Autorotator
15
+ include FFMPEG::Transcoders::Scaler
16
+
17
+ @@timeout = 200
18
+
19
+ def self.timeout=(time)
20
+ @@timeout = time
21
+ end
22
+
23
+ def self.timeout
24
+ @@timeout
25
+ end
26
+
27
+ def initialize(movie, output_file, options = EncodingOptions.new, transcoder_options = {:enlarge => true})
28
+ @movie = movie
29
+ @output_file = output_file
30
+
31
+ if options.is_a?(String) || options.is_a?(EncodingOptions)
32
+ @raw_options = options
33
+ elsif options.is_a?(Hash)
34
+ @raw_options = EncodingOptions.new(options)
35
+ else
36
+ raise ArgumentError, "Unknown options format '#{options.class}', should be either EncodingOptions, Hash or String."
37
+ end
38
+
39
+ @transcoder_options = transcoder_options
40
+ @errors = []
41
+
42
+ apply_transcoder_options
43
+ end
44
+
45
+ # ffmpeg < 0.8: frame= 413 fps= 48 q=31.0 size= 2139kB time=16.52 bitrate=1060.6kbits/s
46
+ # ffmpeg >= 0.8: frame= 4855 fps= 46 q=31.0 size= 45306kB time=00:02:42.28 bitrate=2287.0kbits/
47
+ def run
48
+ command = "#{FFMPEG.ffmpeg_binary} -y -i #{Shellwords.escape(@movie.path)} #{@raw_options} #{Shellwords.escape(@output_file)}"
49
+ FFMPEG.logger.info("Running transcoding...\n#{command}\n")
50
+ output = ""
51
+ last_output = nil
52
+ Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
53
+ begin
54
+ yield(0.0) if block_given?
55
+ next_line = Proc.new do |line|
56
+ fix_encoding(line)
57
+ output << line
58
+ if line.include?("time=")
59
+ if line =~ /time=(\d+):(\d+):(\d+.\d+)/ # ffmpeg 0.8 and above style
60
+ time = ($1.to_i * 3600) + ($2.to_i * 60) + $3.to_f
61
+ elsif line =~ /time=(\d+.\d+)/ # ffmpeg 0.7 and below style
62
+ time = $1.to_f
63
+ else # better make sure it wont blow up in case of unexpected output
64
+ time = 0.0
65
+ end
66
+ progress = time / @movie.duration
67
+ yield(progress) if block_given?
68
+ end
69
+ if line =~ /Unsupported codec/
70
+ FFMPEG.logger.error "Failed encoding...\nCommand\n#{command}\nOutput\n#{output}\n"
71
+ raise "Failed encoding: #{line}"
72
+ end
73
+ end
74
+
75
+ if @@timeout
76
+ stderr.each_with_timeout(wait_thr.pid, @@timeout, "r", &next_line)
77
+ else
78
+ stderr.each("r", &next_line)
79
+ end
80
+
81
+ rescue Timeout::Error => e
82
+ FFMPEG.logger.error "Process hung...\nCommand\n#{command}\nOutput\n#{output}\n"
83
+ raise FFMPEG::Error, "Process hung. Full output: #{output}"
84
+ end
85
+ end
86
+
87
+ if encoding_succeeded?
88
+ yield(1.0) if block_given?
89
+ FFMPEG.logger.info "Transcoding of #{@movie.path} to #{@output_file} succeeded\n"
90
+ else
91
+ errors = "Errors: #{@errors.join(", ")}. "
92
+ FFMPEG.logger.error "Failed encoding...\n#{command}\n\n#{output}\n#{errors}\n"
93
+ raise FFMPEG::Error, "Failed encoding.#{errors}Full output: #{output}"
94
+ end
95
+
96
+ encoded
97
+ end
98
+
99
+ def encoding_succeeded?
100
+ @errors << "no output file created" and return false unless File.exists?(@output_file)
101
+ @errors << "encoded file is invalid" and return false unless encoded.valid?
102
+ true
103
+ end
104
+
105
+ def encoded
106
+ @encoded ||= Movie.new(@output_file)
107
+ end
108
+
109
+ private
110
+
111
+ def apply_transcoder_options
112
+ apply_autorotate
113
+ changes_orientation = changes_orientation?
114
+ apply_preserve_aspect_ratio(changes_orientation)
115
+ end
116
+
117
+ def fix_encoding(output)
118
+ output[/test/]
119
+ rescue ArgumentError
120
+ output.force_encoding("ISO-8859-1")
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,27 @@
1
+ module FFMPEG
2
+ module Transcoders
3
+ module Autorotator
4
+ def apply_autorotate
5
+ return unless autorotate?
6
+ # remove the rotation information on the video stream so rotation-aware players don't rotate twice
7
+ @raw_options[:metadata] = 's:v:0 rotate=0'
8
+ filters = {
9
+ 90 => 'transpose=1',
10
+ 180 => 'hflip,vflip',
11
+ 270 => 'transpose=2'
12
+ }
13
+ @raw_options[:video_filter] = filters[@movie.rotation]
14
+ end
15
+
16
+ # we need to know if orientation changes when we scale
17
+ def changes_orientation?
18
+ autorotate? && [90, 270].include?(@movie.rotation)
19
+ end
20
+
21
+ def autorotate?
22
+ @transcoder_options[:autorotate] && @movie.rotation && @movie.rotation != 0
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ module FFMPEG
2
+ module Transcoders
3
+ module Scaler
4
+ private
5
+
6
+ def preserve_aspect_ratio?
7
+ @movie.calculated_aspect_ratio &&
8
+ [:width, :height].include?(@transcoder_options[:preserve_aspect_ratio])
9
+ end
10
+
11
+ # Scaling with autorotation
12
+ #
13
+ # If scaled in conjuction with autorotation
14
+ # and the rotation results in an orientation change
15
+ # we must "invert" the side that is preserved
16
+ # as scaling takes place prior to rotation
17
+ #
18
+ # Example:
19
+ #
20
+ # Original: resolution => 640x480, rotation => 90
21
+ # Requested: resolution => 660x2, preserved_aspect_ration => :width, autorotate => true
22
+ #
23
+ # => the orientation will change from landscape to portrait
24
+ # => we have to invert the preserved_aspect_ration => :height
25
+ #
26
+ # Expected Output: resolution => 660x880
27
+ #
28
+ # Required Encoding (ffmpeg version < 1.0, scales before rotating, not implemented): resolution => 880x660
29
+ # Required Encoding (ffmpeg version == 1.0, rotates before scaling, this implementation): resolution => 660x880
30
+ #
31
+ def apply_preserve_aspect_ratio(change_orientation=false)
32
+ return unless preserve_aspect_ratio?
33
+
34
+ side = @transcoder_options[:preserve_aspect_ratio]
35
+ size = @raw_options.send(side)
36
+ side = invert_side(side) if change_orientation
37
+
38
+ if @transcoder_options[:enlarge] == false
39
+ original_size = @movie.send(side)
40
+ size = original_size if original_size < size
41
+ end
42
+
43
+ case side
44
+ when :width
45
+ new_height = size / @movie.calculated_aspect_ratio
46
+ new_height = evenize(new_height)
47
+ @raw_options[:resolution] = "#{size}x#{new_height}"
48
+ when :height
49
+ new_width = size * @movie.calculated_aspect_ratio
50
+ new_width = evenize(new_width)
51
+ @raw_options[:resolution] = "#{new_width}x#{size}"
52
+ end
53
+
54
+ invert_resolution if change_orientation
55
+ end
56
+
57
+ def invert_side(side)
58
+ side == :height ? :width : :height
59
+ end
60
+
61
+ def invert_resolution
62
+ @raw_options[:resolution] = @raw_options[:resolution].split("x").reverse.join("x")
63
+ end
64
+
65
+ # ffmpeg requires full, even numbers for its resolution string -- this method ensures that
66
+ def evenize(number)
67
+ number = number.ceil.even? ? number.ceil : number.floor
68
+ number.odd? ? number += 1 : number # needed if new_height ended up with no decimals in the first place
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+
@@ -0,0 +1,3 @@
1
+ module FFMPEG
2
+ VERSION = "0.9.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.nil? ? 'ffmpeg' : @ffmpeg_binary
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainsome_streamio-ffmpeg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - David Backeus
8
+ - Brainsome-Developers
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-08-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '2.7'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '2.7'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 0.9.2
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: 0.9.2
42
+ description: Simple yet powerful wrapper around ffmpeg to get metadata from movies
43
+ and do transcoding.
44
+ email:
45
+ - david@streamio.se
46
+ -
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
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/transcoders/autorotator.rb
57
+ - lib/ffmpeg/transcoders/scaler.rb
58
+ - lib/ffmpeg/version.rb
59
+ - lib/streamio-ffmpeg.rb
60
+ - README.md
61
+ - LICENSE
62
+ - CHANGELOG
63
+ homepage: http://github.com/torial/streamio-ffmpeg
64
+ licenses: []
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.0.14
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Reads metadata and transcodes movies.
86
+ test_files: []