dvd_ripper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,20 @@
1
+ .DS_Store
2
+ .ruby-gemset
3
+ .ruby-version
4
+ /.bundle/
5
+ /.yardoc
6
+ /Gemfile.lock
7
+ /_yardoc/
8
+ /coverage/
9
+ /doc/
10
+ /pkg/
11
+ /spec/reports/
12
+ /tmp/
13
+ Gemfile.lock
14
+ posters
15
+ test.rb
16
+ encode.log
17
+ temp/
18
+ log/
19
+
20
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ before_install: gem install bundler -v 1.10.6
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -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,13 @@
1
+ module MovieExtensions
2
+ def title_distance
3
+ @title_distance
4
+ end
5
+
6
+ def title_distance=(distance)
7
+ @title_distance = distance
8
+ end
9
+
10
+ def year
11
+ Date.parse(release_date).year
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ class MovieStub
2
+ attr_accessor :title, :year, :poster_path, :plot, :genres, :plot_summary
3
+ 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
@@ -0,0 +1,7 @@
1
+ class SearchFactory
2
+ def self.get(name)
3
+ return SearchImdb.new if name == 'imdb'
4
+ return SearchTmdb.new if name == 'tmdb'
5
+ raise "Invalid Search Engine Name"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class SearchImdb
2
+ def search(title)
3
+ Imdb::Search.new(title).movies
4
+ end
5
+ end
6
+
7
+ class Imdb::Movie
8
+ include MovieExtensions
9
+ end
@@ -0,0 +1,9 @@
1
+ class SearchTmdb
2
+ def search(title)
3
+ Tmdb::Movie.find(title)
4
+ end
5
+ end
6
+
7
+ class Tmdb::Movie
8
+ include MovieExtensions
9
+ end
@@ -0,0 +1,3 @@
1
+ module DvdRipper
2
+ VERSION = "0.1.0"
3
+ 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: []