debprado-rvideo 0.9.6

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,42 @@
1
+ module RVideo
2
+ module Tools
3
+ class Ffmpeg2theora
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :raw_metadata
7
+
8
+ def tool_command
9
+ 'ffmpeg2theora'
10
+ end
11
+
12
+ def format_video_quality(params={})
13
+ bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
14
+ factor = (params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f)
15
+ case params[:video_quality]
16
+ when 'low'
17
+ " -v 1 "
18
+ when 'medium'
19
+ "-v 5 "
20
+ when 'high'
21
+ "-v 10 "
22
+ else
23
+ ""
24
+ end
25
+ end
26
+
27
+ def parse_result(result)
28
+ if m = /does not exist or has an unknown data format/.match(result)
29
+ raise TranscoderError::InvalidFile, "I/O error"
30
+ end
31
+
32
+ if m = /General output options/.match(result)
33
+ raise TranscoderError::InvalidCommand, "no command passed to ffmpeg2theora, or no output file specified"
34
+ end
35
+
36
+ @raw_metadata = result.empty? ? "No Results" : result
37
+ return true
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # Warning: If you're dealing with large files, you should consider using yamdi instead.
2
+ module RVideo
3
+ module Tools
4
+ class Flvtool2
5
+ include AbstractTool::InstanceMethods
6
+
7
+ attr_reader :raw_metadata
8
+
9
+ #attr_reader :has_key_frames, :cue_points, :audiodatarate, :has_video, :stereo, :can_seek_to_end, :framerate, :audiosamplerate, :videocodecid, :datasize, :lasttimestamp,
10
+ # :audiosamplesize, :audiosize, :has_audio, :audiodelay, :videosize, :metadatadate, :metadatacreator, :lastkeyframetimestamp, :height, :filesize, :has_metadata, :audiocodecid,
11
+ # :duration, :videodatarate, :has_cue_points, :width
12
+
13
+ def tool_command
14
+ 'flvtool2'
15
+ end
16
+
17
+ private
18
+
19
+ def parse_result(result)
20
+ if result.empty?
21
+ return true
22
+ end
23
+
24
+ if m = /ERROR: No such file or directory(.*)\n/.match(result)
25
+ raise TranscoderError::InputFileNotFound, m[0]
26
+ end
27
+
28
+ if m = /ERROR: IO is not a FLV stream/.match(result)
29
+ raise TranscoderError::InvalidFile, "input must be a valid FLV file"
30
+ end
31
+
32
+ if m = /Copyright.*Norman Timmler/i.match(result)
33
+ raise TranscoderError::InvalidCommand, "command printed flvtool2 help text (and presumably didn't execute)"
34
+ end
35
+
36
+ if m = /ERROR: undefined method .?timestamp.? for nil/.match(result)
37
+ raise TranscoderError::InvalidFile, "Output file was empty (presumably)"
38
+ end
39
+
40
+ if m = /\A---(.*)...\Z/m.match(result)
41
+ @raw_metadata = m[0]
42
+ return true
43
+ end
44
+
45
+ raise TranscoderError::UnexpectedResult, result
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,106 @@
1
+ module RVideo
2
+ module Tools
3
+ class Mencoder
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :frame, :size, :time, :bitrate, :video_size, :audio_size, :output_fps
7
+
8
+ def tool_command
9
+ 'mencoder'
10
+ end
11
+
12
+ def format_fps(params={})
13
+ " -ofps #{params[:fps]}"
14
+ end
15
+
16
+ def format_resolution(params={})
17
+ p = " -vf scale=#{params[:scale][:width]}:#{params[:scale][:height]}"
18
+ if params[:letterbox]
19
+ p += ",expand=#{params[:letterbox][:width]}:#{params[:letterbox][:height]}"
20
+ end
21
+ p += ",harddup"
22
+ end
23
+
24
+ def format_audio_channels(params={})
25
+ " -channels #{params[:channels]}"
26
+ end
27
+
28
+ def format_audio_bit_rate(params={})
29
+ " br=#{params[:bit_rate]}:"
30
+ end
31
+
32
+ def format_audio_sample_rate(params={})
33
+ " -srate #{params[:sample_rate]}"
34
+ end
35
+
36
+ def format_video_quality(params={})
37
+ bitrate = params[:video_bit_rate].blank? ? nil : params[:video_bit_rate]
38
+ factor = (params[:scale][:width].to_f * params[:scale][:height].to_f * params[:fps].to_f)
39
+ case params[:video_quality]
40
+ when 'low'
41
+ bitrate ||= (factor / 12000).to_i
42
+ " -x264encopts threads=auto:subq=1:me=dia:frameref=1:crf=30:bitrate=#{bitrate} "
43
+ when 'medium'
44
+ bitrate ||= (factor / 9000).to_i
45
+ " -x264encopts threads=auto:subq=3:me=hex:frameref=2:crf=22:bitrate=#{bitrate} "
46
+ when 'high'
47
+ bitrate ||= (factor / 3600).to_i
48
+ " -x264encopts threads=auto:subq=6:me=dia:frameref=3:crf=18:bitrate=#{bitrate} "
49
+ else
50
+ ""
51
+ end
52
+ end
53
+
54
+
55
+
56
+ private
57
+
58
+ def parse_result(result)
59
+ if m = /Exiting.*No output file specified/.match(result)
60
+ raise TranscoderError::InvalidCommand, "no command passed to mencoder, or no output file specified"
61
+ end
62
+
63
+ if m = /counldn't set specified parameters, exiting/.match(result)
64
+ raise TranscoderError::InvalidCommand, "a combination of the recipe parameters is invalid: #{result}"
65
+ end
66
+
67
+ if m = /Sorry, this file format is not recognized\/supported/.match(result)
68
+ raise TranscoderError::InvalidFile, "unknown format"
69
+ end
70
+
71
+ if m = /Cannot open file\/device./.match(result)
72
+ raise TranscoderError::InvalidFile, "I/O error"
73
+ end
74
+
75
+ if m = /File not found:$/.match(result)
76
+ raise TranscoderError::InvalidFile, "I/O error"
77
+ end
78
+
79
+ video_details = result.match /Video stream:(.*)$/
80
+ if video_details
81
+ @bitrate = sanitary_match(/Video stream:\s*([0-9.]*)/, video_details[0])
82
+ @video_size = sanitary_match(/size:\s*(\d*)\s*(\S*)/, video_details[0])
83
+ @time = sanitary_match(/bytes\s*([0-9.]*)/, video_details[0])
84
+ @frame = sanitary_match(/secs\s*(\d*)/, video_details[0])
85
+ @output_fps = (@frame.to_f / @time.to_f).round_to(3)
86
+ elsif result =~ /Video stream is mandatory/
87
+ raise TranscoderError::InvalidFile, "Video stream required, and no video stream found"
88
+ end
89
+
90
+ audio_details = result.match /Audio stream:(.*)$/
91
+ if audio_details
92
+ @audio_size = sanitary_match(/size:\s*(\d*)\s*(\S*)/, audio_details[0])
93
+ else
94
+ @audio_size = 0
95
+ end
96
+ @size = (@video_size.to_i + @audio_size.to_i).to_s
97
+ end
98
+
99
+ def sanitary_match(regexp, string)
100
+ match = regexp.match(string)
101
+ return match[1] if match
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ module RVideo
2
+ module Tools
3
+ class Mp4box
4
+ include AbstractTool::InstanceMethods
5
+ attr_reader :raw_metadata
6
+
7
+ def tool_command
8
+ 'MP4Box'
9
+ end
10
+
11
+ private
12
+
13
+ def parse_result(result)
14
+ #currently, no useful info returned in result to determine if successful or not
15
+ @raw_metadata = result.empty? ? "No Results" : result
16
+ return true
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ module RVideo
2
+ module Tools
3
+ class Mp4creator
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :raw_metadata
7
+
8
+ def tool_command
9
+ 'mp4creator'
10
+ end
11
+
12
+ def format_fps(params={})
13
+ " -rate=#{params[:fps]}"
14
+ end
15
+
16
+ def parse_result(result)
17
+ if m = /can't open file/.match(result)
18
+ raise TranscoderError::InvalidFile, "I/O error"
19
+ end
20
+
21
+ if m = /unknown file type/.match(result)
22
+ raise TranscoderError::InvalidFile, "I/O error"
23
+ end
24
+
25
+ if @options['output_file'] && !File.exist?(@options['output_file'])
26
+ raise TranscoderError::UnexpectedResult, "An unknown error has occured with mp4creator:#{result}"
27
+ end
28
+
29
+ @raw_metadata = result.empty? ? "No Results" : result
30
+ return true
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ module RVideo
2
+ module Tools
3
+ class Mplayer
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :raw_metadata
7
+
8
+ def tool_command
9
+ 'mplayer'
10
+ end
11
+
12
+ def parse_result(result)
13
+ if m = /This will likely crash/.match(result)
14
+ raise TranscoderError::InvalidFile, "unknown format"
15
+ end
16
+
17
+ if m = /Failed to open/.match(result)
18
+ raise TranscoderError::InvalidFile, "I/O error"
19
+ end
20
+
21
+ if m = /File not found/.match(result)
22
+ raise TranscoderError::InvalidFile, "I/O error"
23
+ end
24
+
25
+ @raw_metadata = result.empty? ? "No Results" : result
26
+ return true
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ module RVideo
2
+ module Tools
3
+ class Yamdi
4
+ include AbstractTool::InstanceMethods
5
+
6
+ attr_reader :raw_metadata
7
+
8
+ def tool_command
9
+ 'yamdi'
10
+ end
11
+
12
+ private
13
+
14
+ def parse_result(result)
15
+ if result.empty?
16
+ return true
17
+ end
18
+
19
+ if m = /Couldn't stat on (.*)/.match(result)
20
+ raise TranscoderError::InputFileNotFound, m[0]
21
+ end
22
+
23
+ if m = /The input file is not a FLV./.match(result)
24
+ raise TranscoderError::InvalidFile, "input must be a valid FLV file"
25
+ end
26
+
27
+ if m = /\(c\) \d{4} Ingo Oppermann/i.match(result)
28
+ raise TranscoderError::InvalidCommand, "command printed yamdi help text (and presumably didn't execute)"
29
+ end
30
+
31
+ if m = /Please provide at least one output file/i.match(result)
32
+ raise TranscoderError::InvalidCommand, "command did not contain a valid output file. Yamdi expects a -o switch."
33
+ end
34
+
35
+ if m = /ERROR: undefined method .?timestamp.? for nil/.match(result)
36
+ raise TranscoderError::InvalidFile, "Output file was empty (presumably)"
37
+ end
38
+
39
+ raise TranscoderError::UnexpectedResult, result
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,138 @@
1
+ module RVideo # :nodoc:
2
+ class Transcoder
3
+
4
+ attr_reader :executed_commands, :processed, :errors, :warnings, :total_time
5
+
6
+ #
7
+ # To transcode a video, initialize a Transcoder object:
8
+ #
9
+ # transcoder = RVideo::Transcoder.new("/path/to/input.mov")
10
+ #
11
+ # Then pass a recipe and valid options to the execute method
12
+ #
13
+ # recipe = "ffmpeg -i $input_file$ -ar 22050 -ab 64 -f flv -r 29.97 -s"
14
+ # recipe += " $resolution$ -y $output_file$"
15
+ # recipe += "\nflvtool2 -U $output_file$"
16
+ # begin
17
+ # transcoder.execute(recipe, {:output_file => "/path/to/output.flv",
18
+ # :resolution => "640x360"})
19
+ # rescue TranscoderError => e
20
+ # puts "Unable to transcode file: #{e.class} - #{e.message}"
21
+ # end
22
+ #
23
+ # If the job succeeds, you can access the metadata of the input and output
24
+ # files with:
25
+ #
26
+ # transcoder.original # RVideo::Inspector object
27
+ # transcoder.processed # RVideo::Inspector object
28
+ #
29
+ # If the transcoding succeeds, the file may still have problems. RVideo
30
+ # will populate an errors array if the duration of the processed video
31
+ # differs from the duration of the original video, or if the processed
32
+ # file is unreadable.
33
+ #
34
+
35
+ def initialize(input_file = nil)
36
+ # Allow a nil input_file for backwards compatibility. (Change at 1.0?)
37
+ check_input_file(input_file)
38
+
39
+ @input_file = input_file
40
+ @executed_commands = []
41
+ @errors = []
42
+ @warnings = []
43
+ end
44
+
45
+ def original
46
+ @original ||= Inspector.new(:file => @input_file)
47
+ end
48
+
49
+ #
50
+ # Configure logging. Pass a valid Ruby logger object.
51
+ #
52
+ # logger = Logger.new(STDOUT)
53
+ # RVideo::Transcoder.logger = logger
54
+ #
55
+
56
+ def self.logger=(l)
57
+ @logger = l
58
+ end
59
+
60
+ def self.logger
61
+ if @logger.nil?
62
+ @logger = Logger.new('/dev/null')
63
+ end
64
+
65
+ @logger
66
+ end
67
+
68
+ #
69
+ # Requires a command and a hash of various interpolated options. The
70
+ # command should be one or more lines of transcoder tool commands (e.g.
71
+ # ffmpeg, flvtool2). Interpolate options by adding $option_key$ to the
72
+ # recipe, and passing :option_key => "value" in the options hash.
73
+ #
74
+ # recipe = "ffmpeg -i $input_file$ -ar 22050 -ab 64 -f flv -r 29.97
75
+ # recipe += "-s $resolution$ -y $output_file$"
76
+ # recipe += "\nflvtool2 -U $output_file$"
77
+ #
78
+ # transcoder = RVideo::Transcoder.new("/path/to/input.mov")
79
+ # begin
80
+ # transcoder.execute(recipe, {:output_file => "/path/to/output.flv", :resolution => "320x240"})
81
+ # rescue TranscoderError => e
82
+ # puts "Unable to transcode file: #{e.class} - #{e.message}"
83
+ # end
84
+ #
85
+
86
+ def execute(task, options = {})
87
+ t1 = Time.now
88
+
89
+ if @input_file.nil?
90
+ @input_file = options[:input_file]
91
+ end
92
+
93
+ Transcoder.logger.info("\nNew transcoder job\n================\nTask: #{task}\nOptions: #{options.inspect}")
94
+ parse_and_execute(task, options)
95
+ @processed = Inspector.new(:file => options[:output_file])
96
+ result = check_integrity
97
+ Transcoder.logger.info("\nFinished task. Total errors: #{@errors.size}\n")
98
+ @total_time = Time.now - t1
99
+ result
100
+ rescue TranscoderError => e
101
+ raise e
102
+ rescue Exception => e
103
+ Transcoder.logger.error("[ERROR] Unhandled RVideo exception: #{e.class} - #{e.message}\n#{e.backtrace}")
104
+ raise TranscoderError::UnknownError, "Unexpected RVideo error: #{e.message} (#{e.class})"
105
+ end
106
+
107
+ private
108
+
109
+ def check_input_file(input_file)
110
+ if input_file and !FileTest.exist?(input_file.gsub("\"",""))
111
+ raise TranscoderError::InputFileNotFound, "File not found (#{input_file})"
112
+ end
113
+ end
114
+
115
+ def check_integrity
116
+ precision = 1.1
117
+ if @processed.invalid?
118
+ @errors << "Output file invalid"
119
+ elsif (@processed.duration >= (original.duration * precision) or @processed.duration <= (original.duration / precision))
120
+ @errors << "Original file has a duration of #{original.duration}, but processed file has a duration of #{@processed.duration}"
121
+ end
122
+ return @errors.size == 0
123
+ end
124
+
125
+ def parse_and_execute(task, options = {})
126
+ raise TranscoderError::ParameterError, "Expected a recipe class (as a string), but got a #{task.class.to_s} (#{task})" unless task.is_a? String
127
+ options = options.merge(:input_file => @input_file)
128
+
129
+ commands = task.split("\n").compact
130
+ commands.each do |c|
131
+ tool = Tools::AbstractTool.assign(c, options)
132
+ tool.original = @original
133
+ tool.execute
134
+ executed_commands << tool
135
+ end
136
+ end
137
+ end
138
+ end