mediaman 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/mediaman +9 -0
- data/lib/mediaman/command.rb +35 -0
- data/lib/mediaman/document.rb +206 -0
- data/lib/mediaman/library_document.rb +82 -0
- data/lib/mediaman/media_filename.rb +152 -0
- data/lib/mediaman/metadata.rb +90 -0
- data/lib/mediaman/temporary_document.rb +23 -0
- data/lib/mediaman/trakt.rb +66 -0
- data/lib/mediaman/version.rb +3 -0
- data/lib/mediaman.rb +13 -0
- data/mediaman.gemspec +29 -0
- data/spec/mediaman/media_filename_spec.rb +36 -0
- data/spec/spec_helper.rb +13 -0
- metadata +150 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Wil Gieseler
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Mediaman
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'mediaman'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install mediaman
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mediaman
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mediaman
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
class CommandLine < Thor
|
6
|
+
|
7
|
+
desc "add <file>", "sorts the file according to its metadata"
|
8
|
+
method_option :library, type: :string, aliases: "-l", desc: "Media library base folder to sort into.", default: "."
|
9
|
+
method_option :batch, type: :boolean, aliases: "-b", desc: "Adds each file or folder in the passed-in folder to the library.", default: false
|
10
|
+
def add(path)
|
11
|
+
library_document = LibraryDocument.from_path path
|
12
|
+
library_document.library_path = File.expand_path options[:library]
|
13
|
+
# puts "Sidecar path:"
|
14
|
+
# puts library_document.library_sidecar_path
|
15
|
+
puts "Video:"
|
16
|
+
puts library_document.video_files
|
17
|
+
puts "Junk:"
|
18
|
+
puts library_document.junk_files
|
19
|
+
puts "Files to move:"
|
20
|
+
puts library_document.files_to_move.to_yaml
|
21
|
+
puts "moving"
|
22
|
+
library_document.move_to_library!
|
23
|
+
library_document.save_and_apply_metadata!
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "metadata <file>", "returns all the metadata discoverable about this file or directory"
|
27
|
+
def metadata(path)
|
28
|
+
doc = Document.from_path(path)
|
29
|
+
doc.save_and_apply_metadata!
|
30
|
+
puts "Metadata and image saved to #{doc.extras_path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'mini_subler'
|
3
|
+
require "open-uri"
|
4
|
+
|
5
|
+
module Mediaman
|
6
|
+
|
7
|
+
# Represents an on-disk folder or file representation
|
8
|
+
# of some kind of media.
|
9
|
+
|
10
|
+
class Document
|
11
|
+
|
12
|
+
def self.from_path(path)
|
13
|
+
f = self.new
|
14
|
+
f.path = File.expand_path path
|
15
|
+
f
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :path
|
19
|
+
|
20
|
+
def rebase!(path)
|
21
|
+
self.path = path
|
22
|
+
@primary_video_file = nil
|
23
|
+
@junk_files = nil
|
24
|
+
@video_files = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def save_and_apply_metadata!
|
28
|
+
extract_metadata!
|
29
|
+
save_sidecar!
|
30
|
+
download_image!
|
31
|
+
add_metadata_to_file!
|
32
|
+
# Rewrap mkv to mp4.
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_metadata_to_file!
|
36
|
+
h = metadata.to_subler_hash
|
37
|
+
h[:artwork] = artwork_path if File.exists?(artwork_path)
|
38
|
+
puts "Primary: #{primary_video_file}"
|
39
|
+
puts h.to_s
|
40
|
+
MiniSubler::Command.vendored.set_metadata primary_video_file, h
|
41
|
+
rescue
|
42
|
+
puts "Exception while adding metadata to file."
|
43
|
+
end
|
44
|
+
|
45
|
+
def download_image!
|
46
|
+
if !File.exists?(artwork_path) && metadata.canonical_image_url.present?
|
47
|
+
FileUtils.mkdir_p(File.dirname(artwork_path))
|
48
|
+
open(metadata.canonical_image_url) {|f|
|
49
|
+
File.open(artwork_path, "wb") do |file|
|
50
|
+
file.puts f.read
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def artwork_path
|
57
|
+
extra_path "Artwork#{metadata.canonical_image_url.present? ? File.extname(metadata.canonical_image_url) : ".jpg"}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def metadata
|
61
|
+
@metadata ||= begin
|
62
|
+
metadata = {}
|
63
|
+
metadata.merge!(local_metadata || {})
|
64
|
+
metadata.merge!(remote_metadata || {})
|
65
|
+
metadata.reject!{|k, v| v.blank?}
|
66
|
+
Metadata.new(metadata)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias_method :extract_metadata!, :metadata
|
70
|
+
|
71
|
+
def local_metadata
|
72
|
+
@local_metadata ||= begin
|
73
|
+
metadata = {}
|
74
|
+
metadata.merge!(filename_metadata || {})
|
75
|
+
metadata.merge!(video_metadata || {})
|
76
|
+
metadata.merge!(sidecar_metadata || {})
|
77
|
+
metadata.reject!{|k, v| v.blank?}
|
78
|
+
Metadata.new(metadata)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def local_metadata_fetched?
|
83
|
+
@local_metadata.present?
|
84
|
+
end
|
85
|
+
|
86
|
+
def remote_metadata
|
87
|
+
@remote_metadata ||= begin
|
88
|
+
if local_metadata['movie']
|
89
|
+
Metadata.new({'movie_details' => Trakt::Movie.new(title: local_metadata['name'], year: local_metadata['year'])})
|
90
|
+
elsif local_metadata['tv']
|
91
|
+
tv = Trakt::TVEpisode.new show_title: local_metadata['name'], season_number: local_metadata['season_number'], episode_number: local_metadata['episode_number']
|
92
|
+
Metadata.new({'episode_details' => tv.to_hash, 'show_details' => tv.show.to_hash})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def filename
|
98
|
+
File.basename(self.path, '.*')
|
99
|
+
end
|
100
|
+
|
101
|
+
def filename_metadata
|
102
|
+
@filename_metadata ||= MediaFilename.new(self.filename).to_hash.try(:stringify_keys)
|
103
|
+
end
|
104
|
+
|
105
|
+
def video_metadata
|
106
|
+
@video_metadata ||= MiniSubler::Command.vendored.get_metadata(self.video_files.first).try(:stringify_keys)
|
107
|
+
end
|
108
|
+
|
109
|
+
def standalone_extras_path
|
110
|
+
File.join(File.dirname(self.path), File.basename(self.path, '.*') + " Extras")
|
111
|
+
end
|
112
|
+
|
113
|
+
def library_extras_path
|
114
|
+
File.join(File.dirname(self.path), "Extras", File.basename(self.path, '.*'))
|
115
|
+
end
|
116
|
+
|
117
|
+
def extras_paths
|
118
|
+
[library_extras_path, standalone_extras_path]
|
119
|
+
end
|
120
|
+
|
121
|
+
def extras_path
|
122
|
+
d = nil
|
123
|
+
for path in extras_paths.uniq
|
124
|
+
d = path if File.exists?(path)
|
125
|
+
end
|
126
|
+
d = extras_paths.last if d.nil?
|
127
|
+
d
|
128
|
+
end
|
129
|
+
|
130
|
+
def extra_path(file)
|
131
|
+
File.join extras_path, file
|
132
|
+
end
|
133
|
+
|
134
|
+
def extra_paths(file)
|
135
|
+
extras_paths.uniq.map do |x|
|
136
|
+
File.join x, file
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def sidecar_metadata
|
141
|
+
@sidecar_metadata ||= begin
|
142
|
+
y = {}
|
143
|
+
extra_paths("Metadata.yml").each do |path|
|
144
|
+
if File.exists?(path)
|
145
|
+
y.merge! YAML::load(File.open(path))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
y.stringify_keys
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def save_sidecar!(path = extra_path("Metadata.yml"))
|
153
|
+
FileUtils.mkdir_p(File.dirname(path))
|
154
|
+
File.open(path, 'w') {|f| f.write(self.metadata.stringify_keys.to_yaml) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def video_files
|
158
|
+
sort_junk! unless @video_files
|
159
|
+
@video_files
|
160
|
+
end
|
161
|
+
|
162
|
+
def secondary_video_files
|
163
|
+
if video_files && primary_video_file
|
164
|
+
v = video_files.dup
|
165
|
+
v.delete primary_video_file
|
166
|
+
v
|
167
|
+
else
|
168
|
+
[]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def primary_video_file
|
173
|
+
sort_junk! unless @video_files
|
174
|
+
@primary_video_file ||= @video_files.sort{|a, b| File.size?(a) <=> File.size?(b) }.first
|
175
|
+
end
|
176
|
+
|
177
|
+
def junk_files
|
178
|
+
sort_junk! unless @junk_files
|
179
|
+
@junk_files
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
def sort_junk!
|
185
|
+
junk_files = []
|
186
|
+
video_files = []
|
187
|
+
if File.directory?(self.path)
|
188
|
+
Dir.glob File.join(self.path, "*") do |file|
|
189
|
+
filepath = file
|
190
|
+
case File.extname(file)[1..-1]
|
191
|
+
when /mov|mp4|m4v|avi|wmv|mkv/i then
|
192
|
+
video_files << filepath
|
193
|
+
else
|
194
|
+
junk_files << filepath
|
195
|
+
end
|
196
|
+
end
|
197
|
+
else
|
198
|
+
video_files << self.path
|
199
|
+
end
|
200
|
+
@video_files = video_files
|
201
|
+
@junk_files = junk_files
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Mediaman
|
2
|
+
|
3
|
+
# Represents an on-disk folder or file representation
|
4
|
+
# of some kind of media.
|
5
|
+
#
|
6
|
+
# This type is part of a "library" folder strucure.
|
7
|
+
|
8
|
+
class LibraryDocument < Document
|
9
|
+
|
10
|
+
attr_accessor :library_path
|
11
|
+
|
12
|
+
def move_to_library!
|
13
|
+
files_to_move.each do |original_file, new_file|
|
14
|
+
FileUtils.mkdir_p(File.dirname(new_file))
|
15
|
+
FileUtils.move(original_file, new_file)
|
16
|
+
end
|
17
|
+
rebase! library_file_path
|
18
|
+
FileUtils.mkdir_p library_extras_path
|
19
|
+
end
|
20
|
+
|
21
|
+
def files_to_move
|
22
|
+
files = {}
|
23
|
+
files[primary_video_file] = library_file_path if primary_video_file
|
24
|
+
for file in junk_files + secondary_video_files
|
25
|
+
files[file] = File.join desired_library_junk_path, File.basename(file)
|
26
|
+
end
|
27
|
+
files
|
28
|
+
end
|
29
|
+
|
30
|
+
def library_filename
|
31
|
+
if metadata.tv? && metadata.canonical_show_name
|
32
|
+
s = "#{metadata.canonical_show_name}/Season #{metadata.season_number}/#{metadata.episode_id}"
|
33
|
+
s << " - #{metadata.canonical_episode_name}" if metadata.canonical_episode_name.present?
|
34
|
+
s
|
35
|
+
elsif metadata.year.present?
|
36
|
+
"#{metadata.canonical_movie_title.gsub(":", " - ")} (#{metadata.year})"
|
37
|
+
else
|
38
|
+
metadata.name
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def library_versioned_filename(n = 1)
|
43
|
+
extension = File.extname(primary_video_file)[1..-1]
|
44
|
+
old_filename = File.basename(primary_video_file, '.*')
|
45
|
+
v = ""
|
46
|
+
v = " v#{n}" if n > 1
|
47
|
+
if library_filename.present?
|
48
|
+
"#{library_filename}#{v}.#{extension}"
|
49
|
+
else
|
50
|
+
"#{old_filename}#{v}.#{extension}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def library_file_path
|
55
|
+
@library_file_path ||= begin
|
56
|
+
base_path = File.join File.expand_path(library_path), metadata.library_category
|
57
|
+
i = 1
|
58
|
+
dup = true
|
59
|
+
while dup == true
|
60
|
+
path = File.join base_path, library_versioned_filename(i)
|
61
|
+
if File.exists?(path) || File.directory?(path)
|
62
|
+
i = i + 1
|
63
|
+
dup = true
|
64
|
+
else
|
65
|
+
dup = false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def desired_library_extras_path
|
73
|
+
File.join File.dirname(library_file_path), "Extras", File.basename(library_file_path, '.*')
|
74
|
+
end
|
75
|
+
|
76
|
+
def desired_library_junk_path
|
77
|
+
File.join desired_library_extras_path, "Junk"
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Mediaman
|
2
|
+
|
3
|
+
class MediaFilename
|
4
|
+
|
5
|
+
attr_accessor :filename
|
6
|
+
|
7
|
+
def initialize(filename)
|
8
|
+
self.filename = filename
|
9
|
+
parse_name!
|
10
|
+
formatted_name # To init year
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
{
|
15
|
+
filename: self.filename,
|
16
|
+
name: self.formatted_name,
|
17
|
+
raw_name: self.raw_name,
|
18
|
+
year: self.year,
|
19
|
+
tv: self.tv?,
|
20
|
+
movie: self.movie?,
|
21
|
+
season_number: self.season,
|
22
|
+
episode_number: self.episode,
|
23
|
+
hd: self.hd?,
|
24
|
+
hd_format: self.hd_format
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_name!
|
29
|
+
name = self.filename + ".mov"
|
30
|
+
|
31
|
+
# Normal matching
|
32
|
+
normal_matches = name.match(/^([a-zA-Z0-9.]*)\.S([0-9]{1,2})E([0-9]{1,2}).*$/i)
|
33
|
+
if normal_matches
|
34
|
+
@item_name = normal_matches[1]
|
35
|
+
@season = normal_matches[2].to_i
|
36
|
+
@episode = normal_matches[3].to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
unless @item_name && @season && @episode
|
40
|
+
# Mythbusters hack
|
41
|
+
mythbusters_matches = name.match(/(myth.*)([0-9][0-9])([0-9][0-9])/i)
|
42
|
+
if mythbusters_matches && mythbusters_matches[1].downcase.include?("myth")
|
43
|
+
@item_name = "Mythbusters"
|
44
|
+
@season = mythbusters_matches[2].to_i
|
45
|
+
@episode = mythbusters_matches[3].to_i
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def raw_name
|
52
|
+
@item_name || self.filename
|
53
|
+
end
|
54
|
+
|
55
|
+
def formatted_name
|
56
|
+
perioded_name = []
|
57
|
+
|
58
|
+
name =
|
59
|
+
|
60
|
+
name_string = raw_name.gsub("'", "")
|
61
|
+
name_string = raw_name.gsub(";", "")
|
62
|
+
|
63
|
+
splitsies = name_string.split(/[ -.\[\]]/)
|
64
|
+
splitsies.each_index do |i|
|
65
|
+
x = splitsies[i].split(" ")
|
66
|
+
x = x.split("+")
|
67
|
+
x = x.split("_")
|
68
|
+
x = x.split("[")
|
69
|
+
x = x.split("]")
|
70
|
+
splitsies[i] = x
|
71
|
+
end
|
72
|
+
|
73
|
+
name_done = false
|
74
|
+
i = 0
|
75
|
+
for item in splitsies.flatten
|
76
|
+
int = item.to_i
|
77
|
+
if (1900..Time.now.year + 3).cover?(int) && i > 0
|
78
|
+
@year = int
|
79
|
+
name_done = true
|
80
|
+
elsif item == "720p"
|
81
|
+
name_done = true
|
82
|
+
else
|
83
|
+
perioded_name << item unless name_done
|
84
|
+
end
|
85
|
+
i += 1
|
86
|
+
end
|
87
|
+
name = perioded_name.join(" ")
|
88
|
+
|
89
|
+
# Hacks
|
90
|
+
name.gsub! /Extended( Version)/i, ""
|
91
|
+
|
92
|
+
name
|
93
|
+
end
|
94
|
+
|
95
|
+
def title_slug
|
96
|
+
slug = formatted_name.parameterize
|
97
|
+
|
98
|
+
#Hacks
|
99
|
+
slug = "the-newsroom-2012" if slug == "the-newsroom"
|
100
|
+
|
101
|
+
slug
|
102
|
+
end
|
103
|
+
|
104
|
+
def season
|
105
|
+
@season
|
106
|
+
end
|
107
|
+
|
108
|
+
def episode
|
109
|
+
@episode
|
110
|
+
end
|
111
|
+
|
112
|
+
def tv?
|
113
|
+
episode.present? && season.present?
|
114
|
+
end
|
115
|
+
|
116
|
+
def year
|
117
|
+
@year
|
118
|
+
end
|
119
|
+
|
120
|
+
def movie?
|
121
|
+
!tv? && year.present?
|
122
|
+
end
|
123
|
+
|
124
|
+
def hd?
|
125
|
+
self.filename.include?("720p") || self.filename.include?("1080p") || self.filename.include?("HDTV") || self.filename.include?("HDRip")
|
126
|
+
end
|
127
|
+
|
128
|
+
def h264?
|
129
|
+
(self.filename =~ /m4v|mp4|h264|x264/i).present?
|
130
|
+
end
|
131
|
+
|
132
|
+
def hd_format
|
133
|
+
return "SD" unless hd?
|
134
|
+
if self.filename.include?("720p")
|
135
|
+
"720p"
|
136
|
+
elsif self.filename.include?("1080p")
|
137
|
+
"1080p"
|
138
|
+
else
|
139
|
+
"HD"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_s
|
144
|
+
formatted_name
|
145
|
+
end
|
146
|
+
|
147
|
+
def debug
|
148
|
+
"name: #{name}, formatted_name: #{formatted_name}, slug: #{slug}, season: #{season}, episode: #{episode}, hd?: #{hd?}, tv?: #{tv?}, string: #{string}"
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module Mediaman
|
5
|
+
|
6
|
+
class Metadata < Hashie::Mash
|
7
|
+
|
8
|
+
def canonical_show_name
|
9
|
+
show_details.try(:title).presence || name.presence || raw_name.presence
|
10
|
+
end
|
11
|
+
|
12
|
+
def canonical_episode_name
|
13
|
+
episode_details.title.presence
|
14
|
+
end
|
15
|
+
|
16
|
+
def canonical_movie_title
|
17
|
+
movie_details.try(:title).presence || name.presence || raw_name.presence
|
18
|
+
end
|
19
|
+
|
20
|
+
def episode_id
|
21
|
+
"#{season_number}x#{"%02d" % episode_number}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def library_category
|
25
|
+
return "TV Shows" if tv?
|
26
|
+
return "Movies" if movie?
|
27
|
+
"Other Media"
|
28
|
+
end
|
29
|
+
|
30
|
+
def canonical_image_url
|
31
|
+
if tv?
|
32
|
+
episode_details.try(:[], "images").try(:first).try(:last)
|
33
|
+
else
|
34
|
+
movie_details.try(:[], "poster").presence
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def canonical_description
|
39
|
+
if tv?
|
40
|
+
episode_details.try(:[], "overview").presence
|
41
|
+
else
|
42
|
+
movie_details.try(:[], "overview").presence
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def canonical_director
|
47
|
+
if movie?
|
48
|
+
d = movie_details.try(:[], "people").try(:[], "directors").presence
|
49
|
+
d.map{|director| director.name}.join ", "
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def canonical_rating
|
54
|
+
movie_details.try(:[], "certification").presence
|
55
|
+
end
|
56
|
+
|
57
|
+
def canonical_year
|
58
|
+
movie_details.try(:[], "year").presence || year.presence
|
59
|
+
end
|
60
|
+
|
61
|
+
def uuid
|
62
|
+
movie_details.try(:[], "imdb_id") || Digest::MD5.hexdigest("#{filename}")
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_subler_hash
|
66
|
+
h = {}
|
67
|
+
if movie?
|
68
|
+
h[:name] = canonical_movie_title
|
69
|
+
h[:media_kind] = "Movie"
|
70
|
+
h[:rating] = canonical_rating if canonical_rating
|
71
|
+
h[:year] = canonical_year if canonical_year
|
72
|
+
end
|
73
|
+
if tv?
|
74
|
+
h[:name] = canonical_episode_name
|
75
|
+
h[:tv_show] = canonical_show_name
|
76
|
+
h[:album] = canonical_show_name
|
77
|
+
h[:tv_episode_id] = episode_id
|
78
|
+
h[:tv_episode_number] = episode_number
|
79
|
+
h[:tv_season] = season_number
|
80
|
+
h[:track_number] = "#{episode_number}/0"
|
81
|
+
h[:media_kind] = "TV Show"
|
82
|
+
end
|
83
|
+
h[:description] = canonical_description if canonical_description.present?
|
84
|
+
h[:hd_video] = true if hd?
|
85
|
+
h
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Mediaman
|
2
|
+
|
3
|
+
class TemporaryDocument < Document
|
4
|
+
|
5
|
+
def self.from_name(name)
|
6
|
+
f = self.new
|
7
|
+
f.path = name
|
8
|
+
f
|
9
|
+
end
|
10
|
+
|
11
|
+
def metadata
|
12
|
+
@metadata ||= begin
|
13
|
+
metadata = {}
|
14
|
+
metadata.merge!(filename_metadata || {})
|
15
|
+
metadata.merge!(remote_metadata || {})
|
16
|
+
metadata.reject!{|k, v| v.blank?}
|
17
|
+
Metadata.new(metadata)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module Mediaman
|
4
|
+
|
5
|
+
module Trakt
|
6
|
+
|
7
|
+
attr_accessor :api_key
|
8
|
+
def self.api_key=(key)
|
9
|
+
@@api_key = key
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.api_key
|
13
|
+
defined?(@@api_key) ? @@api_key : ENV['TRAKT_API_KEY']
|
14
|
+
end
|
15
|
+
|
16
|
+
class Fetcher < Hash
|
17
|
+
include HTTParty
|
18
|
+
format :json
|
19
|
+
|
20
|
+
def self.from_hash(hash = {})
|
21
|
+
f = self.new()
|
22
|
+
f.merge!(hash) if hash.present?
|
23
|
+
f
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
self.options = options
|
28
|
+
self.merge!(fetch! || {}) if self.respond_to?(:fetch!)
|
29
|
+
end
|
30
|
+
attr_accessor :options
|
31
|
+
attr_accessor :response
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class Movie < Fetcher
|
36
|
+
|
37
|
+
def slug
|
38
|
+
"#{options[:title].parameterize}-#{options[:year].to_s.parameterize}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch!
|
42
|
+
Movie.get("http://api.trakt.tv/movie/summary.json/#{Trakt.api_key}/#{slug}")
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class TVEpisode < Fetcher
|
48
|
+
|
49
|
+
attr_accessor :show
|
50
|
+
|
51
|
+
def fetch!
|
52
|
+
url = "http://api.trakt.tv/show/episode/summary.json/#{Trakt.api_key}/#{options[:show_title].parameterize}/#{options[:season_number]}/#{options[:episode_number]}"
|
53
|
+
self.response = r = self.class.get(url)
|
54
|
+
self.show = Trakt::TVShow.from_hash self.response['show']
|
55
|
+
self.response['episode']
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class TVShow < Fetcher
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/mediaman.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Dependencies
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
# Helpers
|
5
|
+
require "mediaman/version"
|
6
|
+
require "mediaman/media_filename"
|
7
|
+
require "mediaman/trakt"
|
8
|
+
|
9
|
+
# Models
|
10
|
+
require "mediaman/metadata"
|
11
|
+
require "mediaman/document"
|
12
|
+
require "mediaman/temporary_document"
|
13
|
+
require "mediaman/library_document"
|
data/mediaman.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mediaman/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "mediaman"
|
8
|
+
gem.version = Mediaman::VERSION
|
9
|
+
gem.authors = ["Wil Gieseler"]
|
10
|
+
gem.email = ["supapuerco@gmail.com"]
|
11
|
+
gem.description = "Helps you organize your media!"
|
12
|
+
gem.summary = "Helps you organize your media!"
|
13
|
+
gem.homepage = "http://github.com/supapuerco/mediaman"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'activesupport', '>=3.0.0'
|
21
|
+
gem.add_dependency 'thor'
|
22
|
+
gem.add_dependency 'mini_subler'
|
23
|
+
gem.add_dependency 'httparty'
|
24
|
+
gem.add_dependency 'hashie'
|
25
|
+
|
26
|
+
gem.add_development_dependency "rake"
|
27
|
+
gem.add_development_dependency "rspec"
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mediaman::MediaFilename do
|
4
|
+
|
5
|
+
def parse(filename)
|
6
|
+
Mediaman::MediaFilename.new(filename).to_hash
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "correctly parses" do
|
10
|
+
|
11
|
+
it "The Descendants" do
|
12
|
+
h = parse("The Descendants")
|
13
|
+
h[:name].should == "The Descendants"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "The.Newsroom.S01E01.COOOLNESS" do
|
17
|
+
h = parse("The.Newsroom.S01E01.COOOLNESS")
|
18
|
+
h[:name].should == "The Newsroom"
|
19
|
+
h[:tv].should be_true
|
20
|
+
h[:episode_number].should == 1
|
21
|
+
end
|
22
|
+
|
23
|
+
it "John.Adams.S01E01.720p.HDTV.x264" do
|
24
|
+
h = parse("John.Adams.S01E01.720p.HDTV.x264")
|
25
|
+
h[:name].should == "John Adams"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "John Adams.S01E01.720p.HDTV.x264" do
|
29
|
+
h = parse("John Adams.S01E01.720p.HDTV.x264")
|
30
|
+
h[:name].should == "John Adams"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
end
|
12
|
+
|
13
|
+
require_relative "../lib/mediaman"
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mediaman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wil Gieseler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2012-08-20 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.0.0
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: thor
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: mini_subler
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: httparty
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: hashie
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id005
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rake
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
version_requirements: *id006
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rspec
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: "0"
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id007
|
92
|
+
description: Helps you organize your media!
|
93
|
+
email:
|
94
|
+
- supapuerco@gmail.com
|
95
|
+
executables:
|
96
|
+
- mediaman
|
97
|
+
extensions: []
|
98
|
+
|
99
|
+
extra_rdoc_files: []
|
100
|
+
|
101
|
+
files:
|
102
|
+
- .gitignore
|
103
|
+
- .rspec
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- bin/mediaman
|
109
|
+
- lib/mediaman.rb
|
110
|
+
- lib/mediaman/command.rb
|
111
|
+
- lib/mediaman/document.rb
|
112
|
+
- lib/mediaman/library_document.rb
|
113
|
+
- lib/mediaman/media_filename.rb
|
114
|
+
- lib/mediaman/metadata.rb
|
115
|
+
- lib/mediaman/temporary_document.rb
|
116
|
+
- lib/mediaman/trakt.rb
|
117
|
+
- lib/mediaman/version.rb
|
118
|
+
- mediaman.gemspec
|
119
|
+
- spec/mediaman/media_filename_spec.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
homepage: http://github.com/supapuerco/mediaman
|
122
|
+
licenses: []
|
123
|
+
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: "0"
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.8.15
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Helps you organize your media!
|
148
|
+
test_files:
|
149
|
+
- spec/mediaman/media_filename_spec.rb
|
150
|
+
- spec/spec_helper.rb
|