dvd_ripper 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/.gitignore +20 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +60 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/dvd_ripper.gemspec +33 -0
- data/exe/dvd_ripper +54 -0
- data/lib/dvd_ripper/client.rb +129 -0
- data/lib/dvd_ripper/config.rb +84 -0
- data/lib/dvd_ripper/dvd.rb +119 -0
- data/lib/dvd_ripper/movie.rb +58 -0
- data/lib/dvd_ripper/movie_extensions.rb +13 -0
- data/lib/dvd_ripper/movie_stub.rb +3 -0
- data/lib/dvd_ripper/search.rb +55 -0
- data/lib/dvd_ripper/search_factory.rb +7 -0
- data/lib/dvd_ripper/search_imdb.rb +9 -0
- data/lib/dvd_ripper/search_tmdb.rb +9 -0
- data/lib/dvd_ripper/version.rb +3 -0
- data/lib/dvd_ripper.rb +21 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c790edef4adcadfac4f29207280dc95141dbbb9a
|
4
|
+
data.tar.gz: a99b0660fb5c9c064740613b507c2cd44f3ed889
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3cf67739da381c2abdbcb5c3bd346b07491654a00ee4b28be79db604e7774c5346e68f5ade7e6684147e578b73ae58d2155bb5c6fc0ceb0802c746f3a9589b99
|
7
|
+
data.tar.gz: 451cdfc1b3d0a0c91a21cb762b42f8ed18b1d2cc93cb6a38abc338e5c667ae43d9f38ad7a6ec0cb71b5066540a51c6219249bca298d55118bc0d62608a01a0fa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 matthew-nichols
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# dvd_ripper
|
2
|
+
|
3
|
+
A command line tool that will make it easy to rip and tag your dvd collection. It ties together many great tools to get the job done. -- HandBrake, AtomicParsley, exiftool, imdb and tmdb.
|
4
|
+
|
5
|
+
It takes a bit on configuration, but once it is setup, ripping your DVD collection will be a breeze.
|
6
|
+
|
7
|
+
Enjoy!
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Install Handbrake: [downloads](https://handbrake.fr/downloads.php)
|
12
|
+
Install HandBrakeCLI: [downloads](https://handbrake.fr/downloads2.php)
|
13
|
+
|
14
|
+
$ brew install AtomicParsley
|
15
|
+
|
16
|
+
$ brew install exiftool
|
17
|
+
|
18
|
+
$ brew install tag
|
19
|
+
|
20
|
+
$ brew install libdvdcss
|
21
|
+
|
22
|
+
$ gem install dvd_ripper
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
$ dvd_ripper
|
27
|
+
|
28
|
+
## Troubleshooting
|
29
|
+
|
30
|
+
### nokogiri fails to install
|
31
|
+
$ xcode-select --install
|
32
|
+
|
33
|
+
$ gem install nokogiri -- --use-system-libraries
|
34
|
+
|
35
|
+
$ bundle config build.nokogiri --use-system-libraries
|
36
|
+
|
37
|
+
### HandBrake Errors
|
38
|
+
|
39
|
+
* Make sure that you have the same version of HandBrake and HandBrakeCLI
|
40
|
+
* Make sure that you have installed libdvdcss
|
41
|
+
|
42
|
+
|
43
|
+
## Notes
|
44
|
+
|
45
|
+
http://manpages.ubuntu.com/manpages/hardy/man1/AtomicParsley.1.html
|
46
|
+
https://github.com/cparratto/atomic-parsley-ruby
|
47
|
+
|
48
|
+
http://www.sno.phy.queensu.ca/~phil/exiftool/
|
49
|
+
http://miniexiftool.rubyforge.org
|
50
|
+
|
51
|
+
https://github.com/jdberry/tag
|
52
|
+
|
53
|
+
https://github.com/ahmetabdi/themoviedb
|
54
|
+
https://github.com/ariejan/imdb
|
55
|
+
|
56
|
+
|
57
|
+
## To Do
|
58
|
+
|
59
|
+
* Add ability to set HandBrake encoding options
|
60
|
+
* Add better support for TV show ripping
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dvd_ripper"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/dvd_ripper.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dvd_ripper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dvd_ripper"
|
8
|
+
spec.version = DvdRipper::VERSION
|
9
|
+
spec.authors = ["matthew-nichols"]
|
10
|
+
spec.email = ["matt@nichols.link"]
|
11
|
+
|
12
|
+
spec.summary = %q{Quickly rip and tag DVDs}
|
13
|
+
spec.description = %q{A command-line utility that makes it easy to rip and tag DVDs. Just install and run the utility. Then insert a DVD and follow prompts. Repeat.}
|
14
|
+
spec.homepage = "https://github.com/mattnichols/dvd_ripper"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = ["dvd_ripper"]
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'thor'
|
23
|
+
spec.add_runtime_dependency 'nokogiri', '~> 1.6.0'
|
24
|
+
spec.add_runtime_dependency 'themoviedb', '~> 0.0.20'
|
25
|
+
spec.add_runtime_dependency 'imdb', '~> 0.8.1'
|
26
|
+
spec.add_runtime_dependency 'rb-fsevent', '~> 0.9.4'
|
27
|
+
spec.add_runtime_dependency 'levenshtein', '~> 0.2.2'
|
28
|
+
spec.add_runtime_dependency 'atomic-parsley-ruby' #, :git => 'https://github.com/cparratto/atomic-parsley-ruby.git'
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", '~> 3.2'
|
33
|
+
end
|
data/exe/dvd_ripper
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'dvd_ripper'
|
4
|
+
require "thor"
|
5
|
+
|
6
|
+
module DvdRipper
|
7
|
+
class Cli < ::Thor
|
8
|
+
desc "start", "start tool"
|
9
|
+
def start
|
10
|
+
check_config!
|
11
|
+
dvd_ripper = ::DvdRipper::Client.new
|
12
|
+
dvd_ripper.start
|
13
|
+
end
|
14
|
+
map %w[--version -v] => :__print_version
|
15
|
+
|
16
|
+
desc "--version, -v", "print the version"
|
17
|
+
def __print_version
|
18
|
+
puts "DvdRipper v#{::DvdRipper::VERSION}"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "config", "configure dvd_ripper"
|
22
|
+
def config
|
23
|
+
Config.instance.prompt!
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "help", "display this help"
|
27
|
+
def help_banner
|
28
|
+
puts <<-BANNER
|
29
|
+
Dvd Ripper
|
30
|
+
|
31
|
+
This commandline utility cleanly and simply captures your dvd contents and
|
32
|
+
tags the movies.
|
33
|
+
|
34
|
+
Movie information is pulled from www.imdb.com and www.themoviedb.org
|
35
|
+
|
36
|
+
Dvd content captured using Handbrake (https://handbrake.fr/)
|
37
|
+
BANNER
|
38
|
+
help
|
39
|
+
end
|
40
|
+
|
41
|
+
no_commands do
|
42
|
+
def check_config!
|
43
|
+
unless Config.instance.exists?
|
44
|
+
config
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# "www.themoviedb.org"
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
::DvdRipper::Cli.start
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module DvdRipper
|
2
|
+
class Client
|
3
|
+
def initialize
|
4
|
+
@command = nil
|
5
|
+
@title = nil
|
6
|
+
@last_volume = ""
|
7
|
+
@dvd = Dvd.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_dvd
|
11
|
+
puts
|
12
|
+
puts "--------------------------------------------------------------"
|
13
|
+
puts "-- Volume: #{@dvd.volume}"
|
14
|
+
puts "-- Title: #{@dvd.title}"
|
15
|
+
puts "--------------------------------------------------------------"
|
16
|
+
puts
|
17
|
+
|
18
|
+
searcher = Search.new
|
19
|
+
movie = searcher.closest(@title || @dvd.title)
|
20
|
+
if movie.nil?
|
21
|
+
puts "- Provide details for movie -"
|
22
|
+
puts "Enter Movie Title:"
|
23
|
+
movie = ::Movie.new
|
24
|
+
movie.title = $stdin.gets
|
25
|
+
puts "Enter Movie Year:"
|
26
|
+
movie.release_date = "#{$stdin.gets.chomp}-01-01"
|
27
|
+
end
|
28
|
+
title = "#{movie.title} (#{movie.year})"
|
29
|
+
output = File.join(::DvdRipper::Config.instance.working_dir, "#{title.gsub(": ", " - ").gsub(":", " - ")}.m4v")
|
30
|
+
puts
|
31
|
+
puts "--------------------------------------------------------------"
|
32
|
+
puts "-- Title: #{movie.title}"
|
33
|
+
puts "-- Year: #{movie.year}"
|
34
|
+
puts "--------------------------------------------------------------"
|
35
|
+
puts
|
36
|
+
puts "Continue? (y/n) |y|"
|
37
|
+
continue = $stdin.gets.strip
|
38
|
+
continue = 'y' if continue.blank?
|
39
|
+
if continue != 'y'
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
@dvd.rip(output)
|
44
|
+
|
45
|
+
movie.tag(output) unless movie.nil?
|
46
|
+
|
47
|
+
puts "\"#{output}\" complete"
|
48
|
+
@dvd.eject
|
49
|
+
FileUtils.mv(output, ::DvdRipper::Config.instance.dest_dir)
|
50
|
+
end
|
51
|
+
|
52
|
+
def force_user_input(input)
|
53
|
+
@command = input
|
54
|
+
@user_command.raise("wakeup")
|
55
|
+
end
|
56
|
+
|
57
|
+
def start
|
58
|
+
Tmdb::Api.key(::DvdRipper::Config.instance.tmdb_api_key)
|
59
|
+
|
60
|
+
@user_command = Thread.new do
|
61
|
+
while true
|
62
|
+
begin
|
63
|
+
if @command.nil?
|
64
|
+
puts "Choose command [(r)ip, (s)pecify title, (q)uit, (e)ject] or insert a DVD"
|
65
|
+
@command = $stdin.gets.chomp
|
66
|
+
end
|
67
|
+
sleep 1
|
68
|
+
rescue Exception => e
|
69
|
+
puts e.message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
fsevent = FSEvent.new
|
75
|
+
fsevent.watch %W(/Volumes) do |directories|
|
76
|
+
if @command.nil? and @dvd.present? and (@last_volume != @dvd.volume)
|
77
|
+
force_user_input("r")
|
78
|
+
@last_volume = @dvd.volume
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Signal.trap("INT") do
|
83
|
+
fsevent.stop
|
84
|
+
Kernel.exit(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
message_thread = Thread.new do
|
88
|
+
while true
|
89
|
+
begin
|
90
|
+
while @command.nil?
|
91
|
+
sleep 1
|
92
|
+
end
|
93
|
+
if @command == "r"
|
94
|
+
process_dvd
|
95
|
+
end
|
96
|
+
if @command == "s"
|
97
|
+
puts "Enter title:"
|
98
|
+
@title = $stdin.gets
|
99
|
+
process_dvd
|
100
|
+
end
|
101
|
+
if @command == "q"
|
102
|
+
puts "Quitting..."
|
103
|
+
fsevent.stop
|
104
|
+
break
|
105
|
+
end
|
106
|
+
if @command == "e"
|
107
|
+
@dvd.eject
|
108
|
+
end
|
109
|
+
rescue Exception => e
|
110
|
+
puts e.message
|
111
|
+
puts e.class
|
112
|
+
puts e.backtrace
|
113
|
+
ensure
|
114
|
+
@command = nil
|
115
|
+
@title = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
fsevent.run
|
121
|
+
|
122
|
+
message_thread.kill
|
123
|
+
message_thread.join
|
124
|
+
|
125
|
+
@user_command.kill
|
126
|
+
@user_command.join
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module DvdRipper
|
5
|
+
class Config
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
DEFAULTS = {
|
11
|
+
:working_dir => "~/tmp/movies",
|
12
|
+
:dest_dir => "~/movies",
|
13
|
+
:poster_dir => "~/tmp/movies/posters",
|
14
|
+
:distance_threshold => 0.2,
|
15
|
+
:tmdb_api_key => nil
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def load
|
19
|
+
self.config = DEFAULTS.merge(read_config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_config
|
23
|
+
yml = ""
|
24
|
+
yml = File.read(config_path) if exists?
|
25
|
+
|
26
|
+
return YAML.load(yml) unless yml.blank?
|
27
|
+
|
28
|
+
{}
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists?
|
32
|
+
File.exist?(config_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def working_dir
|
36
|
+
config[:working_dir]
|
37
|
+
end
|
38
|
+
|
39
|
+
def dest_dir
|
40
|
+
config[:dest_dir]
|
41
|
+
end
|
42
|
+
|
43
|
+
def tmdb_api_key
|
44
|
+
config[:tmdb_api_key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def poster_dir
|
48
|
+
config[:poster_dir]
|
49
|
+
end
|
50
|
+
|
51
|
+
def distance_threshold
|
52
|
+
config[:distance_threshold]
|
53
|
+
end
|
54
|
+
|
55
|
+
def config_path
|
56
|
+
File.expand_path("~/.dvd_ripper")
|
57
|
+
end
|
58
|
+
|
59
|
+
def save!
|
60
|
+
File.open(config_path, "w+") do |config_file|
|
61
|
+
config_file.write(config.to_yaml)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def prompt!
|
66
|
+
config.each do |k, v|
|
67
|
+
puts "#{k} (ENTER: #{v}):"
|
68
|
+
new_value = $stdin.gets
|
69
|
+
config[k] = new_value.strip unless (new_value.strip.blank?)
|
70
|
+
end
|
71
|
+
|
72
|
+
save!
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
::DvdRipper::Config.instance.load
|
78
|
+
|
79
|
+
# WORKING_DIR = "/Users/mnichols/Movies/Ripping"
|
80
|
+
# DEST_DIR = "/Users/mnichols/Movies/Ripped"
|
81
|
+
# POSTER_PATH = "./Posters"
|
82
|
+
# DISTANCE_THRESHOLD = 0.2
|
83
|
+
|
84
|
+
# @api_key = "867c178725a7e646a0f108ee78781a49"
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class Dvd
|
2
|
+
attr_accessor :volumes
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@volumes = "/Volumes"
|
6
|
+
end
|
7
|
+
|
8
|
+
def volume
|
9
|
+
vol = nil
|
10
|
+
Dir.entries(@volumes).each do |video_dir|
|
11
|
+
if not [".", ".."].member?(video_dir)
|
12
|
+
full_path = File.join(@volumes, video_dir)
|
13
|
+
if Dir.exists?(File.join(full_path, "VIDEO_TS"))
|
14
|
+
vol = full_path
|
15
|
+
break
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
vol
|
20
|
+
end
|
21
|
+
|
22
|
+
def title
|
23
|
+
raise "Invalid DVD Root or DVD not mounted at #{@volumes}" if not present?
|
24
|
+
File.basename(volume).gsub("_", " ").capitalize
|
25
|
+
end
|
26
|
+
|
27
|
+
def present?
|
28
|
+
!volume.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def rip(output)
|
32
|
+
File.delete(output) if File.exists?(output)
|
33
|
+
|
34
|
+
state = :scanning
|
35
|
+
percent = "0.0"
|
36
|
+
eta = "unknown"
|
37
|
+
dc = 0
|
38
|
+
spinner = ["-", "\\", "|", "/"]
|
39
|
+
|
40
|
+
encoding_reg = /Encoding: task \d+ of \d+, (?<percent>\d+\.\d+) %.*ETA (?<eta>\d{2}h\d{2}m\d{2}s)/i
|
41
|
+
status_update = Thread.new do
|
42
|
+
while true do
|
43
|
+
sleep(1)
|
44
|
+
dc = dc + 1
|
45
|
+
|
46
|
+
if state == :encoding
|
47
|
+
print "\r#{spinner[dc%4]} Encoding #{percent} % (time left #{eta})"
|
48
|
+
end
|
49
|
+
|
50
|
+
if state == :scanning
|
51
|
+
print("\r" + (" " * 50)) if ((dc % 20) == 0)
|
52
|
+
print "\r#{spinner[dc%4]} Scanning Titles.#{"." * (dc % 20)}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
command_output = []
|
58
|
+
last_line = nil
|
59
|
+
File.open("./encode.log", "w+") do |log|
|
60
|
+
IO.popen("HandBrakeCLI -Z \"AppleTV 3\" --main-feature -i \"#{volume}\" -o \"#{output}\" 2>&1", "w+") do |f|
|
61
|
+
begin
|
62
|
+
while buf = f.read(255)
|
63
|
+
lines = buf.split(/\r|\n/)
|
64
|
+
lines.compact!
|
65
|
+
|
66
|
+
if (lines.size > 1) and (not last_line.nil?)
|
67
|
+
command_output << last_line + lines.shift
|
68
|
+
end
|
69
|
+
|
70
|
+
if buf[-1] == "\r" or buf[-1] == "\n"
|
71
|
+
last_line = nil
|
72
|
+
else
|
73
|
+
last_line = lines.pop
|
74
|
+
end
|
75
|
+
|
76
|
+
command_output << lines
|
77
|
+
command_output.flatten!
|
78
|
+
|
79
|
+
while ln = command_output.shift
|
80
|
+
ln = ln.strip unless ln.nil?
|
81
|
+
|
82
|
+
# Change to encoding as soon as a matching line is found
|
83
|
+
if encoding_reg =~ ln
|
84
|
+
state = :encoding
|
85
|
+
end
|
86
|
+
|
87
|
+
if state == :encoding
|
88
|
+
if m = encoding_reg.match(ln)
|
89
|
+
percent = m[:percent]
|
90
|
+
eta = m[:eta]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
log.write "#{state}:#{percent}:#{eta} - #{ln}\n"
|
95
|
+
end
|
96
|
+
command_output = []
|
97
|
+
end
|
98
|
+
rescue Exception => e
|
99
|
+
puts e.message
|
100
|
+
puts e.class
|
101
|
+
puts e.backtrace
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
exit_code = $?
|
106
|
+
|
107
|
+
if exit_code.exitstatus != 0
|
108
|
+
`killall HandBrakeCLI`
|
109
|
+
puts "Encoding Failed. See encode.log"
|
110
|
+
end
|
111
|
+
ensure
|
112
|
+
status_update.kill
|
113
|
+
status_update.join
|
114
|
+
end
|
115
|
+
|
116
|
+
def eject
|
117
|
+
system("drutil tray eject")
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Movie
|
2
|
+
extend Forwardable
|
3
|
+
|
4
|
+
def initialize(tmdb_info=nil, imdb_info=nil)
|
5
|
+
@tmdb_info = tmdb_info.nil? ? ::MovieStub.new : tmdb_info
|
6
|
+
@imdb_info = imdb_info.nil? ? ::MovieStub.new : imdb_info
|
7
|
+
end
|
8
|
+
|
9
|
+
def_delegators :@tmdb_info, :title, :year, :poster_path
|
10
|
+
def_delegators :@imdb_info, :plot, :genres, :plot_summary
|
11
|
+
|
12
|
+
require 'open-uri'
|
13
|
+
|
14
|
+
def tag(output)
|
15
|
+
poster_file_path = nil
|
16
|
+
unless poster_path.nil?
|
17
|
+
Dir.mkdir(::DvdRipper::Config.instance.poster_dir) if not Dir.exists?(::DvdRipper::Config.instance.poster_dir)
|
18
|
+
poster_file_path = File.expand_path(File.join(::DvdRipper::Config.instance.poster_dir, poster_path))
|
19
|
+
poster = File.open(poster_file_path, "wb")
|
20
|
+
begin
|
21
|
+
open("http://image.tmdb.org/t/p/w500#{poster_path}", "rb") do |download|
|
22
|
+
poster.write(download.read)
|
23
|
+
end
|
24
|
+
ensure
|
25
|
+
poster.close()
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Remove stik and other artwork, just in case
|
30
|
+
v = AtomicParsleyRuby::Media.new(output)
|
31
|
+
v.artwork "REMOVE_ALL"
|
32
|
+
v.stik "remove"
|
33
|
+
v.overwrite true
|
34
|
+
v.process
|
35
|
+
|
36
|
+
v = AtomicParsleyRuby::Media.new(output)
|
37
|
+
v.overwrite true
|
38
|
+
v.title title
|
39
|
+
v.comment plot unless plot.nil?
|
40
|
+
v.description plot unless plot.nil?
|
41
|
+
v.year year.to_s
|
42
|
+
v.stik "0"
|
43
|
+
v.genre genres[0]
|
44
|
+
v.longdesc plot_summary unless plot_summary.nil?
|
45
|
+
v.artwork poster_file_path unless poster_file_path.nil?
|
46
|
+
v.process
|
47
|
+
|
48
|
+
# m = MiniExiftool.new output
|
49
|
+
# m["director"] = info.director
|
50
|
+
|
51
|
+
# Need to use mp4v2 lib extensions to write this stuff
|
52
|
+
# m["directors//name"] = "Bob"
|
53
|
+
# m["producers//name"] = "George"
|
54
|
+
# m["screenwriters//name"] = "Writers"
|
55
|
+
# m["studio//name"] = "Studio C"
|
56
|
+
# m.save
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Search
|
2
|
+
def closest(title)
|
3
|
+
searcher = SearchFactory.get("tmdb")
|
4
|
+
|
5
|
+
movies = leven_sort(title, searcher.search(title))
|
6
|
+
while movies.empty?
|
7
|
+
puts " - Title Not Found - "
|
8
|
+
puts "Enter movie title (or enter to skip search):"
|
9
|
+
title = $stdin.gets.chomp
|
10
|
+
return nil if title.empty?
|
11
|
+
movies = leven_sort(title, searcher.search(title))
|
12
|
+
end
|
13
|
+
|
14
|
+
movie = auto_select(movies)
|
15
|
+
if movie.nil? or ambiguous?(movies)
|
16
|
+
puts "Please choose:"
|
17
|
+
movies.each_with_index do |movie, i|
|
18
|
+
puts "#{i}. #{movie.title} (#{movie.release_date}) (#{movie.title_distance.round(3)})"
|
19
|
+
end
|
20
|
+
puts "s. Skip"
|
21
|
+
title_sel = $stdin.gets.chomp
|
22
|
+
return nil if title_sel == 's'
|
23
|
+
movie = movies[title_sel.to_i]
|
24
|
+
end
|
25
|
+
info = SearchFactory.get("imdb").search(title)[0]
|
26
|
+
|
27
|
+
movie = Movie.new(movie, info)
|
28
|
+
movie
|
29
|
+
end
|
30
|
+
|
31
|
+
def leven_sort(search_title, movies)
|
32
|
+
movies.each do |movie|
|
33
|
+
distance = Levenshtein.normalized_distance(search_title, movie.title)
|
34
|
+
movie.title_distance = distance
|
35
|
+
end
|
36
|
+
movies.reverse.sort_by { |m| m.title_distance }
|
37
|
+
end
|
38
|
+
|
39
|
+
def auto_select(movies)
|
40
|
+
return movies[0] if (movies.size == 1) or ((not movies.empty?) and (movies[0].title_distance < ::DvdRipper::Config.instance.distance_threshold))
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def ambiguous?(movies)
|
45
|
+
return true if movies.empty?
|
46
|
+
return false if movies.size == 1
|
47
|
+
|
48
|
+
lowest_distance = movies[0].title_distance
|
49
|
+
|
50
|
+
return true if lowest_distance > ::DvdRipper::Config.instance.distance_threshold
|
51
|
+
return true if movies.find_all { |m| m.title_distance == lowest_distance }.size > 1
|
52
|
+
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
data/lib/dvd_ripper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "dvd_ripper/version"
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'rb-fsevent'
|
5
|
+
require 'atomic-parsley-ruby'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'io/console'
|
8
|
+
require 'imdb'
|
9
|
+
require 'themoviedb'
|
10
|
+
require 'levenshtein'
|
11
|
+
|
12
|
+
require_relative 'dvd_ripper/movie_stub.rb'
|
13
|
+
require_relative 'dvd_ripper/movie_extensions.rb'
|
14
|
+
require_relative 'dvd_ripper/search_factory.rb'
|
15
|
+
require_relative 'dvd_ripper/search_imdb.rb'
|
16
|
+
require_relative 'dvd_ripper/search_tmdb.rb'
|
17
|
+
require_relative 'dvd_ripper/search.rb'
|
18
|
+
require_relative 'dvd_ripper/dvd.rb'
|
19
|
+
require_relative 'dvd_ripper/movie.rb'
|
20
|
+
require_relative 'dvd_ripper/config.rb'
|
21
|
+
require_relative 'dvd_ripper/client.rb'
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dvd_ripper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- matthew-nichols
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.6.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.6.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: themoviedb
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.20
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.20
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: imdb
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.8.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rb-fsevent
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.9.4
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: levenshtein
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.2.2
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.2.2
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: atomic-parsley-ruby
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bundler
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.10'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.10'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '10.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '10.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3.2'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3.2'
|
153
|
+
description: A command-line utility that makes it easy to rip and tag DVDs. Just install
|
154
|
+
and run the utility. Then insert a DVD and follow prompts. Repeat.
|
155
|
+
email:
|
156
|
+
- matt@nichols.link
|
157
|
+
executables:
|
158
|
+
- dvd_ripper
|
159
|
+
extensions: []
|
160
|
+
extra_rdoc_files: []
|
161
|
+
files:
|
162
|
+
- ".gitignore"
|
163
|
+
- ".rspec"
|
164
|
+
- ".travis.yml"
|
165
|
+
- CODE_OF_CONDUCT.md
|
166
|
+
- Gemfile
|
167
|
+
- LICENSE.txt
|
168
|
+
- README.md
|
169
|
+
- Rakefile
|
170
|
+
- bin/console
|
171
|
+
- bin/setup
|
172
|
+
- dvd_ripper.gemspec
|
173
|
+
- exe/dvd_ripper
|
174
|
+
- lib/dvd_ripper.rb
|
175
|
+
- lib/dvd_ripper/client.rb
|
176
|
+
- lib/dvd_ripper/config.rb
|
177
|
+
- lib/dvd_ripper/dvd.rb
|
178
|
+
- lib/dvd_ripper/movie.rb
|
179
|
+
- lib/dvd_ripper/movie_extensions.rb
|
180
|
+
- lib/dvd_ripper/movie_stub.rb
|
181
|
+
- lib/dvd_ripper/search.rb
|
182
|
+
- lib/dvd_ripper/search_factory.rb
|
183
|
+
- lib/dvd_ripper/search_imdb.rb
|
184
|
+
- lib/dvd_ripper/search_tmdb.rb
|
185
|
+
- lib/dvd_ripper/version.rb
|
186
|
+
homepage: https://github.com/mattnichols/dvd_ripper
|
187
|
+
licenses:
|
188
|
+
- MIT
|
189
|
+
metadata: {}
|
190
|
+
post_install_message:
|
191
|
+
rdoc_options: []
|
192
|
+
require_paths:
|
193
|
+
- lib
|
194
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
requirements: []
|
205
|
+
rubyforge_project:
|
206
|
+
rubygems_version: 2.4.6
|
207
|
+
signing_key:
|
208
|
+
specification_version: 4
|
209
|
+
summary: Quickly rip and tag DVDs
|
210
|
+
test_files: []
|