diff --git a/Gemfile b/Gemfile index 6ab928f..674cbec 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,13 @@ source "http://rubygems.org" gem "plist", "3.1.0" -gem "tvdb_party", "0.6.0" +gem "tvdb_party", "0.7.0" +gem "itunes" +gem "rash" +gem "trollop" group :development do + gem "pry" gem "rspec" gem "bundler" gem "jeweler" diff --git a/Gemfile.lock b/Gemfile.lock index 72614c6..0eadca0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,12 +3,15 @@ GEM specs: addressable (2.3.5) builder (3.2.2) + coderay (1.1.0) crack (0.4.2) safe_yaml (~> 1.0.0) descendants_tracker (0.0.3) diff-lcs (1.2.5) - faraday (0.9.0) - multipart-post (>= 1.2, < 3) + faraday (0.8.9) + multipart-post (~> 1.2.0) + faraday_middleware (0.9.0) + faraday (>= 0.7.4, < 0.9) git (1.2.6) github_api (0.11.3) addressable (~> 2.3) @@ -23,6 +26,10 @@ GEM httparty (0.13.0) json (~> 1.8) multi_xml (>= 0.5.2) + itunes (0.6.0) + faraday_middleware (~> 0.7) + multi_json (~> 1.0) + rash (~> 0.3) jeweler (2.0.1) builder bundler (>= 1.0) @@ -35,10 +42,11 @@ GEM json (1.8.1) jwt (0.1.11) multi_json (>= 1.5) + method_source (0.8.2) mini_portile (0.5.2) multi_json (1.9.0) multi_xml (0.5.5) - multipart-post (2.0.0) + multipart-post (1.2.0) nokogiri (1.6.1) mini_portile (~> 0.5.0) oauth2 (0.9.3) @@ -48,8 +56,14 @@ GEM multi_xml (~> 0.5) rack (~> 1.2) plist (3.1.0) + pry (0.9.12.6) + coderay (~> 1.0) + method_source (~> 0.8) + slop (~> 3.4) rack (1.5.2) rake (10.1.1) + rash (0.4.0) + hashie (~> 2.0.0) rdoc (4.1.1) json (~> 1.4) rspec (2.14.1) @@ -61,7 +75,9 @@ GEM diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.14.6) safe_yaml (1.0.1) - tvdb_party (0.6.0) + slop (3.5.0) + trollop (2.0) + tvdb_party (0.7.0) httparty (>= 0.6.1) webmock (1.17.4) addressable (>= 2.2.7) @@ -72,8 +88,12 @@ PLATFORMS DEPENDENCIES bundler + itunes jeweler plist (= 3.1.0) + pry + rash rspec - tvdb_party (= 0.6.0) + trollop + tvdb_party (= 0.7.0) webmock diff --git a/bin/AtomicTV b/bin/AtomicTV index 8ce6e34..bf21334 100755 --- a/bin/AtomicTV +++ b/bin/AtomicTV @@ -1,6 +1,8 @@ #!/usr/bin/env ruby -rubygems require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'AtomicTV')) +require 'trollop' +require 'readline' include AtomicTV @@ -8,12 +10,48 @@ def display_error(error) $stderr.puts "! ERROR: #{error.human_message}" end +opts = Trollop.options do + banner("Usage: AtomicTV [options]") + opt :dry_run, "Only output basic episode information for debug", type: :flag + opt :use_itunes, "Use the itunes store api instead of tvdb", type: :flag + opt :try_gsub, "Use gsub to try a filname mutation", type: :string + opt :interactive, "If we can't find a match ask for human edit", type: :flag +end + ARGV.each do |path| begin file_path = Pathname.new(path) - metadata = TVDBEpisode.metadata_for_filename(file_path.basename) - tagger = AtomicParsleyTagger.new(file_path, metadata) - tagger.run + series, episode = begin + TVDBEpisode.series_and_episode_for(file_path.basename) + rescue + if opts[:interactive] + Readline.pre_input_hook = -> do + Readline.insert_text file_path.basename.to_s + Readline.redisplay + + # Remove the hook right away. + Readline.pre_input_hook = nil + end + + filename = Readline.readline("Couldn't find the series/episode try something different?> ", false) + TVDBEpisode.series_and_episode_for(filename) + elsif opts[:try_gsub] + filename = file_path.basename.to_s.gsub(*opts[:try_gsub].split('|')) + TVDBEpisode.series_and_episode_for(filename) + else + raise + end + end + itunes_episode = ItunesEpisode.episode_for(file_path.basename) if opts[:use_itunes] + metadata = EpisodeMetadata.new(series, episode, itunes_episode) + + if opts[:dry_run] + require 'pp' + pp metadata + else + tagger = AtomicParsleyTagger.new(file_path, metadata) + tagger.run + end puts "* Tagged: #{file_path.basename}" rescue AtomicParsleyTagger::AtomicParsleyUnavailable => error display_error(error) diff --git a/lib/AtomicTV.rb b/lib/AtomicTV.rb index 1b1e510..de12fc7 100644 --- a/lib/AtomicTV.rb +++ b/lib/AtomicTV.rb @@ -1,15 +1,17 @@ require 'fileutils' +require 'hashie' require 'open-uri' require 'pathname' require 'tmpdir' require 'plist' require 'tvdb_party' +require 'itunes' module AtomicTV class AtomicTVError < StandardError; end end -['atomic_parsley_tagger', 'episode_metadata', 'filename_parser', 'tvdb_episode'].each do |file| +['atomic_parsley_tagger', 'episode_metadata', 'filename_parser', 'tvdb_episode', 'itunes_episode'].each do |file| require File.expand_path(File.join(File.dirname(__FILE__), 'AtomicTV', file)) end diff --git a/lib/AtomicTV/atomic_parsley_tagger.rb b/lib/AtomicTV/atomic_parsley_tagger.rb index b9a7237..be78503 100644 --- a/lib/AtomicTV/atomic_parsley_tagger.rb +++ b/lib/AtomicTV/atomic_parsley_tagger.rb @@ -1,50 +1,50 @@ module AtomicTV class AtomicParsleyTagger - + class AtomicParsleyUnavailable < ::AtomicTV::AtomicTVError def human_message 'AtomicParsley is not installed or could not be found. Try checking your PATH.' end end - + class FileNotFound < ::AtomicTV::AtomicTVError def initialize(file_path) @file_path = file_path end - + attr_reader :file_path - + def human_message "File not found: #{file_path}" end end - + class TaggingError < ::AtomicTV::AtomicTVError def initialize(command) @command = command end - + attr_reader :command - + def human_message "A tagging error occured: #{command}." end - + end - + def self.executable path = Pathname.new(`which AtomicParsley`.chomp) raise AtomicParsleyUnavailable unless path.executable? path end - + def initialize(file_path, metadata) @file_path = file_path raise FileNotFound.new(file_path) unless file_path.exist? - + @metadata = metadata end - + def cast_metadata format_names = lambda {|name| {'name' => name}} { @@ -53,7 +53,7 @@ def cast_metadata 'screenwriters' => metadata.writers.map(&format_names) }.to_plist end - + def run options = { 'stik' => metadata.media_type, @@ -69,31 +69,35 @@ def run 'TVSeasonNum' => metadata.tv_season_number, 'TVEpisodeNum' => metadata.tv_episode_number, 'tracknum' => metadata.track_number, - 'year' => metadata.air_date + 'year' => metadata.air_date, + 'contentRating' => metadata.content_advisory_rating, + 'advisory' => metadata.track_is_clean ? 'clean' : 'explicit', + 'cnID' => metadata.track_id, } - + metadata.with_loaded_posters do command = %Q{#{self.class.executable} } command << %Q{"#{file_path}" } command << %Q{--overWrite } command << %Q{--rDNSatom "#{escape_double_quotes(cast_metadata)}" name=iTunMOVI domain=com.apple.iTunes } + command << %Q{--artwork REMOVE_ALL } metadata.posters.each do |poster| command << %Q{--artwork #{poster.path} } end command << options.map {|option, value| %Q{--#{option} "#{escape_double_quotes(value)}"}}.join(' ') - + `#{command}` raise TaggingError.new(command) unless $?.success? end end - + private - + attr_reader :file_path, :metadata - + def escape_double_quotes(str) str.to_s.gsub('"', '\"') end - + end end diff --git a/lib/AtomicTV/episode_metadata.rb b/lib/AtomicTV/episode_metadata.rb index 3d910d2..2a724a1 100644 --- a/lib/AtomicTV/episode_metadata.rb +++ b/lib/AtomicTV/episode_metadata.rb @@ -1,91 +1,48 @@ module AtomicTV - class EpisodeMetadata - - ArtworkBaseURL = "http://thetvdb.com/banners/" - - def initialize(series, episode) - @series, @episode = series, episode - end - - attr_reader :series, :episode - - def media_type - 'TV Show' - end - - def artist - series.name - end - - def title - episode.name - end - - def album - "#{series.name}, Season #{episode.season_number}" - end - - def genre - series.genres.first - end - - def description - episode.overview.to_s[0,255].gsub(/\.(.*)\Z/, '.') - end - - def long_description - episode.overview.to_s - end - - def tv_network - series.network - end - - def tv_show_name - series.name - end - - def tv_episode - "#{episode.season_number}#{episode.number.to_s.rjust(2, '0')}" - end - - def tv_season_number - episode.season_number - end - - def tv_episode_number - episode.number - end - - def track_number - episode.number - end - - def air_date - episode.air_date && episode.air_date.to_s + 'T00:00:00Z' - end - - def actors - (Array(series.actors).map {|a| a.name} + episode.guest_stars).uniq - end - - def directors - parse_names(episode.director) - end - - def writers - parse_names(episode.writer) + class EpisodeMetadata < Hash + include Hashie::Extensions::MethodAccess + include Hashie::Extensions::MergeInitializer + + TVDBArtworkBaseURL = "http://thetvdb.com/banners/" + + def initialize(series, episode, itunes_episode) + super({ + :series => series, + :episode => episode, + :itunes_episode => itunes_episode, + :media_type => 'TV Show', + :artist => series.name, + :title => episode.name, + :album => "#{series.name}, Season #{episode.season_number}", + :genre => series.genres.first, + :description => itunes_episode ? itunes_episode.short_description : episode.overview.to_s[0,255].gsub(/\.(.*)\Z/, '.'), + :long_description => itunes_episode ? itunes_episode.long_description : episode.overview.to_s, + :tv_network => series.network, + :tv_show_name => series.name, + :tv_episode => "#{episode.season_number}#{episode.number.to_s.rjust(2, '0')}", + :tv_season_number => episode.season_number, + :tv_episode_number => episode.number, + :track_number => episode.number, + :air_date => episode.air_date && episode.air_date.to_s + 'T00:00:00Z', + :actors => (Array(series.actors).map {|a| a.name} + episode.guest_stars).uniq, + :directors => parse_names(episode.director), + :writers => parse_names(episode.writer), + :artist_id => itunes_episode && itunes_episode.artist_id, + :collection_id => itunes_episode && itunes_episode.collection_id, + :track_id => itunes_episode && itunes_episode.track_id, + :content_advisory_rating => itunes_episode && itunes_episode.content_advisory_rating, + :track_is_clean => !itunes_episode || itunes_episode.track_explicitness == 'notExplicit' + }) end - - attr_reader :posters - + def with_loaded_posters temporary_directory = Dir.mktmpdir - - season_posters = series.season_posters(episode.season_number, 'en') - season_posters = series.posters('en') if season_posters.empty? - @posters = season_posters.map do |poster| - url = ArtworkBaseURL + poster.path + + season_posters = [] + season_posters << itunes_episode.artwork_url100.gsub('.100x100-75','') if itunes_episode + season_posters += series.season_posters(episode.season_number, 'en').map{|poster| TVDBArtworkBaseURL + poster.path } + season_posters += series.posters('en').map{|poster| TVDBArtworkBaseURL + poster.path } if season_posters.empty? + self.posters = season_posters.map do |url| file = File.new(File.join(temporary_directory, File.basename(url)), 'w') file.write(open(url).read) file.close @@ -93,21 +50,21 @@ def with_loaded_posters end yield - + ensure FileUtils.rm_rf(temporary_directory) @posters = nil end - + private - + def parse_names(str) return [] if str.nil? - + str.gsub!(/\A\|/, '') - str.gsub!(/\|\Z/, '') + str.gsub!(/\|\Z/, '') str.split(/\|+/) end - + end end diff --git a/lib/AtomicTV/filename_parser.rb b/lib/AtomicTV/filename_parser.rb index d9a94e6..e533282 100644 --- a/lib/AtomicTV/filename_parser.rb +++ b/lib/AtomicTV/filename_parser.rb @@ -1,37 +1,41 @@ module AtomicTV class FilenameParser - + class InvalidFilename < ::AtomicTV::AtomicTVError def initialize(filename) @filename = filename end - + attr_reader :filename - + def human_message "Invalid filename: #{filename} (filenames must be in the format: 'Series Name - S01E01')." end end - - FilenameFormat = /\A(.*) - S(\d{2})E(\d{2})\.\w{3}\Z/ - + + FilenameFormat = /\A(.*)(?: - |\.)S(\d{2})E(\d{2}).*\.\w{3}\Z/ + def self.parse(filename) if filename.to_s =~ FilenameFormat - new($1, $2.to_i(10), $3.to_i(10)) + series_name, season_num, episode_num = $1, $2, $3 + series_name.gsub!('.', ' ') + season_num = season_num.to_i(10) + episode_num = episode_num.to_i(10) + new(series_name, season_num, episode_num) else raise InvalidFilename.new(filename) end end - + attr_reader :series_name, :season_number, :episode_number - + private - + def initialize(series_name, season_number, episode_number) @series_name = series_name @season_number = season_number @episode_number = episode_number end - + end end diff --git a/lib/AtomicTV/itunes_episode.rb b/lib/AtomicTV/itunes_episode.rb new file mode 100644 index 0000000..6f11a46 --- /dev/null +++ b/lib/AtomicTV/itunes_episode.rb @@ -0,0 +1,51 @@ +require 'itunes' + +module AtomicTV + class ItunesEpisode + + class UnknownSeries < ::AtomicTV::AtomicTVError + def initialize(series_name) + @series_name = series_name + end + + attr_reader :series_name + + def human_message + "ITunes: Unknown TV series: '#{series_name}'" + end + end + + class UnknownEpisode < ::AtomicTV::AtomicTVError + def initialize(series_name, season_number, episode_number) + @episode_id = "#{series_name} - S#{season_number.to_s.rjust(2, '0')}E#{episode_number.to_s.rjust(2, '0')}" + end + + attr_reader :episode_id + + def human_message + "ITunes: Unknown episode: #{episode_id}" + end + end + + def self.episode_for(filename) + parser = FilenameParser.parse(filename) + season_name = "#{parser.series_name}, Season #{parser.season_number}" + search_results = client.tv_show(season_name).results. + select{|s|s.artist_name == parser.series_name && s.collection_name == season_name} + + raise UnknownSeries.new(parser.series_name) if search_results.empty? + + possible_episodes = search_results.select{|s|s.track_number == parser.episode_number} + raise UnknownEpisode.new(parser.series_name, parser.season_number, parser.episode_number) if possible_episodes.size != 1 + possible_episodes.first + end + + private + + def self.client + @client ||= ITunes::Client.new + end + + end +end + diff --git a/lib/AtomicTV/tvdb_episode.rb b/lib/AtomicTV/tvdb_episode.rb index 94acd46..1988bdd 100644 --- a/lib/AtomicTV/tvdb_episode.rb +++ b/lib/AtomicTV/tvdb_episode.rb @@ -1,46 +1,46 @@ module AtomicTV class TVDBEpisode - + class UnknownSeries < ::AtomicTV::AtomicTVError def initialize(series_name) @series_name = series_name end - + attr_reader :series_name - + def human_message - "Unknown TV series: '#{series_name}'" + "TVDB: Unknown TV series: '#{series_name}'" end end - + class UnknownEpisode < ::AtomicTV::AtomicTVError def initialize(series_name, season_number, episode_number) @episode_id = "#{series_name} - S#{season_number.to_s.rjust(2, '0')}E#{episode_number.to_s.rjust(2, '0')}" end - + attr_reader :episode_id - + def human_message - "Unknown episode: #{episode_id}" + "TVDB: Unknown episode: #{episode_id}" end end - - def self.metadata_for_filename(filename) + + def self.series_and_episode_for(filename) parser = FilenameParser.parse(filename) search_results = client.search(parser.series_name) raise UnknownSeries.new(parser.series_name) if search_results.empty? - + series = client.get_series_by_id(search_results.first['seriesid']) episode = client.get_episode(series, parser.season_number, parser.episode_number) raise UnknownEpisode.new(series.name, parser.season_number, parser.episode_number) if episode.nil? - return EpisodeMetadata.new(series, episode) + return series, episode end - + private - + def self.client @client ||= TvdbParty::Search.new('BD90B148E7D9E897', 'en') end - + end end diff --git a/spec/AtomicTV/atomic_parsley_tagger_spec.rb b/spec/AtomicTV/atomic_parsley_tagger_spec.rb index 750628f..408958c 100644 --- a/spec/AtomicTV/atomic_parsley_tagger_spec.rb +++ b/spec/AtomicTV/atomic_parsley_tagger_spec.rb @@ -1,60 +1,60 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper')) describe AtomicTV::AtomicParsleyTagger do - + describe ".executable" do - + before(:each) do Kernel.stub(:`).and_return('') end - + context "with AtomicParsley installed" do - + let(:executable_path) { double(:executable? => true) } - + before(:each) do Pathname.stub(:new).and_return(executable_path) end - + it "should return a pathname with executable's location" do path = AtomicTV::AtomicParsleyTagger.executable path.should == executable_path end - + end - + context "without AtomicParsley installed" do - + before(:each) do Pathname.stub(:new).and_return(double(:executable? => false)) end - + it "should raise a AtomicParsleyUnavailable error" do expect { AtomicTV::AtomicParsleyTagger.executable }.to raise_error(AtomicTV::AtomicParsleyTagger::AtomicParsleyUnavailable) end - + end - + end - + describe "#cast_metadata" do - + let(:file_path) { double(:exist? => true) } - + let(:metadata) do double( - :actors => ['Actor 1', 'Actor 2'], - :directors => ['Director 1', 'Director 2'], + :actors => ['Actor 1', 'Actor 2'], + :directors => ['Director 1', 'Director 2'], :writers => ['Writer 1', 'Writer 2'] ) end - + it "should generate a plist string from the actors, directors and writers" do tagger = AtomicTV::AtomicParsleyTagger.new(file_path, metadata) tagger.cast_metadata.should == "\n\n\n\n\tcast\n\t\n\t\t\n\t\t\tname\n\t\t\tActor 1\n\t\t\n\t\t\n\t\t\tname\n\t\t\tActor 2\n\t\t\n\t\n\tdirectors\n\t\n\t\t\n\t\t\tname\n\t\t\tDirector 1\n\t\t\n\t\t\n\t\t\tname\n\t\t\tDirector 2\n\t\t\n\t\n\tscreenwriters\n\t\n\t\t\n\t\t\tname\n\t\t\tWriter 1\n\t\t\n\t\t\n\t\t\tname\n\t\t\tWriter 2\n\t\t\n\t\n\n\n" end end - + end diff --git a/spec/AtomicTV/episode_metadata_spec.rb b/spec/AtomicTV/episode_metadata_spec.rb index 509e38e..8d2c472 100644 --- a/spec/AtomicTV/episode_metadata_spec.rb +++ b/spec/AtomicTV/episode_metadata_spec.rb @@ -1,13 +1,13 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper')) describe AtomicTV::EpisodeMetadata do - + let(:series) do - double('Mock Series', + double('Mock Series', :name => 'House', :network => 'FOX', :genres => ['Drama'], :artwork => [ - double('Artwork 1', :path => 'seasons/12345-7-1.jpg'), + double('Artwork 1', :path => 'seasons/12345-7-1.jpg'), double('Artwork 2', :path => 'seasons/12345-7-2.jpg')], :actors => [ double('Actor 1', :name => 'Hugh Laurie'), @@ -15,7 +15,7 @@ double('Actor 3', :name => 'Amber Tamblyn')] ) end - + let(:episode) do double('Mock Episode', :name => 'Last Temptation', @@ -28,120 +28,120 @@ :writer => '|David Foster|Liz Friedman|' ) end - + let(:metadata) { AtomicTV::EpisodeMetadata.new(series, episode) } - + context "with complete show information" do - + it "should return 'TV Show' for media_type" do metadata.media_type.should == 'TV Show' end - + it "should return the series name for artist" do metadata.artist.should == 'House' end - + it "should return the episode name for title" do metadata.title.should == 'Last Temptation' end - + it "should return the series name and season for album" do metadata.album.should == 'House, Season 7' end - + it "should return the first genre for genre" do metadata.genre.should == 'Drama' end - + it "should return the first 255 characters, trucated to a complete sentence, of the overview for description" do metadata.description.should == 'Masters faces a career crossroads on her last day as a medical student and struggles with the choice to continue on the path to become a surgeon or to accept the rare opportunity to join House’s team officially.' end - + it "should return the full overview for long description" do metadata.long_description.should == %Q{Masters faces a career crossroads on her last day as a medical student and struggles with the choice to continue on the path to become a surgeon or to accept the rare opportunity to join House’s team officially. Meanwhile, the team treats a 16-year-old girl who inexplicably collapsed days before embarking on an ambitious sailing tour around the globe. Despite the patient's life-changing diagnosis, the patient's family insists on getting her back on the seas in time for her potentially record-breaking launch. But to the team's surprise, including House, Masters makes a bold decision regarding the patient’s treatment.} end - + it "should return the series network for tv network" do metadata.tv_network.should == 'FOX' end - + it "should return the series name for tv show name" do metadata.tv_show_name.should == 'House' end - + it "should return the series number and episode number for tv episode" do metadata.tv_episode.should == '719' end - + it "should return the season number for tv season number" do metadata.tv_season_number.should == '7' end - + it "should return the episode number for tv episode number" do metadata.tv_episode_number.should == '19' end - + it "should return the episode number for track number" do metadata.track_number.should == '19' end - + it "should return a date with timezone for air date" do metadata.air_date.should == '2011-04-18T00:00:00Z' end - + it "should return series cast and guest cast for actors" do metadata.actors.should =~ ['Hugh Laurie', 'Olivia Wilde', 'Amber Tamblyn', 'Ron Perkins', 'Jennifer Landon'] end - + it "should return the episode's directors for directors" do metadata.directors.should =~ ['Tim Southam'] end - + it "should return the episode's writers for writers" do metadata.writers.should =~ ['David Foster', 'Liz Friedman'] end - + end - + context "with a missing air date" do - + before(:each) do episode.stub(:air_date).and_return(nil) end - + it "should return nil for air_date" do metadata.air_date.should be_nil end - + end - + context "with missing artwork" do - + end - + describe "#description" do pending "more logic" end - + describe "#tv_episode" do - + it "should format the episode number to two digits" do episode.stub(:number).and_return('2') metadata.tv_episode.should == '702' end - + end - + describe "#directors" do pending "more logic" end - + describe "#writers" do pending "more logic" end - + describe "#with_loaded_posters" do pending end - + end diff --git a/spec/AtomicTV/filename_parser_spec.rb b/spec/AtomicTV/filename_parser_spec.rb index dc3915f..0bc1dd0 100644 --- a/spec/AtomicTV/filename_parser_spec.rb +++ b/spec/AtomicTV/filename_parser_spec.rb @@ -1,46 +1,46 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper')) describe AtomicTV::FilenameParser do - + describe ".parse" do - + context "with valid filenames" do - + let(:parser) { AtomicTV::FilenameParser.parse('Battlestar Galactica (2003) - S04E20.m4v') } - + it "should extract the series name" do parser.series_name.should == 'Battlestar Galactica (2003)' end - + it "should extract the season number" do parser.season_number.should == 4 end - + it "should extract the episode number" do parser.episode_number.should == 20 end end - + context "with invalid filenames" do - + it "should raise an AtomicTV::FilenameParser::InvalidFilename error" do expect { AtomicTV::FilenameParser.parse('V for Vendetta (2006).m4v') }.to raise_error(AtomicTV::FilenameParser::InvalidFilename) end - + it "should expose the invalid filename in the error" do invalid_filename = 'V for Vendetta (2006).m4v' - + begin AtomicTV::FilenameParser.parse(invalid_filename) rescue AtomicTV::FilenameParser::InvalidFilename => error error.filename.should == invalid_filename end end - + end - + end - + end diff --git a/spec/AtomicTV/tvdb_episode_spec.rb b/spec/AtomicTV/tvdb_episode_spec.rb index f6090fb..c131ce9 100644 --- a/spec/AtomicTV/tvdb_episode_spec.rb +++ b/spec/AtomicTV/tvdb_episode_spec.rb @@ -3,62 +3,62 @@ WebMock.disable_net_connect! describe AtomicTV::TVDBEpisode do - + describe ".metadata_for_filename" do - + context "with a correct series, season and episode" do - + before(:each) do @mock_parser = double('Mock Parser', :series_name => 'The Wire', :season_number => 3, :episode_number => 6) AtomicTV::FilenameParser.stub(:parse).and_return(@mock_parser) - + @result1 = double('Mock Search Result - 1', :[] => '123456789') @result2 = double('Mock Search Result - 2', :[] => '987654321') @episode = double('Mock Episode') @mock_client = double('Mock Client', :search => [@result1, @result2], :get_series_by_id => @result1, :get_episode => @episode) AtomicTV::TVDBEpisode.stub(:client).and_return(@mock_client) end - + it "should search for the series using the name" do @mock_client.should_receive(:search).with('The Wire') - + AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v') end - + it "should use the first search result as the series" do @mock_client.should_receive(:get_series_by_id).with('123456789') - + AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v') end - + it "should return the correct episode for the series" do @mock_client.should_receive(:get_episode).with(@result1, 3, 6) - + AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v') end - + it "should return an EpisodeMetadata instance for the episode" do metadata = AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v') metadata.should be_kind_of(AtomicTV::EpisodeMetadata) metadata.series.should == @result1 metadata.episode.should == @episode end - + end - + context "with an unknown series" do - + before(:each) do @mock_client = double('Mock Client', :search => []) AtomicTV::TVDBEpisode.stub(:client).and_return(@mock_client) end - + it "should raise an AtomicTV::TVDBEpisode::UnknownSeries error" do expect { AtomicTV::TVDBEpisode.metadata_for_filename('Teh Wires - S01E01.m4v') }.to raise_error(AtomicTV::TVDBEpisode::UnknownSeries) end - + it "should expose the invalid series name in the error" do begin AtomicTV::TVDBEpisode.metadata_for_filename('Teh Wires - S01E01.m4v') @@ -66,23 +66,23 @@ error.series_name.should == 'Teh Wires' end end - + end - + context "with an unknown episode" do - + before(:each) do @series = double('Mock Series', :[] => '1234789', :name => 'The Wire') @mock_client = double('Mock Client', :search => [@series], :get_series_by_id => @series, :get_episode => nil) AtomicTV::TVDBEpisode.stub(:client).and_return(@mock_client) end - + it "should raise an AtomicTV::TVDBEpisode::UnknownEpisode error" do expect { AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S01E99.m4v') }.to raise_error(AtomicTV::TVDBEpisode::UnknownEpisode) end - + it "should expose the invalid episode ID in the error" do begin AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S01E99.m4v') @@ -90,9 +90,9 @@ error.episode_id.should == 'The Wire - S01E99' end end - + end - + end - + end