/
avi2dvd
executable file
·198 lines (162 loc) · 6.17 KB
/
avi2dvd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#!/usr/bin/ruby
# avi2dvd: Ruby script to automatically transcode video and author DVD's
require 'getoptlong'
MAX_THREADS = 1 # Edit for 1 per Processor core
MIN_AUDIO_BITRATE = 128 # In kb/s
AUDIO_BITRATE = 192 # In kb/s
MIN_VIDEO_BITRATE = 1500 # In kb/s
MAX_VIDEO_BITRATE = 6000 # In kb/s
DVD_SIZE = 4.2 # In gigabytes
MAX_RUNTIME = ((DVD_SIZE*1024*1024*8)/(MIN_VIDEO_BITRATE+MIN_AUDIO_BITRATE)/60).to_i # In minutes
FFMPEG_COMMAND = "ffmpeg"
#FFMPEG_COMMAND = "/opt/bin/ffmpeg"
def do_convert(options)
files = options[:files].collect do |f|
raise "File not found: #{f}" unless File.file?(f)
{
:filename => f ,
:length => get_duration(f),
:infile_base => f.sub(/\.[a-z0-9]{2,4}$/i,'')
}
end
total_runtime = files.inject(0){|sum,x| sum += x[:length].to_i}
unless total_runtime <= MAX_RUNTIME
puts "ERROR: Total runtime of #{total_runtime} is greater than #{MAX_RUNTIME} minutes"
return 1
end
audio_bitrate = (total_runtime > 300 ? MIN_AUDIO_BITRATE : AUDIO_BITRATE)
video_bitrate = get_video_bitrate(total_runtime, audio_bitrate)
files.each{|f| f[:video_bitrate] = video_bitrate; f[:audio_bitrate] = audio_bitrate}
if (files.size == 1 || options[:one_title])
base_name = "#{files.first[:infile_base]}"
else
base_name = "Compilation"
base_name = (base_name || '')+ "_dvd".gsub(/[^0-9a-z]/i,'_').gsub(/_+/,'_')
end
# Print Summary
puts "\n"
puts "Summary"
puts "======================================================="
puts " * #{files.size} Titles (#{total_runtime}m): Encoding at #{video_bitrate}kb/s VBR, #{audio_bitrate}kb/s ABR"
puts files.collect{|f| " - #{f[:filename]} => #{f[:length]}m"}.join("\n")
# Create DVD compatible MPG's
files.each do |file|
infile_base = file[:infile_base]
infile = file[:filename]
video_bitrate = file[:video_bitrate]
audio_bitrate = file[:audio_bitrate]
length = file[:length]
# Pre-Run Cleanup
system("rm -rf '#{base_name}_dvd/' '#{infile_base}_dvd.mpg' '#{base_name}.iso'")
# Create DVD compatible MPG
puts "\n\n"
puts "Creating DVD compliant video."
puts "======================================================="
puts " * Source: #{infile}"
puts " * Duration: #{length}"
puts " * Output: vbr => #{video_bitrate}, abr => #{audio_bitrate}"
ffmpeg_command = "#{FFMPEG_COMMAND} -threads #{MAX_THREADS} -i '#{infile}' \
-async 2 -aspect #{options[:ratio]} -target ntsc-dvd -b #{video_bitrate}k \
-ab #{audio_bitrate}k -ac 2 '#{infile_base}_dvd.mpg'"
puts "-------------------------------------------------------"
print "Command: "
puts ffmpeg_command
puts "-------------------------------------------------------"
system(ffmpeg_command)
raise "Error transcoding! #{infile_base}_dvd.mpg does not exist." unless File.exist?("#{infile_base}_dvd.mpg")
end
# Author DVD
puts "\n\n\n"
puts "Creating VIDEO_TS..."
puts "======================================================="
title_info = []
if options[:one_title]
if options[:with_chapters]
chapters = (["0"] + (1..length.divmod(5)[0]).to_a.collect{|t| (5*t).to_s + ":00"}).join(',')
end
file_args = files.collect{|f| "-f \"#{f[:infile_base]}_dvd.mpg\""}.join(' ')
file_info = "-t #{"--chapters='#{chapters}'" if chapters} #{file_args}"
title_info << file_info
else
files.each do |file|
if options[:with_chapters]
chapters = (["0"] + (1..file[:length].divmod(5)[0]).to_a.collect{|t| (5*t).to_s + ":00"}).join(',')
end
file_info = "-t #{"--chapters='#{chapters}'" if chapters} -f \"#{file[:infile_base]}_dvd.mpg\" "
title_info << file_info
end
end
author_command = "dvdauthor #{title_info.join(' ')} -o '#{base_name}_dvd/'"
puts author_command
system(author_command)
system("dvdauthor -o '#{base_name}_dvd/' -T")
# Create ISO
puts "\n\n\n"
puts "Creating ISO..."
puts "======================================================="
system("mkisofs -V '#{base_name[0..30]}' -dvd-video -udf -v -o '#{base_name}.iso' '#{base_name}_dvd'")
# Post-Run Cleanup
puts "\n\n\n"
puts "Cleaning Up.."
puts "======================================================="
system ("rm *_dvd.mpg")
end
def get_video_bitrate(length,audio_bitrate=AUDIO_BITRATE)
space_for_video = (DVD_SIZE*1024*1024 - audio_bitrate.to_f*length.to_f)
video_max_bitrate = ((space_for_video / length.to_f) / 8.0).to_i
bitrate = [video_max_bitrate,MAX_VIDEO_BITRATE].min
return bitrate
end
def get_duration(infile)
info = `ffmpeg -i "#{infile}" 2>&1`
duration_string = info.match(/Duration: ([0-9]{2}:[0-9]{2}:[0-9]{2})/)[1]
(hours,minutes,seconds) = duration_string.split(':')
length_in_minutes = (hours.to_i*60 + minutes.to_i + 1).to_i
end
def usage
return <<EOF
Usage: avi2dvd [OPTIONS] FILE FILE2 FILE3
Automatically transcode and author single or multi-title DVD's to ISO.
Video bitrate is automatically optimized for runtime.
Requires ffmpeg, dvdauthor, mkisofs.
-h, --help
show help
--ratio
specify aspect ratio (i.e. 4:3 or default 16:9)
--with-chapters
enable 5 minute chapter intervals
--one-title
merge files into one title
EOF
end
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--ratio', '-r', GetoptLong::REQUIRED_ARGUMENT],
['--with-chapters',GetoptLong::NO_ARGUMENT],
['--one-title',GetoptLong::NO_ARGUMENT]
)
options = {}
options[:files] = []
options[:ratio] = "16:9"
opts.each do |opt, arg|
case opt
when '--help'
show_usage = true
when '--ratio'
options[:ratio] = ((arg == "4:3") ? "4:3" : "16:9")
when '--with-chapters'
options[:with_chapters] = true
when '--one-title'
options[:one_title] = true
end
end
ARGV.each do |arg|
options[:files] << arg if File.file?(arg)
end
show_usage = true if ARGV.size == 0
if show_usage
puts usage()
exit()
else
do_convert(options)
end