video_transcoding 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +573 -0
- data/bin/convert-video +290 -0
- data/bin/detect-crop +130 -0
- data/bin/query-handbrake-log +248 -0
- data/bin/transcode-video +1205 -0
- data/lib/video_transcoding.rb +21 -0
- data/lib/video_transcoding/cli.rb +53 -0
- data/lib/video_transcoding/console.rb +46 -0
- data/lib/video_transcoding/copyright.rb +9 -0
- data/lib/video_transcoding/crop.rb +110 -0
- data/lib/video_transcoding/errors.rb +10 -0
- data/lib/video_transcoding/ffmpeg.rb +47 -0
- data/lib/video_transcoding/handbrake.rb +56 -0
- data/lib/video_transcoding/media.rb +323 -0
- data/lib/video_transcoding/mkvmerge.rb +35 -0
- data/lib/video_transcoding/mkvpropedit.rb +35 -0
- data/lib/video_transcoding/mp4track.rb +35 -0
- data/lib/video_transcoding/mplayer.rb +33 -0
- data/lib/video_transcoding/tool.rb +52 -0
- data/lib/video_transcoding/version.rb +9 -0
- data/video_transcoding.gemspec +22 -0
- metadata +73 -0
data/bin/convert-video
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
#!/usr/bin/env ruby -W
|
2
|
+
#
|
3
|
+
# convert-video
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2015 Don Melton
|
6
|
+
#
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
9
|
+
|
10
|
+
require 'video_transcoding/cli'
|
11
|
+
|
12
|
+
module VideoTranscoding
|
13
|
+
class Command
|
14
|
+
include CLI
|
15
|
+
|
16
|
+
def about
|
17
|
+
<<HERE
|
18
|
+
convert-video #{VERSION}
|
19
|
+
#{COPYRIGHT}
|
20
|
+
HERE
|
21
|
+
end
|
22
|
+
|
23
|
+
def usage
|
24
|
+
<<HERE
|
25
|
+
Convert video file from Matroska to MP4 format or from MP4 to Matroksa format
|
26
|
+
WITHOUT TRANSCODING VIDEO.
|
27
|
+
|
28
|
+
Usage: #{$PROGRAM_NAME} [OPTION]... [FILE]...
|
29
|
+
|
30
|
+
-o, --output DIRECTORY
|
31
|
+
set output path
|
32
|
+
(default: input filename with output format extension
|
33
|
+
in current working directory)
|
34
|
+
--use-m4v use `.m4v` extension instead of `.mp4` for MP4 output
|
35
|
+
|
36
|
+
-v, --verbose increase diagnostic information
|
37
|
+
-q, --quiet decrease " "
|
38
|
+
|
39
|
+
-h, --help display this help and exit
|
40
|
+
--version output version information and exit
|
41
|
+
|
42
|
+
Requires `HandBrakeCLI`, `mp4track`, `ffmpeg` and `mkvmerge`.
|
43
|
+
HERE
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
super
|
48
|
+
@output = nil
|
49
|
+
@use_m4v = false
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_options(opts)
|
53
|
+
opts.on('-o', '--output ARG') { |arg| @output = arg }
|
54
|
+
opts.on('--use-m4v') { @use_m4v = true }
|
55
|
+
end
|
56
|
+
|
57
|
+
def configure
|
58
|
+
unless @output.nil? or File.directory? @output
|
59
|
+
fail UsageError, "not a directory: #{@output}"
|
60
|
+
end
|
61
|
+
|
62
|
+
HandBrake.setup
|
63
|
+
MP4track.setup
|
64
|
+
FFmpeg.setup
|
65
|
+
MKVmerge.setup
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_input(arg)
|
69
|
+
Console.info "Processing: #{arg}..."
|
70
|
+
media = Media.new(path: arg, allow_directory: false)
|
71
|
+
Console.debug media.info
|
72
|
+
output = resolve_output(media)
|
73
|
+
fail "no H.264 format video track: #{arg}" unless media.info[:h264]
|
74
|
+
fail "no video stream: #{arg}" unless media.info.has_key? :stream
|
75
|
+
|
76
|
+
if media.info[:mkv]
|
77
|
+
convert_to_mp4(media, output)
|
78
|
+
else
|
79
|
+
convert_to_mkv(media, output)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def resolve_output(media)
|
84
|
+
if media.info[:mkv]
|
85
|
+
ext = @use_m4v ? '.m4v' : '.mp4'
|
86
|
+
elsif media.info[:mp4]
|
87
|
+
ext = '.mkv'
|
88
|
+
else
|
89
|
+
fail "unsupported input container format: #{media.path}"
|
90
|
+
end
|
91
|
+
|
92
|
+
output = File.basename(media.path, '.*') + ext
|
93
|
+
|
94
|
+
unless @output.nil?
|
95
|
+
output = File.absolute_path(@output) + File::SEPARATOR + output
|
96
|
+
end
|
97
|
+
|
98
|
+
fail "output file exists: #{media.path}" if File.exist? output
|
99
|
+
output
|
100
|
+
end
|
101
|
+
|
102
|
+
def convert_to_mp4(media, output)
|
103
|
+
map_options = [
|
104
|
+
'-map', "0:#{media.info[:stream]}"
|
105
|
+
]
|
106
|
+
copy_options = [
|
107
|
+
'-c:v', 'copy'
|
108
|
+
]
|
109
|
+
|
110
|
+
unless media.info[:audio].empty?
|
111
|
+
track_order = media.info[:audio].keys
|
112
|
+
stream = 0
|
113
|
+
|
114
|
+
if media.info[:audio][1][:channels] > 2.0
|
115
|
+
if track_order.size > 1
|
116
|
+
if media.info[:audio][1][:language] == media.info[:audio][2][:language] and
|
117
|
+
media.info[:audio][2][:format] == 'AAC' and
|
118
|
+
media.info[:audio][2][:channels] <= 2.0
|
119
|
+
first = track_order[0]
|
120
|
+
track_order[0] = track_order[1]
|
121
|
+
track_order[1] = first
|
122
|
+
end
|
123
|
+
else
|
124
|
+
map_options.concat([
|
125
|
+
'-map', "0:#{media.info[:audio][1][:stream]}"
|
126
|
+
])
|
127
|
+
|
128
|
+
if FFmpeg.aac_encoder == 'aac'
|
129
|
+
copy_options.concat([
|
130
|
+
'-strict', 'experimental'
|
131
|
+
])
|
132
|
+
end
|
133
|
+
|
134
|
+
copy_options.concat([
|
135
|
+
'-ac', '2',
|
136
|
+
'-c:a:0', FFmpeg.aac_encoder,
|
137
|
+
'-b:a:0', '160k'
|
138
|
+
])
|
139
|
+
stream += 1
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
track_order.each do |track|
|
144
|
+
map_options.concat([
|
145
|
+
'-map', "0:#{media.info[:audio][track][:stream]}"
|
146
|
+
])
|
147
|
+
|
148
|
+
if media.info[:audio][track][:channels] > 2.0 and
|
149
|
+
media.info[:audio][track][:format] != 'AC3'
|
150
|
+
copy_options.concat([
|
151
|
+
'-ac', '6',
|
152
|
+
"-c:a:#{stream}", 'ac3',
|
153
|
+
"-b:a:#{stream}", '384k'
|
154
|
+
])
|
155
|
+
elsif media.info[:audio][track][:channels] <= 2.0 and
|
156
|
+
media.info[:audio][track][:format] != 'AAC' and
|
157
|
+
media.info[:audio][track][:format] != 'AC3'
|
158
|
+
if FFmpeg.aac_encoder == 'aac'
|
159
|
+
copy_options.concat([
|
160
|
+
'-strict', 'experimental'
|
161
|
+
])
|
162
|
+
end
|
163
|
+
|
164
|
+
copy_options.concat([
|
165
|
+
'-ac', '2',
|
166
|
+
"-c:a:#{stream}", FFmpeg.aac_encoder,
|
167
|
+
"-b:a:#{stream}", '160k'
|
168
|
+
])
|
169
|
+
else
|
170
|
+
copy_options.concat([
|
171
|
+
"-c:a:#{stream}", 'copy'
|
172
|
+
])
|
173
|
+
end
|
174
|
+
|
175
|
+
stream += 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
ffmpeg_command = [
|
180
|
+
FFmpeg.command_name,
|
181
|
+
'-hide_banner',
|
182
|
+
'-nostdin',
|
183
|
+
'-i', media.path,
|
184
|
+
*map_options,
|
185
|
+
*copy_options,
|
186
|
+
output
|
187
|
+
]
|
188
|
+
Console.debug ffmpeg_command
|
189
|
+
Console.info 'Converting with ffmpeg...'
|
190
|
+
|
191
|
+
begin
|
192
|
+
IO.popen(ffmpeg_command, :err=>[:child, :out]) do |io|
|
193
|
+
Signal.trap 'INT' do
|
194
|
+
Process.kill 'INT', io.pid
|
195
|
+
end
|
196
|
+
|
197
|
+
io.each_char do |char|
|
198
|
+
print char
|
199
|
+
end
|
200
|
+
end
|
201
|
+
rescue SystemCallError => e
|
202
|
+
raise "conversion failed: #{e}"
|
203
|
+
end
|
204
|
+
|
205
|
+
fail "conversion failed: #{media.path}" unless $CHILD_STATUS.exitstatus == 0
|
206
|
+
mp4_media = Media.new(path: output, allow_directory: false)
|
207
|
+
Console.debug media.info
|
208
|
+
|
209
|
+
mp4_media.info[:audio].each do |track, info|
|
210
|
+
if track == 1 and not info[:default]
|
211
|
+
enabled = 'true'
|
212
|
+
elsif track != 1 and info[:default]
|
213
|
+
enabled = 'false'
|
214
|
+
else
|
215
|
+
enabled = nil
|
216
|
+
end
|
217
|
+
|
218
|
+
unless enabled.nil?
|
219
|
+
begin
|
220
|
+
IO.popen([
|
221
|
+
MP4track.command_name,
|
222
|
+
'--track-index', track.to_s,
|
223
|
+
'--enabled', enabled,
|
224
|
+
output,
|
225
|
+
], :err=>[:child, :out]) do |io|
|
226
|
+
io.each do |line|
|
227
|
+
Console.debug line
|
228
|
+
end
|
229
|
+
end
|
230
|
+
rescue SystemCallError => e
|
231
|
+
raise "adjusting audio enabled failed: #{e}"
|
232
|
+
end
|
233
|
+
|
234
|
+
fail "adjusting audio enabled failed: #{output}" unless $CHILD_STATUS.exitstatus == 0
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def convert_to_mkv(media, output)
|
240
|
+
track_order = ['0:' + media.info[:stream].to_s]
|
241
|
+
track_name_options = []
|
242
|
+
|
243
|
+
media.info[:audio].each do |_, info|
|
244
|
+
track_order << '0:' + info[:stream].to_s
|
245
|
+
track_name_options.concat([
|
246
|
+
'--track-name', "#{info[:stream]}:#{info[:name]}"
|
247
|
+
])
|
248
|
+
end
|
249
|
+
|
250
|
+
if track_order.size > 2 and
|
251
|
+
media.info[:audio][1][:language] == media.info[:audio][2][:language] and
|
252
|
+
media.info[:audio][1][:format] == 'AAC' and
|
253
|
+
media.info[:audio][1][:channels] <= 2.0 and
|
254
|
+
media.info[:audio][2][:channels] > 2.0
|
255
|
+
first = track_order[1]
|
256
|
+
track_order[1] = track_order[2]
|
257
|
+
track_order[2] = first
|
258
|
+
end
|
259
|
+
|
260
|
+
mkvmerge_command = [
|
261
|
+
MKVmerge.command_name,
|
262
|
+
'--output', output,
|
263
|
+
'--track-order', track_order.join(','),
|
264
|
+
'--disable-track-statistics-tags'
|
265
|
+
]
|
266
|
+
mkvmerge_command += track_name_options unless track_name_options.empty?
|
267
|
+
mkvmerge_command << media.path
|
268
|
+
Console.debug mkvmerge_command
|
269
|
+
Console.info 'Converting with mkvmerge...'
|
270
|
+
|
271
|
+
begin
|
272
|
+
IO.popen(mkvmerge_command, :err=>[:child, :out]) do |io|
|
273
|
+
Signal.trap 'INT' do
|
274
|
+
Process.kill 'INT', io.pid
|
275
|
+
end
|
276
|
+
|
277
|
+
io.each_char do |char|
|
278
|
+
print char
|
279
|
+
end
|
280
|
+
end
|
281
|
+
rescue SystemCallError => e
|
282
|
+
raise "conversion failed: #{e}"
|
283
|
+
end
|
284
|
+
|
285
|
+
fail "conversion failed: #{media.path}" if $CHILD_STATUS.exitstatus == 2
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
VideoTranscoding::Command.new.run
|
data/bin/detect-crop
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
#!/usr/bin/env ruby -W
|
2
|
+
#
|
3
|
+
# detect-crop
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2015 Don Melton
|
6
|
+
#
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
9
|
+
|
10
|
+
require 'video_transcoding/cli'
|
11
|
+
|
12
|
+
module VideoTranscoding
|
13
|
+
class Command
|
14
|
+
include CLI
|
15
|
+
|
16
|
+
def about
|
17
|
+
<<HERE
|
18
|
+
detect-crop #{VERSION}
|
19
|
+
#{COPYRIGHT}
|
20
|
+
HERE
|
21
|
+
end
|
22
|
+
|
23
|
+
def usage
|
24
|
+
<<HERE
|
25
|
+
Detect optimal crop values for video file or disc image directory.
|
26
|
+
|
27
|
+
Usage: #{$PROGRAM_NAME} [OPTION]... [FILE|DIRECTORY]...
|
28
|
+
|
29
|
+
--scan list title(s) and tracks in video media and exit
|
30
|
+
--title NUMBER select numbered title in video media
|
31
|
+
(default: main feature or first listed)
|
32
|
+
--no-constrain don't constrain crop to optimal shape
|
33
|
+
--values-only output only unambiguous crop values, not commands
|
34
|
+
|
35
|
+
-v, --verbose increase diagnostic information
|
36
|
+
-q, --quiet decrease " "
|
37
|
+
|
38
|
+
-h, --help display this help and exit
|
39
|
+
--version output version information and exit
|
40
|
+
|
41
|
+
Requires `HandBrakeCLI` and `mplayer`.
|
42
|
+
HERE
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
super
|
47
|
+
@scan = false
|
48
|
+
@title = nil
|
49
|
+
@constrain = true
|
50
|
+
@values_only = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def define_options(opts)
|
54
|
+
opts.on('--scan') { @scan = true }
|
55
|
+
opts.on('--title ARG', Integer) { |arg| @title = arg }
|
56
|
+
opts.on('--no-constrain') { @constrain = false }
|
57
|
+
opts.on('--values-only') { @values_only = true }
|
58
|
+
end
|
59
|
+
|
60
|
+
def configure
|
61
|
+
HandBrake.setup
|
62
|
+
MPlayer.setup
|
63
|
+
end
|
64
|
+
|
65
|
+
def process_input(arg)
|
66
|
+
Console.info "Processing: #{arg}..."
|
67
|
+
|
68
|
+
if @scan
|
69
|
+
media = Media.new(path: arg, title: @title)
|
70
|
+
Console.debug media.info
|
71
|
+
puts media.summary
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
media = Media.new(path: arg, title: @title, autocrop: true, extended: false)
|
76
|
+
Console.debug media.info
|
77
|
+
width, height = media.info[:width], media.info[:height]
|
78
|
+
directory = media.info[:directory]
|
79
|
+
hb_crop = media.info[:autocrop]
|
80
|
+
hb_crop = Crop.constrain(hb_crop, width, height) if @constrain
|
81
|
+
shell_path = arg.shellescape
|
82
|
+
|
83
|
+
print_transcode = ->(crop) do
|
84
|
+
puts
|
85
|
+
print 'transcode-video '
|
86
|
+
print "--title #{media.info[:title]} " if directory
|
87
|
+
puts "--crop #{Crop.handbrake_string(crop)} #{shell_path}"
|
88
|
+
puts
|
89
|
+
end
|
90
|
+
|
91
|
+
print_all = ->(crop) do
|
92
|
+
str = Crop.mplayer_string(crop, width, height)
|
93
|
+
puts
|
94
|
+
puts "mplayer -really-quiet -nosound -vf rectangle=#{str} #{shell_path}"
|
95
|
+
puts "mplayer -really-quiet -nosound -vf crop=#{str} #{shell_path}"
|
96
|
+
print_transcode.call crop
|
97
|
+
end
|
98
|
+
|
99
|
+
if directory
|
100
|
+
if @values_only
|
101
|
+
puts Crop.handbrake_string(hb_crop)
|
102
|
+
else
|
103
|
+
print_transcode.call hb_crop
|
104
|
+
end
|
105
|
+
else
|
106
|
+
mp_crop = Crop.detect(arg, media.info[:duration], width, height)
|
107
|
+
mp_crop = Crop.constrain(mp_crop, width, height) if @constrain
|
108
|
+
|
109
|
+
if hb_crop == mp_crop
|
110
|
+
if @values_only
|
111
|
+
puts Crop.handbrake_string(hb_crop)
|
112
|
+
else
|
113
|
+
Console.info 'Results from HandBrakeCLI and mplayer are identical...'
|
114
|
+
print_all.call hb_crop
|
115
|
+
end
|
116
|
+
else
|
117
|
+
fail "results from HandBrakeCLI and mplayer differ: #{arg}" if @values_only
|
118
|
+
Console.warn 'Results differ...'
|
119
|
+
puts
|
120
|
+
puts '# From HandBrakeCLI:'
|
121
|
+
print_all.call hb_crop
|
122
|
+
puts '# From mplayer:'
|
123
|
+
print_all.call mp_crop
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
VideoTranscoding::Command.new.run
|
@@ -0,0 +1,248 @@
|
|
1
|
+
#!/usr/bin/env ruby -W
|
2
|
+
#
|
3
|
+
# query-handbrake-log
|
4
|
+
#
|
5
|
+
# Copyright (c) 2013-2015 Don Melton
|
6
|
+
#
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
9
|
+
|
10
|
+
require 'abbrev'
|
11
|
+
require 'video_transcoding/cli'
|
12
|
+
|
13
|
+
module VideoTranscoding
|
14
|
+
class Command
|
15
|
+
include CLI
|
16
|
+
|
17
|
+
def about
|
18
|
+
<<HERE
|
19
|
+
query-handbrake-log #{VERSION}
|
20
|
+
#{COPYRIGHT}
|
21
|
+
HERE
|
22
|
+
end
|
23
|
+
|
24
|
+
def usage
|
25
|
+
<<HERE
|
26
|
+
Report information from HandBrake-generated `.log` files.
|
27
|
+
|
28
|
+
Usage: #{$PROGRAM_NAME} INFO [OPTION]... [FILE|DIRECTORY]...
|
29
|
+
|
30
|
+
Information types:
|
31
|
+
t, time time spent during transcoding
|
32
|
+
(sorted from short to long)
|
33
|
+
s, speed speed of transcoding in frames per second
|
34
|
+
(sorted from fast to slow)
|
35
|
+
b, bitrate video bitrate of transcoded output
|
36
|
+
(sorted from low to high)
|
37
|
+
r, ratefactor average P-frame quantizer for transcoding
|
38
|
+
(sorted from low to high)
|
39
|
+
|
40
|
+
Options:
|
41
|
+
--reverse reverse direction of sort
|
42
|
+
|
43
|
+
-v, --verbose increase diagnostic information
|
44
|
+
-q, --quiet decrease " "
|
45
|
+
|
46
|
+
-h, --help display this help and exit
|
47
|
+
--version output version information and exit
|
48
|
+
HERE
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize
|
52
|
+
super
|
53
|
+
@reverse = false
|
54
|
+
@info = nil
|
55
|
+
@logs = []
|
56
|
+
@paths = []
|
57
|
+
end
|
58
|
+
|
59
|
+
def define_options(opts)
|
60
|
+
opts.on('--reverse') { @reverse = true }
|
61
|
+
end
|
62
|
+
|
63
|
+
def configure
|
64
|
+
fail UsageError, 'missing argument' if ARGV.empty?
|
65
|
+
arg = $ARGV.shift
|
66
|
+
|
67
|
+
@info = case arg
|
68
|
+
when 'time', 't'
|
69
|
+
:time
|
70
|
+
when 'speed', 's'
|
71
|
+
@reverse = !@reverse
|
72
|
+
:speed
|
73
|
+
when 'bitrate', 'b'
|
74
|
+
:bitrate
|
75
|
+
when 'ratefactor', 'r'
|
76
|
+
:ratefactor
|
77
|
+
else
|
78
|
+
fail UsageError, "invalid information type: #{arg}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def process_input(arg)
|
83
|
+
Console.info "Processing: #{arg}..."
|
84
|
+
input = File.absolute_path(arg)
|
85
|
+
|
86
|
+
if File.directory? input
|
87
|
+
logs = Dir[input + File::SEPARATOR + '*.log']
|
88
|
+
fail "does not contain `.log` files: #{input}" if logs.empty?
|
89
|
+
@logs += logs
|
90
|
+
@paths << input
|
91
|
+
else
|
92
|
+
fail "not a `.log` file: #{input}" unless File.extname(input) == '.log'
|
93
|
+
@logs << File.absolute_path(input)
|
94
|
+
@paths << File.dirname(input)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def complete
|
99
|
+
@logs.uniq!
|
100
|
+
@paths.uniq!
|
101
|
+
|
102
|
+
if @paths.size > 1
|
103
|
+
prefix = File.dirname(@paths.abbrev.keys.min_by { |key| key.size }) + File::SEPARATOR
|
104
|
+
else
|
105
|
+
prefix = ''
|
106
|
+
end
|
107
|
+
|
108
|
+
report = []
|
109
|
+
|
110
|
+
@logs.each do |log|
|
111
|
+
Console.info "Reading: #{log}..."
|
112
|
+
video = File.basename(log, '.log')
|
113
|
+
video += " (#{File.dirname(log).sub(prefix, '')})" unless prefix.empty?
|
114
|
+
found = false
|
115
|
+
count = 0
|
116
|
+
duration_line = nil
|
117
|
+
rate_line = nil
|
118
|
+
fps_line = nil
|
119
|
+
fps_line_2 = nil
|
120
|
+
ratefactor_line = nil
|
121
|
+
bitrate_line = nil
|
122
|
+
|
123
|
+
begin
|
124
|
+
File.foreach(log) do |line|
|
125
|
+
found = true if not found and line =~ /^HandBrake/
|
126
|
+
count += 1
|
127
|
+
fail "not a HandBrake-generated `.log` file: #{log}" if count > 5 and not found
|
128
|
+
duration_line = line if duration_line.nil? and line =~ /\+ duration: /
|
129
|
+
rate_line = line if line =~ /\+ frame rate: /
|
130
|
+
|
131
|
+
if line =~ /average encoding speed/
|
132
|
+
if fps_line.nil?
|
133
|
+
fps_line = line
|
134
|
+
elsif fps_line_2.nil?
|
135
|
+
fps_line_2 = line
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
ratefactor_line = line if line =~ /x26[45] \[info\]: frame P:/
|
140
|
+
bitrate_line = line if bitrate_line.nil? and line =~ /mux: track 0/
|
141
|
+
end
|
142
|
+
rescue SystemCallError => e
|
143
|
+
raise "reading failed: #{e}"
|
144
|
+
end
|
145
|
+
|
146
|
+
fail "not a HandBrake-generated `.log` file: #{log}" unless found
|
147
|
+
|
148
|
+
case @info
|
149
|
+
when :time, :speed
|
150
|
+
if fps_line.nil?
|
151
|
+
if @info == :time
|
152
|
+
report << "00:00:00 #{video}"
|
153
|
+
else
|
154
|
+
report << "00.000000 fps #{video}"
|
155
|
+
end
|
156
|
+
else
|
157
|
+
Console.debug fps_line
|
158
|
+
|
159
|
+
if fps_line =~ / ([0-9.]+) fps$/
|
160
|
+
fps = $1
|
161
|
+
else
|
162
|
+
fail "fps not found: #{log}"
|
163
|
+
end
|
164
|
+
|
165
|
+
unless fps_line_2.nil?
|
166
|
+
Console.debug fps_line_2
|
167
|
+
|
168
|
+
if fps_line_2 =~ / ([0-9.]+) fps$/
|
169
|
+
fps_2 = $1
|
170
|
+
fps = (1 / ((1 / fps.to_f) + (1 / fps_2.to_f))).round(6).to_s
|
171
|
+
else
|
172
|
+
fail "fps not found: #{log}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if @info == :time
|
177
|
+
fail "duration not found: #{log}" if duration_line.nil?
|
178
|
+
Console.debug duration_line
|
179
|
+
|
180
|
+
if duration_line =~ /([0-9]{2}):([0-9]{2}):([0-9]{2})/
|
181
|
+
duration = ($1.to_i * 60 * 60) + ($2.to_i * 60) + $3.to_i
|
182
|
+
fail "frame rate not found: #{log}" if rate_line.nil?
|
183
|
+
Console.debug rate_line
|
184
|
+
rate = rate_line.sub(/^.*: /, '').sub(/^.*constant /, '').sub(/ fps.*$/, '').to_f
|
185
|
+
seconds = ((duration * rate) / fps.to_f).to_i
|
186
|
+
hours = seconds / (60 * 60)
|
187
|
+
minutes = (seconds / 60) % 60
|
188
|
+
seconds = seconds % 60
|
189
|
+
report << format("%02d:%02d:%02d %s", hours, minutes, seconds, video)
|
190
|
+
else
|
191
|
+
fail "duration not found: #{log}"
|
192
|
+
end
|
193
|
+
# TODO
|
194
|
+
else
|
195
|
+
report << "#{fps} fps #{video}"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
when :bitrate
|
199
|
+
if bitrate_line.nil?
|
200
|
+
report << "0000.00 kbps #{video}"
|
201
|
+
else
|
202
|
+
Console.debug bitrate_line
|
203
|
+
|
204
|
+
if bitrate_line =~ /[0-9.]+ kbps/
|
205
|
+
report << "#{$MATCH} #{video}"
|
206
|
+
else
|
207
|
+
fail "bitrate not found: #{log}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
when :ratefactor
|
211
|
+
if ratefactor_line.nil?
|
212
|
+
report << "00.00 #{video}"
|
213
|
+
else
|
214
|
+
Console.debug ratefactor_line
|
215
|
+
|
216
|
+
if ratefactor_line =~ /Avg QP:([0-9.]+)/
|
217
|
+
report << "#{$1} #{video}"
|
218
|
+
else
|
219
|
+
fail "ratefactor not found: #{log}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
if @info == :time
|
226
|
+
report.sort!
|
227
|
+
else
|
228
|
+
report.sort! do |a, b|
|
229
|
+
number_a = a.sub(/ .*$/, '').to_f
|
230
|
+
number_b = b.sub(/ .*$/, '').to_f
|
231
|
+
|
232
|
+
if number_a < number_b
|
233
|
+
-1
|
234
|
+
elsif number_a > number_b
|
235
|
+
1
|
236
|
+
else
|
237
|
+
a <=> b
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
report.reverse! if @reverse
|
243
|
+
puts report
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
VideoTranscoding::Command.new.run
|