ez_video 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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})", e.backtrace
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