movier 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e14e4ce7fa47a7540508730f642030474a8c000
4
- data.tar.gz: a259177d845f25aeed6c19de3b2a8a4764563a88
3
+ metadata.gz: af01724f8aee1a5789dc504d4e15af1aef675b84
4
+ data.tar.gz: 7635c03202e6ea156369c73229e8367bf64494eb
5
5
  SHA512:
6
- metadata.gz: e7af2d17325877a58ddfe689ca02c0f1654626c52242d2f3028ae62a77df7ee37c399dfe6d68bb2d796b1dab4ea6e03707465396d51226a7919ee0b15f201b46
7
- data.tar.gz: 8e88d837cc42e4ccff70e29be1380fc75f1be23366822a974b53eb4d375b01b688873aa31f8b9bcbf4ee576f0dfafbf645b34d8edfa9c7ab8dc644678aaf16ce
6
+ metadata.gz: 85b89a53bdd233dd18be3e352708d70629e44823e0b2b3cc0e83e7fbc0e4a5abb864df5b273594b778cff87acb05bb44d81934634e26aa454cda8244d1aecd7f
7
+ data.tar.gz: bbaf57f751f896609a9365fd2dc12a94eb57eadf33e847332765322a7453b08a69c4384d4735a6da51a01ef971bda6d9d69f5e047f1684d65afe2bf57a8ffda0
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .rvmrc
2
+ Gemfile.lock
3
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require 'rake/clean'
2
+ require 'rubygems'
3
+ require 'rubygems/package_task'
4
+ require 'rdoc/task'
5
+ require 'cucumber'
6
+ require 'cucumber/rake/task'
7
+ Rake::RDocTask.new do |rd|
8
+ rd.main = "README.rdoc"
9
+ rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
10
+ rd.title = 'Your application title'
11
+ end
12
+
13
+ spec = eval(File.read('movier.gemspec'))
14
+
15
+ Gem::PackageTask.new(spec) do |pkg|
16
+ end
17
+ CUKE_RESULTS = 'results.html'
18
+ CLEAN << CUKE_RESULTS
19
+ desc 'Run features'
20
+ Cucumber::Rake::Task.new(:features) do |t|
21
+ opts = "features --format html -o #{CUKE_RESULTS} --format progress -x"
22
+ opts += " --tags #{ENV['TAGS']}" if ENV['TAGS']
23
+ t.cucumber_opts = opts
24
+ t.fork = false
25
+ end
26
+
27
+ desc 'Run features tagged as work-in-progress (@wip)'
28
+ Cucumber::Rake::Task.new('features:wip') do |t|
29
+ tag_opts = ' --tags ~@pending'
30
+ tag_opts = ' --tags @wip'
31
+ t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}"
32
+ t.fork = false
33
+ end
34
+
35
+ task :cucumber => :features
36
+ task 'cucumber:wip' => 'features:wip'
37
+ task :wip => 'features:wip'
38
+ require 'rake/testtask'
39
+ Rake::TestTask.new do |t|
40
+ t.libs << "test"
41
+ t.test_files = FileList['test/*_test.rb']
42
+ end
43
+
44
+ task :default => [:test,:features]
@@ -0,0 +1,8 @@
1
+ Feature: My bootstrapped app kinda works
2
+ In order to get going on coding my awesome app
3
+ I want to have aruba and cucumber setup
4
+ So I don't have to do it myself
5
+
6
+ Scenario: App just runs
7
+ When I get help for "movier"
8
+ Then the exit status should be 0
@@ -0,0 +1,6 @@
1
+ When /^I get help for "([^"]*)"$/ do |app_name|
2
+ @app_name = app_name
3
+ step %(I run `#{app_name} help`)
4
+ end
5
+
6
+ # Add more step definitions here
@@ -0,0 +1,15 @@
1
+ require 'aruba/cucumber'
2
+
3
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
4
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
5
+
6
+ Before do
7
+ # Using "announce" causes massive warnings on 1.9.2
8
+ @puts = true
9
+ @original_rubylib = ENV['RUBYLIB']
10
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
11
+ end
12
+
13
+ After do
14
+ ENV['RUBYLIB'] = @original_rubylib
15
+ end
data/lib/movier/api.rb ADDED
@@ -0,0 +1,56 @@
1
+ module Movier
2
+
3
+ # Fetch information about a movie from IMDB.com
4
+ #
5
+ # * *Args* :
6
+ # - +id+ -> IMDB.com id for the movie
7
+ # * *Returns* :
8
+ # - hash of movie information
9
+ # * *Raises* :
10
+ # - +RuntimeError+ -> error that occurred when making a request to the API
11
+ #
12
+ def self.fetch_details(id)
13
+ response = omdbapi({ i: id })
14
+ response["votes"] = response["imdbVotes"].delete(",").to_i
15
+ response["rating"] = response["imdbRating"].to_f
16
+ response["weight"] = response["votes"] * response["rating"]
17
+ response
18
+ end
19
+
20
+ # Search for a title on IMDB.com
21
+ #
22
+ # * *Args* :
23
+ # - +keyword+ -> keywords to search with
24
+ # - +year+ -> (optional) year in which the title was released
25
+ # - +type+ -> (default: movie) type of the title to return
26
+ # * *Returns* :
27
+ # - hash with the information about movie titles found
28
+ # * *Raises* :
29
+ # - +RuntimeError+ -> error that occurred when making a request to the API
30
+ #
31
+ def self.search(keyword, year = nil, type = "Movie")
32
+ response = omdbapi({ s: keyword })["Search"]
33
+ response = response.select{ |m| m["Type"].downcase == type.downcase } unless type.downcase == "all"
34
+ response = response.select{ |m| m["Year"] == year } if year
35
+ response
36
+ end
37
+
38
+ # Make a request to OMDBApi.com
39
+ #
40
+ # * *Args* :
41
+ # - +params+ -> hash of params to pass with this request
42
+ # * *Returns* :
43
+ # - hash of response received
44
+ # * *Raises* :
45
+ # - +RuntimeError+ -> error that occurred when making a request to the API
46
+ #
47
+ def self.omdbapi(params)
48
+ query = ""; params.each { |k, v| query += "#{k}=#{v}" }
49
+ response = HTTParty.get("http://omdbapi.com/?#{URI::encode(query)}")
50
+ failed_with response.response unless response.success?
51
+ response = JSON.parse(response.parsed_response)
52
+ failed_with response["Error"] if response["Error"]
53
+ response
54
+ end
55
+
56
+ end
@@ -0,0 +1,69 @@
1
+ module Movier
2
+
3
+ # ask a user for a directory path, until we find one
4
+ #
5
+ # * *Args* :
6
+ # - +message+ -> question to show to the user
7
+ # * *Returns* :
8
+ # - path to the provided directory
9
+ #
10
+ def self.ask_for_directory(message = nil)
11
+ message ||= "Please, provide a directory path to use. "
12
+ dir = nil
13
+ until dir && File.directory?(dir)
14
+ warn_with "Found no such directory!" if dir
15
+ dir = ask(message) { |x| x.default = ENV['HOME'] }
16
+ end
17
+ dir
18
+ end
19
+
20
+ # colorize output based on a movie's rating
21
+ #
22
+ # * *Args* :
23
+ # - +status+ -> status for this message
24
+ # - +message+ -> actual message
25
+ # - +rating+ -> rating for this movie
26
+ #
27
+ def self.say_rated(status, message, rating, do_break = false)
28
+ rating = rating.to_f
29
+ scheme = :below6 if rating < 6
30
+ scheme = :above6 if rating >= 6
31
+ scheme = :above8 if rating >= 8
32
+ say_with_status status, message, scheme, do_break
33
+ end
34
+
35
+ # titleize a string
36
+ #
37
+ # * *Args* :
38
+ # - +string+ -> string that will be titleized
39
+ # * *Returns* :
40
+ # - titleized string
41
+ #
42
+ def self.titleize(string)
43
+ string.gsub(/\w+/) { |word| word.capitalize }
44
+ end
45
+
46
+ # find the imdb.txt file for a given movie path
47
+ # this file is created by Movier, once the movie has been parsed
48
+ #
49
+ # * *Args* :
50
+ # - +movie_path+ -> path to the movie being checked
51
+ # * *Returns* :
52
+ # - path to the imdb.txt file for the given movie path
53
+ #
54
+ def self.imdb_file_for(movie_path)
55
+ File.join(Filename.dirname(movie_path), "imdb.txt")
56
+ end
57
+
58
+ # check whether the movie with given path has already been organized
59
+ #
60
+ # * *Args* :
61
+ # - +movie_path+ -> path to the movie being checked
62
+ # * *Returns* :
63
+ # - true, if the movie has been organized. false, otherwise
64
+ #
65
+ def self.organized?(movie_path)
66
+ File.exists?(imdb_file_for(movie_path))
67
+ end
68
+
69
+ end
@@ -0,0 +1,32 @@
1
+ module Movier
2
+ # Find and return information about a movie with the given keywords
3
+ #
4
+ # * *Args* :
5
+ # - +keyword+ -> keywords to search for in movie title
6
+ # - +options+ -> hash that alters the behavior of the search
7
+ # if +:year+ key is specified, restricts search to that year
8
+ # if +:detailed+ key is specified, shows detailed results
9
+ def self.info(keyword, options)
10
+ lmdb = Movier::LMDB.new
11
+ movies = search(keyword, options[:year], options[:all] ? "all" : "movie")
12
+ movies.each do |m|
13
+ m = fetch_details m["imdbID"]
14
+ nice_name = m["Title"] + " [" + m["Year"] + "]"
15
+ next if m["votes"] < 1000 && !options[:all]
16
+ if options[:detailed]
17
+ tip_now nice_name, titleize(m["Type"])
18
+ rating = "#{m["Rated"]} at #{m["imdbRating"]} points with #{m["imdbVotes"]} votes"
19
+ say_rated "Rating", rating, m["rating"]
20
+ say_rated "Plot", m["Plot"], m["rating"], true
21
+ say_rated "Actors", m["Actors"], m["rating"], true
22
+ say_rated "Runtime", m["Runtime"], m["rating"]
23
+ say_rated "Genre", m["Genre"], m["rating"]
24
+ puts
25
+ else
26
+ message = "#{nice_name} @ #{m["imdbRating"]} ( #{m["imdbVotes"]} votes )"
27
+ type = lmdb.lookup(m["imdbID"]) ? "Available Movie" : "Movie"
28
+ say_rated titleize(type), message, m["rating"]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,248 @@
1
+ module Movier
2
+ class LMDB
3
+ DataFolder = File.join(ENV['HOME'], ".movier")
4
+ DataFile = File.join(DataFolder, "data.yaml")
5
+
6
+ attr_reader :movies, :boxes
7
+
8
+ def initialize
9
+ FileUtils.mkdir_p DataFolder
10
+ FileUtils.touch DataFile unless File.exists?(DataFile)
11
+ read_data
12
+ end
13
+
14
+ def read_data
15
+ data = Movier.read_yaml(DataFile)
16
+ @boxes = data ? data[:boxes] : []
17
+ @movies = data ? data[:movies] : []
18
+ end
19
+
20
+ def write_data(movies = nil, boxes = nil)
21
+ boxes ||= @boxes; movies ||= @movies;
22
+ data = { boxes: boxes, movies: movies }
23
+ Movier.write_yaml DataFile, data
24
+ read_data
25
+ end
26
+
27
+ def find_persons(kind = :actors)
28
+ invalid_kind = @movies.first && !@movies.first.has_key?(kind.to_sym)
29
+ raise "I don't have any info about #{Movier.titleize(kind.to_s)}" if invalid_kind
30
+ persons = @params[kind.to_sym]
31
+ if persons
32
+ persons.split(",").each do |person|
33
+ @movies.select!{ |movie| movie[kind.to_sym].join(", ").downcase.include? person.downcase.strip}
34
+ end
35
+ end
36
+ end
37
+
38
+ def genre
39
+ genre = [ "Action", "Adventure", "Animation", "Biography", "Comedy",
40
+ "Crime", "Documentary", "Drama", "Family", "Fantasy",
41
+ "Film-Noir", "Game-Show", "History", "Horror", "Music",
42
+ "Musical", "Mystery", "News", "Reality-TV", "Romance",
43
+ "Sci-Fi", "Sport", "Talk-Show", "Thriller", "War", "Western" ]
44
+ Movier.tip_now "Listing all IMDB.com defined genre"
45
+ puts " " * 20 + "=" * 20
46
+ genre.each_slice(6) do |g|
47
+ puts " "*20 + g.join(", ")
48
+ end
49
+
50
+ end
51
+
52
+ def lookup(id)
53
+ @movies.each do |movie|
54
+ return movie if movie[:id] == id
55
+ end
56
+ false
57
+ end
58
+
59
+ def find(params)
60
+ read_data; @params = params; @params[:verbose] = true if @params[:shuffle]
61
+ raise "Please, add some movie boxes, before searching in the local movie database" unless @movies.any?
62
+
63
+ @movies.select!{|movie| movie[:title].downcase.include? @params[:keywords].downcase} if @params[:keywords]
64
+ [:directors, :writers, :actors, :genre, :tags].each { |kind| find_persons(kind) }
65
+ @movies.select!{|movie| movie[:rated] == @params[:rated] } if @params[:rated]
66
+ @movies.select!{|movie| movie[:rating] >= @params[:points].to_f } if @params[:points]
67
+
68
+ # filter on tag exclusion
69
+ tags = @params[:exclude_tags]
70
+ if tags
71
+ tags.split(",").each do |tag|
72
+ @movies.reject!{ |movie| movie[:tags].join(", ").downcase.include? tag.downcase.strip}
73
+ end
74
+ end
75
+ @movies.reject!{|movie| movie[:tags].join(", ").downcase.include? "watched"} unless @params[:tags] &&
76
+ @params[:tags].include?("watched")
77
+
78
+ sort_movies
79
+ @movies = @movies.slice(0, @params[:limit].to_i) if @params[:limit].to_i > 0
80
+ @movies = [ @movies.shuffle.first ] if @params[:shuffle]
81
+
82
+ counter = 1
83
+ @movies.each do |movie|
84
+ nice_name = "#{movie[:title]} [#{movie[:year]}]"
85
+ if @params[:verbose]
86
+ Movier.tip_now "%03d" % counter + ") " + nice_name, Movier.titleize(movie[:type])
87
+ Movier.tip_now movie[:path], "Path"
88
+ rating = "#{movie[:rated]} at #{movie[:rating]} points."
89
+ Movier.say_rated "Rating", rating, movie[:rating]
90
+ Movier.say_rated "Votes", "#{movie[:votes]} IMDB.com votes", movie[:rating]
91
+ Movier.say_rated "Genre", movie[:genre].join(", "), movie[:rating], true
92
+ Movier.say_rated "Runtime", movie[:runtime], movie[:rating]
93
+ Movier.say_rated "Directors", movie[:directors].join(", "), movie[:rating], true
94
+ Movier.say_rated "Actors", movie[:actors].join(", "), movie[:rating], true
95
+ Movier.say_rated "Writers", movie[:writers].join(", "), movie[:rating], true if @params[:writer]
96
+ Movier.say_rated "Plot", movie[:plot], movie[:rating], true
97
+ Movier.say_rated "Tags", movie[:tags].join(", "), movie[:rating] if movie[:tags].any?
98
+ else
99
+ message = "#{"%03d" % counter}) #{nice_name}"
100
+ Movier.say_rated Movier.titleize(movie[:type]), message, movie[:rating]
101
+ rating = "#{movie[:rated]} at #{movie[:rating]} points with #{movie[:votes]} votes."
102
+ Movier.say_rated "Rating", rating, movie[:rating]
103
+ Movier.say_rated "Directors", movie[:directors].join(", "), movie[:rating], true if @params[:directors]
104
+ Movier.say_rated "Actors", movie[:actors].join(", "), movie[:rating], true
105
+ Movier.say_rated "Tags", movie[:tags].join(", "), movie[:rating] if movie[:tags].any?
106
+ end
107
+ puts
108
+ counter += 1
109
+ end
110
+ return if @movies.empty?
111
+ filtered = @movies
112
+
113
+ # add some tags to this search
114
+ if @params[:add_tags]
115
+ tags = @params[:add_tags].split(",").map{|t| Movier.titleize(t.strip)}
116
+ read_data
117
+ filtered.each do |f|
118
+ @movies.each_with_index do |m,i|
119
+ @movies[i][:tags] |= tags if m[:id] == f[:id]
120
+ end
121
+ end
122
+ write_data
123
+ Movier.tip_now "Added tags: '#{tags.join(", ")}' to this search!"
124
+ end
125
+
126
+ # TODO: remove tags?
127
+
128
+ message = "Do you want me to play #{@params[:shuffle] ? "this" : "some"} movie for you? [enter number from above] "
129
+ begin; open = ask(message) {|x| x.default = "no" }; rescue Exception; end
130
+ if open == "no" && @params[:shuffle]
131
+ find @params
132
+ elsif open && open.to_i > 0
133
+ movie = filtered[open.to_i - 1]
134
+ # TODO: fix this to use a pure ruby implementation
135
+ nice_name = "#{movie[:title]} [#{movie[:year]}]"
136
+ movie_dir = "#{movie[:path]}".gsub(" ", "\\ ")
137
+ movie_file = `find #{movie_dir} -type f`.strip.split("\n")
138
+ movie_file = movie_file.select{|f| File.size(f) > 100 * 2**20}.first
139
+ Movier.tip_now "Opening: #{nice_name} with VLC Player"
140
+ `open "#{movie_file}" -a VLC &`
141
+ end
142
+ end
143
+
144
+ def sort_movies
145
+ @movies = @movies.sort_by {|movie| movie[:weight] }.reverse
146
+ end
147
+
148
+ # Update the local movie database,
149
+ # by revisiting all tracked directories,
150
+ # and building the database from there.
151
+ #
152
+ def update_itself
153
+ message = "Found no movie box in the local database.\n"
154
+ message += "Please, run `movier add` to add some movie boxes, before updating me!"
155
+ raise message if @boxes.empty?
156
+ @boxes.each { |box| delete(box); add(box) }
157
+ end
158
+
159
+ def delete(dir = nil)
160
+ dir = File.expand_path dir
161
+ @movies.reject!{ |movie| movie[:box] == dir }
162
+ @boxes.reject!{ |box| box == dir }
163
+ write_data
164
+ current_state
165
+ end
166
+
167
+ # Add a given directory to the local movie database.
168
+ # The directory should be pre-organized using the Organize command.
169
+ #
170
+ # * *Args* :
171
+ # - +dir+ -> directory to add to the local movie database
172
+ #
173
+ def add(dir = nil)
174
+ dir = File.expand_path dir
175
+ raise "No such directory!" unless File.directory?(dir)
176
+
177
+ imdb = Dir.glob("#{dir}/**/imdb.txt")
178
+ raise 'You should first run `movier organize` on this directory!' unless imdb.count > 0
179
+
180
+ count = 0
181
+ imdb.each do |file|
182
+ movie = Movier.read_yaml file
183
+ if in_database?(movie) && !@boxes.include?(dir)
184
+ Movier.warn_with "#{movie[:imdb]["Title"]} already exists in database!"
185
+ elsif !in_database?(movie)
186
+ @movies.push sanitize(movie, dir, file)
187
+ count += 1
188
+ end
189
+ end
190
+
191
+ @boxes.push dir unless @boxes.include? dir
192
+ write_data
193
+
194
+ Movier.passed_with "Added #{count} new movies in LMDB."
195
+ current_state
196
+ end
197
+
198
+ def current_state
199
+ Movier.tip_now "LMDB now contains #{@movies.count} movies, and #{@boxes.count} boxes."
200
+ end
201
+
202
+ def sanitize(movie, dir, imdb_file)
203
+ imdb = movie[:imdb]
204
+ nice_name = "#{imdb["Title"]} [#{imdb["Year"]}]"
205
+ hash = (Digest::MD5.new << nice_name).to_s.slice(0,8)
206
+ data = {
207
+ title: imdb["Title"],
208
+ year: imdb["Year"],
209
+ rated: imdb["Rated"],
210
+ released: imdb["Released"],
211
+ runtime: imdb["Runtime"],
212
+ genre: make_parts(imdb["Genre"]),
213
+ directors: make_parts(imdb["Director"]),
214
+ writers: make_parts(imdb["Writer"]),
215
+ actors: make_parts(imdb["Actors"]),
216
+ plot: imdb["Plot"],
217
+ poster: imdb["Poster"],
218
+ _poster: File.join(dir, nice_name, "poster#{File.extname(imdb["Poster"])}"),
219
+ rating: imdb["imdbRating"].to_f,
220
+ votes: imdb["imdbVotes"].to_s.delete(",").to_i,
221
+ type: imdb["Type"],
222
+ id: imdb["imdbID"],
223
+ hash: hash.force_encoding("UTF-8"),
224
+ tags: [],
225
+ box: dir,
226
+ path: File.dirname(imdb_file)
227
+ }
228
+ data[:weight] = (data[:rating] * data[:votes]).to_i
229
+ data
230
+ end
231
+
232
+ def make_parts(string, delimiter = ",")
233
+ string.split(delimiter).map{|x| x.strip}
234
+ end
235
+
236
+ def in_database?(movie)
237
+ @movies.select{|m| m[:id] == movie[:imdb]["imdbID"]}.any?
238
+ end
239
+ end
240
+
241
+ def self.read_yaml(file)
242
+ YAML.load_file(file)
243
+ end
244
+
245
+ def self.write_yaml(file, data)
246
+ File.open(file, "w") {|f| f.puts data.to_yaml }
247
+ end
248
+ end
@@ -0,0 +1,17 @@
1
+ # __BEGIN__
2
+ #
3
+ #
4
+ # i am here, because of bundler's love for me :)
5
+ #
6
+ #
7
+ # ============================================
8
+ # do not hate me for I have no class,
9
+ # my creator was an idiot, alas!
10
+ # what started as a simple idea,
11
+ # soon grew up, like his love for Tia!
12
+ # maybe, he will give me a brand new skin,
13
+ # till then, I am just a module, to begin!! :(
14
+ # ============================================
15
+ #
16
+ #
17
+ # __END__
@@ -0,0 +1,251 @@
1
+ module Movier
2
+
3
+ # Organize a given folder of movies
4
+ #
5
+ # * *Args* :
6
+ # - +path+ -> path to the folder with movies that need to be organized
7
+ #
8
+ def self.organize(path, options = {})
9
+ # path where files that need to be organized are.
10
+ path = path.first
11
+ path = Dir.pwd if not path or path.empty?
12
+ path = File.expand_path(path)
13
+
14
+ # select files that are larger than 100 MB and not yet, organized.
15
+ movies = Dir.glob("#{path}/**/*")
16
+ movies = movies.select{ |f| File.size(f) >= 100*2**20 }
17
+ movies = movies.reject{ |f| organized?(f) }
18
+
19
+ # if we can't find such files, let the user know so.
20
+ # otherwise, display how many files, we have found
21
+ if movies.count > 0
22
+ tip_now("Found approx. #{movies.count} movies")
23
+
24
+ # ask user where the organized files will be put up?
25
+ tip_now "Movies will be saved as: <organized>/<Language>/<rating>+/<movie_name> [<year>]"
26
+ options[:dir] = ask_for_directory("Where should I put the organized files? ")
27
+
28
+ # organize movies one by one
29
+ movies.each { |movie_path| organize_movie movie_path, options }
30
+
31
+ # show that we are done
32
+ passed_with("All movies were organized!")
33
+ else
34
+ passed_with("All movies have already been organized.")
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # Organize a single movie, given a path
41
+ #
42
+ # * *Args* :
43
+ # - +movie_path+ -> path of the movie that will be organized
44
+ #
45
+ def self.organize_movie(movie_path, options)
46
+ movie_path = File.expand_path(movie_path)
47
+ # let the user know which movie we are parsing..
48
+ tip_now movie_path, "Checking.."
49
+
50
+ # pick a target movie from IMDB.com based on the file path
51
+ movie = pick_movie movie_path, options[:guess]
52
+
53
+ # if we were unable to find the movie, or it was ignored, move ahead
54
+ return unless movie
55
+
56
+ # let the user know, which target was selected
57
+ imdb = movie[:imdb]
58
+ selected = "#{imdb["Title"]} [#{imdb["Year"]}"
59
+ selected += " at #{imdb["imdbRating"]} points with #{imdb["imdbVotes"]} votes"
60
+ say_rated "Selected", selected, imdb["imdbRating"]
61
+
62
+ # since, we can't find movie's language, ask user for it.
63
+ lang = options[:lang] || ask("What language this movie is in? ") {|x| x.default = "en" }
64
+
65
+ # rearrange/reorganize the movie
66
+ set_language_and_rearrange(movie, options[:dir], lang)
67
+ end
68
+
69
+ # pick a target movie from IMDB.com based on the movie's file path
70
+ #
71
+ # * *Args* :
72
+ # - +movie+ -> path to the movie
73
+ # * *Returns* :
74
+ # - hash of information about the selected movie target
75
+ #
76
+ def self.pick_movie(movie_path, guess = true)
77
+ # pick -> our selected target from IMDB.com
78
+ pick = false
79
+
80
+ # get whatever information we can get from movie's path
81
+ movie = sanitize_and_get_information_from movie_path
82
+
83
+ # search for movies with this information
84
+ search = search_movie(movie[:name], movie[:year])
85
+
86
+ # get information from IMDB.com for each movie found
87
+ search.map!{ |mov| fetch_details mov["imdbID"] }
88
+ # sort our movies
89
+ search.sort_by {|m| m["weight"] }
90
+
91
+ # can we make an intelligent guess ?
92
+ guessable = guess &&
93
+ search.count > 1 && (
94
+ search[1]["votes"] < 5000 &&
95
+ search[0]["votes"] > 100000 ) || (
96
+ search[0]['votes'] > 10000 &&
97
+ search[0]["weight"] > 40 * search[1]["weight"] )
98
+
99
+ # pick if only one movie was found, or if search is guessable
100
+ pick = search[0] if search.count == 1 || guessable
101
+
102
+ # let the user pick a movie otherwise from given options
103
+ unless pick
104
+ tip_now "Please, choose the correct movie title below:"
105
+ choose do |menu|
106
+ # let the user pick from found movie titles
107
+ search.each do |m|
108
+ m["details"] = "#{m["Title"]} [#{m["Year"]}] at "
109
+ m["details"] += "#{m["imdbRating"]} points "
110
+ m["details"] += "with #{m["imdbVotes"]} votes.\n\t"
111
+ m["details"] += "Plot: #{m["Plot"]}\n\t"
112
+ m["details"] += "Actors: #{m["Actors"]}\n"
113
+ menu.choice(m["details"]) { pick = m }
114
+ end
115
+ # let the user provide an IMDB ID
116
+ menu.choice("[ Use IMDB ID ]") do
117
+ id = ask("IMDB.com ID for the given title? ")
118
+ pick = fetch_details id
119
+ end
120
+ menu.choice("[ Use another keyword ]") do
121
+ keyword = ask("Keyword to search with? ")
122
+ pick_movie keyword
123
+ end
124
+ # let the user ignore this movie for organizing purposes
125
+ menu.choice("[ Ignore ]") { return false }
126
+ end
127
+ end
128
+
129
+ # return our pick or false for skipping this movie
130
+ movie[:imdb] = pick
131
+ pick ? movie : false
132
+ end
133
+
134
+ # Search a movie given some keywords, and optionally, an year.
135
+ # Loop until we have a target movie from IMDB.com.
136
+ # If need arises, ask user to provide us some keywords or an IMDB.com ID.
137
+ #
138
+ # * *Args* :
139
+ # - +keyword+ -> keywords for the search
140
+ # - +year+ -> (optional) year of this movie
141
+ # - +ask_user+ -> if true, asks a user for movie name/id
142
+ # * *Returns* :
143
+ # - hash of search results
144
+ #
145
+ def self.search_movie(keyword, year = nil, ask_user = false)
146
+ search = []; counter = 1
147
+ until search.any?
148
+ # on first search, it will search with keyword being passed
149
+ # on second search, will search with initial two words in the keyword
150
+ # on third search and onwards, will ask user for keywords/id
151
+ tip_now "Using keywords: #{keyword}", "Searching.." if counter > 1
152
+ begin; search = search(keyword, year, "movie"); rescue; end
153
+ begin; search = search(keyword, nil, "Movie") if year and not search.any?; rescue; end
154
+
155
+ if counter == 1
156
+ keyword = keyword.split
157
+ keyword = keyword.count == 1 ? keyword[0] : "#{keyword[0]} #{keyword[1]}"
158
+ elsif not search.any?
159
+ keyword = ask("Please, provide keywords/IMDB ID to search with: ")
160
+ return [{ "imdbID" => keyword }] if keyword =~ /tt\d{7}/
161
+ end
162
+
163
+ counter += 1;
164
+ end
165
+ search
166
+ end
167
+
168
+ # Set language for a movie, and then rearrange/reorganize it on the disk
169
+ #
170
+ # * *Args* :
171
+ # - +movie+ -> hash of information about this movie
172
+ # - +path+ -> directory where the organized movies will be kept
173
+ # - +lang+ -> language of this movie
174
+ #
175
+ def self.set_language_and_rearrange(movie, org_path, lang = "en")
176
+ # get the language for this movie
177
+ lang = case lang.downcase
178
+ when "en", "eng", "english" then "English"
179
+ when "hi", "hindi" then "Hindi"
180
+ when "fr", "french" then "French"
181
+ else lang.titlecase
182
+ end
183
+
184
+ # set the directory path for this movie
185
+ # directory structure is:
186
+ # <org_path>/<language>/<rating>+/<movie name> [<year>]/
187
+ # e.g. for the Top Gun movie:
188
+ # /Volumes/JukeBox/Movies/English/6+/Top Gun [1986]/
189
+ imdb = movie[:imdb]
190
+ nice_name = "#{imdb["Title"]} [#{imdb["Year"]}]".gsub(/(\\|\/|\:)/, ' - ')
191
+ movie_path = File.join(org_path, lang, "#{imdb["imdbRating"]}+", nice_name)
192
+ FileUtils.mkdir_p movie_path
193
+
194
+ Dir.chdir(movie_path) do
195
+ # move the movie to this folder
196
+ # TODO: skip copying the file if this movie already exists
197
+ FileUtils.mv movie[:path], nice_name + File.extname(movie[:path])
198
+ # create imdb.txt file inside this folder
199
+ File.open("imdb.txt", "w") {|f| f.puts movie.to_yaml}
200
+ # download the movie posted to this folder
201
+ `wget #{imdb["Poster"]} -qO poster#{File.extname(imdb["Poster"])}` unless
202
+ imdb["Poster"] == "N/A"
203
+ end
204
+ end
205
+
206
+ # Sanitize whatever information we got from movie's path
207
+ #
208
+ # * *Args* :
209
+ # - +path+ -> path to the movie
210
+ # * *Returns* :
211
+ # - hash of sanitized information about the movie
212
+ #
213
+ def self.sanitize_and_get_information_from(path)
214
+ movie = capture_movie(path)
215
+ movie[:name] = movie[:name].gsub(/(BR|DVD|[a-z]*)Rip/i, "")
216
+ movie[:name] = movie[:name].gsub(/divx/i, "")
217
+ movie[:name] = movie[:name].gsub(/[._-]/, " ").strip
218
+ movie[:parent] = File.basename(File.dirname(path))
219
+ movie[:path] = path
220
+ movie
221
+ end
222
+
223
+ # Capture information about the movie from its path
224
+ #
225
+ # * *Args* :
226
+ # - +path+ -> path of this movie
227
+ # * *Returns* :
228
+ # - hash of information about the movie
229
+ #
230
+ def self.capture_movie(path)
231
+ movie = File.basename(path, File.extname(path))
232
+
233
+ # try for: movie name [year] something.something
234
+ regex = /(.*)\[(\d{4})\].*/
235
+ match = movie.match regex
236
+ return {type: 1, name: match[1], year: match[2]} if match
237
+
238
+ # try for: movie name (year) something.something
239
+ regex = /(.*)\((\d{4})\).*/
240
+ match = movie.match regex
241
+ return {type: 2, name: match[1], year: match[2]} if match
242
+
243
+ # try for: movie name year something.something
244
+ regex = /(.*)(\d{4}).*/
245
+ match = movie.match regex
246
+ return {type: 3, name: match[1], year: match[2]} if match
247
+
248
+ # go generic
249
+ return {type: 0, name: movie }
250
+ end
251
+ end
data/lib/movier/say.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Movier
2
+ # say something in a nicely formatted way
3
+ #
4
+ # * *Args* :
5
+ # - +status+ -> status for this message
6
+ # - +message+ -> actual message
7
+ # - +colorscheme+ -> color scheme to be used for this message
8
+ def self.say_with_status(status, message, colorscheme = nil, do_break = false)
9
+ # Create a color scheme, naming color patterns with symbol names.
10
+ ft = HighLine::ColorScheme.new do |cs|
11
+ cs[:regular] = [ ]
12
+ cs[:above8] = [:bold, :green]
13
+ cs[:above6] = [:bold, :yellow]
14
+ cs[:below6] = [:red]
15
+ cs[:information] = [ :bold, :cyan ]
16
+ cs[:success] = [ :green ]
17
+ cs[:error] = [ :bold, :red]
18
+ cs[:warning] = [ :bold, :yellow ]
19
+ end
20
+ # Assign that color scheme to HighLine...
21
+ HighLine.color_scheme = ft
22
+ # default color scheme
23
+ colorscheme ||= :regular
24
+ status += " " * (20 - status.length)
25
+ message = message.gsub("'", %q(\\\'))
26
+ if do_break
27
+ message = message.split.each_slice(10).map{|x| x.join(" ") }
28
+ message = message.join("\n" + " " * 25)
29
+ end
30
+ say("<%= color(' #{status} #{message}', '#{colorscheme}') %>")
31
+ end
32
+
33
+ def self.tip_now(message, status="Information", do_break = false)
34
+ say_with_status status, message, :information, do_break
35
+ end
36
+
37
+ def self.passed_with(message, do_break = false)
38
+ say_with_status "Success", message, :success, do_break
39
+ end
40
+
41
+ def self.warn_with(message, do_break = false)
42
+ say_with_status "Warning", message, :warning, do_break
43
+ end
44
+
45
+ def self.failed_with(message, do_break = false)
46
+ say_with_status "ERROR", message, :error, do_break
47
+ raise message
48
+ end
49
+ end
@@ -1,3 +1,3 @@
1
1
  module Movier
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
data/lib/movier.rb CHANGED
@@ -3,11 +3,11 @@ require 'movier/version.rb'
3
3
  # Add requires for other files you add to your project here, so
4
4
  # you just need to require this one file in your bin file
5
5
 
6
+ # require 'pry'
6
7
  require 'open-uri'
7
8
  require 'json'
8
9
  require 'httparty'
9
10
  require 'highline/import'
10
- # require 'pry'
11
11
  require 'digest/md5'
12
12
 
13
13
  require 'movier/say.rb'
data/movier.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # Ensure we require the local version and not one we might have installed already
2
+ require File.join([File.dirname(__FILE__),'lib','movier','version.rb'])
3
+ Gem::Specification.new do |s|
4
+ s.name = 'movier'
5
+ s.version = Movier::VERSION
6
+ s.author = 'Nikhil Gupta'
7
+ s.email = 'me@nikhgupta.com'
8
+ s.homepage = 'http://nikhgupta.com'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Movier is a gem that allows you to quickly organize your movies'
11
+ s.description = 'Movier is a gem that allows you to quickly organize your movies'
12
+ # Add your other files here if you make them
13
+ s.files = `git ls-files`.split
14
+ s.require_paths << 'lib'
15
+ s.has_rdoc = true
16
+ s.extra_rdoc_files = ['README.rdoc','movier.rdoc']
17
+ s.rdoc_options << '--title' << 'movier' << '--main' << 'README.rdoc' << '-ri'
18
+ s.bindir = 'bin'
19
+ s.executables << 'movier'
20
+
21
+ s.add_dependency 'json'
22
+ s.add_dependency 'highline'
23
+ s.add_dependency 'httparty'
24
+
25
+ s.add_development_dependency 'pry'
26
+ s.add_development_dependency 'rake'
27
+ s.add_development_dependency 'rdoc'
28
+ s.add_development_dependency 'aruba'
29
+
30
+ s.add_runtime_dependency 'gli','2.5.3'
31
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: movier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikhil Gupta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-01 00:00:00.000000000 Z
11
+ date: 2013-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
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'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: highline
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +122,7 @@ dependencies:
108
122
  - - '='
109
123
  - !ruby/object:Gem::Version
110
124
  version: 2.5.3
111
- description:
125
+ description: Movier is a gem that allows you to quickly organize your movies
112
126
  email: me@nikhgupta.com
113
127
  executables:
114
128
  - movier
@@ -117,10 +131,24 @@ extra_rdoc_files:
117
131
  - README.rdoc
118
132
  - movier.rdoc
119
133
  files:
134
+ - .gitignore
135
+ - Gemfile
136
+ - README.rdoc
137
+ - Rakefile
120
138
  - bin/movier
121
- - lib/movier/version.rb
139
+ - features/movier.feature
140
+ - features/step_definitions/movier_steps.rb
141
+ - features/support/env.rb
122
142
  - lib/movier.rb
123
- - README.rdoc
143
+ - lib/movier/api.rb
144
+ - lib/movier/helpers.rb
145
+ - lib/movier/info.rb
146
+ - lib/movier/lmdb.rb
147
+ - lib/movier/movier.rb
148
+ - lib/movier/organize.rb
149
+ - lib/movier/say.rb
150
+ - lib/movier/version.rb
151
+ - movier.gemspec
124
152
  - movier.rdoc
125
153
  homepage: http://nikhgupta.com
126
154
  licenses: []