dvd-converter 0.2.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.
- data/LICENSE +21 -0
- data/README.markdown +33 -0
- data/bin/dvd-converter +6 -0
- data/dvd-converter.gemspec +26 -0
- data/lib/dvd_converter.rb +10 -0
- data/lib/dvd_converter/audio.rb +39 -0
- data/lib/dvd_converter/command_line.rb +130 -0
- data/lib/dvd_converter/constants.rb +11 -0
- data/lib/dvd_converter/converter.rb +178 -0
- data/lib/dvd_converter/movie.rb +116 -0
- data/lib/dvd_converter/requirements.rb +50 -0
- data/lib/dvd_converter/subtitle.rb +26 -0
- data/lib/dvd_converter/track.rb +53 -0
- metadata +90 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2010 Stephen H. Gerstacker
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
|
data/README.markdown
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
This script is a hack to help me convert DVD's to MKV's. It's fragile, so
|
|
2
|
+
please be careful.
|
|
3
|
+
|
|
4
|
+
This is for personal use only. Pirating is illegal.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Dependencies For Ubuntu
|
|
8
|
+
-----------------------
|
|
9
|
+
|
|
10
|
+
For Ubuntu 9.10 and greater, you can run the following commands to get all of your requirements.
|
|
11
|
+
|
|
12
|
+
# sudo gem install log4r
|
|
13
|
+
# sudo add-apt-repository ppa:stebbins/handbrake-snapshots
|
|
14
|
+
# sudo apt-get update
|
|
15
|
+
# sudo apt-get install handbrake-cli mkvtoolnix transcode subtitleripper
|
|
16
|
+
|
|
17
|
+
Dependencies for Mac OS X
|
|
18
|
+
-------------------------
|
|
19
|
+
|
|
20
|
+
For Mac OS X, I've created packages for [brew][brew]. Follow the links to install [brew][brew] and then run the following:
|
|
21
|
+
|
|
22
|
+
# brew install mkvtoolnix
|
|
23
|
+
# brew install transcode
|
|
24
|
+
# brew install subtitleripper
|
|
25
|
+
|
|
26
|
+
You will still need to install Handbrake CLI by hand. Get it from the [Handbrake CLI download page][handbrake] and copy it `/usr/local/bin`.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Author: Stephen H. Gerstacker <stephen@shortround.net>
|
|
30
|
+
|
|
31
|
+
[brew]: http://mxcl.github.com/homebrew/ "Homebrew"
|
|
32
|
+
[handbrake]: http://handbrake.fr/downloads2.php "HandBrake"
|
|
33
|
+
|
data/bin/dvd-converter
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = 'dvd-converter'
|
|
3
|
+
s.version = '0.2.0'
|
|
4
|
+
s.date = '2010-12-24'
|
|
5
|
+
|
|
6
|
+
s.homepage = 'http://shortround.net/projects/dvd-converter'
|
|
7
|
+
s.summary = "A set of helper scripts to back up DVD's"
|
|
8
|
+
s.description = <<-EOS
|
|
9
|
+
dvd-converter is a set of helper scripts to consistantly back up DVD's
|
|
10
|
+
use good compression, choice of audio tracks and embedded (not burned in)
|
|
11
|
+
subtitles.
|
|
12
|
+
EOS
|
|
13
|
+
|
|
14
|
+
s.authors = [ 'Stephen H. Gerstacker' ]
|
|
15
|
+
s.email = 'stephen@shortround.net'
|
|
16
|
+
|
|
17
|
+
s.require_paths = ['lib']
|
|
18
|
+
s.executables = ['dvd-converter']
|
|
19
|
+
|
|
20
|
+
s.has_rdoc = false
|
|
21
|
+
|
|
22
|
+
s.add_dependency 'log4r', ['>= 1.1.8']
|
|
23
|
+
|
|
24
|
+
s.files = Dir['lib/**/*', 'bin/*', 'dvd-converter.gemspec', 'LICENSE', 'README.markdown']
|
|
25
|
+
end
|
|
26
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
$LOAD_PATH.push File.expand_path(File.dirname(__FILE__))
|
|
2
|
+
|
|
3
|
+
require 'dvd_converter/converter'
|
|
4
|
+
require 'dvd_converter/constants'
|
|
5
|
+
require 'dvd_converter/requirements'
|
|
6
|
+
require 'dvd_converter/movie'
|
|
7
|
+
require 'dvd_converter/track'
|
|
8
|
+
require 'dvd_converter/audio'
|
|
9
|
+
require 'dvd_converter/subtitle'
|
|
10
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module DVDConverter
|
|
2
|
+
|
|
3
|
+
class Audio
|
|
4
|
+
|
|
5
|
+
attr_accessor :audio_type
|
|
6
|
+
attr_accessor :code
|
|
7
|
+
attr_accessor :ext
|
|
8
|
+
attr_accessor :track_id
|
|
9
|
+
attr_accessor :hex
|
|
10
|
+
attr_accessor :language
|
|
11
|
+
|
|
12
|
+
def initialize(id, hex, language, code, ext)
|
|
13
|
+
@track_id = id
|
|
14
|
+
@hex = hex
|
|
15
|
+
@language = language
|
|
16
|
+
@code = code
|
|
17
|
+
@ext = ext
|
|
18
|
+
|
|
19
|
+
# Set the Audio Type
|
|
20
|
+
if @language =~ /dts/i
|
|
21
|
+
@audio_type = 'dts'
|
|
22
|
+
elsif @language =~ /ac3/i
|
|
23
|
+
@audio_type = 'ac3'
|
|
24
|
+
else
|
|
25
|
+
@audio_type = 'faac'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def print_description
|
|
30
|
+
print "+ Audio Track #{@track_id}: "
|
|
31
|
+
print "#{@hex} - "
|
|
32
|
+
print "#{@language} - "
|
|
33
|
+
print "#{@code} - "
|
|
34
|
+
print "#{@ext}\n"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../dvd_converter')
|
|
3
|
+
|
|
4
|
+
module DVDConverter
|
|
5
|
+
|
|
6
|
+
class CommandLine
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
exit if requirements_failed?
|
|
10
|
+
parse_options
|
|
11
|
+
exit if temps_exist?
|
|
12
|
+
do_conversion
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def do_conversion
|
|
18
|
+
movie = Movie.new @options[:input]
|
|
19
|
+
movie.scan_titles
|
|
20
|
+
|
|
21
|
+
if @options[:list]
|
|
22
|
+
movie.print_description
|
|
23
|
+
elsif @options[:everything]
|
|
24
|
+
movie.convert_everything @options
|
|
25
|
+
else
|
|
26
|
+
movie.convert @options
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_options
|
|
31
|
+
# Default options
|
|
32
|
+
@options = {
|
|
33
|
+
:audio => [],
|
|
34
|
+
:decomb => false,
|
|
35
|
+
:detelecine => false,
|
|
36
|
+
:exclude => [],
|
|
37
|
+
:everything => false,
|
|
38
|
+
:input => nil,
|
|
39
|
+
:list => false,
|
|
40
|
+
:preview => nil,
|
|
41
|
+
:subtitles => [],
|
|
42
|
+
:track => nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Build the options parser
|
|
46
|
+
@option_parser = OptionParser.new
|
|
47
|
+
@option_parser.banner = ''
|
|
48
|
+
|
|
49
|
+
@option_parser.separator "Information mode:"
|
|
50
|
+
@option_parser.separator "Usage: dvd_converter -i SOURCE -l"
|
|
51
|
+
@option_parser.on('-i', '--input SOURCE', 'The path to the DVD source') do |input|
|
|
52
|
+
@options[:input] = input
|
|
53
|
+
end
|
|
54
|
+
@option_parser.on('-l', '--list', 'List the contents of the DVD') do
|
|
55
|
+
@options[:list] = true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
@option_parser.separator ''
|
|
59
|
+
@option_parser.separator "Conversion mode:"
|
|
60
|
+
@option_parser.separator "Usage: dvd_converter -i SOURCE -t 1 -a 1,2 [-s 1,2,3] [-p 2] [-d]"
|
|
61
|
+
@option_parser.on('-t', '--track N', Integer, 'The track number to rip') do |track|
|
|
62
|
+
@options[:track] = track
|
|
63
|
+
end
|
|
64
|
+
@option_parser.on('-a', '--audio x,y,z', Array, 'The audio track(s) to rip') do |audio|
|
|
65
|
+
@options[:audio] = audio.map { |i| i.to_i }
|
|
66
|
+
end
|
|
67
|
+
@option_parser.on('-s', '--subtitles x,y,z', Array, 'The subtitle(s) to rip') do |subtitles|
|
|
68
|
+
@options[:subtitles] = subtitles.map { |i| i.to_i }
|
|
69
|
+
end
|
|
70
|
+
@option_parser.on('--decomb', 'Decomb the video') do
|
|
71
|
+
@options[:decomb] = true
|
|
72
|
+
end
|
|
73
|
+
@option_parser.on('--detelecine', 'Detelecine the video') do
|
|
74
|
+
@options[:detelecine] = true
|
|
75
|
+
end
|
|
76
|
+
@option_parser.on('-p', '--preview N', Integer, 'Preview a chapter instead of a full rip') do |preview|
|
|
77
|
+
@options[:preview] = p
|
|
78
|
+
end
|
|
79
|
+
@option_parser.on('-e', '--everything', 'Instead of individual tracks, just rip everything') do
|
|
80
|
+
@options[:everything] = true
|
|
81
|
+
end
|
|
82
|
+
@option_parser.on('-x', '--exclude x,y,z', Array, 'Skip the tracks given tracks') do |exclude|
|
|
83
|
+
@options[:exclude] = exclude.map { |i| i.to_i }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@option_parser.separator ''
|
|
87
|
+
@option_parser.separator "Common options:"
|
|
88
|
+
@option_parser.on('-h', '--help', 'Print this help message') do
|
|
89
|
+
puts @option_parser
|
|
90
|
+
exit
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
@option_parser.separator ''
|
|
94
|
+
|
|
95
|
+
# Parse the options
|
|
96
|
+
@option_parser.parse! ARGV
|
|
97
|
+
|
|
98
|
+
# Does the input exist?
|
|
99
|
+
if @options[:input].nil? || !File.exists?(@options[:input])
|
|
100
|
+
$stderr.puts "A valid input directory is required"
|
|
101
|
+
exit
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if !@options[:list] && !@options[:everything]
|
|
105
|
+
# Check and clean the track
|
|
106
|
+
if @options[:track].nil?
|
|
107
|
+
$stderr.puts "A track number is required"
|
|
108
|
+
exit
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
if @options[:audio].empty?
|
|
112
|
+
$stderr.puts "At least 1 valid audio track is required"
|
|
113
|
+
exit
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def requirements_failed?
|
|
119
|
+
r = Requirements.new
|
|
120
|
+
return !r.test_requirements
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def temps_exist?
|
|
124
|
+
return false
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'log4r'
|
|
4
|
+
|
|
5
|
+
module DVDConverter
|
|
6
|
+
|
|
7
|
+
class Converter
|
|
8
|
+
include Log4r
|
|
9
|
+
|
|
10
|
+
def initialize(track, audio_tracks, subtitle_tracks = [], options = {})
|
|
11
|
+
# Store the tracks
|
|
12
|
+
@track = track
|
|
13
|
+
@audio_tracks = audio_tracks
|
|
14
|
+
@subtitle_tracks = subtitle_tracks
|
|
15
|
+
@options = options
|
|
16
|
+
|
|
17
|
+
# Build the environment
|
|
18
|
+
@temp_dir = "#{TEMP_DIR}-#{track.track_id}"
|
|
19
|
+
Dir.mkdir @temp_dir
|
|
20
|
+
|
|
21
|
+
# Build the logger
|
|
22
|
+
@logger = Logger.new 'converter_log'
|
|
23
|
+
|
|
24
|
+
formatter = PatternFormatter.new :pattern => "Track #{track.track_id}: %m"
|
|
25
|
+
|
|
26
|
+
console_out = StdoutOutputter.new 'console'
|
|
27
|
+
console_out.level = INFO
|
|
28
|
+
console_out.formatter = formatter
|
|
29
|
+
@logger.outputters << console_out
|
|
30
|
+
|
|
31
|
+
file_out = FileOutputter.new 'file', :filename => File.join(@temp_dir, 'converter.log')
|
|
32
|
+
file_out.level = DEBUG
|
|
33
|
+
file_out.formatter = formatter
|
|
34
|
+
@logger.outputters << file_out
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def cleanup
|
|
38
|
+
# Build some paths
|
|
39
|
+
output_file = File.join @temp_dir, @track.track_id.to_s + '.mkv'
|
|
40
|
+
merged_file = File.join @temp_dir, @track.track_id.to_s + '-with-subs.mkv'
|
|
41
|
+
logger_file = File.join @temp_dir, 'converter.log'
|
|
42
|
+
|
|
43
|
+
# Move out the files
|
|
44
|
+
if File.exists? merged_file
|
|
45
|
+
FileUtils.mv merged_file, "#{@track.track_id}.mkv"
|
|
46
|
+
else
|
|
47
|
+
FileUtils.mv output_file, "#{@track.track_id}.mkv"
|
|
48
|
+
end
|
|
49
|
+
FileUtils.mv logger_file, "#{@track.track_id}.log"
|
|
50
|
+
|
|
51
|
+
# Remove the temp folder
|
|
52
|
+
FileUtils.rm_rf @temp_dir
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def convert_movie
|
|
56
|
+
# Build some paths
|
|
57
|
+
output_file = File.join @temp_dir, @track.track_id.to_s + '.mkv'
|
|
58
|
+
|
|
59
|
+
# Build the command to extract the movie
|
|
60
|
+
command = "#{HANDBRAKECLI_PATH} -i \"#{@track.movie.source}\" -t #{@track.track_id} "
|
|
61
|
+
command += "-o \"#{output_file}\" -f mkv "
|
|
62
|
+
command += "-e x264 -q 0.65 -b 2000 -2 -T -m "
|
|
63
|
+
command += "-9 " if @options[:detelecine]
|
|
64
|
+
command += "-5 " if @options[:decomb]
|
|
65
|
+
command += "-a #{@audio_tracks.map { |a| a.track_id }.join(',')} "
|
|
66
|
+
command += "-E #{@audio_tracks.map { |a| a.audio_type }.join(',')} "
|
|
67
|
+
command += "-c #{@options[:preview]} " if @options[:preview]
|
|
68
|
+
command += "2>&1"
|
|
69
|
+
|
|
70
|
+
# Run the command
|
|
71
|
+
@logger.info "Extracting Track #{@track.track_id}:"
|
|
72
|
+
@logger.info command
|
|
73
|
+
|
|
74
|
+
f = IO.popen(command)
|
|
75
|
+
bytes = []
|
|
76
|
+
f.each_byte do |b|
|
|
77
|
+
if b == 13 || b == 10 # Carriage Return or New Line
|
|
78
|
+
line = bytes.pack('c*')
|
|
79
|
+
if line =~ /Encoding: task \d+ of \d+/
|
|
80
|
+
@logger.info line
|
|
81
|
+
else
|
|
82
|
+
@logger.debug line
|
|
83
|
+
end
|
|
84
|
+
bytes = []
|
|
85
|
+
else
|
|
86
|
+
bytes << b
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
@logger.debug bytes.pack('c*') unless bytes.empty?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def convert_subtitles
|
|
93
|
+
output_file = File.join @temp_dir, @track.track_id.to_s + '.mkv'
|
|
94
|
+
|
|
95
|
+
@subtitle_tracks.each do |subtitle|
|
|
96
|
+
# Build some paths
|
|
97
|
+
subtitle_file = File.join @temp_dir, 'subs-' + subtitle.code + '-' + subtitle.track_id.to_s
|
|
98
|
+
vobsub_file = File.join @temp_dir, 'vobsubs-' + subtitle.code + '-' + subtitle.track_id.to_s
|
|
99
|
+
vts_file = File.join @track.movie.source, 'VIDEO_TS', 'VTS_' + sprintf('%02d', @track.track_id) + '_0.IFO'
|
|
100
|
+
|
|
101
|
+
# Build the command to convert the subtitle
|
|
102
|
+
command = "#{SUBTITLE2VOBSUB_PATH} -o \"#{vobsub_file}\" -i "
|
|
103
|
+
command += "\"#{vts_file}\" "
|
|
104
|
+
command += "< \"#{subtitle_file}\""
|
|
105
|
+
|
|
106
|
+
# Run the command to convert the subtitle
|
|
107
|
+
@logger.info "Converting Subtitle #{subtitle.track_id}, #{subtitle.code}"
|
|
108
|
+
@logger.info command
|
|
109
|
+
|
|
110
|
+
f = IO.popen(command)
|
|
111
|
+
f.each_line do |line|
|
|
112
|
+
@logger.debug line.strip
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def extract_subtitles
|
|
118
|
+
output_file = File.join @temp_dir, @track.track_id.to_s + '.mkv'
|
|
119
|
+
|
|
120
|
+
@subtitle_tracks.each do |subtitle|
|
|
121
|
+
# Build some paths
|
|
122
|
+
subtitle_file = File.join @temp_dir, 'subs-' + subtitle.code + '-' + subtitle.track_id.to_s
|
|
123
|
+
|
|
124
|
+
# Build the command to extract the subtitle
|
|
125
|
+
command = "#{TCCAT_PATH} -i \"#{@track.movie.source}\" -T #{@track.track_id},-1 2>/dev/null | "
|
|
126
|
+
command += "#{TCEXTRACT_PATH} -x ps1 -t vob -a 0x#{subtitle.hex} "
|
|
127
|
+
command += "> \"#{subtitle_file}\""
|
|
128
|
+
|
|
129
|
+
# Run the command to extract the subtitle
|
|
130
|
+
@logger.info "Extracting Subtitle #{subtitle.track_id}, #{subtitle.code}"
|
|
131
|
+
@logger.info command
|
|
132
|
+
|
|
133
|
+
f = IO.popen(command)
|
|
134
|
+
f.each_line do |line|
|
|
135
|
+
@logger.debug line.strip
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def merge_subtitles
|
|
141
|
+
output_file = File.join @temp_dir, @track.track_id.to_s + '.mkv'
|
|
142
|
+
merged_file = File.join @temp_dir, @track.track_id.to_s + '-with-subs.mkv'
|
|
143
|
+
|
|
144
|
+
# Build the command to merge the subtitles
|
|
145
|
+
command = "#{MKVMERGE_PATH} -o \"#{merged_file}\" \"#{output_file}\" "
|
|
146
|
+
|
|
147
|
+
# Add the subtitles to the command
|
|
148
|
+
@subtitle_tracks.each do |subtitle|
|
|
149
|
+
vobsub_file = File.join @temp_dir, 'vobsubs-' + subtitle.code + '-' + subtitle.track_id.to_s + '.idx'
|
|
150
|
+
command += "--language 0:#{subtitle.code} \"#{vobsub_file}\" "
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Run the command to merge the subtitles
|
|
154
|
+
@logger.info "Merging Subtitles in to movie"
|
|
155
|
+
@logger.info command
|
|
156
|
+
|
|
157
|
+
f = IO.popen(command)
|
|
158
|
+
f.each_line do |line|
|
|
159
|
+
@logger.debug line.strip
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def process
|
|
164
|
+
convert_movie
|
|
165
|
+
|
|
166
|
+
unless @subtitle_tracks.empty?
|
|
167
|
+
extract_subtitles
|
|
168
|
+
convert_subtitles
|
|
169
|
+
merge_subtitles
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
cleanup
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module DVDConverter
|
|
2
|
+
|
|
3
|
+
class Movie
|
|
4
|
+
|
|
5
|
+
AUDIO_INFO_REGEX = /scan: id=(.+)bd, lang=(.+), 3cc=(.+) ext=(\d+)/.freeze
|
|
6
|
+
AUDIO_REGEX = /scan: checking audio (\d+)/.freeze
|
|
7
|
+
DURATION_REGEX = /scan: duration is (.+) \((\d+) ms\)/
|
|
8
|
+
IGNORE_TITLE_REGEX = /scan: ignoring title/.freeze
|
|
9
|
+
TITLE_REGEX = /scan: scanning title (\d+)/.freeze
|
|
10
|
+
SUBTITLE_INFO_REGEX = /scan: id=(.+)bd, lang=(.+), 3cc=(.+)/.freeze
|
|
11
|
+
SUBTITLE_REGEX = /scan: checking subtitle (\d+)/.freeze
|
|
12
|
+
|
|
13
|
+
attr_accessor :source
|
|
14
|
+
attr_accessor :tracks
|
|
15
|
+
|
|
16
|
+
def initialize(source)
|
|
17
|
+
# Does the given source exist?
|
|
18
|
+
unless File.exists? source
|
|
19
|
+
raise ArgumentError, 'Movie source is not a valid path'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Scan the movie for information
|
|
23
|
+
@source = source
|
|
24
|
+
self.tracks = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def convert(options = {})
|
|
28
|
+
track = self.tracks[options[:track]]
|
|
29
|
+
track.convert options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def convert_everything(options = {})
|
|
33
|
+
self.tracks.keys.sort.each do |k|
|
|
34
|
+
self.tracks[k].convert_everything(options) unless options[:exclude].include?(k)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def print_description
|
|
39
|
+
self.tracks.keys.sort.each do |id|
|
|
40
|
+
track = self.tracks[id]
|
|
41
|
+
track.print_description
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def scan_titles
|
|
46
|
+
# Set some defaults
|
|
47
|
+
audio_id = nil
|
|
48
|
+
id = nil
|
|
49
|
+
subtitle_id = nil
|
|
50
|
+
|
|
51
|
+
# Run the command to pull information
|
|
52
|
+
f = IO.popen("#{HANDBRAKECLI_PATH} -i \"#{@source}\" -t 0 2>&1")
|
|
53
|
+
f.each_line do |line|
|
|
54
|
+
# Did we read a title?
|
|
55
|
+
match = TITLE_REGEX.match(line)
|
|
56
|
+
if match
|
|
57
|
+
id = match[1].to_i
|
|
58
|
+
self.tracks[id] = Track.new id, self
|
|
59
|
+
audio_id = nil
|
|
60
|
+
subtitle_id = nil
|
|
61
|
+
next
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Did we read a duration
|
|
65
|
+
match = DURATION_REGEX.match(line)
|
|
66
|
+
if match && id
|
|
67
|
+
self.tracks[id].duration = match[1]
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Do we ignore the title?
|
|
72
|
+
match = IGNORE_TITLE_REGEX.match(line)
|
|
73
|
+
if match && id
|
|
74
|
+
self.tracks.delete(id)
|
|
75
|
+
id = nil
|
|
76
|
+
audio_id = nil
|
|
77
|
+
subtitle_id = nil
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Did we read a subtitle?
|
|
82
|
+
match = SUBTITLE_REGEX.match(line)
|
|
83
|
+
if match && self.tracks[id]
|
|
84
|
+
subtitle_id = match[1].to_i
|
|
85
|
+
next
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Did we read a subtitle info?
|
|
89
|
+
match = SUBTITLE_INFO_REGEX.match(line)
|
|
90
|
+
if match && subtitle_id
|
|
91
|
+
self.tracks[id].subtitles[subtitle_id] = Subtitle.new subtitle_id, match[1], match[2], match[3]
|
|
92
|
+
subtitle_id = nil
|
|
93
|
+
next
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Did we read an audio channel
|
|
97
|
+
match = AUDIO_REGEX.match(line)
|
|
98
|
+
if match && self.tracks[id]
|
|
99
|
+
audio_id = match[1].to_i
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Did we read an audio channel info?
|
|
104
|
+
match = AUDIO_INFO_REGEX.match(line)
|
|
105
|
+
if match && audio_id
|
|
106
|
+
self.tracks[id].audio[audio_id] = Audio.new audio_id, match[1], match[2], match[3], match[4]
|
|
107
|
+
audio_id = nil
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module DVDConverter
|
|
2
|
+
|
|
3
|
+
class Requirements
|
|
4
|
+
|
|
5
|
+
def find_command(name)
|
|
6
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).detect do |directory|
|
|
7
|
+
path = File.join(directory, name)
|
|
8
|
+
if File.executable?(path)
|
|
9
|
+
return path
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
return nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def print_missing
|
|
17
|
+
puts ''
|
|
18
|
+
puts 'Some required binaries are missing. Please install them and try again'
|
|
19
|
+
puts ''
|
|
20
|
+
puts 'Handbrake: http://handbrake.fr'
|
|
21
|
+
puts 'mkvtoolnix: http://www.bunkus.org/videotools/mkvtoolnix'
|
|
22
|
+
puts 'transcode: http://tcforge.berlios.de'
|
|
23
|
+
puts ''
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_requirements
|
|
27
|
+
commands = [
|
|
28
|
+
HANDBRAKECLI_PATH, MKVMERGE_PATH, TCCAT_PATH,
|
|
29
|
+
TCEXTRACT_PATH, SUBTITLE2VOBSUB_PATH
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
commands.each do |c|
|
|
33
|
+
print "Looking for #{c}... "
|
|
34
|
+
if find_command c
|
|
35
|
+
puts "found!"
|
|
36
|
+
else
|
|
37
|
+
puts "missing!"
|
|
38
|
+
print_missing
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
puts ''
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module DVDConverter
|
|
2
|
+
class Subtitle
|
|
3
|
+
|
|
4
|
+
attr_accessor :code
|
|
5
|
+
attr_accessor :track_id
|
|
6
|
+
attr_accessor :hex
|
|
7
|
+
attr_accessor :language
|
|
8
|
+
|
|
9
|
+
def initialize(id, hex, language, code)
|
|
10
|
+
@track_id = id
|
|
11
|
+
@hex = hex
|
|
12
|
+
@language = language
|
|
13
|
+
@code = code
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def print_description
|
|
17
|
+
print "+ Subtitle Track #{@track_id}: "
|
|
18
|
+
print "#{@hex} - "
|
|
19
|
+
print "#{@language} - "
|
|
20
|
+
print "#{@code}\n"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module DVDConverter
|
|
2
|
+
|
|
3
|
+
class Track
|
|
4
|
+
|
|
5
|
+
attr_accessor :audio
|
|
6
|
+
attr_accessor :duration
|
|
7
|
+
attr_accessor :track_id
|
|
8
|
+
attr_accessor :movie
|
|
9
|
+
attr_accessor :subtitles
|
|
10
|
+
|
|
11
|
+
def initialize(id, movie)
|
|
12
|
+
self.track_id = id
|
|
13
|
+
self.movie = movie
|
|
14
|
+
self.audio = {}
|
|
15
|
+
self.subtitles = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def convert(options = {})
|
|
19
|
+
as = options[:audio].map { |a| self.audio[a] }
|
|
20
|
+
ss = options[:subtitles].map { |s| self.subtitles[s] }
|
|
21
|
+
|
|
22
|
+
converter = Converter.new self, as, ss, options
|
|
23
|
+
converter.process
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def convert_everything(options = {})
|
|
27
|
+
options[:audio] = self.audio.keys.sort
|
|
28
|
+
options[:subtitles] = self.subtitles.keys.sort
|
|
29
|
+
convert options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def print_description
|
|
33
|
+
print "Track: #{self.track_id}"
|
|
34
|
+
print " Duration: #{self.duration}" if self.duration
|
|
35
|
+
print "\n"
|
|
36
|
+
|
|
37
|
+
self.audio.keys.sort.each do |key|
|
|
38
|
+
a = self.audio[key]
|
|
39
|
+
a.print_description
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
self.subtitles.keys.sort.each do |key|
|
|
43
|
+
s = self.subtitles[key]
|
|
44
|
+
s.print_description
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
puts ''
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
metadata
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dvd-converter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 2
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.2.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Stephen H. Gerstacker
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-12-24 00:00:00 -05:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: log4r
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
none: false
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
segments:
|
|
29
|
+
- 1
|
|
30
|
+
- 1
|
|
31
|
+
- 8
|
|
32
|
+
version: 1.1.8
|
|
33
|
+
type: :runtime
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
description: " dvd-converter is a set of helper scripts to consistantly back up DVD's\n use good compression, choice of audio tracks and embedded (not burned in)\n subtitles.\n"
|
|
36
|
+
email: stephen@shortround.net
|
|
37
|
+
executables:
|
|
38
|
+
- dvd-converter
|
|
39
|
+
extensions: []
|
|
40
|
+
|
|
41
|
+
extra_rdoc_files: []
|
|
42
|
+
|
|
43
|
+
files:
|
|
44
|
+
- lib/dvd_converter/audio.rb
|
|
45
|
+
- lib/dvd_converter/command_line.rb
|
|
46
|
+
- lib/dvd_converter/constants.rb
|
|
47
|
+
- lib/dvd_converter/converter.rb
|
|
48
|
+
- lib/dvd_converter/movie.rb
|
|
49
|
+
- lib/dvd_converter/requirements.rb
|
|
50
|
+
- lib/dvd_converter/subtitle.rb
|
|
51
|
+
- lib/dvd_converter/track.rb
|
|
52
|
+
- lib/dvd_converter.rb
|
|
53
|
+
- bin/dvd-converter
|
|
54
|
+
- dvd-converter.gemspec
|
|
55
|
+
- LICENSE
|
|
56
|
+
- README.markdown
|
|
57
|
+
has_rdoc: true
|
|
58
|
+
homepage: http://shortround.net/projects/dvd-converter
|
|
59
|
+
licenses: []
|
|
60
|
+
|
|
61
|
+
post_install_message:
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
|
|
64
|
+
require_paths:
|
|
65
|
+
- lib
|
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
|
+
none: false
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
segments:
|
|
72
|
+
- 0
|
|
73
|
+
version: "0"
|
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
|
+
none: false
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
segments:
|
|
80
|
+
- 0
|
|
81
|
+
version: "0"
|
|
82
|
+
requirements: []
|
|
83
|
+
|
|
84
|
+
rubyforge_project:
|
|
85
|
+
rubygems_version: 1.3.7
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 3
|
|
88
|
+
summary: A set of helper scripts to back up DVD's
|
|
89
|
+
test_files: []
|
|
90
|
+
|