movie-manager-gem 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6cbb8b1dbd1e301de10676e2b67cbc035cdc2559
4
+ data.tar.gz: 3b6067ea40a7292d12c7cab4f1aa9889ecf123a0
5
+ SHA512:
6
+ metadata.gz: a422d226d39ec8d47ca7c1ba940fb059a7a73bba261ee8e040cc970cd306d928cdea4d5831bc193a5529a7ea38e30cdafb5f77a84f015db90801ff28b6ff78e9
7
+ data.tar.gz: 92de05d5652c0791b8d93bc105ad0ba11f26fc5ef480b957694345894264f7249afe76d0e0d709a6ad28e2e438ebadc35418d114a200c09b28f1a7b7857a4041
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in movie-manager-gem.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 David Hamme
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 David Hamme
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # MovieManagerGem
2
+
3
+ DH Movie Manager is a command line tool for organizing local movie files.
4
+
5
+ DHMM finds local movie files, queies Rotten Tomatoes, and stores the data in a sqlite3 database that you can use to run commands. Search for specific actors, directors, and genres. Filter results by audience score. Update your filenames to reflect the correct title.
6
+ Feeling indecisive? use the 'play unseen [genre]' command to play a random movie that suits your mood.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'movie_manager_gem'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install movie_manager_gem
21
+
22
+ ## Usage
23
+
24
+ From your home directory, simply run 'movie-manager-gem'
25
+ That's it.
26
+
27
+ Below are a list of valid commands. Brackets [ ] indicate user input.
28
+
29
+ To begin, drag and drop in the folder that holds your movies to add them to the database:
30
+ start [file path]
31
+
32
+ Return a list of actors with the given name:
33
+
34
+ search actor [name] [rating (optional)]
35
+
36
+ Return a list of directors with the given name:
37
+
38
+ search director [name] [rating (optional)]
39
+
40
+ Return a list of movies with the given genre:
41
+
42
+ search genre [genre] [rating (optional)]
43
+
44
+ Return a list of all movies with a RottenTomatoes Audience Score above [rating]:
45
+
46
+ search movies [rating]'
47
+
48
+
49
+ Play a movie with the given title:
50
+
51
+ play [title]
52
+
53
+ Play a random movie that you haven't seen from the given genre:
54
+
55
+ play unseen [genre] [rating (optional)]
56
+
57
+
58
+ User will be asked which movies they have seen. This improves the 'play unseen [genre] function:
59
+
60
+ update watched
61
+
62
+ User can choose to update movie file names with the correct titles:
63
+
64
+ update file names
65
+
66
+
67
+ Tips:
68
+ - Rating is an optional argument
69
+ - Partial searches (eg com vs comedy) can be used
70
+ - Searches can be multiple words (eg 'search actor Gary Oldman 60')
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it ( https://github.com/hammeiam/movie_manager_gem/fork )
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+ #Dir[File.join(File.dirname(__FILE__), 'lib', '*.rb')].each {|file| require file }
3
+
4
+ require_relative '../lib/movie-manager-gem'
5
+
6
+ session = MovieManagerGem::Finder.new
7
+ session.update_directories_status
8
+
9
+ puts "Welcome to DH Movie Manager"
10
+ puts "Type your command, 'exit' to leave, or 'help' to get started..."
11
+ puts ""
12
+ puts "First time? Type 'start' and drag & drop the folder that contains your movies" if !session.initialized?
13
+ puts "eg 'start /Users/me/Movies'" if !session.initialized?
14
+
15
+ #command = []
16
+ while true
17
+ print '> '
18
+ command = $stdin.gets.chomp.split
19
+
20
+ if command[0] == 'start'
21
+ session.find_files_in(command[1..-1].join.gsub("\\", " "))
22
+
23
+ elsif command[0] == 'search'
24
+ if command[1] == 'movies'
25
+ session.list_all_movies(command[2]?command[2]:-1) # sets a default value of -1 for empty rating arguments
26
+
27
+ elsif command[1] == 'actor'
28
+ if command[-1] =~ /[0-9]+/
29
+ session.list_movies_with_actor(command[2..-2].join(" "), command[-1])
30
+ else
31
+ session.list_movies_with_actor(command[2..-1].join(" "))
32
+ end
33
+ elsif command[1] == 'director'
34
+ if command[-1] =~ /[0-9]+/
35
+ session.list_movies_with_director(command[2..-2].join(" "), command[-1])
36
+ else
37
+ session.list_movies_with_director(command[2..-1].join(" "))
38
+ end
39
+ elsif command[1] == 'genre'
40
+ if command[-1] =~ /[0-9]+/
41
+ session.list_movies_by_genre(command[2..-2].join(" "), command[-1])
42
+ else
43
+ session.list_movies_by_genre(command[2..-1].join(" "))
44
+ end
45
+ end
46
+
47
+ elsif command[0] == 'play'
48
+ if command[1] == 'unseen' # distinguish btw the 'play' and 'play unseen genre' functions
49
+ if command[-1] =~ /[0-9]+/
50
+ session.play_unseen_genre(command[2..-2].join(" "), command[-1])
51
+ else
52
+ session.play_unseen_genre(command[2..-1].join(" "))
53
+ end
54
+ else
55
+ session.play(command[1])
56
+ end
57
+
58
+ elsif command[0] == 'update'
59
+ if command[1] == 'watched' # distinguish btw update watched and update file names functions
60
+ session.update_watched_list
61
+ elsif command[1] == 'file' && command[2] == 'names'
62
+ session.update_file_names
63
+ end
64
+
65
+ elsif command[0] == 'help'
66
+ puts ""
67
+ puts "--- Below are a list of valid commands. Brackets [] indicate user input. ---"
68
+ puts ""
69
+ puts "'start [file path]' drag and drop in the folder that holds your movies to add them to the database"
70
+ puts ""
71
+ puts "'search actor [name] [rating (optional)]' returns a list of actors with the given name"
72
+ puts "'search director [name] [rating (optional)]' returns a list of directors with the given name"
73
+ puts "'search genre [genre] [rating (optional)]' returns a list of movies with the given genre"
74
+ puts "'search movies [rating]' returns a list of all movies with a RottenTomatoes Audience Score above [rating]"
75
+ puts ""
76
+ puts "'play [title]' plays movie with the given title"
77
+ puts "'play unseen [genre] [rating (optional)]' plays random movie that you haven't seen from the given genre"
78
+ puts ""
79
+ puts "'update watched' user will be asked which movies they have seen. This improves the 'play unseen [genre] function."
80
+ puts "'update file names' user can choose to update movie file names with the correct titles"
81
+ puts ""
82
+ puts "Examples:"
83
+ puts "search actor Stallone 60 (note that first or last names can be used. Min movie rating is optional)"
84
+ puts "play finding nemo (note that names and title searches can be more than 1 word long)"
85
+ puts "play unseen com (note that partial searches for names and genres are allowed. This will play a comedy)"
86
+ puts ""
87
+ puts "--- end of help ---"
88
+
89
+
90
+ elsif command[0] == 'exit'
91
+ print 'Bye '
92
+ system("echo $USER!")
93
+ break
94
+
95
+ else
96
+ puts "Does not compute. Type 'help' for a list of valid commands."
97
+ end
98
+ end
@@ -0,0 +1,628 @@
1
+ require "movie-manager-gem/version"
2
+
3
+ module MovieManagerGem
4
+ ### DH Searcher ###
5
+ class Finder
6
+ require 'rubygems'
7
+ require 'rottentomatoes'
8
+ require 'sequel'
9
+ require 'thread'
10
+ require 'sqlite3' # sqlite3 implementation
11
+ # require 'pg' # postgres implementation
12
+
13
+ include RottenTomatoes
14
+
15
+ def initialize()
16
+ # input Rotentomatoes api key
17
+ Rotten.api_key = "9t2nx4s6bb62s8hvjftx8sx4"
18
+
19
+ # start database
20
+ @@DB = Sequel.sqlite('movies.db') # sqlite3 implementation
21
+ # @@DB = Sequel.postgres('testdb', :host=>'localhost', :user=>'David', :password=>'password') # postgres implementation
22
+
23
+ # create tables within database
24
+ create_all_tables
25
+
26
+ # tie ruby-accessible datasets to database table
27
+ @movies_dataset = @@DB[:movies]
28
+ @movie_genre_dataset = @@DB[:movie_genre]
29
+ @genres_dataset = @@DB[:genres]
30
+ @directories_dataset = @@DB[:directories]
31
+ @movie_actor_dataset = @@DB[:movie_actor]
32
+ @actors_dataset = @@DB[:actors]
33
+ @movie_director_dataset = @@DB[:movie_director]
34
+ @directors_dataset = @@DB[:directors]
35
+
36
+ # joins
37
+ @movie_directories_join = @movies_dataset.join(:directories, :id => :directory_id) # must be before other joins
38
+ @movie_genre_join = @movie_directories_join.join(:movie_genre, :movie_id => :movies__id).join(:genres, :id => :genre_id)
39
+ @movie_actor_join = @movie_directories_join.join(:movie_actor, :movie_id => :movies__id).join(:actors, :id => :actor_id)
40
+ @movie_director_join = @movie_directories_join.join(:movie_director, :movie_id => :movies__id).join(:directors, :id => :movie_director__director_id)
41
+
42
+ # queues and threads
43
+ @local_movies_queue = Queue.new
44
+ @processed_movies_queue = Queue.new
45
+ @threads = []
46
+ end
47
+
48
+ ### Update and Refine Data ###
49
+ def initialized?
50
+ @movies_dataset.first
51
+ end
52
+
53
+ def refine_unfound_movie_titles
54
+ # for n in movies where original title == title
55
+ # puts "What should the title of #{title} be?"
56
+ # title = gets.chop
57
+ end
58
+
59
+ def update_file_names
60
+ # find where movie titles =0. 0=unchecked, 1=correct
61
+ # for each, ask if title is correct
62
+ # if yes, dataset.where(:id => each[:id]).update(:correct_filename => 1)
63
+ # else
64
+ incorrect_names = @movies_dataset.select(:id, :original_title, :title).where(:correct_filename => 0).all
65
+ incorrect_names.each do |name|
66
+ puts "Is \"#{name[:title]}\" the correct title for \"#{File.basename(name[:original_title],".*")}\"? \n y/n"
67
+ print '> '
68
+ response = $stdin.gets.chomp.downcase
69
+ #name[:id] == basename(name[:original_title],".*")
70
+ case response
71
+ when 'y','yes'
72
+ new_title = File.dirname(name[:original_title]) + '/' + name[:title].gsub(/[\.|\_]/," ").gsub(/[\/|:]/,"-") + File.extname(name[:original_title])
73
+ File.rename(name[:original_title], new_title) #this is also in normalize
74
+ # mac replaces ':' with '/', this is a problem
75
+ # directories get confused when a movie title has a '/' in it
76
+ @movies_dataset.where(:id => name[:id]).update(:correct_filename => 1, :original_title => new_title)
77
+ puts "File updated"
78
+ when 'n', 'no'
79
+ # incorrect title, must refine, replace our RT search records
80
+ @movies_dataset.where(:id => name[:id]).update(:correct_filename => -1)
81
+ puts "crap, sorry..."
82
+ when 'end','exit'
83
+ break
84
+ else
85
+ puts "Respond with 'y' or 'n'"
86
+ # you'll have to run the command again to continue
87
+ end
88
+ end
89
+
90
+ # http://sequel.jeremyevans.net/rdoc/files/doc/cheat_sheet_rdoc.html#label-Update%2FDelete+rows
91
+ # be sure to bake in http://www.ruby-doc.org/core-2.1.2/File.html#method-c-rename
92
+ #File.rename(old,new)
93
+ #File.extname(file) gets extension
94
+ #File.dirname(file)
95
+ # change db record, change filename
96
+ end
97
+
98
+ def find_files_in(path) # works
99
+ # should there be a default path? Path to this Dir.
100
+ Dir.chdir(path) do
101
+ enqueue_local_movies
102
+ add_all_movies_to_table
103
+ update_directories_status
104
+ end
105
+ end
106
+
107
+ def update_watched_list # works
108
+ @movies_dataset.where(:watched => -1).all.each do |movie|
109
+ puts "Have you seen #{movie[:title]}? Y/N or end"
110
+ print '> '
111
+ response = $stdin.gets.chomp.downcase
112
+ case response
113
+ when 'y', 'yes'
114
+ update_watched_status(movie[:title],1)
115
+ puts 'Record updated'
116
+ when 'n', 'no'
117
+ update_watched_status(movie[:title],0)
118
+ puts 'Record updated'
119
+ when 'end'
120
+ break
121
+ else
122
+ puts 'I didn\'t catch that..'
123
+ update_watched_list
124
+ end
125
+ end
126
+ end
127
+
128
+ ### List and Play Movies ###
129
+ def list_all_movies(min_score=-1) # works
130
+ movie_list = @movie_director_join.where({:available => 1}, (Sequel.expr(:audience_score) >= min_score)).group(:movies__id).order(:title).all # sqlite3 impementation
131
+ # movie_list = @movie_directories_join.order(:title).distinct(:movies__id,:title).where({:available => 1}, (Sequel.expr(:audience_score) >= min_score)).all? # postgres implementation
132
+
133
+ if movie_list.empty?
134
+ puts "Sorry, we don\'t have any movies with a score higher than #{min_score}"
135
+ else
136
+ puts "--- Movies Rated Higher Than #{min_score} ---"
137
+ movie_list.each{ |movie| puts movie[:title]}
138
+ end
139
+ end
140
+
141
+ def list_movies_with_director(director, min_score = -1) # works
142
+ results_list = @directors_dataset.where(Sequel.ilike(:name, '%'+director+'%')).all
143
+ if results_list.length > 1
144
+ temp = []
145
+ results_list.each {|result| temp << result[:name]}
146
+ puts "Here are the results for '#{director}'. Please enter the number of the one you want."
147
+ temp.each_with_index do |name,i|
148
+ puts "#{i+1}: #{name}"
149
+ end
150
+ print '> '
151
+ director_name = temp[$stdin.gets.chomp.to_i - 1]
152
+
153
+ elsif results_list.length == 1
154
+ director_name = results_list[0][:name]
155
+
156
+ else
157
+ puts "Sorry, we couldn't find a director named '#{director}'. Here are the directors we have:"
158
+ @directors_dataset.select(:name).order(:name).all.each {|director| puts '- ' + director[:name]}
159
+ return
160
+ end
161
+ movie_list = @movie_director_join.where({:name => director_name}, {:available => 1}, (Sequel.expr(:audience_score) >= min_score)).group(:movies__id).order(:title).all # sqlite3 implementation
162
+ #movie_list = @movie_director_join.order(:title).distinct(:movies__id,:title).where({:name => director_name} & {:available => 1} & (Sequel.expr(:audience_score) >= min_score)).all # postgres implementation
163
+
164
+ if movie_list.empty?
165
+ puts "Sorry, we don\'t have any movies directed by \'#{director_name}\'."
166
+ else
167
+ puts "--- Movies directed by '#{director_name}' ---"
168
+ movie_list.each{ |movie| puts movie[:title]}
169
+ end
170
+ end
171
+
172
+ def list_movies_with_actor(actor, min_score = -1) # works
173
+ results_list = @actors_dataset.where(Sequel.ilike(:name, '%'+actor+'%')).all
174
+
175
+ if results_list.length > 1
176
+ temp = []
177
+ results_list.each {|result| temp << result[:name]}
178
+ puts "Here are the results for '#{actor}'. Please enter the number of the one you want."
179
+ temp.each_with_index do |name,i|
180
+ puts "#{i+1}: #{name}"
181
+ end
182
+ print '> '
183
+ actor_name = temp[$stdin.gets.chomp.to_i - 1]
184
+
185
+ elsif results_list.length == 1
186
+ actor_name = results_list[0][:name]
187
+
188
+ else
189
+ puts "Sorry, we couldn't find an actor named '#{actor}'. Here are the actors we have:"
190
+ @actors_dataset.select(:name).order(:name).all.each {|actor| puts '- ' + actor[:name]}
191
+ return
192
+ end
193
+ movie_list = @movie_actor_join.where({:name => actor_name}, {:available => 1}, (Sequel.expr(:audience_score) >= min_score)).group(:movies__id).order(:title).all # sqlite3 implementation
194
+ # movie_list = @movie_actor_join.order(:title).distinct(:movies__id,:title).where({:name => actor_name}, {:available => 1}, (Sequel.expr(:audience_score) >= min_score)).all # postgres implementation
195
+
196
+ if movie_list.empty?
197
+ puts "Sorry, we don\'t have any movies starring \'#{actor_name}\'."
198
+ else
199
+ puts "--- Movies starring '#{actor_name}' ---"
200
+ movie_list.each{ |movie| puts movie[:title]}
201
+ end
202
+ end
203
+
204
+ def list_movies_by_genre(genre, min_score = -1) # works
205
+ movie_list = @movie_genre_join.where(Sequel.ilike(:genre, '%'+genre+'%') & {:available => 1} & (Sequel.expr(:audience_score) >= min_score)).group(:movies__id).order(:title).all # sqlite3 implementation
206
+ # movie_list = @movie_genre_join.order(:title).distinct(:movies__id,:title).where(Sequel.ilike(:genre, '%'+genre+'%') & {:available => 1} & (Sequel.expr(:audience_score) >= min_score)).all # postgres implementation
207
+
208
+ if movie_list.empty?
209
+ puts 'Sorry, we don\'t have that genre. Please enter one from the list:'
210
+ @genres_dataset.select(:genre).order(:genre).all.each {|genre| puts '- ' + genre[:genre]}
211
+ else
212
+ search_genre = movie_list.first[:genre]
213
+ puts "--- Movies with genre '#{search_genre}' ---"
214
+ movie_list.each{ |movie| puts movie[:title]}
215
+ end
216
+ end
217
+
218
+ def play_unseen_genre(genre, min_score = -1) # works
219
+ movie_genre_list = @movie_genre_join.where(Sequel.ilike(:genre, '%'+genre+'%') & {:available => 1} & (Sequel.expr(:audience_score) >= min_score)).group(:movies__id).all # sqlite3 implementation
220
+ # movie_genre_list = @movie_genre_join.order(:title).distinct(:movies__id,:title).where(Sequel.ilike(:genre, '%'+genre+'%') & {:available => 1} & (Sequel.expr(:audience_score) >= min_score)).all # postgres implementation
221
+ if movie_genre_list.empty?
222
+ puts "Sorry, we don\'t have any movies with the genre #{genre}."
223
+ # this exception doesn't reveal if there are no movies of that genre above the minimum score
224
+ else
225
+ unwatched_list = movie_genre_list.select{|movie| movie[:watched] != 1}
226
+ if unwatched_list.empty?
227
+ puts "Sorry, you\'ve seen all of your #{genre} movies"
228
+ else
229
+ movie_title = unwatched_list.sample[:title]
230
+ play(movie_title,false)
231
+ end
232
+ end
233
+ end
234
+
235
+ def play(movie_title, user_input=true) # works
236
+ # does a search for the title if a user input it. opens the exact file if another function provided the name
237
+ if user_input
238
+ movie_to_play = @movie_directories_join.where(Sequel.ilike(:title, '%'+movie_title+'%'), :available => 1).group(:movies__id).first # sqlite3 implementation
239
+ # movie_to_play = @movie_directories_join.order(:title).distinct(:movies__id,:title).where(Sequel.ilike(:title, '%'+movie_title+'%'), :available => 1).first # postgres implementation
240
+ else
241
+ movie_to_play = @movies_dataset.where(:title => movie_title).first
242
+ end
243
+
244
+ if movie_to_play
245
+ puts "Play #{movie_to_play[:title]}? Y/N"
246
+ print '> '
247
+ response = $stdin.gets.chomp.downcase
248
+ case response
249
+ when 'y', 'yes'
250
+ update_watched_status(movie_to_play[:title],1)
251
+ system("open \"#{movie_to_play[:original_title]}\"") # unix needs double quotes around file names with spaces
252
+ when 'n', 'no'
253
+ puts 'Okay...'
254
+ else
255
+ puts 'Speak English, man!'
256
+ play(movie_title)
257
+ end
258
+ else
259
+ puts "No movie named '#{movie_title}' found, try a different search"
260
+ end
261
+ end
262
+
263
+ ### Movie-handling fuctions ###
264
+
265
+ def enqueue_local_movies # works
266
+ movies_glob = Dir.glob('**/*.{mkv,MKV,avi,AVI,mp4,MP4,mpg,MPG,mov,MOV}').uniq
267
+ movies_glob.each {|movie| @local_movies_queue << [File.absolute_path(movie), normalize_title(movie), nil]}
268
+ #movies.select!{|movie| File.size(movie) > 600_000_000} # works
269
+ end
270
+
271
+ def normalize_title(title) # works
272
+ # output should seperate path, suffix. Change periods and underscores to spaces. Possibly change / to :
273
+ File.basename(title,'.*').gsub(/[\.|\_]/," ").gsub(/[\/|:]/,"-")
274
+ end
275
+
276
+ def update_directories_status # works
277
+ @directories_dataset.select(:directory_path).all.each do |dir|
278
+ if Dir.exists?(dir[:directory_path])
279
+ @directories_dataset.where(:directory_path => dir[:directory_path]).update(:available => 1)
280
+ else
281
+ @directories_dataset.where(:directory_path => dir[:directory_path]).update(:available => 0)
282
+ end
283
+ end
284
+ end
285
+
286
+ def update_watched_status(movie_title, status) # works.
287
+ @movies_dataset.where(:title => movie_title).update(:watched => status)
288
+ end
289
+
290
+ def add_all_movies_to_table # works # add func to auto-find/add new movies. may need to rework enqueue_local_movies to output array
291
+ # RottenTomatoes' API seems to error at >3 threads
292
+ 2.times do
293
+ # this code was supplied by Theo on SO
294
+ # http://stackoverflow.com/questions/6558828/thread-and-queue
295
+ @threads << Thread.new do
296
+ until @local_movies_queue.empty?
297
+ long_name, clean_name, data = @local_movies_queue.pop(true) rescue nil
298
+ if long_name
299
+ if movies_record_exists?(long_name)
300
+ movie_title = @movies_dataset.select(:title).where(:original_title => long_name).first[:title]
301
+ puts "#{movie_title} record already exists"
302
+ else
303
+ data = get_rt_movie_info(clean_name)
304
+ @processed_movies_queue << [long_name, clean_name, data]
305
+ add_movie(@processed_movies_queue.pop)
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
311
+ @threads.each { |t| t.join }
312
+ end
313
+
314
+ def add_movie((long_name, clean_name, data)) # works
315
+ if data
316
+
317
+ # Add Directories to directories_dataset
318
+ @directories_dataset.insert(:directory_path => File.dirname(long_name)) unless directories_record_exists?(File.dirname(long_name))
319
+
320
+ # Add Movie to movies_dataset
321
+ @movies_dataset.insert( :original_title => long_name,
322
+ :title => data.title,
323
+ :critic_score => data.ratings.critics_score,
324
+ :audience_score => data.ratings.audience_score,
325
+ :date_added => Time.new(),
326
+ :directory_id => @directories_dataset.select(:id).where(:directory_path => File.dirname(long_name)).first[:id])
327
+
328
+ movie_id = @movies_dataset.select(:id).where(:title => data.title).first[:id]
329
+
330
+ # Add Actors to actors_dataset and movie_actor_dataset
331
+ data.abridged_cast.each do |actor|
332
+ @actors_dataset.insert(:name => actor[:name]) unless actors_record_exists?(actor[:name]) # used to be :name => actor.name. Make sure this works online!
333
+ @movie_actor_dataset.insert(:movie_id => movie_id,
334
+ :actor_id => @actors_dataset.select(:id).where(:name => actor[:name]).first[:id])
335
+ end if data.abridged_cast
336
+
337
+ # Add Genres to genres_dataset and movie_genre_dataset
338
+ data.genres.each do |genre|
339
+ @genres_dataset.insert(:genre => genre) unless genres_record_exists?(genre)
340
+ @movie_genre_dataset.insert(:movie_id => movie_id,
341
+ :genre_id => @genres_dataset.select(:id).where(:genre => genre).first[:id])
342
+ end if data.genres
343
+
344
+ # Add Directors to directors_dataset and movie_director_dataset
345
+ data.abridged_directors.each do |director|
346
+ @directors_dataset.insert(:name => director[:name]) unless directors_record_exists?(director[:name])
347
+ @movie_director_dataset.insert(:movie_id => movie_id,
348
+ :director_id => @directors_dataset.select(:id).where(:name => director[:name]).first[:id])
349
+ end if data.abridged_directors
350
+
351
+ puts "#{data.title} added to table"
352
+
353
+ else
354
+ @movies_dataset.insert(:original_title => long_name, :title => clean_name, :date_added => Time.new())
355
+ puts "#{clean_name} added to table"
356
+ end
357
+ end
358
+
359
+ def get_rt_movie_info(clean_name) # works
360
+ # with internet
361
+ begin
362
+ output = RottenMovie.find(:title => clean_name, :expand_results => true, :limit => 1) # hits RT once to get general movie info
363
+ sleep 1
364
+ output = RottenMovie.find(:id => output.id) if output.class == PatchedOpenStruct # hits RT a second time with id# to get most detailed info :(
365
+ rescue # addresses the occasional crash that RT limits plus our volume of calls can bring on.
366
+ sleep 1
367
+ output = RottenMovie.find(:title => clean_name, :expand_results => true, :limit => 1) # hits RT once to get general movie info
368
+ sleep 1
369
+ output = RottenMovie.find(:id => output.id) if output.class == PatchedOpenStruct # hits RT a second time with id# to get most detailed info :(
370
+ end
371
+ return output if output.class == PatchedOpenStruct
372
+ return nil
373
+
374
+ # without internet (local offline testing)
375
+ # output = FakeMovie.new(clean_name)
376
+ # return output
377
+ end
378
+
379
+
380
+ ### Exists? ###
381
+ def movies_record_exists?(original)
382
+ return false if @movies_dataset.select(:id).where(:original_title => original).all.length == 0
383
+ return true
384
+ end
385
+
386
+ def actors_record_exists?(name)
387
+ return false if @actors_dataset.select(:id).where(:name => name).all.length == 0
388
+ return true
389
+ end
390
+
391
+ def directors_record_exists?(name)
392
+ return false if @directors_dataset.select(:id).where(:name => name).all.length == 0
393
+ return true
394
+ end
395
+
396
+ def genres_record_exists?(genre)
397
+ return false if @genres_dataset.select(:id).where(:genre => genre).all.length == 0
398
+ return true
399
+ end
400
+
401
+ def directories_record_exists?(path)
402
+ return false if @directories_dataset.select(:directory_path).where(:directory_path => path).all.length == 0
403
+ return true
404
+ end
405
+
406
+ ### Tables & DBs ###
407
+ def create_all_tables
408
+ create_directories_table unless @@DB.table_exists?(:directories)
409
+
410
+ create_movies_table unless @@DB.table_exists?(:movies)
411
+
412
+ create_genres_table unless @@DB.table_exists?(:genres)
413
+ create_movie_genre_table unless @@DB.table_exists?(:movie_genre)
414
+
415
+ create_actors_table unless @@DB.table_exists?(:actors)
416
+ create_movie_actor_table unless @@DB.table_exists?(:movie_actor)
417
+
418
+ create_directors_table unless @@DB.table_exists?(:directors)
419
+ create_movie_director_table unless @@DB.table_exists?(:movie_director)
420
+ end
421
+
422
+ def create_movies_table
423
+ if @@DB.table_exists?(:movies)
424
+ raise StandardError, 'Movies table already exists, try a different name'
425
+ else
426
+ @@DB.create_table :movies do
427
+ primary_key :id
428
+ String :original_title
429
+ String :title
430
+ Integer :critic_score, :default => -1 #1-100
431
+ Integer :audience_score, :default => -1 #1-100
432
+ Integer :my_score, :default => -1 #1-100
433
+ Integer :correct_filename, :default => 0 #0/no, 1/yes
434
+ Integer :watched, :default => -1 #-1/unknown, 0/no, 1/yes
435
+ String :date_added
436
+ Integer :directory_id
437
+ end
438
+ end
439
+ end
440
+
441
+ def create_directories_table
442
+ if @@DB.table_exists?(:directories)
443
+ raise StandardError, 'Directories table already exists, try a different name'
444
+ else
445
+ @@DB.create_table :directories do
446
+ primary_key :id
447
+ String :directory_path
448
+ Integer :available, :default => 1
449
+ end
450
+ end
451
+ end
452
+
453
+ def create_genres_table
454
+ if @@DB.table_exists?(:genres)
455
+ raise StandardError, 'Genres table already exists, try a different name'
456
+ else
457
+ @@DB.create_table :genres do
458
+ primary_key :id
459
+ String :genre
460
+ end
461
+ end
462
+ end
463
+
464
+ def create_movie_genre_table
465
+ if @@DB.table_exists?(:movie_genre)
466
+ raise StandardError, 'Movie_Genre table already exists, try a different name'
467
+ else
468
+ @@DB.create_table :movie_genre do
469
+ primary_key :id
470
+ Integer :movie_id
471
+ Integer :genre_id
472
+ end
473
+ end
474
+ end
475
+
476
+ def create_actors_table
477
+ if @@DB.table_exists?(:actors)
478
+ raise StandardError, 'Actors table already exists, try a different name'
479
+ else
480
+ @@DB.create_table :actors do
481
+ primary_key :id
482
+ String :name
483
+ end
484
+ end
485
+ end
486
+
487
+ def create_movie_actor_table
488
+ if @@DB.table_exists?(:movie_actor)
489
+ raise StandardError, 'Movie_Actor table already exists, try a different name'
490
+ else
491
+ @@DB.create_table :movie_actor do
492
+ primary_key :id
493
+ Integer :movie_id
494
+ Integer :actor_id
495
+ end
496
+ end
497
+ end
498
+
499
+ def create_directors_table
500
+ if @@DB.table_exists?(:directors)
501
+ raise StandardError, 'Directors table already exists, try a different name'
502
+ else
503
+ @@DB.create_table :directors do
504
+ primary_key :id
505
+ String :name
506
+ end
507
+ end
508
+ end
509
+
510
+ def create_movie_director_table
511
+ if @@DB.table_exists?(:movie_director)
512
+ raise StandardError, 'Movie_Director table already exists, try a different name'
513
+ else
514
+ @@DB.create_table :movie_director do
515
+ primary_key :id
516
+ Integer :movie_id
517
+ Integer :director_id
518
+ end
519
+ end
520
+ end
521
+
522
+ def drop_table(table_name = table_name.to_sym)
523
+ if @@DB.table_exists?(table_name)
524
+ puts "Are you sure you want to drop table \'#{table_name}\'? Y/N"
525
+ response = 1000.chomp.downcase
526
+
527
+ case response
528
+ when 'y', 'yes'
529
+ @@DB.drop_table(table_name)
530
+ puts 'Table dropped'
531
+ when 'n', 'no'
532
+ puts 'Table not dropped'
533
+ else
534
+ puts 'Reply with Y or N'
535
+ drop_table(table_name)
536
+ end
537
+ else
538
+ raise StandardError, 'Table doesn\'t exist'
539
+ end
540
+ end
541
+ end
542
+
543
+ class FakeMovie # works
544
+ # returns results when testing offline
545
+ attr_reader :title, :ratings, :critics_score, :audience_score, :my_score, :genres, :abridged_directors, :abridged_cast, :correct_filename, :watched, :name
546
+ def initialize(movie_title)
547
+ sleep 2 # you have to wait for RT, you have to wait for me!
548
+
549
+ @random = Random.new
550
+ @title = movie_title.upcase
551
+ @my_score = @random.rand(100)+1
552
+ @genres = %w(Comedy Documentary Drama Horror Western XXX).sample(@random.rand(3)+1)
553
+ @correct_filename = @random.rand(2)
554
+ @watched = @random.rand(2)
555
+
556
+ # cast & director names
557
+ @first = %w(Abe Bob Carl Dolf Earl)
558
+ @last = %w(Buler Crabtree Daniels McDonald)
559
+
560
+ # ratings
561
+ @critics_score = 101
562
+ @audience_score = 101
563
+ end
564
+
565
+ def ratings
566
+ return self
567
+ end
568
+
569
+ def abridged_cast
570
+ out = []
571
+ (@random.rand(5)+1).times do
572
+ out << {name: @first.sample + ' ' + @last.sample }
573
+ end
574
+ return out
575
+ end
576
+
577
+ def abridged_directors
578
+ out = []
579
+ (@random.rand(3)+1).times do
580
+ out << {name: 'Director ' + @last.sample }
581
+ end
582
+ return out
583
+ end
584
+ end
585
+
586
+ #### TO DO: ####
587
+ #
588
+ # some directories are listed as NULL. Find out why and fix it.
589
+ #
590
+ # X add directories DB
591
+ # X - add "directory present?" column to movies DB 0/1
592
+ # X - add "directory_present?" function to update dir status, runs on initialization. We assume no devices are being removed within a session.
593
+ # X - add "directory_present => 1" to all existing searches
594
+ #
595
+ # X update add_all_movies_to_table. Auto-find/add new movies.
596
+ #
597
+ # X if RT call fails, restart and continue.
598
+ #
599
+ # X add ARGV for command line input, woo! Look into Thor to help list commands
600
+ #
601
+ # X Let user specify directory root. use Dir.chdir(new_dir). Volume can be dragged and dropped in. request on init.
602
+ #
603
+ # X func search by actor/director
604
+ # X improve act/dir search with last name refinement
605
+ #
606
+ # Look into using Find instead of Dir.glob to allow the user to exclude some folders.
607
+ # http://ruby-doc.org/stdlib-1.9.3/libdoc/find/rdoc/Find.html
608
+ # can maybe also use reject on glob
609
+ # http://stackoverflow.com/questions/4505566/is-there-a-way-to-glob-a-directory-in-ruby-but-exclude-certain-directories
610
+ #
611
+ # X func is x the correct name? y/n
612
+ #
613
+ # swtich back to sqlite3 to make this a one click startup.
614
+ #
615
+ # X create a setup command. if db is empty, spcify a dir to look in, add movies, update dirs.
616
+ #
617
+ # X consider filtering actor/dir results if dir is unattached
618
+ # consider tracking play count rather than having it be binary
619
+ # consider making better use of the directory & origial movie name combo. feels redundant.
620
+ #
621
+ # X func update file names to reflect correct titles. Will need to be unix-safe
622
+ # http://superuser.com/questions/358855/what-characters-are-safe-in-cross-platform-file-names-for-linux-windows-and-os
623
+ #
624
+ # put on the web.
625
+ # Add filetypes.
626
+ # How to deal with file being renamed by user?
627
+ # Look at 'index on expressions'
628
+ end
@@ -0,0 +1,3 @@
1
+ module MovieManagerGem
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'movie-manager-gem/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "movie-manager-gem"
8
+ spec.version = MovieManagerGem::VERSION
9
+ spec.authors = ["David Hamme"]
10
+ spec.email = ["dhamme@gmail.com"]
11
+ spec.summary = %q{A command line tool for organizing local movie files. }
12
+ spec.description = %q{DH Movie Manager finds local movie files, queries Rotten Tomatoes, and stores the data in a sqlite3 database that you can use to run commands. Search for specific actors, directors, and genres. Filter results by audience score. Update your filenames to reflect the correct title.
13
+ Feeling indecisive? use the 'play unseen [genre]' command to play a random movie that suits your mood.}
14
+ spec.homepage = "https://github.com/hammeiam/movie-manager-gem"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_dependency "rottentomatoes"
26
+ spec.add_dependency "sequel"
27
+ spec.add_dependency "thread"
28
+ spec.add_dependency "sqlite3"
29
+ end
data/movies.db ADDED
Binary file
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: movie-manager-gem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - David Hamme
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rottentomatoes
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sequel
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: thread
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: "DH Movie Manager finds local movie files, queries Rotten Tomatoes, and
98
+ stores the data in a sqlite3 database that you can use to run commands. Search for
99
+ specific actors, directors, and genres. Filter results by audience score. Update
100
+ your filenames to reflect the correct title. \nFeeling indecisive? use the 'play
101
+ unseen [genre]' command to play a random movie that suits your mood."
102
+ email:
103
+ - dhamme@gmail.com
104
+ executables:
105
+ - movie-manager-gem
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - .gitignore
110
+ - Gemfile
111
+ - LICENSE
112
+ - LICENSE.txt
113
+ - README.md
114
+ - Rakefile
115
+ - bin/movie-manager-gem
116
+ - lib/movie-manager-gem.rb
117
+ - lib/movie-manager-gem/version.rb
118
+ - movie-manager-gem.gemspec
119
+ - movies.db
120
+ homepage: https://github.com/hammeiam/movie-manager-gem
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.1.9
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: A command line tool for organizing local movie files.
144
+ test_files: []