video_transcoding 0.1.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.
- 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
|