AtomicTV 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/AtomicTV.gemspec +85 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/AtomicTV +24 -0
- data/lib/AtomicTV.rb +15 -0
- data/lib/AtomicTV/atomic_parsley_tagger.rb +99 -0
- data/lib/AtomicTV/episode_metadata.rb +111 -0
- data/lib/AtomicTV/filename_parser.rb +37 -0
- data/lib/AtomicTV/tvdb_episode.rb +46 -0
- data/spec/AtomicTV/atomic_parsley_tagger_spec.rb +60 -0
- data/spec/AtomicTV/episode_metadata_spec.rb +147 -0
- data/spec/AtomicTV/filename_parser_spec.rb +46 -0
- data/spec/AtomicTV/tvdb_episode_spec.rb +98 -0
- data/spec/spec_helper.rb +13 -0
- metadata +198 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/AtomicTV.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{AtomicTV}
|
8
|
+
s.version = "1.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Olly Legg"]
|
12
|
+
s.date = %q{2011-06-01}
|
13
|
+
s.description = %q{A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com. It uses AtomicParsley to process the file.}
|
14
|
+
s.email = %q{olly@51degrees.net}
|
15
|
+
s.executables = ["AtomicTV"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.rdoc"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".document",
|
22
|
+
".rspec",
|
23
|
+
"AtomicTV.gemspec",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"bin/AtomicTV",
|
31
|
+
"lib/AtomicTV.rb",
|
32
|
+
"lib/AtomicTV/atomic_parsley_tagger.rb",
|
33
|
+
"lib/AtomicTV/episode_metadata.rb",
|
34
|
+
"lib/AtomicTV/filename_parser.rb",
|
35
|
+
"lib/AtomicTV/tvdb_episode.rb",
|
36
|
+
"spec/AtomicTV/atomic_parsley_tagger_spec.rb",
|
37
|
+
"spec/AtomicTV/episode_metadata_spec.rb",
|
38
|
+
"spec/AtomicTV/filename_parser_spec.rb",
|
39
|
+
"spec/AtomicTV/tvdb_episode_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/olly/AtomicTV}
|
43
|
+
s.licenses = ["MIT"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.7.2}
|
46
|
+
s.summary = %q{A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com.}
|
47
|
+
s.test_files = [
|
48
|
+
"spec/AtomicTV/atomic_parsley_tagger_spec.rb",
|
49
|
+
"spec/AtomicTV/episode_metadata_spec.rb",
|
50
|
+
"spec/AtomicTV/filename_parser_spec.rb",
|
51
|
+
"spec/AtomicTV/tvdb_episode_spec.rb",
|
52
|
+
"spec/spec_helper.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
s.specification_version = 3
|
57
|
+
|
58
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
59
|
+
s.add_runtime_dependency(%q<plist>, ["= 3.1.0"])
|
60
|
+
s.add_runtime_dependency(%q<tvdb_party>, ["= 0.6.0"])
|
61
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
62
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
63
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
64
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<webmock>, ["~> 1.6.2"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<plist>, ["= 3.1.0"])
|
68
|
+
s.add_dependency(%q<tvdb_party>, ["= 0.6.0"])
|
69
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
70
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
71
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
72
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
73
|
+
s.add_dependency(%q<webmock>, ["~> 1.6.2"])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<plist>, ["= 3.1.0"])
|
77
|
+
s.add_dependency(%q<tvdb_party>, ["= 0.6.0"])
|
78
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
79
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
80
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
81
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
82
|
+
s.add_dependency(%q<webmock>, ["~> 1.6.2"])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.2.5)
|
5
|
+
crack (0.1.8)
|
6
|
+
diff-lcs (1.1.2)
|
7
|
+
git (1.2.5)
|
8
|
+
httparty (0.7.7)
|
9
|
+
crack (= 0.1.8)
|
10
|
+
jeweler (1.5.2)
|
11
|
+
bundler (~> 1.0.0)
|
12
|
+
git (>= 1.2.5)
|
13
|
+
rake
|
14
|
+
plist (3.1.0)
|
15
|
+
rake (0.8.7)
|
16
|
+
rcov (0.9.9)
|
17
|
+
rspec (2.3.0)
|
18
|
+
rspec-core (~> 2.3.0)
|
19
|
+
rspec-expectations (~> 2.3.0)
|
20
|
+
rspec-mocks (~> 2.3.0)
|
21
|
+
rspec-core (2.3.1)
|
22
|
+
rspec-expectations (2.3.0)
|
23
|
+
diff-lcs (~> 1.1.2)
|
24
|
+
rspec-mocks (2.3.0)
|
25
|
+
tvdb_party (0.6.0)
|
26
|
+
httparty (>= 0.6.1)
|
27
|
+
webmock (1.6.2)
|
28
|
+
addressable (>= 2.2.2)
|
29
|
+
crack (>= 0.1.7)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
bundler (~> 1.0.0)
|
36
|
+
jeweler (~> 1.5.2)
|
37
|
+
plist (= 3.1.0)
|
38
|
+
rcov
|
39
|
+
rspec (~> 2.3.0)
|
40
|
+
tvdb_party (= 0.6.0)
|
41
|
+
webmock (~> 1.6.2)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Oliver Legg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= AtomicTV
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to AtomicTV
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Oliver Legg. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "AtomicTV"
|
16
|
+
gem.homepage = "http://github.com/olly/AtomicTV"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com.}
|
19
|
+
gem.description = %Q{A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com. It uses AtomicParsley to process the file.}
|
20
|
+
gem.email = "olly@51degrees.net"
|
21
|
+
gem.authors = ["Olly Legg"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "AtomicTV #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.1
|
data/bin/AtomicTV
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby -rubygems
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'AtomicTV'))
|
4
|
+
|
5
|
+
include AtomicTV
|
6
|
+
|
7
|
+
def display_error(error)
|
8
|
+
$stderr.puts "! ERROR: #{error.human_message}"
|
9
|
+
end
|
10
|
+
|
11
|
+
ARGV.each do |path|
|
12
|
+
begin
|
13
|
+
file_path = Pathname.new(path)
|
14
|
+
metadata = TVDBEpisode.metadata_for_filename(file_path.basename)
|
15
|
+
tagger = AtomicParsleyTagger.new(file_path, metadata)
|
16
|
+
tagger.run
|
17
|
+
puts "* Tagged: #{file_path.basename}"
|
18
|
+
rescue AtomicParsleyTagger::AtomicParsleyUnavailable => error
|
19
|
+
display_error(error)
|
20
|
+
exit 1
|
21
|
+
rescue AtomicTVError => error
|
22
|
+
display_error(error)
|
23
|
+
end
|
24
|
+
end
|
data/lib/AtomicTV.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'pathname'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
require 'plist'
|
7
|
+
require 'tvdb_party'
|
8
|
+
|
9
|
+
module AtomicTV
|
10
|
+
class AtomicTVError < StandardError; end
|
11
|
+
end
|
12
|
+
|
13
|
+
['atomic_parsley_tagger', 'episode_metadata', 'filename_parser', 'tvdb_episode'].each do |file|
|
14
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'AtomicTV', file))
|
15
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module AtomicTV
|
2
|
+
class AtomicParsleyTagger
|
3
|
+
|
4
|
+
class AtomicParsleyUnavailable < ::AtomicTV::AtomicTVError
|
5
|
+
def human_message
|
6
|
+
'AtomicParsley is not installed or could not be found. Try checking your PATH.'
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class FileNotFound < ::AtomicTV::AtomicTVError
|
11
|
+
def initialize(file_path)
|
12
|
+
@file_path = file_path
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :file_path
|
16
|
+
|
17
|
+
def human_message
|
18
|
+
"File not found: #{file_path}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class TaggingError < ::AtomicTV::AtomicTVError
|
23
|
+
def initialize(command)
|
24
|
+
@command = command
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :command
|
28
|
+
|
29
|
+
def human_message
|
30
|
+
"A tagging error occured: #{command}."
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.executable
|
36
|
+
path = Pathname.new(`which AtomicParsley`.chomp)
|
37
|
+
raise AtomicParsleyUnavailable unless path.executable?
|
38
|
+
path
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(file_path, metadata)
|
42
|
+
@file_path = file_path
|
43
|
+
raise FileNotFound.new(file_path) unless file_path.exist?
|
44
|
+
|
45
|
+
@metadata = metadata
|
46
|
+
end
|
47
|
+
|
48
|
+
def cast_metadata
|
49
|
+
format_names = lambda {|name| {'name' => name}}
|
50
|
+
{
|
51
|
+
'cast' => metadata.actors.map(&format_names),
|
52
|
+
'directors' => metadata.directors.map(&format_names),
|
53
|
+
'screenwriters' => metadata.writers.map(&format_names)
|
54
|
+
}.to_plist
|
55
|
+
end
|
56
|
+
|
57
|
+
def run
|
58
|
+
options = {
|
59
|
+
'stik' => metadata.media_type,
|
60
|
+
'artist' => metadata.artist,
|
61
|
+
'title' => metadata.title,
|
62
|
+
'album' => metadata.album,
|
63
|
+
'genre' => metadata.genre,
|
64
|
+
'description' => metadata.description,
|
65
|
+
'longdesc' => metadata.long_description,
|
66
|
+
'TVNetwork' => metadata.tv_network,
|
67
|
+
'TVShowName' => metadata.tv_show_name,
|
68
|
+
'TVEpisode' => metadata.tv_episode,
|
69
|
+
'TVSeasonNum' => metadata.tv_season_number,
|
70
|
+
'TVEpisodeNum' => metadata.tv_episode_number,
|
71
|
+
'tracknum' => metadata.track_number,
|
72
|
+
'year' => metadata.air_date
|
73
|
+
}
|
74
|
+
|
75
|
+
metadata.with_loaded_posters do
|
76
|
+
command = %Q{#{self.class.executable} }
|
77
|
+
command << %Q{"#{file_path}" }
|
78
|
+
command << %Q{--overWrite }
|
79
|
+
command << %Q{--rDNSatom '#{cast_metadata}' name=iTunMOVI domain=com.apple.iTunes }
|
80
|
+
metadata.posters.each do |poster|
|
81
|
+
command << %Q{--artwork #{poster.path} }
|
82
|
+
end
|
83
|
+
command << options.map {|option, value| %Q{--#{option} "#{escape_double_quotes(value)}"}}.join(' ')
|
84
|
+
|
85
|
+
`#{command}`
|
86
|
+
raise TaggingError.new(command) unless $?.success?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
attr_reader :file_path, :metadata
|
93
|
+
|
94
|
+
def escape_double_quotes(str)
|
95
|
+
str.gsub('"', '\"')
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module AtomicTV
|
2
|
+
class EpisodeMetadata
|
3
|
+
|
4
|
+
ArtworkBaseURL = "http://thetvdb.com/banners/"
|
5
|
+
|
6
|
+
def initialize(series, episode)
|
7
|
+
@series, @episode = series, episode
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :series, :episode
|
11
|
+
|
12
|
+
def media_type
|
13
|
+
'TV Show'
|
14
|
+
end
|
15
|
+
|
16
|
+
def artist
|
17
|
+
series.name
|
18
|
+
end
|
19
|
+
|
20
|
+
def title
|
21
|
+
episode.name
|
22
|
+
end
|
23
|
+
|
24
|
+
def album
|
25
|
+
"#{series.name}, Season #{episode.season_number}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def genre
|
29
|
+
series.genres.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def description
|
33
|
+
episode.overview[0,255].gsub(/\.(.*)\Z/, '.')
|
34
|
+
end
|
35
|
+
|
36
|
+
def long_description
|
37
|
+
episode.overview
|
38
|
+
end
|
39
|
+
|
40
|
+
def tv_network
|
41
|
+
series.network
|
42
|
+
end
|
43
|
+
|
44
|
+
def tv_show_name
|
45
|
+
series.name
|
46
|
+
end
|
47
|
+
|
48
|
+
def tv_episode
|
49
|
+
"#{episode.season_number}#{episode.number.to_s.rjust(2, '0')}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def tv_season_number
|
53
|
+
episode.season_number
|
54
|
+
end
|
55
|
+
|
56
|
+
def tv_episode_number
|
57
|
+
episode.number
|
58
|
+
end
|
59
|
+
|
60
|
+
def track_number
|
61
|
+
episode.number
|
62
|
+
end
|
63
|
+
|
64
|
+
def air_date
|
65
|
+
episode.air_date && episode.air_date.to_s + 'T00:00:00Z'
|
66
|
+
end
|
67
|
+
|
68
|
+
def actors
|
69
|
+
(series.actors.map {|a| a.name} + episode.guest_stars).uniq
|
70
|
+
end
|
71
|
+
|
72
|
+
def directors
|
73
|
+
parse_names(episode.director)
|
74
|
+
end
|
75
|
+
|
76
|
+
def writers
|
77
|
+
parse_names(episode.writer)
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :posters
|
81
|
+
|
82
|
+
def with_loaded_posters
|
83
|
+
temporary_directory = Dir.mktmpdir
|
84
|
+
|
85
|
+
@posters = series.season_posters(episode.season_number, 'en').map do |poster|
|
86
|
+
url = ArtworkBaseURL + poster.path
|
87
|
+
file = File.new(File.join(temporary_directory, File.basename(url)), 'w')
|
88
|
+
file.write(open(url).read)
|
89
|
+
file.close
|
90
|
+
file
|
91
|
+
end
|
92
|
+
|
93
|
+
yield
|
94
|
+
|
95
|
+
ensure
|
96
|
+
FileUtils.rm_rf(temporary_directory)
|
97
|
+
@posters = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def parse_names(str)
|
103
|
+
return [] if str.nil?
|
104
|
+
|
105
|
+
str.gsub!(/\A\|/, '')
|
106
|
+
str.gsub!(/\|\Z/, '')
|
107
|
+
str.split(/\|+/)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module AtomicTV
|
2
|
+
class FilenameParser
|
3
|
+
|
4
|
+
class InvalidFilename < ::AtomicTV::AtomicTVError
|
5
|
+
def initialize(filename)
|
6
|
+
@filename = filename
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :filename
|
10
|
+
|
11
|
+
def human_message
|
12
|
+
"Invalid filename: #{filename} (filenames must be in the format: 'Series Name - S01E01')."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
FilenameFormat = /\A(.*) - S(\d{2})E(\d{2})\.\w{3}\Z/
|
17
|
+
|
18
|
+
def self.parse(filename)
|
19
|
+
if filename.to_s =~ FilenameFormat
|
20
|
+
new($1, $2.to_i(10), $3.to_i(10))
|
21
|
+
else
|
22
|
+
raise InvalidFilename.new(filename)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :series_name, :season_number, :episode_number
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def initialize(series_name, season_number, episode_number)
|
31
|
+
@series_name = series_name
|
32
|
+
@season_number = season_number
|
33
|
+
@episode_number = episode_number
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module AtomicTV
|
2
|
+
class TVDBEpisode
|
3
|
+
|
4
|
+
class UnknownSeries < ::AtomicTV::AtomicTVError
|
5
|
+
def initialize(series_name)
|
6
|
+
@series_name = series_name
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :series_name
|
10
|
+
|
11
|
+
def human_message
|
12
|
+
"Unknown TV series: '#{series_name}'"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UnknownEpisode < ::AtomicTV::AtomicTVError
|
17
|
+
def initialize(series_name, season_number, episode_number)
|
18
|
+
@episode_id = "#{series_name} - S#{season_number.to_s.rjust(2, '0')}E#{episode_number.to_s.rjust(2, '0')}"
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :episode_id
|
22
|
+
|
23
|
+
def human_message
|
24
|
+
"Unknown episode: #{episode_id}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.metadata_for_filename(filename)
|
29
|
+
parser = FilenameParser.parse(filename)
|
30
|
+
search_results = client.search(parser.series_name)
|
31
|
+
raise UnknownSeries.new(parser.series_name) if search_results.empty?
|
32
|
+
|
33
|
+
series = client.get_series_by_id(search_results.first['seriesid'])
|
34
|
+
episode = client.get_episode(series, parser.season_number, parser.episode_number)
|
35
|
+
raise UnknownEpisode.new(series.name, parser.season_number, parser.episode_number) if episode.nil?
|
36
|
+
return EpisodeMetadata.new(series, episode)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.client
|
42
|
+
@client ||= TvdbParty::Search.new('BD90B148E7D9E897', 'en')
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe AtomicTV::AtomicParsleyTagger do
|
4
|
+
|
5
|
+
describe ".executable" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
stub!(:`).and_return('')
|
9
|
+
end
|
10
|
+
|
11
|
+
context "with AtomicParsley installed" do
|
12
|
+
|
13
|
+
let(:executable_path) { stub(:executable? => true) }
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
Pathname.stub!(:new).and_return(executable_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return a pathname with executable's location" do
|
20
|
+
path = AtomicTV::AtomicParsleyTagger.executable
|
21
|
+
path.should == executable_path
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context "without AtomicParsley installed" do
|
27
|
+
|
28
|
+
before(:each) do
|
29
|
+
Pathname.stub!(:new).and_return(stub(:executable? => false))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise a AtomicParsleyUnavailable error" do
|
33
|
+
expect {
|
34
|
+
AtomicTV::AtomicParsleyTagger.executable
|
35
|
+
}.to raise_error(AtomicTV::AtomicParsleyTagger::AtomicParsleyUnavailable)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#cast_metadata" do
|
43
|
+
|
44
|
+
let(:file_path) { stub(:exist? => true) }
|
45
|
+
|
46
|
+
let(:metadata) do
|
47
|
+
stub(
|
48
|
+
:actors => ['Actor 1', 'Actor 2'],
|
49
|
+
:directors => ['Director 1', 'Director 2'],
|
50
|
+
:writers => ['Writer 1', 'Writer 2']
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should generate a plist string from the actors, directors and writers" do
|
55
|
+
tagger = AtomicTV::AtomicParsleyTagger.new(file_path, metadata)
|
56
|
+
tagger.cast_metadata.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>cast</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Actor 1</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Actor 2</string>\n\t\t</dict>\n\t</array>\n\t<key>directors</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Director 1</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Director 2</string>\n\t\t</dict>\n\t</array>\n\t<key>screenwriters</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Writer 1</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>name</key>\n\t\t\t<string>Writer 2</string>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe AtomicTV::EpisodeMetadata do
|
4
|
+
|
5
|
+
let(:series) do
|
6
|
+
stub('Mock Series',
|
7
|
+
:name => 'House', :network => 'FOX',
|
8
|
+
:genres => ['Drama'],
|
9
|
+
:artwork => [
|
10
|
+
stub('Artwork 1', :path => 'seasons/12345-7-1.jpg'),
|
11
|
+
stub('Artwork 2', :path => 'seasons/12345-7-2.jpg')],
|
12
|
+
:actors => [
|
13
|
+
stub('Actor 1', :name => 'Hugh Laurie'),
|
14
|
+
stub('Actor 2', :name => 'Olivia Wilde'),
|
15
|
+
stub('Actor 3', :name => 'Amber Tamblyn')]
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:episode) do
|
20
|
+
stub('Mock Episode',
|
21
|
+
:name => 'Last Temptation',
|
22
|
+
:season_number => '7',
|
23
|
+
:number => '19',
|
24
|
+
:overview => %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.},
|
25
|
+
:air_date => Date.new(2011, 4, 18),
|
26
|
+
:guest_stars => ['Amber Tamblyn', 'Ron Perkins', 'Jennifer Landon'],
|
27
|
+
:director => 'Tim Southam',
|
28
|
+
:writer => '|David Foster|Liz Friedman|'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:metadata) { AtomicTV::EpisodeMetadata.new(series, episode) }
|
33
|
+
|
34
|
+
context "with complete show information" do
|
35
|
+
|
36
|
+
it "should return 'TV Show' for media_type" do
|
37
|
+
metadata.media_type.should == 'TV Show'
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return the series name for artist" do
|
41
|
+
metadata.artist.should == 'House'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return the episode name for title" do
|
45
|
+
metadata.title.should == 'Last Temptation'
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return the series name and season for album" do
|
49
|
+
metadata.album.should == 'House, Season 7'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the first genre for genre" do
|
53
|
+
metadata.genre.should == 'Drama'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return the first 255 characters, trucated to a complete sentence, of the overview for description" do
|
57
|
+
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.'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return the full overview for long description" do
|
61
|
+
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.}
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return the series network for tv network" do
|
65
|
+
metadata.tv_network.should == 'FOX'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return the series name for tv show name" do
|
69
|
+
metadata.tv_show_name.should == 'House'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return the series number and episode number for tv episode" do
|
73
|
+
metadata.tv_episode.should == '719'
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return the season number for tv season number" do
|
77
|
+
metadata.tv_season_number.should == '7'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return the episode number for tv episode number" do
|
81
|
+
metadata.tv_episode_number.should == '19'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should return the episode number for track number" do
|
85
|
+
metadata.track_number.should == '19'
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return a date with timezone for air date" do
|
89
|
+
metadata.air_date.should == '2011-04-18T00:00:00Z'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return series cast and guest cast for actors" do
|
93
|
+
metadata.actors.should =~ ['Hugh Laurie', 'Olivia Wilde', 'Amber Tamblyn', 'Ron Perkins', 'Jennifer Landon']
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should return the episode's directors for directors" do
|
97
|
+
metadata.directors.should =~ ['Tim Southam']
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return the episode's writers for writers" do
|
101
|
+
metadata.writers.should =~ ['David Foster', 'Liz Friedman']
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with a missing air date" do
|
107
|
+
|
108
|
+
before(:each) do
|
109
|
+
episode.stub!(:air_date).and_return(nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should return nil for air_date" do
|
113
|
+
metadata.air_date.should be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
context "with missing artwork" do
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "#description" do
|
123
|
+
pending "more logic"
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#tv_episode" do
|
127
|
+
|
128
|
+
it "should format the episode number to two digits" do
|
129
|
+
episode.stub!(:number).and_return('2')
|
130
|
+
metadata.tv_episode.should == '702'
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "#directors" do
|
136
|
+
pending "more logic"
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "#writers" do
|
140
|
+
pending "more logic"
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#with_loaded_posters" do
|
144
|
+
pending
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
describe AtomicTV::FilenameParser do
|
4
|
+
|
5
|
+
describe ".parse" do
|
6
|
+
|
7
|
+
context "with valid filenames" do
|
8
|
+
|
9
|
+
let(:parser) { AtomicTV::FilenameParser.parse('Battlestar Galactica (2003) - S04E20.m4v') }
|
10
|
+
|
11
|
+
it "should extract the series name" do
|
12
|
+
parser.series_name.should == 'Battlestar Galactica (2003)'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should extract the season number" do
|
16
|
+
parser.season_number.should == 4
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should extract the episode number" do
|
20
|
+
parser.episode_number.should == 20
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with invalid filenames" do
|
25
|
+
|
26
|
+
it "should raise an AtomicTV::FilenameParser::InvalidFilename error" do
|
27
|
+
expect {
|
28
|
+
AtomicTV::FilenameParser.parse('V for Vendetta (2006).m4v')
|
29
|
+
}.to raise_error(AtomicTV::FilenameParser::InvalidFilename)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should expose the invalid filename in the error" do
|
33
|
+
invalid_filename = 'V for Vendetta (2006).m4v'
|
34
|
+
|
35
|
+
begin
|
36
|
+
AtomicTV::FilenameParser.parse(invalid_filename)
|
37
|
+
rescue AtomicTV::FilenameParser::InvalidFilename => error
|
38
|
+
error.filename.should == invalid_filename
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
|
3
|
+
WebMock.disable_net_connect!
|
4
|
+
|
5
|
+
describe AtomicTV::TVDBEpisode do
|
6
|
+
|
7
|
+
describe ".metadata_for_filename" do
|
8
|
+
|
9
|
+
context "with a correct series, season and episode" do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@mock_parser = stub('Mock Parser', :series_name => 'The Wire', :season_number => 3, :episode_number => 6)
|
13
|
+
AtomicTV::FilenameParser.stub!(:parse).and_return(@mock_parser)
|
14
|
+
|
15
|
+
@result1 = stub('Mock Search Result - 1', :[] => '123456789')
|
16
|
+
@result2 = stub('Mock Search Result - 2', :[] => '987654321')
|
17
|
+
@episode = stub('Mock Episode')
|
18
|
+
@mock_client = stub('Mock Client', :search => [@result1, @result2], :get_series_by_id => @result1, :get_episode => @episode)
|
19
|
+
AtomicTV::TVDBEpisode.stub!(:client).and_return(@mock_client)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should search for the series using the name" do
|
23
|
+
@mock_client.should_receive(:search).with('The Wire')
|
24
|
+
|
25
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should use the first search result as the series" do
|
29
|
+
@mock_client.should_receive(:get_series_by_id).with('123456789')
|
30
|
+
|
31
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v')
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return the correct episode for the series" do
|
35
|
+
@mock_client.should_receive(:get_episode).with(@result1, 3, 6)
|
36
|
+
|
37
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return an EpisodeMetadata instance for the episode" do
|
41
|
+
metadata = AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S03E06.m4v')
|
42
|
+
metadata.should be_kind_of(AtomicTV::EpisodeMetadata)
|
43
|
+
metadata.series.should == @result1
|
44
|
+
metadata.episode.should == @episode
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with an unknown series" do
|
50
|
+
|
51
|
+
before(:each) do
|
52
|
+
@mock_client = stub('Mock Client', :search => [])
|
53
|
+
AtomicTV::TVDBEpisode.stub!(:client).and_return(@mock_client)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise an AtomicTV::TVDBEpisode::UnknownSeries error" do
|
57
|
+
expect {
|
58
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('Teh Wires - S01E01.m4v')
|
59
|
+
}.to raise_error(AtomicTV::TVDBEpisode::UnknownSeries)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should expose the invalid series name in the error" do
|
63
|
+
begin
|
64
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('Teh Wires - S01E01.m4v')
|
65
|
+
rescue AtomicTV::TVDBEpisode::UnknownSeries => error
|
66
|
+
error.series_name.should == 'Teh Wires'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with an unknown episode" do
|
73
|
+
|
74
|
+
before(:each) do
|
75
|
+
@series = stub('Mock Series', :[] => '1234789', :name => 'The Wire')
|
76
|
+
@mock_client = stub('Mock Client', :search => [@series], :get_series_by_id => @series, :get_episode => nil)
|
77
|
+
AtomicTV::TVDBEpisode.stub!(:client).and_return(@mock_client)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise an AtomicTV::TVDBEpisode::UnknownEpisode error" do
|
81
|
+
expect {
|
82
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S01E99.m4v')
|
83
|
+
}.to raise_error(AtomicTV::TVDBEpisode::UnknownEpisode)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should expose the invalid episode ID in the error" do
|
87
|
+
begin
|
88
|
+
AtomicTV::TVDBEpisode.metadata_for_filename('The Wire - S01E99.m4v')
|
89
|
+
rescue AtomicTV::TVDBEpisode::UnknownEpisode => error
|
90
|
+
error.episode_id.should == 'The Wire - S01E99'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'webmock/rspec'
|
5
|
+
require 'AtomicTV'
|
6
|
+
|
7
|
+
# Requires supporting files with custom matchers and macros, etc,
|
8
|
+
# in ./support/ and its subdirectories.
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: AtomicTV
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Olly Legg
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-01 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - "="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 3
|
27
|
+
segments:
|
28
|
+
- 3
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
version: 3.1.0
|
32
|
+
name: plist
|
33
|
+
prerelease: false
|
34
|
+
type: :runtime
|
35
|
+
requirement: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - "="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 7
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 6
|
46
|
+
- 0
|
47
|
+
version: 0.6.0
|
48
|
+
name: tvdb_party
|
49
|
+
prerelease: false
|
50
|
+
type: :runtime
|
51
|
+
requirement: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 2
|
61
|
+
- 3
|
62
|
+
- 0
|
63
|
+
version: 2.3.0
|
64
|
+
name: rspec
|
65
|
+
prerelease: false
|
66
|
+
type: :development
|
67
|
+
requirement: *id003
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ~>
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 23
|
75
|
+
segments:
|
76
|
+
- 1
|
77
|
+
- 0
|
78
|
+
- 0
|
79
|
+
version: 1.0.0
|
80
|
+
name: bundler
|
81
|
+
prerelease: false
|
82
|
+
type: :development
|
83
|
+
requirement: *id004
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 7
|
91
|
+
segments:
|
92
|
+
- 1
|
93
|
+
- 5
|
94
|
+
- 2
|
95
|
+
version: 1.5.2
|
96
|
+
name: jeweler
|
97
|
+
prerelease: false
|
98
|
+
type: :development
|
99
|
+
requirement: *id005
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
name: rcov
|
111
|
+
prerelease: false
|
112
|
+
type: :development
|
113
|
+
requirement: *id006
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
version_requirements: &id007 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ~>
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
hash: 11
|
121
|
+
segments:
|
122
|
+
- 1
|
123
|
+
- 6
|
124
|
+
- 2
|
125
|
+
version: 1.6.2
|
126
|
+
name: webmock
|
127
|
+
prerelease: false
|
128
|
+
type: :development
|
129
|
+
requirement: *id007
|
130
|
+
description: A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com. It uses AtomicParsley to process the file.
|
131
|
+
email: olly@51degrees.net
|
132
|
+
executables:
|
133
|
+
- AtomicTV
|
134
|
+
extensions: []
|
135
|
+
|
136
|
+
extra_rdoc_files:
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.rdoc
|
139
|
+
files:
|
140
|
+
- .document
|
141
|
+
- .rspec
|
142
|
+
- AtomicTV.gemspec
|
143
|
+
- Gemfile
|
144
|
+
- Gemfile.lock
|
145
|
+
- LICENSE.txt
|
146
|
+
- README.rdoc
|
147
|
+
- Rakefile
|
148
|
+
- VERSION
|
149
|
+
- bin/AtomicTV
|
150
|
+
- lib/AtomicTV.rb
|
151
|
+
- lib/AtomicTV/atomic_parsley_tagger.rb
|
152
|
+
- lib/AtomicTV/episode_metadata.rb
|
153
|
+
- lib/AtomicTV/filename_parser.rb
|
154
|
+
- lib/AtomicTV/tvdb_episode.rb
|
155
|
+
- spec/AtomicTV/atomic_parsley_tagger_spec.rb
|
156
|
+
- spec/AtomicTV/episode_metadata_spec.rb
|
157
|
+
- spec/AtomicTV/filename_parser_spec.rb
|
158
|
+
- spec/AtomicTV/tvdb_episode_spec.rb
|
159
|
+
- spec/spec_helper.rb
|
160
|
+
homepage: http://github.com/olly/AtomicTV
|
161
|
+
licenses:
|
162
|
+
- MIT
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
hash: 3
|
174
|
+
segments:
|
175
|
+
- 0
|
176
|
+
version: "0"
|
177
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
hash: 3
|
183
|
+
segments:
|
184
|
+
- 0
|
185
|
+
version: "0"
|
186
|
+
requirements: []
|
187
|
+
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 1.7.2
|
190
|
+
signing_key:
|
191
|
+
specification_version: 3
|
192
|
+
summary: A command line tool to tag MP4 TV shows with metadata pulled from TheTVDB.com.
|
193
|
+
test_files:
|
194
|
+
- spec/AtomicTV/atomic_parsley_tagger_spec.rb
|
195
|
+
- spec/AtomicTV/episode_metadata_spec.rb
|
196
|
+
- spec/AtomicTV/filename_parser_spec.rb
|
197
|
+
- spec/AtomicTV/tvdb_episode_spec.rb
|
198
|
+
- spec/spec_helper.rb
|