├── .ruby-version
├── spec
├── .gitignore
├── rank_spec.rb
├── download_spec.rb
├── the_rotten_pirate_spec.rb
├── name_cleaner_spec.rb
└── upcoming_dvds.txt
├── .ruby-gemset
├── Manifest
├── config
├── prowl.template.yml
├── schedule.rb
└── config.template.yml
├── .gitignore
├── Gemfile
├── lib
├── yaml_writer.rb
├── rank.rb
├── fork_logger.rb
├── download.rb
├── the_rotten_pirate.rb
└── name_cleaner.rb
├── Gemfile.lock
├── Rakefile
└── README.md
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.0.0
2 |
--------------------------------------------------------------------------------
/spec/.gitignore:
--------------------------------------------------------------------------------
1 | junk*
2 |
--------------------------------------------------------------------------------
/.ruby-gemset:
--------------------------------------------------------------------------------
1 | rotten_pirate
2 |
--------------------------------------------------------------------------------
/Manifest:
--------------------------------------------------------------------------------
1 | README.md
2 | Rakefile
3 | lib/the_rotten_pirate.rb
4 | Manifest
5 |
--------------------------------------------------------------------------------
/config/prowl.template.yml:
--------------------------------------------------------------------------------
1 | ---
2 | active: true
3 | api_key: your_api_key_here
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | db/*
2 | tmp/*
3 | logs/*
4 | config/prowl.yml
5 | config/config.yml
6 |
--------------------------------------------------------------------------------
/config/schedule.rb:
--------------------------------------------------------------------------------
1 | set :output, "/dev/null"
2 |
3 | every 2.days, :at => '2:43 pm' do
4 | rake "execute"
5 | end
6 |
7 | every 5.minutes do
8 | rake "download_from_watch_file"
9 | end
10 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | group :test do
4 | gem 'rspec', '~>2.7'
5 | gem 'awesome_print'
6 | end
7 |
8 | gem 'torrent_api', '0.2.7'
9 | gem 'sequel', '~>3.2'
10 | gem 'whenever'
11 | gem 'sqlite3'
12 | gem 'i18n'
13 | gem 'rake'
14 | gem 'prowl'
15 | gem 'pry'
16 |
--------------------------------------------------------------------------------
/lib/yaml_writer.rb:
--------------------------------------------------------------------------------
1 | require 'yaml'
2 |
3 | class YAMLWriter
4 | def initialize object
5 | @yaml = object.to_yaml
6 | end
7 |
8 | def write
9 | FileUtils.mkdir_p('logs')
10 | timestamp = Time.now.strftime("%Y%m%d%H%M%S")
11 | filename = "logs/#{timestamp}_results.yaml"
12 | File.open(filename, 'w') { |f| f.puts @yaml }
13 | end
14 | end
--------------------------------------------------------------------------------
/config/config.template.yml:
--------------------------------------------------------------------------------
1 | ---
2 | filter_out_less_than_percentage: 85
3 | filter_out_non_certified_fresh: true
4 | filter_out_already_downloaded: true
5 | comments:
6 | analyze: true
7 | quality: high # or low
8 | num_to_analyze: 10
9 | minimum_seeds: 50
10 | download_directory: ~/Torrents
11 | watch_file: /Users/admin/Sites/the_mobile_pirate/movies_to_download.txt
--------------------------------------------------------------------------------
/lib/rank.rb:
--------------------------------------------------------------------------------
1 | class Rank
2 | BASE = 6
3 | OFFSET = 2
4 |
5 | def initialize scores
6 | @scores = scores
7 | end
8 |
9 | def score
10 | return 0.0 if @scores.empty?
11 | score = @scores.inject{ |sum, score| sum + score }.to_f / @scores.size
12 | offset = [(@scores.size - Rank::OFFSET), 1 ].max
13 | score += Math.log offset, Rank::BASE
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/rank_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/the_rotten_pirate'
2 |
3 | describe Rank do
4 | describe "initialize" do
5 | it "should take the scores hash as an initializer" do
6 | scores_array = [9.0, 9.0, 9.0, 9.0]
7 | rank = Rank.new scores_array
8 | scores = rank.instance_variable_get(:@scores)
9 | scores.should == scores_array
10 | rank.should_not be_nil
11 | end
12 | end
13 |
14 | describe "score" do
15 | it "should have a higher score if there are more votes (by at least OFFSET) but the average is the same" do
16 | scores_array = [9.0, 9.0, 9.0, 9.0]
17 | rank_higher = Rank.new scores_array
18 | rank_lower = Rank.new [9.0]
19 | rank_higher.score.should > rank_lower.score
20 | end
21 |
22 | it "should have a higher ranking if there are 10 9.125's than if there is 2 10s." do
23 | rank_higher = Rank.new [9.125,9.125,9.125,9.125,9.125,9.125,9.125,9.125]
24 | rank_lower = Rank.new [10.0, 10.0]
25 | rank_higher.score.should > rank_lower.score
26 | end
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/lib/fork_logger.rb:
--------------------------------------------------------------------------------
1 | require 'logger'
2 |
3 | class ForkLogger
4 | def initialize
5 | @loggers = []
6 | FileUtils.mkdir_p('logs')
7 | timestamp = Time.now.strftime("%Y%m%d%H%M%S")
8 | @loggers << Logger.new("logs/#{timestamp}.log")
9 | @loggers << Logger.new(STDOUT)
10 | @prowl
11 | end
12 |
13 | def prowl_message event, description
14 | require 'prowl'
15 |
16 | prowl_config_file = File.join 'config', 'prowl.yml'
17 | if File.exists? prowl_config_file
18 | prowl_config = YAML.load File.open(prowl_config_file).read
19 | if prowl_config["active"]
20 | Prowl.add(
21 | :apikey => prowl_config["api_key"],
22 | :application => "The Rotten Pirate",
23 | :event => event,
24 | :description => description
25 | )
26 | end
27 | end
28 | puts "Prowl notification sent '#{event}'"
29 | end
30 |
31 | def puts message
32 | @loggers.each do |logger|
33 | logger.info message
34 | end
35 | end
36 |
37 | def method_missing method, message
38 | @loggers.each do |logger|
39 | logger.send(method, message)
40 | end
41 | end
42 | end
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | activesupport (3.1.1)
5 | multi_json (~> 1.0)
6 | awesome_print (0.4.0)
7 | chronic (0.6.5)
8 | coderay (1.1.0)
9 | diff-lcs (1.1.3)
10 | hpricot (0.8.6)
11 | i18n (0.6.0)
12 | method_source (0.8.2)
13 | multi_json (1.0.3)
14 | nokogiri (1.5.2)
15 | prowl (0.1.3)
16 | pry (0.9.12.4)
17 | coderay (~> 1.0)
18 | method_source (~> 0.8)
19 | slop (~> 3.4)
20 | rake (0.9.2.2)
21 | rspec (2.7.0)
22 | rspec-core (~> 2.7.0)
23 | rspec-expectations (~> 2.7.0)
24 | rspec-mocks (~> 2.7.0)
25 | rspec-core (2.7.1)
26 | rspec-expectations (2.7.0)
27 | diff-lcs (~> 1.1.2)
28 | rspec-mocks (2.7.0)
29 | sequel (3.29.0)
30 | slop (3.4.7)
31 | sqlite3 (1.3.4)
32 | torrent_api (0.2.7)
33 | hpricot
34 | nokogiri
35 | whenever (0.7.0)
36 | activesupport (>= 2.3.4)
37 | chronic (~> 0.6.3)
38 |
39 | PLATFORMS
40 | ruby
41 |
42 | DEPENDENCIES
43 | awesome_print
44 | i18n
45 | prowl
46 | pry
47 | rake
48 | rspec (~> 2.7)
49 | sequel (~> 3.2)
50 | sqlite3
51 | torrent_api (= 0.2.7)
52 | whenever
53 |
--------------------------------------------------------------------------------
/lib/download.rb:
--------------------------------------------------------------------------------
1 | require 'sequel'
2 | require 'uri'
3 |
4 | class Download
5 | def self.connection
6 | Sequel.sqlite('db/downloads.sqlite')
7 | end
8 |
9 | def self.exists? name
10 | db = connection
11 | !!(db[:downloads].filter(:name => name).first)
12 | end
13 |
14 | def self.insert name
15 | db = connection
16 | db[:downloads].insert(:name => name)
17 | end
18 |
19 | def self.delete name
20 | connection[:downloads].filter(:name => name).delete
21 | end
22 |
23 | def self.clean_title movie_title
24 | movie_title.gsub(/-/, '')
25 | end
26 |
27 | def self.download_directory
28 | config = YAML.load(File.open(File.dirname(__FILE__) + '/../config/config.yml').read)
29 | download_dir = config["download_directory"] || 'tmp/torrent'
30 | dirs = download_dir.strip.split('/')
31 | # This converts any relative home directories to one ruby likes better (osx only probably)
32 | dirs.map { |dir| dir == "~" ? Dir.home : dir }
33 | end
34 |
35 | def self.torrent_from_url url
36 | require 'net/http'
37 |
38 | torrent_filename_match = url.match(/.*\/(.*)/)
39 | torrent_name = torrent_filename_match.nil? ? "tmp.torrent" : torrent_filename_match[1]
40 | torrent_domain, torrent_uri = url.gsub(/https?:\/\//, '').split('.se')
41 | torrent_uri = URI.escape(torrent_uri)
42 | torrent_domain += '.se'
43 |
44 | FileUtils.mkdir_p File.join(self.download_directory)
45 | filename = File.join(self.download_directory, torrent_name)
46 | Net::HTTP.start(torrent_domain) do |http|
47 | resp = http.get(torrent_uri)
48 | if resp.msg == 'OK'
49 | open(filename, "wb") { |file| file.write(resp.body) }
50 | else
51 | puts "HTTP Response not OK; was #{resp.msg}: #{resp.code}"
52 | nil
53 | end
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/spec/download_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/the_rotten_pirate'
2 |
3 | describe Download do
4 | describe "exists?" do
5 | before do
6 | db = Download.connection
7 | @dataset = db[:downloads]
8 | @dataset.insert(:name => 'Movie Title')
9 | end
10 |
11 | after do
12 | @dataset.filter(:name => 'Movie Title').delete
13 | end
14 |
15 | it "should detect if the download exists in the database" do
16 | Download.exists?("Movie Title").should be_true
17 | end
18 |
19 | it "should return false if the download doesn't exist in the database" do
20 | Download.exists?("Movie Poopy").should be_false
21 | end
22 | end
23 |
24 | describe "insert" do
25 | after do
26 | Download.connection[:downloads].filter(:name => 'Movie Title').delete
27 | end
28 |
29 | it "should insert a download into the database" do
30 | Download.exists?("Movie Title").should be_false
31 | Download.insert("Movie Title").should be_true
32 | Download.exists?("Movie Title").should be_true
33 | end
34 | end
35 |
36 | describe "clean_title" do
37 | it "should remove any minus signs" do
38 | clean_title = Download.clean_title "Harry Potter and the Deathly Hallows - Part 2"
39 | clean_title.should == "Harry Potter and the Deathly Hallows Part 2"
40 | end
41 | end
42 |
43 | describe "torrent_from_url" do
44 | it "should be able to download movies with square brackets in it's name" do
45 | lambda {
46 | Download.torrent_from_url "http://torrents.thepiratebay.se/6581033/Bad_Teacher[2011]R5_Line_XviD-ExtraTorrentRG.6581033.TPB.torrent"
47 | }.should_not raise_error
48 | end
49 | end
50 |
51 | describe "delete" do
52 | it "should delete the download of the same name" do
53 | Download.insert("Movie Title").should be_true
54 | Download.exists?("Movie Title").should be_true
55 | Download.delete("Movie Title").should eq 1
56 | Download.delete("Movie Title").should eq 0
57 | end
58 | end
59 | end
60 |
61 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | task :default => :console
2 |
3 | desc "Loads up a console environment"
4 | task :console do
5 | exec "irb -I lib -r #{File.join(File.dirname(__FILE__), "lib/the_rotten_pirate")}"
6 | end
7 |
8 | desc "Runs download specs"
9 | task :spec do
10 | exec "rspec #{File.join(File.dirname(__FILE__), 'spec/download_spec.rb')}"
11 | end
12 |
13 | desc "Creates the initial database for storing movie downloads and missing config files"
14 | task :initialize do
15 |
16 | require "sequel"
17 |
18 | FileUtils.mkdir_p('db')
19 | DB = Sequel.sqlite('db/downloads.sqlite')
20 |
21 | begin
22 | DB.create_table :downloads do
23 | primary_key :id
24 | String :name
25 | end
26 | puts "Creating sqlite database..."
27 | rescue Exception
28 | puts "Database already exists"
29 | end
30 |
31 | config_templpate = File.join(File.dirname(__FILE__), "config", "config.template.yml")
32 | prowl_template = File.join(File.dirname(__FILE__), "config", "prowl.template.yml")
33 | target_config_file = File.join(File.dirname(__FILE__), "config", "config.yml")
34 | target_prowl_file = File.join(File.dirname(__FILE__), "config", "prowl.yml")
35 | unless File.exists? target_prowl_file
36 | FileUtils.copy(prowl_template, target_prowl_file)
37 | puts "Creating working prowl config file..."
38 | end
39 |
40 | unless File.exists? target_config_file
41 | FileUtils.copy(config_templpate, target_config_file)
42 | puts "Creating working rotten pirate config file..."
43 | end
44 | end
45 |
46 | desc "This task loads the config file, fetches the rotten tomatoes feed, and downloads torrent files."
47 | task :execute do
48 | require_relative 'lib/the_rotten_pirate'
49 | TheRottenPirate.execute
50 | end
51 |
52 | desc "This task loads a file (newline delimited) and downloads each of the movies."
53 | task :download_from_watch_file do
54 | require_relative 'lib/the_rotten_pirate'
55 | trp = TheRottenPirate.new
56 | filename = trp.config['watch_file']
57 | if filename.nil?
58 | puts "There was no filename specified in the config file"
59 | return
60 | end
61 |
62 | File.open(filename, 'r').each do |movie_title|
63 | trp.initialize_download movie_title
64 | end
65 |
66 | File.open(filename, 'w') {} # truncate file
67 | end
68 |
69 |
70 | desc "This task will download a single movie"
71 |
72 | task :download, :movie do |t, args|
73 | movie = args[:movie]
74 |
75 | abort "You must specify a movie to download: rake download[\"The Lion King\"]" if movie.nil?
76 |
77 | require_relative 'lib/the_rotten_pirate.rb'
78 | trp = TheRottenPirate.new
79 | trp.initialize_download movie
80 |
81 | end
82 |
--------------------------------------------------------------------------------
/spec/the_rotten_pirate_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/the_rotten_pirate'
2 |
3 | describe TheRottenPirate do
4 | describe "#extract dvd info" do
5 | it "should extract some dvds given a text file with them" do
6 | text = File.open(File.join('spec', 'upcoming_dvds.txt'),'r').read
7 | dvds = TheRottenPirate.extract_new_dvds text
8 | dvds.first.should == {
9 | "MovieID"=>"770687943",
10 | "Title"=>"Harry Potter and the Deathly Hallows - Part 2",
11 | "URL"=>"http://www.rottentomatoes.com/m/harry_potter_and_the_deathly_hallows_part_2/",
12 | "NumTomatometerPercent"=>"96",
13 | "TomatometerRating"=>"FRESH",
14 | "CertifiedFresh"=>"1",
15 | "ModifiedDate"=>"",
16 | "ShowtimeURL"=>"",
17 | "Description"=>"Thrilling, powerfully acted, and visually dazzling, Deathly Hallows Part II brings the Harry Potter franchise to a satisfying -- and suitably magical -- conclusion."}
18 | end
19 | end
20 |
21 | describe "#fetch_new_dvds" do
22 | it "should grab the upcoming dvds text file and then call extract_new_dvds" do
23 | TheRottenPirate.should_receive(:extract_new_dvds)
24 | TheRottenPirate.new.fetch_new_dvds
25 | end
26 | end
27 |
28 | describe "#filter_out_non_certified_fresh" do
29 | it "should not show any non certified movies" do
30 | dvds = TheRottenPirate.new.filter_out_non_certified_fresh
31 | dvds.each { |dvd| dvd["CertifiedFresh"].should_not eq "0" }
32 | end
33 | end
34 |
35 | describe "#filter_out_already_downloaded" do
36 | before do
37 | Download.insert "The Strange Case Of Angelica"
38 | end
39 |
40 | after do
41 | Download.connection[:downloads].filter(:name => 'Movie Title').delete
42 | end
43 |
44 | it "should filter out already downloaded movies" do
45 | trp = TheRottenPirate.new
46 | trp.filter_out_already_downloaded
47 | dvds = trp.instance_variable_get(:@dvds)
48 | dvds.each { |dvd| dvd["Title"].should_not eq "The Strange Case Of Angelica" }
49 | end
50 | end
51 |
52 | describe "#filter_percentage" do
53 | it "should filter out anything lower than the parameter" do
54 | dvds = TheRottenPirate.new
55 | dvds.fetch_new_dvds
56 | before = dvds.instance_variable_get(:@dvds)
57 | filtered_dvds = dvds.filter_percentage(80)
58 | filtered_dvds.each { |dvd| dvd["NumTomatometerPercent"].to_i.should > 80 }
59 | after = dvds.instance_variable_get(:@dvds)
60 |
61 | before.size.should > after.size
62 | end
63 |
64 | it "should be chainable with non certified" do
65 | filter = TheRottenPirate.new
66 | filter.filter_percentage(80)
67 | eightieth_percentile = filter.instance_variable_get(:@dvds)
68 |
69 | filter.filter_out_non_certified_fresh
70 | certified_and_eighteith_percentile = filter.instance_variable_get(:@dvds)
71 | (eightieth_percentile - certified_and_eighteith_percentile).size.should == 4
72 | end
73 |
74 | end
75 |
76 | end
77 |
78 |
--------------------------------------------------------------------------------
/spec/name_cleaner_spec.rb:
--------------------------------------------------------------------------------
1 | require_relative '../lib/the_rotten_pirate'
2 |
3 | describe NameCleaner do
4 | describe "#inititalize" do
5 | it "should take a string and set it to it's raw_name" do
6 | name = NameCleaner.new "string"
7 | name.raw_name.should == "string"
8 | end
9 | end
10 |
11 | describe "#clean_punctuation" do
12 | it "should get rid of periods and parenthesis" do
13 | nc = NameCleaner.new("Submarine.2011.720p.BDRip.x264.AC3.dxva-HDLiTE")
14 | nc.clean_punctuation
15 | nc.clean_name.should == "Submarine 2011 720p BDRip x264 AC3 dxva HDLiTE"
16 | end
17 | end
18 |
19 | describe "#remove_release_year" do
20 | it "should delete it from the clean name" do
21 | nc = NameCleaner.new("String[2010]")
22 | nc.remove_release_year
23 | nc.clean_name.should == "String[]"
24 | end
25 | end
26 |
27 | describe "#remove_urls" do
28 | it "should delete it from the clean name" do
29 | nc = NameCleaner.new("String www.root.com www.example.com")
30 | nc.remove_urls
31 | nc.clean_name.should == "String "
32 | end
33 | end
34 |
35 | describe "#remove_release_groups" do
36 | it "should delete it from the clean name" do
37 | nc = NameCleaner.new("String MAXSPEED NOVA")
38 | nc.remove_release_groups
39 | nc.clean_name.should == "String "
40 | end
41 | end
42 |
43 | describe "#remove_video_types" do
44 | it "should delete it from the clean name" do
45 | nc = NameCleaner.new("String DVDRip BRRip")
46 | nc.remove_video_types
47 | nc.clean_name.should == "String "
48 | end
49 | end
50 |
51 | describe "the dynamically generated remove methods" do
52 | ["release_year", "video_types", "filetype", "release_groups", "urls"].each do |method|
53 | it "should respond to remove_#{method}" do
54 | nc = NameCleaner.new "doesn't matter"
55 | method = :"remove_#{method}"
56 | nc.should respond_to method
57 | end
58 | end
59 | end
60 |
61 | describe "#release_year" do
62 | it "should extract any four digit number" do
63 | NameCleaner.new("String[2010]").release_year.should == 2010
64 | end
65 |
66 | it "should only extract the year if it's an appropriate year (1950 - Next Year)" do
67 | NameCleaner.new("String[1337]").release_year.should == nil
68 | NameCleaner.new("String[1894]").release_year.should == nil
69 | NameCleaner.new("String[1895]").release_year.should == 1895
70 | NameCleaner.new("String[2050]").release_year.should == nil
71 | end
72 |
73 | it "should only extract the proper four digit number if there are two and one is a year" do
74 | NameCleaner.new("String[1337]2010").release_year.should == 2010
75 | end
76 | end
77 |
78 | describe "#video_types" do
79 | it "should detect if a movie is 720, 1080p, xvid, etc..." do
80 | NameCleaner.new("Submarine 2011 DVDRip Xvid UnKnOwN").video_types.should =~ [index_of("video_types", "dvdrip"), index_of("video_types", "xvid")]
81 | NameCleaner.new("Submarine.2011.720p.BDRip.x264.AC3.dxva-HDLiTE").video_types.should =~ [index_of("video_types", "720p"), index_of("video_types", "bdrip"), index_of("video_types", "x264"), index_of("video_types", "ac3")]
82 | end
83 |
84 | it "should not detect a stupid end of word with 'ts', but should detect a Theatrical Screening" do
85 | NameCleaner.new("Rise.of.the.Planet.of.the.Apes.2011.TS.XviD-NOVA.avi").video_types.should =~ [index_of("video_types", "TS"), index_of("video_types", "xvid")]
86 | NameCleaner.new("Rare.Exports.A.Christmas.Tale.2010.[UsaBit.com].avi").video_types.should =~ []
87 | end
88 | end
89 |
90 | describe "#remove_whitespace" do
91 | it "should remove spaces on both sides" do
92 | nm = NameCleaner.new " hallllllelujah "
93 | nm.remove_whitespace
94 | nm.clean_name.should == "hallllllelujah"
95 | end
96 |
97 | it "should switch anything greater than one spaces to one spaces" do
98 | nm = NameCleaner.new "there was once many spaces between here"
99 | nm.remove_whitespace
100 | nm.clean_name.should == "there was once many spaces between here"
101 | end
102 | end
103 |
104 | describe "#clean" do
105 | it "should remove all of the junk. ALL OF IT!" do
106 | NameCleaner.new("Rise.of.the.Planet.of.the.Apes.2011.TS.XviD-NOVA.avi").clean.should == "Rise of the Planet of the Apes"
107 | NameCleaner.new("True Grit[2010]BRRip XviD-ExtraTorrentRG").clean.should == "True Grit"
108 | NameCleaner.new("Submarine.2011.720p.BDRip.x264.AC3.dxva-HDLiTE").clean.should == "Submarine"
109 | NameCleaner.new("The.Triplets.of Belleville.2011.TS.XviD-NOVA.avi").clean.should == "The Triplets of Belleville"
110 | end
111 | end
112 |
113 | describe "#filetype" do
114 | it "should detect the type of video that it is if it looks like a filename" do
115 | NameCleaner.new("Rise.of.the.Planet.of.the.Apes.2011.TS.XviD-NOVA.avi").filetype.should == 'avi'
116 | end
117 | end
118 |
119 | describe "#release_groups" do
120 | it "should detect release groups" do
121 | NameCleaner.new("Rise.of.the.Planet.of.the.Apes.2011.TS.XviD-NOVA.avi").release_groups.should == [index_of('release_groups', 'NOVA')]
122 | NameCleaner.new("Cars 2 (2011) DVDRip XviD-MAXSPEED").release_groups.should == [index_of('release_groups', 'MAXSPEED')]
123 | NameCleaner.new("Beginners.2010.LIMITED.BDRip.XviD-TARGET").release_groups.should == [index_of('release_groups', 'TARGET')]
124 | NameCleaner.new("Bridesmaids [2011] UNRATED DVDRip XviD-DUBBY").release_groups.should == [index_of('release_groups', 'DUBBY')]
125 | NameCleaner.new("Bridesmaids.2011.BRRip.XviD.Ac3.Feel-Free.avi.part").release_groups.should == [index_of('release_groups', 'Feel-Free')]
126 | NameCleaner.new("Captain America The First Avenger (2011) DVDRip XviD-MAXSPEED").release_groups.should == [index_of('release_groups', 'MAXSPEED')]
127 | end
128 | end
129 |
130 | describe "#urls" do
131 | it "should extract any and all urls" do
132 | NameCleaner.new("District 9 (2009) DVDRip XviD-MAXSPEED www.torrentzalive.com").urls.should == ['www.torrentzalive.com']
133 | end
134 |
135 | it "shouldn't die if no URLs were found" do
136 | NameCleaner.new("District 9 (2009) DVDRip XviD-MAXSPEED").urls.should == []
137 | end
138 | end
139 | end
140 |
141 | def index_of(type, string)
142 | method = :"#{type}_terms"
143 | terms = NameCleaner.send(method)
144 | terms.index { |term| term[:name] == string }
145 | end
146 |
--------------------------------------------------------------------------------
/lib/the_rotten_pirate.rb:
--------------------------------------------------------------------------------
1 | require 'awesome_print'
2 | require 'torrent_api'
3 | require 'uri'
4 | require 'yaml'
5 | require_relative 'download'
6 | require_relative 'fork_logger'
7 | require_relative 'name_cleaner'
8 | require_relative 'rank'
9 | require_relative 'yaml_writer'
10 |
11 | class TheRottenPirate
12 | attr_reader :config
13 |
14 | def initialize
15 | @config = YAML.load(File.open(File.dirname(__FILE__) + '/../config/config.yml').read)
16 | @dvds = nil
17 | @l = ForkLogger.new
18 | end
19 |
20 | def initialize_download movie_title
21 | torrent_to_download, full_results = search_for_dvd movie_title
22 | if torrent_to_download.nil?
23 | puts "No results found for #{movie_title}"
24 | return
25 | end
26 | puts "Starting the download for #{movie_title}"
27 | if Download.torrent_from_url URI.escape(torrent_to_download[:link])
28 | Download.insert torrent_to_download[:title]
29 | puts "Download successfully started."
30 | else
31 | exit("Download failed while starting.")
32 | end
33 | end
34 |
35 | def search_for_dvd(title, full_analysis=[])
36 | @l.puts "*" * 80
37 | @l.puts "Searching for #{title}"
38 | @l.puts "*" * 80
39 | search = PirateBay::Search.new Download.clean_title(title)
40 | results = search.execute
41 | @l.puts "Found #{results.size} results from the pirate bay."
42 |
43 | return if results.empty?
44 |
45 | if @config["comments"]["analyze"]
46 | num_to_analyze = @config["comments"]["num_to_analyze"]
47 | minimum_seeds = @config["comments"]["minimum_seeds"]
48 |
49 | quality_level = @config["comments"]["quality"] == "low" ? :init : :full
50 | @l.puts "Processing #{[num_to_analyze, results.size].min} torrent pages for comments (as configured)."
51 |
52 | analysis_results = analyze_results results, num_to_analyze, quality_level, minimum_seeds
53 | analysis_results = analysis_results.sort_by { |r| -(r[:video][:rank]) }
54 |
55 | [{ :link => analysis_results.first[:link], :title => title }, analysis_results]
56 | else
57 | { :link => results.first.link, :title => title }
58 | end
59 | end
60 |
61 |
62 | def self.execute
63 |
64 | captain = TheRottenPirate.new
65 | output = captain.instance_variable_get(:@l)
66 | captain.gather_and_filter_dvds
67 |
68 | full_analysis_results = []
69 | torrents_to_download = []
70 |
71 | captain.summarize_process_to_output
72 |
73 | captain.dvds.each do |dvd|
74 | torrent_to_download, full_analysis_result = captain.search_for_dvd(dvd["Title"])
75 | torrents_to_download << torrent_to_download unless torrent_to_download.nil?
76 | full_analysis_results << full_analysis_result unless full_analysis_result.nil?
77 | end
78 |
79 | YAMLWriter.new({ :full_analysis_results => full_analysis_results, :links_to_download => torrents_to_download }).write
80 |
81 | torrents_to_download.each do |download|
82 | output.puts "Starting the download for #{download[:title]}"
83 | if Download.torrent_from_url download[:link]
84 | Download.insert download[:title]
85 | output.puts "Download successfully started."
86 | else
87 | output.error "Download failed while starting."
88 | end
89 | end
90 |
91 | output.puts "Done!"
92 | output.puts "Downloaded a total of #{torrents_to_download.size} torrents"
93 | output.prowl_message "Downloaded #{torrents_to_download.size} movies", torrents_to_download.map{|m| m[:title] }.join(", ")
94 | end
95 |
96 | def analyze_results results, num_to_analyze, quality_level, minimum_seeds
97 | results = results[0,num_to_analyze].map do |result|
98 | url = "http://www.thepiratebay.se/torrent/#{result.id}/"
99 |
100 | if result.seeds < minimum_seeds
101 | @l.puts "Number of seeds are lower than the threshold - not querying for comments."
102 | construct_result_hash result, url
103 | else
104 | html = open(url).read
105 | details = PirateBay::Details.new html, quality_level
106 | @l.puts "Fetching comments results from #{url}"
107 | construct_result_hash result, url, details
108 | end
109 | end
110 | end
111 |
112 | def construct_result_hash result, url, details=nil
113 | if details.nil?
114 | video_average = 0
115 | video_sum = 0
116 | video_votes = 0
117 | video_rank = 0
118 | else
119 | video_average = details.video_quality_average
120 | video_sum = details.video_quality_score_sum
121 | video_votes = details.video_scores.size
122 | video_rank = Rank.new(details.video_scores).score
123 | end
124 |
125 | {
126 | :seeds => result.seeds,
127 | :size => result.size,
128 | :name => result.name,
129 | :video =>
130 | {
131 | :average=> video_average,
132 | :sum => video_sum,
133 | :votes => video_votes,
134 | :rank => video_rank
135 | },
136 | :url => url,
137 | :link => result.link
138 | }
139 | end
140 |
141 | def gather_and_filter_dvds
142 | @l.puts "Searching..."
143 | fetch_new_dvds
144 |
145 | @l.puts "Filtering..."
146 | filter_percentage @config["filter_out_less_than_percentage"] if @config["filter_out_less_than_percentage"]
147 | filter_out_non_certified_fresh if @config["filter_out_non_certified_fresh"]
148 | filter_out_already_downloaded if @config["filter_out_already_downloaded"]
149 | end
150 |
151 | def summarize_process_to_output
152 | @l.puts "*" * 80
153 | @l.puts "Attempting to download the following titles: "
154 | @l.puts dvds.map { |dvd| dvd["Title"] }
155 | @l.puts "*" * 80
156 | end
157 |
158 | def filter_out_already_downloaded
159 | @dvds = dvds.reject { |f| Download.exists? f["Title"] }
160 | end
161 |
162 | def filter_out_non_certified_fresh
163 | @dvds = dvds.select { |f| f["CertifiedFresh"] == "1" }
164 | end
165 |
166 | def filter_percentage threshold
167 | @dvds = dvds.select { |f| f["NumTomatometerPercent"].to_i >= threshold }
168 | end
169 |
170 | def dvds
171 | @dvds || fetch_new_dvds
172 | end
173 |
174 | def fetch_new_dvds
175 | require 'open-uri'
176 | text = open('http://www.rottentomatoes.com/syndication/tab/new_releases.txt').read
177 | @dvds = TheRottenPirate.extract_new_dvds text
178 | end
179 |
180 | def self.extract_new_dvds text
181 | headers = []
182 | dvds = []
183 |
184 | text.strip.split("\n").each_with_index do |row, row_index|
185 | dvd = {} unless row_index == 0
186 | row.split("\t").each_with_index do |field, header_index|
187 | field.chomp!
188 | if row_index == 0
189 | headers << field
190 | else
191 | dvd[headers[header_index]] = field
192 | end
193 | end
194 | dvds << dvd unless row_index == 0
195 | end
196 | dvds
197 | end
198 | end
199 |
200 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The Rotten Pirate!
2 | (or: "STOP STEALING MY MOVIES YA DAMN KIDS!")
3 |
4 | ### Quick 'n' dirty
5 |
6 | - Fetches the [Rotten Tomatoes New DVDs feed](http://www.rottentomatoes.com/syndication/tab/new_releases.txt)
7 | - Filters the DVDs with *configurable* criterion
8 | - Only "Certified Fresh" movies
9 | - Only movies whose "TomatoMeter Percent" is greater than XX%
10 | - Movies not already downloaded through TRP
11 | - We then pass the movies to the [torrent_api](https://github.com/hjhart/torrent_api) gem.
12 | - The gem looks at comments left by users for ratings of audio and video
13 | - Performs an average of all ratings given
14 | - Selects the highest scoring torrent and
15 | - Then it downloads the appropriate torrents to your configured directory
16 | - If you set this directory up to be "watched" by your torrent program these will start automatically!
17 | - Prowl support to send push notifications to iOS!
18 | - Also, command line interface to download any single movie you want (e.g. rake download["The Big Lebowski"])
19 |
20 | Pretty neat, yeah?
21 |
22 | ### Setup
23 |
24 | Clone the source and install proper gems
25 |
26 | git clone git@github.com:hjhart/the_rotten_pirate.git
27 | cd the_rotten_pirate
28 |
29 | At this point if RVM is installed you'll get prompted to trust this new .rvmrc file. I *highly* recommend you use RVM (for every project really). Allow it and it should create a new gemset for you.
30 |
31 | Now, after the proper ruby has been picked and you've got a clean gemset, let's get our gemset populated.
32 |
33 | gem install bundler --pre # You may need to install bundler with your fresh gemset – I prefer bundler 1.1 right now
34 | bundle
35 |
36 | Run this command to initialize your sqlite database and make working copies of the config files.
37 |
38 | rake initialize
39 |
40 | ### Configure stuff!
41 |
42 | Important configurations in config/config.yml
43 |
44 | * `["download_directory"]`
45 |
46 | Set your download_directory to a directory you want to drop the torrent files to.
47 |
48 | * `["filter_out_less_than_percentage"]`
49 |
50 | Change this to lowest percent (based on RTs TomatoMeter) that you want to download.
51 |
52 | * `["comments"]["quality"]`
53 |
54 | If this is turned on The Rotten Pirate will query the comments on The Pirate Bay for ratings. It will then calculate a score and choose the highest "quality" torrent for you to download. Takes considerably longer but is well worth the wait.
55 |
56 | * `["comments"]["num_of_results"]`
57 |
58 | The number of search results to query for comments. If there are 30 search results from pirate bay, but you only want to query the top 10 seeded torrents for comments – use this variable.
59 |
60 | * `["comments"]["minimum_seeds"]`
61 |
62 | Don't query for comments unless there are this many seeds on the torrent (there are hardly any comments for lower seeded torrents)
63 |
64 | * `["comments"]["watch_file"]`
65 |
66 | You can point this towards a new-line delimited file – it will download each movie that is listed in the file. (Really nice with the side-project [the_mobile_pirate](https://github.com/hjhart/the_mobile_pirate))
67 |
68 | If you want to configure prowl notifications, see section below.
69 |
70 | ### Run the process!
71 |
72 | rake execute
73 |
74 | You'll see the search begin (this can take anywhere from 10 seconds to 5 minutes) and output will follow.
75 |
76 | ### Download a Single Movie
77 |
78 | rake download["The Big Lebowski"]
79 |
80 | This will run the search operation as configured – and download a single torrent file.
81 |
82 |
83 | ### Download an Array of Movies
84 |
85 | rake download_from_watch_file
86 |
87 | This will run against any text file that is new-line delimited. Parses through the file and downloads each movie. Really a nice to have with [the_mobile_pirate](https://github.com/hjhart/the_mobile_pirate).
88 |
89 | ### Run tests
90 |
91 | rspec spec
92 |
93 | So far this has been tested on ruby-1.9.2-p290 on OSX 10.6
94 |
95 | ### Automate with a cron job
96 |
97 | Run `crontab -e` and paste the following into there to start a new cronjob. It will run every 2 days at 12:30am. Make sure the change `cd ~/Sites/the_rotten_pirate` to the directory where your code lives.
98 |
99 | 30 0 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31 * * /bin/bash -l -c 'cd ~/Sites/the_rotten_pirate && bundle exec rake execute --silent >> /dev/null 2>&1'
100 |
101 | ### Play with code
102 |
103 | rake console
104 | TheRottenPirate.execute
105 |
106 | ### The algorithm for scoring torrents
107 |
108 | Inside of The Pirate Bay results you'll see comments looking like: `"Great movie, A - 10, V - 10"`
109 |
110 | This means that the audio track is rated a 10, and the video the same. I'm paginating through every page of comments from the pirate bay and analyzing the comments for those sort of ratings. I then average them up. Since one vote of a 9 should be "scored" less than 5 votes of 9's, I'm adding a booster to scores that have a lot of votes. This boost is calculated on a logarithmic scale (So that 40 votes of 5 doesn't end up beating 5 votes of 9). The boost is then summed to the average rating to produce the total.
111 |
112 | When you turn on the `["comments"]["quality"]` setting in the configuration it will parse through the comments and try to find the highest video quality of all the videos. If you turn it off, it will just grab the search result with the highest seeds (for quick downloading)
113 |
114 | ### Prowl notifications
115 |
116 | - Config goes into config/prowl.yml
117 | - If this file doesn't exist yet run `rake initialize` once more to create it.
118 | - Fill out your api_key – this can be found at the [prowl api keys](https://www.prowlapp.com/api_settings.php) page.
119 |
120 | You'll receive prowl notifications at the end of every run with a rundown of what movies were added.
121 |
122 | ### Potential TODOS
123 |
124 | * Name parser that will take downloaded files and tidy them up!
125 | * Can we search for movies on Amazon/Netflix for instant streaming or purchase?
126 | * Add the ability to organize your files. So, drop the subtitles and the other movie files into another directory.
127 | * Download a single movie rake download "Revenge of the Nerds"
128 | * Generate a little summary of movies that were fetched
129 | * Synapsis
130 | * Link to trailer
131 | * Link to watch the movie
132 | * Show any screenshots of the movie if they have any
133 | * Links to download subtitles (if it's a foreign movie?)
134 | * Allow for import of top movies?
135 | * Add more of http://www.rottentomatoes.com/help_desk/syndication_txt.php these
136 | * Have a method of deleting movies (or at least wiping the database clean)
137 | * I'm getting some Timeout::Errors when I'm downloading too many torrents at once. Should we be catching those?
138 | * Figure out when the rotten tomatoes files update and configure the crontab to run at those times. (DVDS generally come out on Tuesdays, but in some cases they are done other days (e.g. Harry Potter, Twilight, etc...)
139 | * Don't bother to download if the quality isn't up to a configurable rating.
140 | * Database file needs to be not a relative directory. Running specs outside of the root folder fails.
141 |
142 | ### Support
143 |
144 | Tested on ruby 1.9.2-p290 on Mac OSX.
145 | Also tested on ruby 1.8.7-p330 on Mac OSX.
146 |
147 |
148 | 
149 |
--------------------------------------------------------------------------------
/lib/name_cleaner.rb:
--------------------------------------------------------------------------------
1 | class NameCleaner
2 | attr_accessor :raw_name, :clean_name
3 |
4 | def initialize(raw_name)
5 | @raw_name = raw_name
6 | @clean_name = raw_name
7 | end
8 |
9 | def clean_punctuation
10 | punctuation_to_remove = /[\.\[\]\(\)-]/
11 | @clean_name = @clean_name.gsub(punctuation_to_remove, " ")
12 | end
13 |
14 | def remove_release_year
15 | @clean_name = @clean_name.gsub(release_year.to_s, "")
16 | end
17 |
18 | ["filetype", "urls"].each do |method|
19 | define_method "remove_#{method}" do
20 | extractions = Array(self.send(method))
21 | extractions.each do |extraction|
22 | @clean_name = @clean_name.gsub(extraction, "")
23 | end
24 | end
25 | end
26 |
27 | # def remove_filetype
28 | # extractions = Array(filetype)
29 | # extractions.each do |extraction|
30 | # @clean_name = @clean_name.gsub(extraction, "")
31 | # end
32 | # end
33 | #
34 | # def remove_urls
35 | # extractions = Array(urls)
36 | # extractions.each do |extraction|
37 | # @clean_name = @clean_name.gsub(extraction, "")
38 | # end
39 | # end
40 |
41 | ["video_types", "release_groups"].each do |method|
42 | remove_method_name = :"remove_#{method}"
43 | terms_method_name = :"#{method}_terms"
44 |
45 | define_method remove_method_name do
46 | extraction_indices = Array(self.send(method))
47 | extraction_indices.each do |extraction_index|
48 | term = NameCleaner.send(terms_method_name).at(extraction_index)
49 | @clean_name = @clean_name.gsub(Regexp.new(term[:name], term[:modifier]), "")
50 | end
51 | end
52 | end
53 |
54 | def remove_whitespace
55 | @clean_name = @clean_name.gsub(/\s+/, " ")
56 | @clean_name = @clean_name.strip
57 | end
58 |
59 | def clean
60 | remove_release_year
61 | remove_video_types
62 | remove_filetype
63 | remove_release_groups
64 | remove_urls
65 | clean_punctuation
66 | remove_whitespace
67 | end
68 |
69 | def release_year
70 | year_matches = @raw_name.scan(/(\d{4})/).flatten
71 | two_years_from_now= Time.now.strftime("%Y").to_i + 2
72 |
73 | year_matches = year_matches.map { |year_match|
74 | year = year_match.to_i
75 | }.select { |year_match|
76 | (1895..two_years_from_now).include? year_match
77 | }
78 |
79 | year_matches.first if year_matches.size == 1
80 | end
81 |
82 | def self.video_types_terms
83 | [
84 | { :name => "dvdrip", :modifier => Regexp::IGNORECASE },
85 | { :name => "xvid", :modifier => Regexp::IGNORECASE },
86 | { :name => "720p", :modifier => Regexp::IGNORECASE },
87 | { :name => "x264", :modifier => Regexp::IGNORECASE },
88 | { :name => "brrip", :modifier => Regexp::IGNORECASE },
89 | { :name => "bdrip", :modifier => Regexp::IGNORECASE },
90 | { :name => "Widescreen", :modifier => Regexp::IGNORECASE },
91 | { :name => "1080p", :modifier => Regexp::IGNORECASE },
92 | { :name => "aac", :modifier => Regexp::IGNORECASE },
93 | { :name => "5.1", :modifier => Regexp::IGNORECASE },
94 | { :name => "ac3", :modifier => Regexp::IGNORECASE },
95 | { :name => "TS", :modifier => nil },
96 | { :name => "scr", :modifier => Regexp::IGNORECASE },
97 | { :name => "rerip", :modifier => Regexp::IGNORECASE }
98 | ]
99 | end
100 |
101 | def video_types
102 | video_type = []
103 |
104 | NameCleaner.video_types_terms.each_with_index do |term, index|
105 | regexp = term[:name]
106 | modifier = term[:modifier]
107 | video_type << index if @raw_name.match Regexp.new regexp, modifier # case insensitive
108 | end
109 |
110 | video_type
111 | end
112 |
113 | def self.release_groups_terms
114 | [
115 | { :name => "NOVA", :modifier => nil },
116 | { :name => "TWiZTED", :modifier => nil },
117 | { :name => "Stealthmaster", :modifier => nil },
118 | { :name => "DUBBY", :modifier => nil },
119 | { :name => "DoNE", :modifier => nil },
120 | { :name => "TARGET", :modifier => nil },
121 | { :name => "Feel-Free", :modifier => nil },
122 | { :name => "MAXSPEED", :modifier => nil },
123 | { :name => "1337x", :modifier => nil },
124 | { :name => "Dita496", :modifier => nil },
125 | { :name => "AbSurdiTy", :modifier => nil },
126 | { :name => "dxva", :modifier => nil },
127 | { :name => "V3nDetta", :modifier => nil },
128 | { :name => "ExtraTorrentRG", :modifier => nil },
129 | { :name => "GHZ", :modifier => nil },
130 | { :name => "AMIABLE", :modifier => nil },
131 | { :name => "honchorella", :modifier => nil },
132 | { :name => "MRShanku", :modifier => nil },
133 | { :name => "Silver RG", :modifier => nil },
134 | { :name => "ArtSubs", :modifier => nil },
135 | { :name => "HORiZON", :modifier => nil },
136 | { :name => "ZJM", :modifier => nil },
137 | { :name => "PSiG", :modifier => nil },
138 | { :name => "UsaBit.com", :modifier => nil },
139 | { :name => "STB", :modifier => nil },
140 | { :name => "iLG", :modifier => nil },
141 | { :name => "UnKnOwN", :modifier => nil },
142 | { :name => "HDLiTE", :modifier => nil },
143 | { :name => "LPD", :modifier => nil },
144 | { :name => "Hiest-1337x", :modifier => nil },
145 | { :name => "aLeX", :modifier => nil },
146 | { :name => "aXXo", :modifier => nil },
147 | { :name => "NLT", :modifier => nil },
148 | { :name => "NeDiVx", :modifier => nil },
149 | { :name => "PSYCHD", :modifier => nil },
150 | { :name => "N3WS", :modifier => nil },
151 | ]
152 | end
153 |
154 | def release_groups
155 | release_groups = []
156 |
157 | NameCleaner.release_groups_terms.each_with_index do |group, index|
158 | regexp = group[:name]
159 | modifier = group[:modifier]
160 | release_groups << index if @raw_name.match Regexp.new regexp #, modifier
161 | end
162 |
163 | release_groups
164 | end
165 |
166 | def filetype
167 | filetypes = ["avi", "mp4"]
168 | filetypes.find do |filetype|
169 | regexp = Regexp.new "\.#{filetype}"
170 | @raw_name.match regexp
171 | end
172 | end
173 |
174 | def urls
175 | url_regex = /((https?:\/\/)?(www.)\w+\.(\w+\.)?\w{2,3})/
176 | urls = @raw_name.scan(url_regex)
177 | urls.map { |url| url[0] }
178 | end
179 | end
180 |
181 | # A.Better.Life.LIMITED.DVDRip.XviD-TWiZTED
182 | # Aladdin[1992]DvDrip[Eng]-Stealthmaster.avi
183 | # Attack.The.Block.DVDRip.XviD-DoNE
184 | # Austin Powers International Man of Mystery 1997.720p.x264.BRRip.GokU61
185 | # Beginners.2010.LIMITED.BDRip.XviD-TARGET
186 | # Bridesmaids [2011] UNRATED DVDRip XviD-DUBBY
187 | # Bridesmaids.2011.BRRip.XviD.Ac3.Feel-Free.avi.part
188 | # Captain America The First Avenger (2011) DVDRip XviD-MAXSPEED
189 | # Cars 2 (2011) DVDRip XviD-MAXSPEED
190 | # District 9 (2009) DVDRip XviD-MAXSPEED www.torentz.3xforum.ro.avi
191 | # DodgeBall [2004] [DVDRip XviD] [1337x]-Dita496
192 | # Donnie.Darko.Directors.Cut.DVDRip.Xvid.2001-tots.avi
193 | # Everything.Must.Go.2010.BRRiP.XviD.AbSurdiTy
194 | # Fast Five[2011]BDRip XviD-ExtraTorrentRG.avi
195 | # Finding Nemo (2003) Widescreen DVDrip V3nDetta.avi
196 | # GLADIATOR[2000]DvDrip-GHZ
197 | # Hall Pass 2011 BRRiP XviD AbSurdiTy
198 | # Hanna.2011.DVDRip.XviD-AMIABLE
199 | # Harry Potter And The Deathly Hallows Pt1 2010 BRRip 1080p x264 AAC - honchorella (Kingdom Release)
200 | # Harry Potter and the Deathly Hallows Part 1[2010]DVDRip XviD-ExtraTorrentRG
201 | # Harry Potter and the Deathly Hallows Part 2 720p BRRip AAC 5.1- MRShanku - Silver RG
202 | # Incendies.2010.DVDRip.XviD.AC3.HORiZON-ArtSubs
203 | # Kung Fu Panda 2 (2011) DVDSCR XviD-ZJM.avi
204 | # Melancholia.2011.DVDRiP.XViD-PSiG
205 | # Pirates of the Caribbean On Stranger Tides (2011) DVDRip XviD-MAXSPEED
206 | # Rare.Exports.A.Christmas.Tale.2010.DVDRIP.XVID-STB.[UsaBit.com].avi
207 | # The.Triplets.of Belleville..2011.TS.XviD-NOVA.avi
208 | # Somewhere.2010.DVDRip.XviD-iLG
209 | # Submarine 2011 DVDRip Xvid UnKnOwN
210 | # Submarine.2011.720p.BDRip.x264.AC3.dxva-HDLiTE
211 | # Super 8[2011]DVDScr XviD-ExtraTorrentRG
212 | # Tabloid.2010.LiMiTED.DVDRip.XviD-LPD
213 | # Team America.avi
214 | # The Departed (2006)
215 | # The Guard (2011) DivX [Hiest-1337x].avi.part
216 | # The Tree of Life 2011 (Eng)
217 | # The.Great.Buck.Howard.2008.LiMiTED.DVDRip.XviD-HNR.[www.FilmsBT.com]
218 | # The.Triplets.of Belleville.2003.DVDRip-aLeX
219 | # Transsiberian[2008]DvDrip-aXXo
220 | # True Grit (2011) SCR NL Sub NLT-Release
221 | # True Grit[2010]BRRip XviD-ExtraTorrentRG
222 | # Win.Win.2011.ENG.HDRip.avi
223 | # Winnie.the.Pooh.RERIP.DVDRip.XviD-NeDiVx
224 | # X-Men First Class[2011]BRRip XviD-ExtraTorrentRG
225 | # [ UsaBit.com ] - Rare.Exports.A.Christmas.Tale.2010.LIMITED.BDRip.XviD-PSYCHD
226 | # [ www.Torrenting.com ] - A.Better.Tomorrow.2010.DVDRip.XviD-N3WS
227 | # [ www.Torrenting.com ] - Beats.Rhymes.and.Life.The.Travels.of.a.Tribe.Called.Quest.2011.LIMITED.DOCU.BDRip.XviD-PSYCHD
228 | # brian.regan.i.walked.on.the.moon.2004.xvid
229 |
--------------------------------------------------------------------------------
/spec/upcoming_dvds.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | MovieID Title URL NumTomatometerPercent TomatometerRating CertifiedFresh ModifiedDate ShowtimeURL Description
22 | 770687943 Harry Potter and the Deathly Hallows - Part 2 http://www.rottentomatoes.com/m/harry_potter_and_the_deathly_hallows_part_2/ 96 FRESH 1 Thrilling, powerfully acted, and visually dazzling, Deathly Hallows Part II brings the Harry Potter franchise to a satisfying -- and suitably magical -- conclusion.
23 | 16403 A Better Tomorrow http://www.rottentomatoes.com/m/better_tomorrow/ 93 FRESH 0 John Woo established himself as one of Hong Kong's premiere action directors with this ultra-hip, ultra-violent action classic. The film centers around the complex relationship between two brothers: Sung Tse-kit (Leslie Cheung) is a recent graduate...
24 | 771197495 Incendies http://www.rottentomatoes.com/m/incendies/ 92 FRESH 1 It's messy, overlong, and a touch melodramatic, but those flaws pale before Incendies' impressive acting and devastating emotional impact.
25 | 771208471 Tabloid http://www.rottentomatoes.com/m/tabloid_2010/ 92 FRESH 1 It's far from his most thought-provoking work, but Tabloid finds Errol Morris as smart, spirited, and engaging as ever.
26 | 771041148 Winnie the Pooh http://www.rottentomatoes.com/m/winnie_the_pooh_2011/ 91 FRESH 1 Short, nostalgic, and gently whimsical, Winnie the Pooh offers young audiences -- and their parents -- a sweetly traditional family treat.
27 | 771224156 Beats, Rhymes and Life: The Travels of A Tribe Called Quest http://www.rottentomatoes.com/m/beats_rhymes_and_life_the_travels_of_a_tribe_called_quest/ 91 FRESH 1 This documentary focuses less on the music and more on the personality clashes and in-group tensions to great, compelling effect.
28 | 771200719 Rare Exports: A Christmas Tale http://www.rottentomatoes.com/m/rare_exports/ 90 FRESH 1 It's the eve of Christmas in northern Finland, and an 'archeological' dig has just unearthed the real Santa Claus. But this particular Santa isn't the one you want coming to town. When the local children begin mysteriously disappearing, young Pietari...
29 | 771203801 Attack the Block http://www.rottentomatoes.com/m/attack_the_block/ 90 FRESH 1 A thrilling sci-fi yarn whose distinct British flavor supplies energy, wit, and style to burn.
30 | 771039065 Bridesmaids http://www.rottentomatoes.com/m/bridesmaids_2011/ 90 FRESH 1 A marriage of genuine characters, gross out gags, and pathos, Bridesmaids is a female-driven comedy that refuses to be boxed in as Kristen Wiig emerges as a real star.
31 | 702035972 I'm a Cyborg, But That's OK (Saibogujiman kwenchana) http://www.rottentomatoes.com/m/im_a_cyborg_but_thats_ok/ 90 FRESH 0 Young-goon is admitted to a mental institution. Believing herself a cyborg, she charges herself with a transistor radio. Il-soon, a fellow inmate, steals the other inmates' personality traits and believes he is fading and will one day turn into a...
32 | 771041145 X-Men: First Class http://www.rottentomatoes.com/m/x_men_first_class/ 87 FRESH 1 With a strong script, stylish direction, and powerful performances from its well-rounded cast, X-Men: First Class is a welcome return to form for the franchise.
33 | 771224186 Buck http://www.rottentomatoes.com/m/buck/ 87 FRESH 1 "Your horse is a mirror to your soul, and sometimes you may not like what you see. Sometimes, you will." So says Buck Brannaman, a true American cowboy and sage on horseback who travels the country for nine grueling months a year helping horses with...
34 | 770875099 Submarine http://www.rottentomatoes.com/m/submarine-2010/ 86 FRESH 1 Funny, stylish, and ringing with adolescent truth, Submarine marks Richard Ayoade as a talent to watch.
35 | 771225867 Terri http://www.rottentomatoes.com/m/terri_2011/ 86 FRESH 1 A hit at the Sundance 2011 Film Festival, Terri is a moving and often funny film about the relationship between Terri, an oversized teen misfit, and the garrulous but well-meaning vice principal (John C. Reilly) who takes an interest in him. Terri is...
36 | 771182967 A Better Life http://www.rottentomatoes.com/m/a_better_life/ 86 FRESH 1 From the director of About a Boy comes A Better Life - a touching, poignant, multi-generational story about a father's love and the lengths a parent will go to give his child the opportunities he never had. -- (C) Summit
37 | 771205009 The Strange Case Of Angelica http://www.rottentomatoes.com/m/the_strange_case_of_angelica_2010/ 84 FRESH 0 The new film from master filmmaker Manoel de Oliveira, The Strange Case of Angelica is a magical tale about a young photographer who falls madly in love with a woman he can never have, except in his dreams. Late one night, Isaac is summoned by a...
38 | 771205000 The Princess Of Montpensier http://www.rottentomatoes.com/m/the_princess_of_montpensier/ 84 FRESH 1 France, 1562. Against a background of the savage Catholic/Protestant wars, Marie de Mézières (Mélanie Thierry), a beautiful young aristocrat, and the rakish Henri de Guise (Gaspard Ulliel), fall in love, but Marie's father has promised her hand in...
39 | 770793166 The Tree of Life http://www.rottentomatoes.com/m/the_tree_of_life_2011/ 84 FRESH 1 Terrence Malick's singularly deliberate style may prove unrewarding for some, but for patient viewers, Tree of Life is an emotional as well as visual treat.
40 | 771225882 Life in a Day http://www.rottentomatoes.com/m/life_in_a_day_2011/ 81 FRESH 1 The 24th July 2010.... 80,000 Lives.... 4,500 Hours of Footage... 2 Award winning Filmmakers..... Now one incredible motion picture event. What began life as a startling cinematic experiment becomes the must see movie experience of the Summer....
41 | 771208507 The Last Circus http://www.rottentomatoes.com/m/balada_triste_de_trompeta/ 81 FRESH 0 1937, Spain is in the midst of the brutal Spanish Civil War. A "Happy" circus clown is interrupted mid-performance and forcibly recruited by a militia. Still in his costume, he is handed a machete and led into battle against National soldiers, where...
42 | 771236491 Conan O'Brien Can't Stop http://www.rottentomatoes.com/m/conan_obrien_cant_stop_2011/ 79 FRESH 1 After a much-publicized departure from hosting NBC's Tonight Show - and the severing of a 22-year relationship with the network - O'Brien hit the road with a 32-city music-and-comedy show to exercise his performing chops and exorcise a few demons....
43 | 770739679 Captain America: The First Avenger http://www.rottentomatoes.com/m/captain-america/ 79 FRESH 1 With plenty of pulpy action, a pleasantly retro vibe, and a handful of fine performances, Captain America is solidly old-fashioned blockbuster entertainment.
44 | 771203531 Crazy, Stupid, Love. http://www.rottentomatoes.com/m/crazy_stupid_love/ 78 FRESH 1 It never lives up to the first part of its title, but Crazy, Stupid, Love's unabashed sweetness -- and its terrifically talented cast -- more than make up for its flaws.
45 | 771201465 Chalet Girl http://www.rottentomatoes.com/m/chalet_girl/ 78 FRESH 0 Chalet Girl is light comedic fun geared for teenage girls, featuring a charming performance from Felicity Jones.
46 | 770873459 Fast Five http://www.rottentomatoes.com/m/fast_five/ 78 FRESH 1 Sleek, loud, and over the top, Fast Five proudly embraces its brainless action thrills.
47 | 770783549 Thor http://www.rottentomatoes.com/m/thor/ 77 FRESH 1 A dazzling blockbuster that tempers its sweeping scope with wit, humor, and human drama, Thor is mighty Marvel entertainment.
48 | 771205671 Everything Must Go http://www.rottentomatoes.com/m/everything_must_go/ 74 FRESH 1 It may not improve on the Raymond Carver short story that inspired it, but Everything Must Go resists cliche and boasts a pair of magnetic performances from the perfectly cast Ferrell and Wallace.
49 | 771091481 Hanna http://www.rottentomatoes.com/m/hanna/ 71 FRESH 1 Fantastic acting and crisply choreographed action sequences propel this unique, cool take on the revenge thriller.
50 | 771224804 Shaolin http://www.rottentomatoes.com/m/shaolin/ 71 FRESH 0 As feuding warlords fight to expand their power, the noble monks of the Shaolin Temple clean up the mess left behind, tending to the injured while trying their best to protect the poor and weak. General Hou (Andy Lau) has caused much of this mess...
51 | 771208626 Beautiful Boy http://www.rottentomatoes.com/m/beautiful_boy/ 70 FRESH 0 Bill (Michael Sheen) and Kate (Maria Bello) hopelessly try to find some hint of an explanation after finding out that their only son committed a mass shooting at his university before taking his own life. They struggle numbly through the funeral, the...
52 | 771041149 Horrible Bosses http://www.rottentomatoes.com/m/horrible_bosses/ 69 FRESH 1 It's nasty, uneven, and far from original, but thanks to a smartly assembled cast that makes the most of a solid premise, Horrible Bosses works.
53 | 771036678 African Cats http://www.rottentomatoes.com/m/african_cats/ 69 FRESH 0 It isn't quite as majestic as its subjects, but African Cats boasts enough astounding footage -- and a big enough heart -- to keep things entertaining.
54 | 771204250 Water for Elephants http://www.rottentomatoes.com/m/water_for_elephants/ 61 FRESH 0 It's a tale tastefully told and beautifully filmed, but Water for Elephants suffers from a pronounced lack of chemistry between its leads.
55 | 771217942 We Are The Night http://www.rottentomatoes.com/m/we_are_the_night/ 60 FRESH 0 Dennis Gansel, responsible for the hit The Wave, explores night time in Berlin to find a sect of seductive lady vampires that hide out in the city's alternative clubs and enjoy the luxury and pleasures their attained immortality provides them. A...
56 | 771034093 Red State http://www.rottentomatoes.com/m/red_state/ 59 ROTTEN 0 Red State is an audacious and brash affair that ultimately fails to provide competent scares or thrills.
57 | 770807411 Scream 4 http://www.rottentomatoes.com/m/scream-4/ 58 ROTTEN 0 The franchise is showing its age, but Scream 4 is undeniably an improvement over its predecessor, with just enough meta humor and clever kills.
58 | 770814817 Hesher http://www.rottentomatoes.com/m/hesher/ 54 ROTTEN 0 It has a dark sense of humor and a refreshing lack of sentimentality, but like its title character, Hesher isn't really interested in going anywhere.
59 | 771030879 Bad Teacher http://www.rottentomatoes.com/m/bad_teacher/ 44 ROTTEN 0 In spite of a promising concept and a charmingly brazen performance from Cameron Diaz, Bad Teacher is never as funny as it should be.
60 | 770816610 A Little Help http://www.rottentomatoes.com/m/a-little-help/ 44 ROTTEN 0 It's a movie for everyone whose life has been thrown off-course, out of whack, or simply not turned out the way they planned it. In other words, it's a movie for everyone, period. Set in suburban Long Island in the summer of 2002, with the psychic...
61 | 770805427 Cars 2 http://www.rottentomatoes.com/m/cars_2/ 38 ROTTEN 0 Cars 2 is as visually appealing as any other Pixar production, but all that dazzle can't disguise the rusty storytelling under the hood.
62 | 771202906 Monte Carlo http://www.rottentomatoes.com/m/monte_carlo_2011/ 38 ROTTEN 0 Although it has its charming moments, Monte Carlo is mostly silly, predictable stuff that never pushes beyond the boundaries of formula.
63 | 771223136 Transformers: Dark of the Moon http://www.rottentomatoes.com/m/transformers_dark_of_the_moon/ 35 ROTTEN 0 Its special effects -- and 3D shots -- are undeniably impressive, but they aren't enough to fill up its loud, bloated running time, or mask its thin, indifferent script.
64 | 770855546 Pirates of the Caribbean: On Stranger Tides http://www.rottentomatoes.com/m/pirates_of_the_caribbean_on_stranger_tides/ 33 ROTTEN 0 It's shorter and leaner than the previous sequel, but this Pirates runs aground on a disjointed plot and a non-stop barrage of noisy action sequences.
65 | 770805051 The Tempest http://www.rottentomatoes.com/m/tempest/ 30 ROTTEN 0 Director Julie Taymor's gender-swapping of roles and some frenzied special effects can't quite disguise an otherwise stagey, uninspired take on Shakespeare's classic.
66 | 770677993 Green Lantern http://www.rottentomatoes.com/m/green_lantern/ 27 ROTTEN 0 Noisy, overproduced, and thinly written, Green Lantern squanders an impressive budget and decades of comics mythology.
67 | 771041150 The Change-Up http://www.rottentomatoes.com/m/the_change_up/ 24 ROTTEN 0 There's a certain amount of fun to be had from watching Bateman and Reynolds play against type, but it isn't enough to carry The Change-Up through its crude humor and formulaic plot.
68 | 771205663 Snow Flower And The Secret Fan http://www.rottentomatoes.com/m/snow_flower_and_the_secret_fan/ 20 ROTTEN 0 In 19th-century China, seven year old girls Snow Flower and Lily are matched as laotong - or "old sames" - bound together for eternity. Isolated by their families, they furtively communicate by taking turns writing in a secret language, nu shu,...
69 | 771229930 Judy Moody and the NOT Bummer Summer http://www.rottentomatoes.com/m/judy_moody_and_the_not_bummer_summer/ 19 ROTTEN 0 Entertaining for some very young viewers, but for those with normal attention spans, Judy Moody is loud hyperactive overload.
70 | 770805419 Zookeeper http://www.rottentomatoes.com/m/zookeeper/ 14 ROTTEN 0 Zookeeper smothers Kevin James's with a sodden script and a surfeit of jokes inappropriate for the young viewers who would be intrigued by its juvenile storyline.
71 | 771033843 Atlas Shrugged Part I http://www.rottentomatoes.com/m/atlas_shrugged_part_i/ 12 ROTTEN 0 Passionate ideologues may find it compelling, but most filmgoers will find this low-budget adaptation of the Ayn Rand bestseller decidedly lacking.
72 |
--------------------------------------------------------------------------------