tivohmo-streamio-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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b496d2d98d1135036fd67e133db072313c9b1eee
4
+ data.tar.gz: 503db0a5d379c21887857d64ee9609d276308ddd
5
+ SHA512:
6
+ metadata.gz: c1d537bbb5873762cad2564f1b8eb5f3f6a9c41fee65df16fea3bfd759e8d554e5f495578f60d8bd67363fb6af9e8fe5772f7fbd5ac9b686e50eb241d9128381
7
+ data.tar.gz: edc7d421a80f227ff331066926c7e0aab0bffe3b7f263b29b73e79126ae0707b1e0413d527d9fef115f3a876aba86b0f6e7b92db49a37520487265a0fb3a37c2
@@ -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.
@@ -0,0 +1,6 @@
1
+ TivoHMO FFMPEG
2
+ ===============
3
+
4
+ A fork of [streamio-ffmpeg](https://github.com/streamio/streamio-ffmpeg) including [PR73](https://github.com/streamio/streamio-ffmpeg/pull/73) by RLovelett to allow it to work with ffmoeg 2.4.3. Forked to allow releasing to rubygems for use as a dependency in the [tivohmo gem](https://github.com/wr0ngway/tivohmo).
5
+
6
+ See the [streamio-ffmpeg readme](https://github.com/streamio/streamio-ffmpeg/blob/master/README.md) for more 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,168 @@
1
+ require 'time'
2
+ require 'multi_json'
3
+
4
+ module FFMPEG
5
+ class Movie
6
+ attr_reader :path, :duration, :time, :bitrate, :rotation, :creation_time
7
+ attr_reader :video_stream, :video_codec, :video_bitrate, :colorspace, :width, :height, :sar, :dar, :frame_rate
8
+ attr_reader :audio_stream, :audio_codec, :audio_bitrate, :audio_sample_rate, :audio_channels
9
+ attr_reader :container
10
+
11
+ def initialize(path)
12
+ raise Errno::ENOENT, "the file '#{path}' does not exist" unless File.exists?(path)
13
+
14
+ @path = path
15
+
16
+ # ffmpeg will output to stderr
17
+ command = "#{FFMPEG.ffprobe_binary} -i #{Shellwords.escape(path)} -print_format json -show_format -show_streams -show_error"
18
+ std_output = ''
19
+ std_error = ''
20
+
21
+ Open3.popen3(command) do |stdin, stdout, stderr|
22
+ std_output = stdout.read unless stdout.nil?
23
+ std_error = stderr.read unless stderr.nil?
24
+ end
25
+
26
+ fix_encoding(std_output)
27
+
28
+ metadata = MultiJson.load(std_output, symbolize_keys: true)
29
+
30
+ if metadata.key?(:error)
31
+
32
+ @duration = 0
33
+
34
+ else
35
+
36
+ video_streams = metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'video' }
37
+ audio_streams = metadata[:streams].select { |stream| stream.key?(:codec_type) and stream[:codec_type] === 'audio' }
38
+
39
+ @container = metadata[:format][:format_name]
40
+
41
+ @duration = metadata[:format][:duration].to_f
42
+
43
+ @time = metadata[:format][:start_time].to_f
44
+
45
+ @creation_time = if metadata[:format].key?(:tags) and metadata[:format][:tags].key?(:creation_time)
46
+ Time.parse(metadata[:format][:tags][:creation_time])
47
+ else
48
+ nil
49
+ end
50
+
51
+ @bitrate = metadata[:format][:bit_rate].to_i
52
+
53
+ unless video_streams.empty?
54
+ # TODO: Handle multiple video codecs (is that possible?)
55
+ video_stream = video_streams.first
56
+ @video_codec = video_stream[:codec_name]
57
+ @colorspace = video_stream[:pix_fmt]
58
+ @width = video_stream[:width]
59
+ @height = video_stream[:height]
60
+ @video_bitrate = video_stream[:bit_rate].to_i
61
+ @sar = video_stream[:sample_aspect_ratio]
62
+ @dar = video_stream[:display_aspect_ratio]
63
+
64
+ @frame_rate = unless video_stream[:avg_frame_rate] == '0/0'
65
+ Rational(video_stream[:avg_frame_rate])
66
+ else
67
+ nil
68
+ end
69
+
70
+ @video_stream = "#{video_stream[:codec_name]} (#{video_stream[:profile]}) (#{video_stream[:codec_tag_string]} / #{video_stream[:codec_tag]}), #{colorspace}, #{resolution} [SAR #{sar} DAR #{dar}]"
71
+
72
+ @rotation = if video_stream.key?(:tags) and video_stream[:tags].key?(:rotate)
73
+ video_stream[:tags][:rotate].to_i
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ unless audio_streams.empty?
80
+ # TODO: Handle multiple audio codecs
81
+ audio_stream = audio_streams.first
82
+ @audio_channels = audio_stream[:channels].to_i
83
+ @audio_codec = audio_stream[:codec_name]
84
+ @audio_sample_rate = audio_stream[:sample_rate].to_i
85
+ @audio_bitrate = audio_stream[:bit_rate].to_i
86
+ @audio_channel_layout = audio_stream[:channel_layout]
87
+ @audio_stream = "#{audio_codec} (#{audio_stream[:codec_tag_string]} / #{audio_stream[:codec_tag]}), #{audio_sample_rate} Hz, #{audio_channel_layout}, #{audio_stream[:sample_fmt]}, #{audio_bitrate} bit/s"
88
+ end
89
+
90
+ end
91
+
92
+ @invalid = true if metadata.key?(:error)
93
+ @invalid = true if std_error.include?("Unsupported codec")
94
+ @invalid = true if std_error.include?("is not supported")
95
+ @invalid = true if std_error.include?("could not find codec parameters")
96
+ end
97
+
98
+ def valid?
99
+ not @invalid
100
+ end
101
+
102
+ def resolution
103
+ unless width.nil? or height.nil?
104
+ "#{width}x#{height}"
105
+ end
106
+ end
107
+
108
+ def calculated_aspect_ratio
109
+ aspect_from_dar || aspect_from_dimensions
110
+ end
111
+
112
+ def calculated_pixel_aspect_ratio
113
+ aspect_from_sar || 1
114
+ end
115
+
116
+ def size
117
+ File.size(@path)
118
+ end
119
+
120
+ def audio_channel_layout
121
+ # TODO Whenever support for ffmpeg/ffprobe 1.2.1 is dropped this is no longer needed
122
+ @audio_channel_layout || case(audio_channels)
123
+ when 1
124
+ 'stereo'
125
+ when 2
126
+ 'stereo'
127
+ when 6
128
+ '5.1'
129
+ else
130
+ 'unknown'
131
+ end
132
+ end
133
+
134
+ def transcode(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
135
+ Transcoder.new(self, output_file, options, transcoder_options).run &block
136
+ end
137
+
138
+ def screenshot(output_file, options = EncodingOptions.new, transcoder_options = {}, &block)
139
+ Transcoder.new(self, output_file, options.merge(screenshot: true), transcoder_options).run &block
140
+ end
141
+
142
+ protected
143
+ def aspect_from_dar
144
+ return nil unless dar
145
+ w, h = dar.split(":")
146
+ aspect = w.to_f / h.to_f
147
+ aspect.zero? ? nil : aspect
148
+ end
149
+
150
+ def aspect_from_sar
151
+ return nil unless sar
152
+ w, h = sar.split(":")
153
+ aspect = w.to_f / h.to_f
154
+ aspect.zero? ? nil : aspect
155
+ end
156
+
157
+ def aspect_from_dimensions
158
+ aspect = width.to_f / height.to_f
159
+ aspect.nan? ? nil : aspect
160
+ end
161
+
162
+ def fix_encoding(output)
163
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
164
+ rescue ArgumentError
165
+ output.force_encoding("ISO-8859-1")
166
+ end
167
+ end
168
+ 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,90 @@
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
+ # @raise Errno::ENOENT if the ffmpeg binary cannot be found
39
+ def self.ffmpeg_binary=(bin)
40
+ if bin.is_a?(String) && !File.executable?(bin)
41
+ raise Errno::ENOENT, "the ffmpeg binary, \'#{bin}\', is not executable"
42
+ end
43
+ @ffmpeg_binary = bin
44
+ end
45
+
46
+ # Get the path to the ffmpeg binary, defaulting to 'ffmpeg'
47
+ #
48
+ # @return [String] the path to the ffmpeg binary
49
+ # @raise Errno::ENOENT if the ffmpeg binary cannot be found
50
+ def self.ffmpeg_binary
51
+ @ffmpeg_binary || which('ffmpeg')
52
+ end
53
+
54
+ # Get the path to the ffprobe binary, defaulting to what is on ENV['PATH']
55
+ #
56
+ # @return [String] the path to the ffprobe binary
57
+ # @raise Errno::ENOENT if the ffprobe binary cannot be found
58
+ def self.ffprobe_binary
59
+ @ffprobe_binary || which('ffprobe')
60
+ end
61
+
62
+ # Set the path of the ffprobe binary.
63
+ # Can be useful if you need to specify a path such as /usr/local/bin/ffprobe
64
+ #
65
+ # @param [String] path to the ffprobe binary
66
+ # @return [String] the path you set
67
+ # @raise Errno::ENOENT if the ffprobe binary cannot be found
68
+ def self.ffprobe_binary=(bin)
69
+ if bin.is_a?(String) && !File.executable?(bin)
70
+ raise Errno::ENOENT, "the ffprobe binary, \'#{bin}\', is not executable"
71
+ end
72
+ @ffprobe_binary = bin
73
+ end
74
+
75
+ # Cross-platform way of finding an executable in the $PATH.
76
+ #
77
+ # which('ruby') #=> /usr/bin/ruby
78
+ # see: http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
79
+ def self.which(cmd)
80
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
81
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
82
+ exts.each { |ext|
83
+ exe = File.join(path, "#{cmd}#{ext}")
84
+ return exe if File.executable? exe
85
+ }
86
+ end
87
+ raise Errno::ENOENT, "the #{cmd} binary could not be found in #{ENV['PATH']}"
88
+ end
89
+
90
+ end
@@ -0,0 +1 @@
1
+ require_relative 'streamio-ffmpeg'
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tivohmo-streamio-ffmpeg
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - David Backeus
8
+ - Ryan Lovelett
9
+ - Matt Conway
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2014-11-23 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: multi_json
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - "~>"
20
+ - !ruby/object:Gem::Version
21
+ version: '1.8'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1.8'
29
+ - !ruby/object:Gem::Dependency
30
+ name: rspec
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '2.14'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '2.14'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rake
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '10.1'
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '10.1'
57
+ description:
58
+ email:
59
+ - david@streamio.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - CHANGELOG
65
+ - LICENSE
66
+ - README.md
67
+ - lib/ffmpeg/encoding_options.rb
68
+ - lib/ffmpeg/errors.rb
69
+ - lib/ffmpeg/io_monkey.rb
70
+ - lib/ffmpeg/movie.rb
71
+ - lib/ffmpeg/transcoder.rb
72
+ - lib/ffmpeg/version.rb
73
+ - lib/streamio-ffmpeg.rb
74
+ - lib/tivohmo-streamio-ffmpeg.rb
75
+ homepage: http://github.com/wr0ngway/tivohmo-streamio-ffmpeg
76
+ licenses: []
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.2.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Forked from streamio-ffmpeg for use by tivohmo gem
98
+ test_files: []