movier 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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: []