rff 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 246923c57cdd775302fc981bf79ed642effccf5f
4
+ data.tar.gz: a77efae571f4648bee009534e10f608d094d65a9
5
+ SHA512:
6
+ metadata.gz: 3ffbd9f2fb140174e8c2e927b7fa4d5dd4ee4bf1529bbb51573be045d22ea2a95fef8e8b966e70c9b4ca2ab9f793ccabaff172cc1fadc026033498094f9d000d
7
+ data.tar.gz: 967e748b929e7c7121fa020b48065cf70426ed4944cbb5abd39eafee810ff18e4df3ad9ca59e4a728fc661d0a39d6256189df7a93e057b3e59b462ef19fccd2f
@@ -0,0 +1,184 @@
1
+ require_relative 'processor'
2
+
3
+ # The main module for _rff_ - a Ruby gem for simple audio and video conversion for HTML5 using FFmpeg
4
+ # Author:: Phitherek_ <phitherek [at] gmail [dot] com>
5
+ # License:: Open Source Software
6
+
7
+ module RFF
8
+
9
+ # This class provides an "All audio to HTML5" conversion functionality. It takes every compatible with FFmpeg audio format and converts it to the three HTML5 audio formats - mp3, ogg and wav. If the input is already in one of these formats it is only converted to the two other formats, because it can be used as one of HTML5 sources.
10
+
11
+ class AudioHandler
12
+
13
+ # This constructor initializes the class with the following arguments:
14
+ # * _input_ <b>(required)</b> - the full path to the input file
15
+ # * <i>output_path</i> - a path to place the output file in. Defaults to nil, which means that the input' s directory path is used
16
+ # * <i>custom_args</i> - passes custom arguments to FFmpeg. Defaults to nil, which means no custom arguments are given
17
+ # * <i>recommended_audio_quality</i> - determines if recommended by FFmpeg community audio quality settings should be used. Defaults to true, which means audio conversion with good, recommended quality. Set to false if you are giving additional arguments that determine this quality.
18
+ # All of the arguments are passed on to underlying Processor instances. This method also determines input type, initializes processing percentage and creates needed Processor instances.
19
+
20
+ def initialize input, output_path=nil, custom_args=nil, recommended_audio_quality=true
21
+ @input = input
22
+ @input_type = File.basename(@input).split(".")[1]
23
+ @output_path = output_path
24
+ @custom_args = custom_args
25
+ @processing_percentage = 0
26
+ @processors = []
27
+ types = [:mp3, :ogg, :wav]
28
+ if types.include?(@input_type.to_sym)
29
+ types.delete(@input_type.to_sym)
30
+ end
31
+ types.each do |type|
32
+ @processors << RFF::Processor.new(@input, type, @output_path, nil, @custom_args, recommended_audio_quality)
33
+ end
34
+ end
35
+
36
+ # This method fires all the Processor instances (conversion processes) in a separate thread at once. Then it counts the overall processing percentage from all the Processor instances as the process goes and sets it to 100% on finish
37
+
38
+ def fire_all
39
+ @processing_thread = Thread.new do |th|
40
+ begin
41
+ @processors.each do |proc|
42
+ proc.fire
43
+ #sleep(5)
44
+ end
45
+ status = :processing
46
+ while status != :done
47
+ donecount = 0
48
+ @processors.each do |proc|
49
+ #puts "Process status:" + proc.status.to_s
50
+ if proc.status == :completed || proc.status == :failed || proc.status == :aborted
51
+ donecount = donecount + 1
52
+ end
53
+ end
54
+ #puts "Done count: " + donecount.to_s
55
+ if donecount == @processors.count
56
+ status = :done
57
+ break
58
+ end
59
+ processing_percentage = 0
60
+ @processors.each do |proc|
61
+ processing_percentage += proc.processing_percentage
62
+ end
63
+ @processing_percentage = (processing_percentage.to_f/@processors.count).to_i
64
+ end
65
+ @processing_percentage = 100
66
+ rescue => e
67
+ puts "Caught exception: " + e.to_s
68
+ puts "Backtrace:"
69
+ puts e.backtrace
70
+ @status = :done
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+ # This method fires all the Processor instances (conversion processes) in a separate thread sequentially - next Processor in the row is fired only after the Processor before finishes. It also counts the overall processing percentage from all the Processor instances as the process goes and sets it to 100% on finish
77
+
78
+ def fire_sequential
79
+ @processing_thread = Thread.new do |th|
80
+ begin
81
+ i = 0
82
+ @processors.each do |proc|
83
+ proc.fire
84
+ sleep(1)
85
+ while proc.status == :processing
86
+ if proc.processing_percentage != nil
87
+ @processing_percentage = (i*(100/@processors.count))+(proc.processing_percentage.to_f/@processors.count).to_i
88
+ end
89
+ end
90
+ i = i+1
91
+ #sleep(5)
92
+ end
93
+ @processing_percentage = 100
94
+ rescue => e
95
+ puts "Caught exception: " + e.to_s
96
+ puts "Backtrace:"
97
+ puts e.backtrace
98
+ @status = :done
99
+ end
100
+ end
101
+ end
102
+
103
+ # This method kills all the processes in Processor instances and its own processing thread
104
+
105
+ def killall
106
+ @processors.each do |proc|
107
+ proc.kill
108
+ end
109
+ @processing_thread.kill
110
+ end
111
+
112
+ # This method returns the "to MP3" Processor instance if it exists or nil otherwise
113
+
114
+ def mp3_processor
115
+ ret = nil
116
+ @processors.each do |proc|
117
+ if proc.output_type == :mp3
118
+ ret = proc
119
+ end
120
+ end
121
+ ret
122
+ end
123
+
124
+ # This method returns the "to OGG" Processor instance if it exists or nil otherwise
125
+
126
+ def ogg_processor
127
+ ret = nil
128
+ @processors.each do |proc|
129
+ if proc.output_type == :ogg
130
+ ret = proc
131
+ end
132
+ end
133
+ ret
134
+ end
135
+
136
+ # This method returns the "to WAV" Processor instance if it exists or nil otherwise
137
+
138
+ def wav_processor
139
+ ret = nil
140
+ @processors.each do |proc|
141
+ if proc.output_type == :wav
142
+ ret = proc
143
+ end
144
+ end
145
+ ret
146
+ end
147
+
148
+ # This method returns full input path
149
+
150
+ def input
151
+ @input
152
+ end
153
+
154
+ # This method returns full output file name
155
+
156
+ def output_name
157
+ @output_name
158
+ end
159
+
160
+ # This method returns the path in which output file is saved
161
+
162
+ def output_path
163
+ @output_path
164
+ end
165
+
166
+ # This method returns custom arguments passed to FFmpeg
167
+
168
+ def custom_args
169
+ @custom_args
170
+ end
171
+
172
+ # This method returns percentage of process completion
173
+
174
+ def processing_percentage
175
+ @processing_percentage || 0
176
+ end
177
+
178
+ # This method returns percentage of process completion formatted for output
179
+
180
+ def format_processing_percentage
181
+ @processing_percentage.nil? ? "0%" : @processing_percentage.to_s + "%"
182
+ end
183
+ end
184
+ end
data/lib/exceptions.rb ADDED
@@ -0,0 +1,31 @@
1
+ module RFF
2
+
3
+ # This exception is thrown on insufficient arguments to the method
4
+
5
+ class ArgumentError < ::ArgumentError
6
+ end
7
+
8
+ # This exception is thrown on FFmpeg processing error
9
+
10
+ class ProcessingFailure < ::RuntimeError
11
+
12
+ # Initializes the exception with arguments
13
+ # * _exitcode_ <b>(required)</b> - FFmpeg exit code
14
+ # * _msg_ - Message describing the error. Defaults to nil
15
+
16
+ def initialize(exitcode, msg=nil)
17
+ @msg = msg
18
+ @exitcode = exitcode
19
+ end
20
+
21
+ # Returns message describing the error (exit code and describing message if it is present)
22
+
23
+ def to_s
24
+ if msg.nil?
25
+ exitcode.to_s
26
+ else
27
+ msg + exitcode.to_s
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ module RFF
2
+
3
+ # A class that reads the given IO stream and provides advanced reading functions for it. It reads the given stream to the internal buffer in separate thread and uses this buffer to provide reading functionality that is not available in IO stream alone
4
+
5
+ class OutputReader
6
+
7
+ # This constructor initializes the class instance with IO stream and starts the stream reading thread
8
+ # * _io_ - the IO stream to read from
9
+
10
+ def initialize io
11
+ @buffer = ""
12
+ @writecount = 0
13
+ @readcount = 0
14
+ @eof = false
15
+ @reading_thread = Thread.new do |th|
16
+ while data = io.read(10)
17
+ @buffer += data
18
+ @writecount += data.length
19
+ end
20
+ @eof = true
21
+ end
22
+ end
23
+
24
+ # This method provides an implementation of IO gets method for streams containing lines with different line separators in some parts
25
+ # * _seps_ - an array defining the line separators. Defaults to one, default LF separator
26
+ # Outputs next line from the stream or "EOF\\n" when the stream reaches its end
27
+
28
+ def gets seps=["\n"]
29
+ if @writecount > @readcount
30
+ line = ""
31
+ begin
32
+ c = @buffer[@readcount]
33
+ if !c.nil?
34
+ @readcount = @readcount+1
35
+ line += c
36
+ if seps.include?(c)
37
+ break
38
+ end
39
+ end
40
+ end while !@eof
41
+ line
42
+ elsif @eof
43
+ "EOF\n"
44
+ else
45
+ nil
46
+ end
47
+ end
48
+
49
+ # This method can be used to join the reading thread in some place of the script
50
+
51
+ def join_reading_thread
52
+ @reading_thread.join
53
+ end
54
+
55
+ # This method outputs the internal buffer without any additional processing
56
+
57
+ def get_raw_buffer
58
+ @buffer
59
+ end
60
+ end
61
+ end
data/lib/processor.rb ADDED
@@ -0,0 +1,626 @@
1
+ require_relative 'exceptions'
2
+ require_relative 'output_reader'
3
+ require 'open3'
4
+ require 'time'
5
+ module RFF
6
+
7
+ # The main processing class of the _rff_ gem. It spawns FFmpeg conversion process and parses its output, providing information about input and output files and current process status
8
+
9
+ class Processor
10
+
11
+ # This constructor initializes the class with the following arguments:
12
+ # * _input_ <b>(required)</b> - the full path to the input file
13
+ # * <i>output_type</i> <b>(required)</b> - defines the type of the output. Must be one of [:mp3, :ogg, :wav] for audio conversion or [:mp4, :ogv, :webm] for video conversion
14
+ # * <i>output_path</i> - a path to place the output file in. Defaults to nil, which means that the input' s directory path is used
15
+ # * _quality_ - only affects video conversion. Sets the video conversion quality. Defaults to 5000k, which is tested value for good video conversion quality
16
+ # * <i>custom_args</i> - passes custom arguments to FFmpeg. Defaults to nil, which means no custom arguments are given
17
+ # * <i>recommended_audio_quality</i> - determines if recommended by FFmpeg community audio quality settings should be used. Defaults to true, which means audio conversion with good, recommended quality. Set to false if you are giving additional arguments that determine this quality.
18
+ # This method also validates arguments, determines full output name, generates appropriate FFmpeg command, determines conversion type and initializes status (it is :pending at this point).
19
+
20
+ def initialize input, output_type, output_path=nil, quality="5000k", custom_args=nil, recommended_audio_quality=true
21
+ if input.nil? || input.empty? || output_type.nil?
22
+ raise RFF::ArgumentError.new("Input and output type can not be empty nor nil!")
23
+ end
24
+ if ![:mp3, :ogg, :wav, :mp4, :webm, :ogv].include?(output_type.to_sym)
25
+ raise RFF::ArgumentError.new("Output type must be one of [:mp3, :ogg, :wav, :mp4, :webm, :ogv]!")
26
+ end
27
+ @input = input
28
+ @output_type = output_type
29
+ @output_name = File.basename(@input).split(".")[0]
30
+ @output_path = output_path || File.dirname(input)
31
+ @quality = quality
32
+ @custom_args = custom_args
33
+ if [:mp3, :ogg, :wav].include?(@output_type)
34
+ @command = "ffmpeg -y -i #{@input} -acodec #{@output_type == :mp3 ? "libmp3lame" : (@output_type == :ogg ? "libvorbis" : "pcm_s16le")}#{recommended_audio_quality ? (@output_type == :mp3 ? " -aq 2" : (@output_type == :ogg ? " -aq 4" : "")) : ""}#{@custom_args.nil? ? "" : " #{@custom_args}"} #{@output_path}/#{@output_name}.#{@output_type.to_s}"
35
+ @conversion_type = :audio
36
+ else
37
+ @command = "ffmpeg -y -i #{@input} -acodec #{(@output_type == :webm || @output_type == :ogv) ? "libvorbis" : "aac"} -vcodec #{@output_type == :webm ? "libvpx" : (@output_type == :ogv ? "libtheora" : "mpeg4")}#{@output_type == :mp4 ? " -strict -2" : ""}#{(!@quality.nil? && !@quality.empty?) ? " -b:v #{@quality}" : ""}#{recommended_audio_quality ? (@output_type == :webm || @output_type == :ogv ? " -aq 4" : " -b:a 240k") : ""}#{@custom_args.nil? ? "" : " #{@custom_args}"} #{@output_path}/#{@output_name}.#{@output_type.to_s}"
38
+ @conversion_type = :video
39
+ end
40
+ @status = :pending
41
+ end
42
+
43
+ # This method runs the FFmpeg conversion process in a separate thread. First it initializes processing percentage and then spawns a new thread, in which FFmpeg conversion process is spawned through Open3.popen2e. It sets the processing status, passes the command output to OutputReader instance and initializes all the needed structures for information. Then it parses the output until it ends to extract the information, which is available through this class' getter methods. All the information is filled in as soon as it appears in the command output. When the process finishes, it cleans up the streams, sets percentage to 100% and gets the command' s exit status. Then it sets :completed or :failed status according to the command' s status. At the end it catches and displays any exceptions that can occur in the thread
44
+
45
+ def fire
46
+ @processing_percentage = 0
47
+ @processing_thread = Thread.new do |th|
48
+ begin
49
+ Open3.popen2e(@command) do |progin, progout, progthread|
50
+ @status = :processing
51
+ @parser_status = :normal
52
+ outputreader = OutputReader.new(progout)
53
+ @rawoutput = []
54
+ @input_meta_common = {}
55
+ @input_meta_audio = {}
56
+ @input_meta_video = {}
57
+ @output_meta_common = {}
58
+ @output_meta_audio = {}
59
+ @output_meta_video = {}
60
+ @processing_status = {}
61
+ begin
62
+ #puts "DEBUG: Processing next line..."
63
+ line = outputreader.gets(["\n", "\r"])
64
+ line = line.chomp! if !line.nil?
65
+ #puts "DEBUG: Line: " + line.to_s
66
+ #puts "DEBUG: Raw OutputReader buffer content: "
67
+ #puts outputreader.get_raw_buffer
68
+ if !line.nil? && line != "EOF"
69
+ #puts "DEBUG: Adding line to rawoutput..."
70
+ @rawoutput << line
71
+ # Getting rid of unnecessary indentation spaces
72
+ #puts "DEBUG: Removing unnecessary spaces..."
73
+ if line[/^[ ]+/] != nil
74
+ line[/^[ ]+/] = ""
75
+ end
76
+ if line[/[ ]+/] != nil
77
+ line[/[ ]+/] = " "
78
+ end
79
+ if line[/[ ]+$/] != nil
80
+ line[/[ ]+$/] = ""
81
+ end
82
+ line.gsub!(/=[ ]+/, "=")
83
+ # Parsing
84
+ #puts "DEBUG: Line after spaces removal: " + line.to_s
85
+ #puts "DEBUG: Parsing line..."
86
+ if @conversion_type == :audio
87
+ if @parser_status == :meta
88
+ #puts "DEBUG: Parser in metadata parsing mode"
89
+ if line[0..7] == "Duration" || line[0..5] == "Stream" || line[0] == "[" || line[0..5] == "Output" || line[0..4] == "Input"
90
+ @parser_status = :normal
91
+ else
92
+ #puts "DEBUG: Reading metadata line..."
93
+ if @last_met_io == :input
94
+ @input_meta_audio[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
95
+ elsif @last_met_io == :output
96
+ @output_meta_audio[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
97
+ end
98
+ end
99
+ end
100
+ if @parser_status == :strmap
101
+ #puts "DEBUG: Parser in stream mapping parsing mode"
102
+ #puts "DEBUG: Reading stream mapping information..."
103
+ @stream_mapping_audio = line[21..-2]
104
+ @parser_status = :retnormal
105
+ end
106
+ if @parser_status == :normal
107
+ #puts "DEBUG: Parser in normal mode"
108
+ if line[0..5] == "ffmpeg"
109
+ #puts "DEBUG: Approached version line"
110
+ @ff_versionline = line
111
+ elsif line[0..4] == "built"
112
+ #puts "DEBUG: Approached build line"
113
+ @ff_buildline = line
114
+ elsif line[0..4] == "Input"
115
+ #puts "DEBUG: Approached input declaration"
116
+ @input_type = line.split(",")[1][1..-1]
117
+ @last_met_io = :input
118
+ elsif line[0..5] == "Output"
119
+ #puts "DEBUG: Approached output declaration"
120
+ @detected_output_type = line.split(",")[1][1..-1]
121
+ @last_met_io = :output
122
+ elsif line == "Metadata:"
123
+ #puts "DEBUG: Approached metadata start"
124
+ @parser_status = :meta
125
+ elsif line[0..7] == "Duration"
126
+ #puts "DEBUG: Approached duration line"
127
+ @input_duration = line.split(",")[0][10..-1]
128
+ if line.split(",")[1][1..5] == "start"
129
+ #puts "DEBUG: Detected start variation of the line"
130
+ @input_start = line.split(",")[1][6..-1]
131
+ @input_bitrate = line.split(",")[2][10..-1]
132
+ else
133
+ #puts "DEBUG: Detected only bitrate variation of the line"
134
+ @input_bitrate = line.split(",")[1][10..-1]
135
+ end
136
+ elsif line == "Stream mapping:"
137
+ #puts "DEBUG: Approached stream mapping declaration"
138
+ @parser_status = :strmap
139
+ elsif line[0..5] == "Stream"
140
+ #puts "DEBUG: Approached stream information line"
141
+ if @last_met_io == :input
142
+ @audio_input_format = line.split(",")[0][20..-1]
143
+ @audio_input_freq = line.split(",")[1][1..-1]
144
+ @audio_input_channelmode = line.split(",")[2][1..-1]
145
+ @audio_input_format_type = line.split(",")[3][1..-1]
146
+ if line.split(",")[4] != nil
147
+ @audio_input_bitrate2 = line.split(",")[4][1..-1]
148
+ end
149
+ elsif @last_met_io == :output
150
+ @audio_output_format = line.split(",")[0][20..-1]
151
+ @audio_output_freq = line.split(",")[1][1..-1]
152
+ @audio_output_channelmode = line.split(",")[2][1..-1]
153
+ @audio_output_format_type = line.split(",")[3][1..-1]
154
+ if line.split(",")[4] != nil
155
+ @audio_output_bitrate2 = line.split(",")[4][1..-1]
156
+ end
157
+ end
158
+ elsif line[0..3] == "size"
159
+ #puts "DEBUG: Approached processing status line"
160
+ line.split(" ").each do |spl|
161
+ @processing_status[spl.split("=")[0].to_sym] = spl.split("=")[1]
162
+ end
163
+ if @processing_status[:time] != nil && @input_duration != nil
164
+ @processing_percentage = ((((Time.parse(@processing_status[:time])-Time.parse("0:0"))/(Time.parse(@input_duration)-Time.parse("0:0")))).round(2)*100).to_i #/ This is for jEdit syntax highlighting to fix
165
+ end
166
+ end
167
+ end
168
+ if @parser_status == :retnormal
169
+ #puts "DEBUG: Parser returning to normal mode"
170
+ @parser_status = :normal
171
+ end
172
+ elsif @conversion_type == :video
173
+ if @parser_status == :meta
174
+ #puts "DEBUG: Parser in metadata parsing mode"
175
+ if line[0..7] == "Duration" || line[0..5] == "Stream" || line[0] == "[" || line[0..5] == "Output" || line[0..4] == "Input"
176
+ @parser_status = :normal
177
+ else
178
+ #puts "DEBUG: Reading metadata line..."
179
+ if @last_met_io == :input
180
+ if @last_stream_type == nil
181
+ @input_meta_common[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
182
+ elsif @last_stream_type == :audio
183
+ @input_meta_audio[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
184
+ elsif @last_stream_type == :video
185
+ @input_meta_video[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
186
+ end
187
+ elsif @last_met_io == :output
188
+ if @last_stream_type == nil
189
+ @output_meta_common[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
190
+ elsif @last_stream_type == :audio
191
+ @output_meta_audio[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
192
+ elsif @last_stream_type == :video
193
+ @output_meta_video[line.split(":")[0].downcase[0..-2].to_sym] = line.split(":")[1][1..-1]
194
+ end
195
+ end
196
+ end
197
+ end
198
+ if @parser_status == :strmap
199
+ #puts "DEBUG: Parser in stream mapping parsing mode"
200
+ #puts "DEBUG: Reading stream mapping information..."
201
+ @stream_mapping_video = line[21..-2]
202
+ @parser_status = :strmap2
203
+ elsif @parser_status == :strmap2
204
+ @stream_mapping_audio = line[21..-2]
205
+ @parser_status = :retnormal
206
+ end
207
+ if @parser_status == :normal
208
+ #puts "DEBUG: Parser in normal mode"
209
+ if line[0..5] == "ffmpeg"
210
+ #puts "DEBUG: Approached version line"
211
+ @ff_versionline = line
212
+ elsif line[0..4] == "built"
213
+ #puts "DEBUG: Approached build line"
214
+ @ff_buildline = line
215
+ elsif line[0..4] == "Input"
216
+ #puts "DEBUG: Approached input declaration"
217
+ @input_type = line.split(",")[1][1..-1]
218
+ @last_met_io = :input
219
+ @last_stream_type = nil
220
+ elsif line[0..5] == "Output"
221
+ #puts "DEBUG: Approached output declaration"
222
+ @detected_output_type = line.split(",")[1][1..-1]
223
+ @last_met_io = :output
224
+ @last_stream_type = nil
225
+ elsif line == "Metadata:"
226
+ #puts "DEBUG: Approached metadata start"
227
+ @parser_status = :meta
228
+ elsif line[0..7] == "Duration"
229
+ #puts "DEBUG: Approached duration line"
230
+ @input_duration = line.split(",")[0][10..-1]
231
+ if line.split(",")[1][1..5] == "start"
232
+ #puts "DEBUG: Detected start variation of the line"
233
+ @input_start = line.split(",")[1][8..-1]
234
+ @input_bitrate = line.split(",")[2][10..-1]
235
+ else
236
+ #puts "DEBUG: Detected only bitrate variation of the line"
237
+ @input_bitrate = line.split(",")[1][10..-1]
238
+ end
239
+ elsif line == "Stream mapping:"
240
+ #puts "DEBUG: Approached stream mapping declaration"
241
+ @parser_status = :strmap
242
+ elsif line[0..5] == "Stream"
243
+ #puts "DEBUG: Approached stream information line"
244
+ if line[13..17] == "Video"
245
+ @last_stream_type = :video
246
+ elsif line[13..17] == "Audio"
247
+ @last_stream_type = :audio
248
+ else
249
+ @last_stream_type = nil
250
+ end
251
+ if @last_met_io == :input
252
+ if @last_stream_type == :video
253
+ @video_input_format = line.split(",")[0][20..-1]
254
+ @video_input_colorspace = line.split(",")[1][1..-1]
255
+ @video_input_resolution = line.split(",")[2][1..-1]
256
+ @video_input_additional = line.split(",")[3..-1]
257
+ elsif @last_stream_type == :audio
258
+ @audio_input_format = line.split(",")[0][20..-1]
259
+ @audio_input_freq = line.split(",")[1][1..-1]
260
+ @audio_input_channelmode = line.split(",")[2][1..-1]
261
+ @audio_input_format_type = line.split(",")[3][1..-1]
262
+ if line.split(",")[4] != nil
263
+ @audio_input_bitrate2 = line.split(",")[4][1..-1]
264
+ end
265
+ end
266
+ elsif @last_met_io == :output
267
+ if @last_stream_type == :video
268
+ @video_output_format = line.split(",")[0][20..-1]
269
+ @video_output_colorspace = line.split(",")[1][1..-1]
270
+ @video_output_resolution = line.split(",")[2][1..-1]
271
+ @video_output_additional = line.split(",")[3..-1]
272
+ elsif @last_stream_type == :audio
273
+ @audio_output_format = line.split(",")[0][20..-1]
274
+ @audio_output_freq = line.split(",")[1][1..-1]
275
+ @audio_output_channelmode = line.split(",")[2][1..-1]
276
+ @audio_output_format_type = line.split(",")[3][1..-1]
277
+ if line.split(",")[4] != nil
278
+ @audio_output_bitrate2 = line.split(",")[4][1..-1]
279
+ end
280
+ end
281
+ end
282
+ elsif line[0..4] == "frame"
283
+ #puts "DEBUG: Approached processing status line"
284
+ line.split(" ").each do |spl|
285
+ @processing_status[spl.split("=")[0].to_sym] = spl.split("=")[1]
286
+ end
287
+ if @processing_status[:time] != nil && @input_duration != nil
288
+ @processing_percentage = ((((Time.parse(@processing_status[:time])-Time.parse("0:0"))/(Time.parse(@input_duration)-Time.parse("0:0")))).round(2)*100).to_i #/ This is for jEdit syntax highlighting to fix
289
+ end
290
+ end
291
+ end
292
+ if @parser_status == :retnormal
293
+ #puts "DEBUG: Parser returning to normal mode"
294
+ @parser_status = :normal
295
+ end
296
+ end
297
+ end
298
+ end while line != "EOF"
299
+ #puts "DEBUG: After EOF, closing streams..."
300
+ progout.close
301
+ progin.close
302
+ progthread.join
303
+ progst = progthread.value
304
+ @exit_status = progst.exitstatus
305
+ #puts "Got output status: #{progst.exitstatus}"
306
+ if progst.success?
307
+ @status = :completed
308
+ @processing_percentage = 100
309
+ else
310
+ @status = :failed
311
+ end
312
+ end
313
+ if @status == :failed
314
+ raise RFF::ProcessingFailure.new(@exit_status, "Inspect the output as FFmpeg returned with status: ")
315
+ end
316
+ rescue => e
317
+ puts "Caught exception: " + e.to_s
318
+ puts "Backtrace:"
319
+ puts e.backtrace
320
+ @status = :failed
321
+ end
322
+ end
323
+ end
324
+
325
+ # This method returns full input path
326
+
327
+ def input
328
+ @input
329
+ end
330
+
331
+ # This method returns output type read from the filename
332
+
333
+ def output_type
334
+ @output_type
335
+ end
336
+
337
+ # This method returns output type read by FFmpeg
338
+
339
+ def detected_output_type
340
+ @detected_output_type
341
+ end
342
+
343
+ # This method returns full output name
344
+
345
+ def output_name
346
+ @output_name
347
+ end
348
+
349
+ # This method returns path where the output is (being) saved
350
+
351
+ def output_path
352
+ @output_path
353
+ end
354
+
355
+ # This method returns used video quality
356
+
357
+ def quality
358
+ @quality
359
+ end
360
+
361
+ # This method returns the FFmpeg command used for conversion
362
+
363
+ def command
364
+ @command
365
+ end
366
+
367
+ # This method returns custom arguments passed to FFmpeg
368
+
369
+ def custom_args
370
+ @custom_args
371
+ end
372
+
373
+ # This method returns conversion type (:audio or :video)
374
+
375
+ def conversion_type
376
+ @conversion_type
377
+ end
378
+
379
+ # This method returns full path to the output file
380
+
381
+ def full_output_path
382
+ "#{@output_path}/#{@output_name}.#{@output_type.to_s}"
383
+ end
384
+
385
+ # This method returns raw command output as an array of lines after getting rid of unneeded whitespaces
386
+
387
+ def raw_command_output
388
+ @rawoutput
389
+ end
390
+
391
+ # This method returns current processing status (:pending, :processing, :completed, :failed, :aborted)
392
+
393
+ def status
394
+ @status
395
+ end
396
+
397
+ # This method returns current output parser status
398
+
399
+ def parser_status
400
+ @parser_status
401
+ end
402
+
403
+ # This method returns common metadata for input streams as a hash with keys being symbols representing each metadata downcased name
404
+
405
+ def common_input_metadata
406
+ @input_meta_common
407
+ end
408
+
409
+ # This method returns metadata for audio input stream as a hash with keys being symbols representing each metadata downcased name
410
+
411
+ def audio_input_metadata
412
+ @input_meta_audio
413
+ end
414
+
415
+ # This method returns metadata for video input stream as a hash with keys being symbols representing each metadata downcased name
416
+
417
+ def video_input_metadata
418
+ @input_meta_video
419
+ end
420
+
421
+ # This method returns common metadata for output streams as a hash with keys being symbols representing each metadata downcased name
422
+
423
+ def common_output_metadata
424
+ @output_meta_common
425
+ end
426
+
427
+ # This method returns metadata for audio output stream as a hash with keys being symbols representing each metadata downcased name
428
+
429
+ def audio_output_metadata
430
+ @output_meta_audio
431
+ end
432
+
433
+ # This method returns metadata for video output stream as a hash with keys being symbols representing each metadata downcased name
434
+
435
+ def video_output_metadata
436
+ @output_meta_video
437
+ end
438
+
439
+ # This method returns a hash which represents current processing status (eg. frames processed, time processed etc.) with keys being symbols representing each status value
440
+
441
+ def processing_status
442
+ @processing_status
443
+ end
444
+
445
+ # This method returns audio stream mapping information (input_format -> output_format)
446
+
447
+ def audio_stream_mapping
448
+ @stream_mapping_audio
449
+ end
450
+
451
+ # This method returns video stream mapping information (input_format -> output_format)
452
+
453
+ def video_stream_mapping
454
+ @stream_mapping_video
455
+ end
456
+
457
+ # This method returns FFmpeg version line
458
+
459
+ def ffmpeg_version_line
460
+ @ff_versionline
461
+ end
462
+
463
+ # This method returns FFmpeg build line
464
+
465
+ def ffmpeg_build_line
466
+ @ff_buildline
467
+ end
468
+
469
+ # This method returns input type detected by FFmpeg
470
+
471
+ def input_type
472
+ @input_type
473
+ end
474
+
475
+ # This method returns input duration
476
+
477
+ def input_duration
478
+ @input_duration
479
+ end
480
+
481
+ # This method returns start point of the input
482
+
483
+ def input_start
484
+ @input_start
485
+ end
486
+
487
+ # This method returns input bitrate (from the duration line)
488
+
489
+ def input_bitrate
490
+ @input_bitrate
491
+ end
492
+
493
+ # This method returns format of audio input
494
+
495
+ def audio_input_format
496
+ @audio_input_format
497
+ end
498
+
499
+ # This method returns frequency of audio input
500
+
501
+ def audio_input_frequency
502
+ @audio_input_freq
503
+ end
504
+
505
+ # This method returns channel mode (eg. mono, stereo) of audio input
506
+
507
+ def audio_input_channelmode
508
+ @audio_input_channelmode
509
+ end
510
+
511
+ # This method returns type of format of audio input
512
+
513
+ def audio_input_format_type
514
+ @audio_input_format_type
515
+ end
516
+
517
+ # This method returns bitrate of audio input (from input information line)
518
+
519
+ def audio_input_bitrate2
520
+ @audio_input_bitrate2
521
+ end
522
+
523
+ # This method returns format of audio output
524
+
525
+ def audio_output_format
526
+ @audio_output_format
527
+ end
528
+
529
+ # This method returns frequency of audio output
530
+
531
+ def audio_output_frequency
532
+ @audio_output_freq
533
+ end
534
+
535
+ # This method returns channel mode (eg. mono, stereo) of audio output
536
+
537
+ def audio_output_channelmode
538
+ @audio_output_channelmode
539
+ end
540
+
541
+ # This method returns type of format of audio output
542
+
543
+ def audio_output_format_type
544
+ @audio_output_format_type
545
+ end
546
+
547
+ # This method returns bitrate of audio output (from output information line)
548
+
549
+ def audio_output_bitrate2
550
+ @audio_output_bitrate2
551
+ end
552
+
553
+ # This method returns format of video input
554
+
555
+ def video_input_format
556
+ @video_input_format
557
+ end
558
+
559
+ # This method returns color space of video input
560
+
561
+ def video_input_colorspace
562
+ @video_input_colorspace
563
+ end
564
+
565
+ # This method returns resolution of video input
566
+
567
+ def video_input_resolution
568
+ @video_input_resolution
569
+ end
570
+
571
+ # This method returns additional information about video input as an array of values
572
+
573
+ def video_input_additional
574
+ @video_input_additional
575
+ end
576
+
577
+ # This method returns format of video output
578
+
579
+ def video_output_format
580
+ @video_output_format
581
+ end
582
+
583
+ # This method returns color space of video output
584
+
585
+ def video_output_colorspace
586
+ @video_output_colorspace
587
+ end
588
+
589
+ # This method returns resolution of video output
590
+
591
+ def video_output_resolution
592
+ @video_output_resolution
593
+ end
594
+
595
+ # This method returns additional information about video output as an array of values
596
+
597
+ def video_output_additional
598
+ @video_output_additional
599
+ end
600
+
601
+ # This method returns percentage of process completion
602
+
603
+ def processing_percentage
604
+ @processing_percentage || 0
605
+ end
606
+
607
+ # This method returns percentage of process completion formatted for output
608
+
609
+ def format_processing_percentage
610
+ @processing_percentage.nil? ? "0%" : @processing_percentage.to_s + "%"
611
+ end
612
+
613
+ # This method returns the exit status of the FFmpeg command
614
+
615
+ def command_exit_status
616
+ @exit_status
617
+ end
618
+
619
+ # This method kills processing thread and sets status to :aborted
620
+
621
+ def kill
622
+ @processing_thread.kill
623
+ @status = :aborted
624
+ end
625
+ end
626
+ end
data/lib/rff.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'output_reader'
2
+ require 'exceptions'
3
+ require 'processor'
4
+ require 'audio_handler'
5
+ require 'video_handler'
@@ -0,0 +1,187 @@
1
+ require_relative 'processor'
2
+
3
+ module RFF
4
+
5
+ # This class provides an "All video to HTML5" conversion functionality. It takes every compatible with FFmpeg video format and converts it to the three HTML5 video formats - mp4, ogv and webm. If the input is already in one of these formats it is only converted to the two other formats, because it can be used as one of HTML5 sources.
6
+
7
+ class VideoHandler
8
+
9
+ # This constructor initializes the class with the following arguments:
10
+ # * _input_ <b>(required)</b> - the full path to the input file
11
+ # * <i>output_path</i> - a path to place the output file in. Defaults to nil, which means that the input' s directory path is used
12
+ # * _quality_ - only affects video conversion. Sets the video conversion quality. Defaults to 5000k, which is tested value for good video conversion quality
13
+ # * <i>custom_args</i> - passes custom arguments to FFmpeg. Defaults to nil, which means no custom arguments are given
14
+ # * <i>recommended_audio_quality</i> - determines if recommended by FFmpeg community audio quality settings should be used. Defaults to true, which means audio conversion with good, recommended quality. Set to false if you are giving additional arguments that determine this quality.
15
+ # All of the arguments are passed on to underlying Processor instances. This method also determines input type, initializes processing percentage and creates needed Processor instances.
16
+
17
+ def initialize input, output_path=nil, quality="5000k", custom_args=nil, recommended_audio_quality=true
18
+ @input = input
19
+ @input_type = File.basename(@input).split(".")[1]
20
+ @output_path = output_path
21
+ @quality = quality
22
+ @custom_args = custom_args
23
+ @processing_percentage = 0
24
+ @processors = []
25
+ types = [:mp4, :ogv, :webm]
26
+ if types.include?(@input_type.to_sym)
27
+ types.delete(@input_type.to_sym)
28
+ end
29
+ types.each do |type|
30
+ @processors << RFF::Processor.new(@input, type, @output_path, @quality, @custom_args, recommended_audio_quality)
31
+ end
32
+ end
33
+
34
+ # This method fires all the Processor instances (conversion processes) in a separate thread at once. Then it counts the overall processing percentage from all the Processor instances as the process goes and sets it to 100% on finish
35
+
36
+ def fire_all
37
+ @processing_thread = Thread.new do |th|
38
+ begin
39
+ @processors.each do |proc|
40
+ proc.fire
41
+ #sleep(5)
42
+ end
43
+ status = :processing
44
+ while status != :done
45
+ donecount = 0
46
+ @processors.each do |proc|
47
+ #puts "Process status:" + proc.status.to_s
48
+ if proc.status == :completed || proc.status == :failed || proc.status == :aborted
49
+ donecount = donecount + 1
50
+ end
51
+ end
52
+ #puts "Done count: " + donecount.to_s
53
+ if donecount == @processors.count
54
+ status = :done
55
+ break
56
+ end
57
+ processing_percentage = 0
58
+ @processors.each do |proc|
59
+ processing_percentage += proc.processing_percentage
60
+ end
61
+ @processing_percentage = (processing_percentage.to_f/@processors.count).to_i
62
+ end
63
+ @processing_percentage = 100
64
+ rescue => e
65
+ puts "Caught exception: " + e.to_s
66
+ puts "Backtrace:"
67
+ puts e.backtrace
68
+ @status = :done
69
+ end
70
+ end
71
+ end
72
+
73
+ # This method fires all the Processor instances (conversion processes) in a separate thread sequentially - next Processor in the row is fired only after the Processor before finishes. It also counts the overall processing percentage from all the Processor instances as the process goes and sets it to 100% on finish
74
+
75
+ def fire_sequential
76
+ @processing_thread = Thread.new do |th|
77
+ begin
78
+ i = 0
79
+ @processors.each do |proc|
80
+ proc.fire
81
+ sleep(1)
82
+ while proc.status == :processing
83
+ if proc.processing_percentage != nil
84
+ @processing_percentage = (i*(100/@processors.count))+(proc.processing_percentage.to_f/@processors.count).to_i
85
+ end
86
+ end
87
+ i = i+1
88
+ #sleep(5)
89
+ end
90
+ @processing_percentage = 100
91
+ rescue => e
92
+ puts "Caught exception: " + e.to_s
93
+ puts "Backtrace:"
94
+ puts e.backtrace
95
+ @status = :done
96
+ end
97
+ end
98
+ end
99
+
100
+ # This method kills all the processes in Processor instances and its own processing thread
101
+
102
+ def killall
103
+ @processors.each do |proc|
104
+ proc.kill
105
+ end
106
+ @processing_thread.kill
107
+ end
108
+
109
+ # This method returns the "to MP4" Processor instance if it exists or nil otherwise
110
+
111
+ def mp4_processor
112
+ ret = nil
113
+ @processors.each do |proc|
114
+ if proc.output_type == :mp4
115
+ ret = proc
116
+ end
117
+ end
118
+ ret
119
+ end
120
+
121
+ # This method returns the "to OGV" Processor instance if it exists or nil otherwise
122
+
123
+ def ogv_processor
124
+ ret = nil
125
+ @processors.each do |proc|
126
+ if proc.output_type == :ogv
127
+ ret = proc
128
+ end
129
+ end
130
+ ret
131
+ end
132
+
133
+ # This method returns the "to WEBM" Processor instance if it exists or nil otherwise
134
+
135
+ def webm_processor
136
+ ret = nil
137
+ @processors.each do |proc|
138
+ if proc.output_type == :webm
139
+ ret = proc
140
+ end
141
+ end
142
+ ret
143
+ end
144
+
145
+ # This method returns full input path
146
+
147
+ def input
148
+ @input
149
+ end
150
+
151
+ # This method returns full output file name
152
+
153
+ def output_name
154
+ @output_name
155
+ end
156
+
157
+ # This method returns the path in which output file is saved
158
+
159
+ def output_path
160
+ @output_path
161
+ end
162
+
163
+ # This method returns used video quality
164
+
165
+ def quality
166
+ @quality
167
+ end
168
+
169
+ # This method returns custom arguments passed to FFmpeg
170
+
171
+ def custom_args
172
+ @custom_args
173
+ end
174
+
175
+ # This method returns percentage of process completion
176
+
177
+ def processing_percentage
178
+ @processing_percentage || 0
179
+ end
180
+
181
+ # This method returns percentage of process completion formatted for output
182
+
183
+ def format_processing_percentage
184
+ @processing_percentage.nil? ? "0%" : @processing_percentage.to_s + "%"
185
+ end
186
+ end
187
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rff
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.2'
5
+ platform: ruby
6
+ authors:
7
+ - Phitherek_
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This gem provides a simple Ruby interface to FFmpeg enabling users to
14
+ convert audio and video to HTML5 supported formats and monitor the process as it
15
+ goes.
16
+ email: phitherek@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/audio_handler.rb
22
+ - lib/exceptions.rb
23
+ - lib/output_reader.rb
24
+ - lib/processor.rb
25
+ - lib/rff.rb
26
+ - lib/video_handler.rb
27
+ homepage: https://github.com/Phitherek/rff
28
+ licenses: []
29
+ metadata: {}
30
+ post_install_message: The 0.2 version of rff has fixed a very important bug! Please
31
+ do not use version 0.1 of this gem!
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: 1.9.2
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements:
46
+ - ffmpeg
47
+ rubyforge_project:
48
+ rubygems_version: 2.2.2
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: A simple Ruby audio/video converter to HTML5 formats using FFmpeg
52
+ test_files: []