plexify 0.1.1 → 0.2.0
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/Gemfile +3 -1
- data/README.md +32 -9
- data/Rakefile +2 -0
- data/lib/plexify.rb +13 -9
- data/lib/plexify/execute.rb +155 -145
- data/lib/plexify/logger.rb +14 -14
- data/lib/plexify/media.rb +9 -9
- data/lib/plexify/media_manager.rb +90 -72
- data/lib/plexify/media_organizer.rb +55 -55
- data/lib/plexify/prepare.rb +73 -73
- data/lib/plexify/version.rb +1 -1
- data/plexify.gemspec +3 -1
- data/spec/plexify_spec.rb +13 -0
- data/spec/spec_helper.rb +8 -0
- metadata +26 -6
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Plexify
|
2
2
|
|
3
|
-
|
3
|
+
Plexify is a file organizer tool to manage medias. It analyses files and search for movies in TheMovieDB to have
|
4
|
+
informations about them and create good folder names and add some additional stuffs like posters, desriptions
|
5
|
+
(these two lasts aren't supported yet, but will be ;))
|
4
6
|
|
5
|
-
TODO: Write a gem description
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -18,14 +19,36 @@ Or install it yourself as:
|
|
18
19
|
|
19
20
|
$ gem install plexify
|
20
21
|
|
22
|
+
## Ruby
|
23
|
+
|
24
|
+
Plexify is compatible with
|
25
|
+
|
26
|
+
ruby 1.9.3
|
27
|
+
|
21
28
|
## Usage
|
22
29
|
|
23
|
-
|
30
|
+
Plexify analyses files and move them to an oranized folder, so it creates destination folders and media folders.
|
31
|
+
|
32
|
+
The default usage is:
|
33
|
+
|
34
|
+
$ plexify -s /media/Temp -d /media/PlexMediaFolder
|
35
|
+
|
36
|
+
or more simple
|
37
|
+
|
38
|
+
$ plexify /media/Temp /media/PlexMediaFolder
|
39
|
+
|
40
|
+
If you specify a destination with the -d flag, it will create two folders inside the destination folder named "Movies" and "TV_Shows".
|
41
|
+
|
42
|
+
You can also specify movies and series folders like that:
|
43
|
+
|
44
|
+
$ plexify -s /media/Temp --movies-destination /media/PlexMediaFolder/Movies --series-destination /media/PlexMediaFolder/Series
|
45
|
+
|
46
|
+
If you aren't really sure about the tool, you can either use plexify in test mode like that:
|
47
|
+
|
48
|
+
$ plexify -s /media/Temp -d /media/PlexMediaFolder --test
|
49
|
+
|
50
|
+
It will just print found medias and not move files.
|
24
51
|
|
25
|
-
##
|
52
|
+
## Contributors
|
26
53
|
|
27
|
-
1.
|
28
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
-
5. Create new Pull Request
|
54
|
+
1. Pierre FILSTROFF ([reaper](https://github.com/reaper/))
|
data/Rakefile
CHANGED
data/lib/plexify.rb
CHANGED
@@ -5,19 +5,23 @@ require 'optparse'
|
|
5
5
|
require 'colorize'
|
6
6
|
require 'ruby-tmdb'
|
7
7
|
require 'yaml'
|
8
|
-
require '
|
8
|
+
require 'amatch'
|
9
9
|
|
10
10
|
Dir[File.dirname(__FILE__) + "/plexify/**/*"].each{ |file| require file }
|
11
11
|
|
12
12
|
module Plexify
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
class Plexify
|
14
|
+
def initialize(args)
|
15
|
+
@opts = Execute::Plexify.new(args)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
18
|
+
# Run plexify !
|
19
|
+
def run!
|
20
|
+
@opts.parse!
|
22
21
|
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.version
|
25
|
+
return "Plexify version #{VERSION}"
|
26
|
+
end
|
23
27
|
end
|
data/lib/plexify/execute.rb
CHANGED
@@ -1,170 +1,180 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module Execute
|
3
|
+
class Plexify
|
4
|
+
# Plexify::Execute initialize method
|
5
|
+
def initialize(args)
|
6
|
+
@args = args
|
7
|
+
@options = {}
|
8
|
+
@missing_args = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Parses the command-line arguments and execute files processing
|
12
|
+
def parse!
|
13
|
+
begin
|
14
|
+
@opts = OptionParser.new(&method(:set_opts))
|
15
|
+
@opts.parse!(@args)
|
16
|
+
check_arguments
|
17
|
+
|
18
|
+
if @args.any?
|
19
|
+
@options[:source] = @args[0] unless @options[:source]
|
20
|
+
@options[:destination] = @args[1] if @args[1] && !@options[:destination]
|
21
|
+
end
|
22
|
+
|
23
|
+
process_result
|
24
|
+
|
25
|
+
# Puts missing arguments
|
26
|
+
rescue OptionParser::MissingArgument => e
|
27
|
+
built_string = e.message
|
28
|
+
for arg in @missing_args
|
29
|
+
built_string += arg
|
30
|
+
unless @missing_args.last == arg
|
31
|
+
built_string += ", "
|
32
|
+
else
|
33
|
+
built_string += "\n\n"
|
9
34
|
end
|
35
|
+
end
|
36
|
+
puts built_string
|
37
|
+
puts @opts
|
10
38
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@opts = OptionParser.new(&method(:set_opts))
|
15
|
-
@opts.parse!(@args)
|
16
|
-
check_arguments
|
17
|
-
|
18
|
-
if @args.any?
|
19
|
-
@options[:source] = @args[0] unless @options[:source]
|
20
|
-
@options[:destination] = @args[1] unless @options[:destination]
|
21
|
-
end
|
22
|
-
|
23
|
-
process_result
|
24
|
-
|
25
|
-
# Puts missing arguments
|
26
|
-
rescue OptionParser::MissingArgument => e
|
27
|
-
built_string = e.message
|
28
|
-
for arg in @missing_args
|
29
|
-
built_string += arg
|
30
|
-
unless @missing_args.last == arg
|
31
|
-
built_string += ", "
|
32
|
-
else
|
33
|
-
built_string += "\n\n"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
puts built_string
|
37
|
-
puts @opts
|
38
|
-
|
39
|
-
# Puts exception message
|
40
|
-
rescue Exception => e
|
41
|
-
raise e if @options[:trace] || e.is_a?(SystemExit)
|
42
|
-
|
43
|
-
print "\n#{e.class}: " unless e.class == RuntimeError
|
44
|
-
puts "#{e.message}"
|
45
|
-
puts " Use --trace for backtrace."
|
46
|
-
end
|
47
|
-
exit 0
|
48
|
-
end
|
39
|
+
# Puts exception message
|
40
|
+
rescue Exception => e
|
41
|
+
raise e if @options[:trace] || e.is_a?(SystemExit)
|
49
42
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
43
|
+
print "\n#{e.class}: " unless e.class == RuntimeError
|
44
|
+
puts "#{e.message}"
|
45
|
+
puts " Use --trace for backtrace."
|
46
|
+
end
|
47
|
+
exit 0
|
48
|
+
end
|
54
49
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
50
|
+
private
|
51
|
+
# Check missing arguments
|
52
|
+
def check_arguments
|
53
|
+
return if @args.size == 2
|
59
54
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
55
|
+
@missing_args << "-s [source]" if !@options[:source] && !@args[0]
|
56
|
+
@missing_args << "-d [destination]" if !@options[:test] && !@options[:destination] && !@options[:movies_destination] && !@options[:series_destination]
|
57
|
+
@missing_args << "--movies-destination [destination]" if !@options[:test] && !@options[:destination] && !@options[:movies_destination] && !@options[:series_destination]
|
58
|
+
@missing_args << "--series-destination [destination]" if !@options[:test] && !@options[:destination] && !@options[:series_destination] && !@options[:movies_destination]
|
64
59
|
|
65
|
-
|
66
|
-
|
67
|
-
|
60
|
+
if @missing_args.any?
|
61
|
+
raise OptionParser::MissingArgument
|
62
|
+
end
|
63
|
+
end
|
68
64
|
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
# Set options definition for command-line execution
|
66
|
+
def set_opts(opts)
|
67
|
+
opts.banner = "Usage: plexify -s /media/temp --movies-destination /media/Movies --series-destination /media/Series\n\n"
|
72
68
|
|
73
|
-
|
74
|
-
|
75
|
-
|
69
|
+
opts.on('-s [source]', '--source [source]', 'Source folder which includes media files. Temp folder.') do |source|
|
70
|
+
@options[:source] = source
|
71
|
+
end
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
opts.on('-d [destination]', '--destination [dest]', 'Destination folder to put organized.') do |destination|
|
74
|
+
@options[:destination] = destination
|
75
|
+
end
|
80
76
|
|
81
|
-
|
82
|
-
|
83
|
-
|
77
|
+
opts.on('--movies-destination [dest]', 'Destination folder to put organized movies files.') do |movies_destination|
|
78
|
+
@options[:movies_destination] = movies_destination
|
79
|
+
end
|
84
80
|
|
85
|
-
|
86
|
-
|
87
|
-
|
81
|
+
opts.on('--series-destination [dest]', 'Destination folder to put organized series files.') do |series_destination|
|
82
|
+
@options[:series_destination] = series_destination
|
83
|
+
end
|
88
84
|
|
89
|
-
|
90
|
-
|
91
|
-
|
85
|
+
opts.on_tail('-t', '--test', 'Test files name searching in media databases: only print results.') do
|
86
|
+
@options[:test] = true
|
87
|
+
end
|
92
88
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
89
|
+
opts.on_tail('--accept-removing-all', :NONE, 'Accept removing source files. [NOT WORKING YET]') do
|
90
|
+
@options[:accept_removing_all] = true
|
91
|
+
end
|
97
92
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
93
|
+
opts.on_tail("-?", "-h", "--help", "Show this message.") do
|
94
|
+
puts opts
|
95
|
+
exit
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on_tail("-v", "--version", "Print version") do
|
99
|
+
puts("Plexify #{VERSION}")
|
100
|
+
exit
|
101
|
+
end
|
107
102
|
|
108
|
-
|
109
|
-
|
103
|
+
opts.on_tail('--trace', :NONE, 'Show a full traceback on error') do
|
104
|
+
@options[:trace] = true
|
105
|
+
end
|
106
|
+
end
|
110
107
|
|
111
|
-
|
112
|
-
|
113
|
-
source.prepare
|
114
|
-
Logger.info(" : #{source.files.size} #{source.files.size > 1 ? 'files' : 'file'} found")
|
108
|
+
# Process result
|
109
|
+
def process_result
|
115
110
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
111
|
+
Logger.info("=> Preparing source folder")
|
112
|
+
source = Prepare::Source.new(@options)
|
113
|
+
source.prepare
|
114
|
+
Logger.info(" : #{source.files.size} #{source.files.size > 1 ? 'files' : 'file'} found")
|
115
|
+
|
116
|
+
unless @options[:test]
|
117
|
+
Logger.info("\n=> Preparing destination folder")
|
118
|
+
destination = Prepare::Destination.new(@options)
|
119
|
+
destination.prepare
|
120
|
+
end
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
files_processed_count += 1
|
156
|
-
elsif resp == -1
|
157
|
-
Logger.info("Aborting...")
|
158
|
-
next
|
159
|
-
end
|
160
|
-
end
|
161
|
-
else
|
162
|
-
Logger.error("\nMedia not found")
|
163
|
-
end
|
122
|
+
media_manager = MediaManager.new
|
123
|
+
media_organizer = MediaOrganizer.new(source, destination) unless @options[:test]
|
124
|
+
|
125
|
+
files_processed_count = 0
|
126
|
+
|
127
|
+
# Process files
|
128
|
+
for file in source.files
|
129
|
+
basename = File.basename(file.path)
|
130
|
+
Logger.info("\n\n=> Analyzing '#{basename}' file...")
|
131
|
+
medias = media_manager.analyze(file)
|
132
|
+
|
133
|
+
# Choose the good media
|
134
|
+
if medias.any?
|
135
|
+
|
136
|
+
# If the test argument is present, don't move files or prompt the user
|
137
|
+
if @options[:test]
|
138
|
+
Logger.info("\nFound medias :")
|
139
|
+
|
140
|
+
for media in medias
|
141
|
+
Logger.success("\n#{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
|
142
|
+
end
|
143
|
+
else
|
144
|
+
Logger.success("\nThe movie is '#{medias.first.data.name} #{medias.first.data.released ? "(#{Date.parse(medias.first.data.released).year})" : nil}'. Right ? (y/n) ")
|
145
|
+
resp = STDIN.gets.chomp
|
146
|
+
|
147
|
+
if resp.eql?("y")
|
148
|
+
|
149
|
+
media_organizer.move_to_destination(file, medias.first)
|
150
|
+
files_processed_count += 1
|
151
|
+
else
|
152
|
+
Logger.success("\n0: The movie isn't in the list, go next without doing anything.")
|
153
|
+
medias.each_with_index do |media, index|
|
154
|
+
Logger.success("\n#{index+1}: #{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
|
164
155
|
end
|
165
156
|
|
166
|
-
Logger.
|
157
|
+
Logger.success("\nChoose the movie: ")
|
158
|
+
resp = STDIN.gets.chomp.to_i-1
|
159
|
+
|
160
|
+
if resp >= 0 && resp <= medias.size
|
161
|
+
media = medias[resp]
|
162
|
+
Logger.success("\nMedia found : #{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
|
163
|
+
media_organizer.move_to_destination(file, media)
|
164
|
+
files_processed_count += 1
|
165
|
+
elsif resp == -1
|
166
|
+
Logger.info("Aborting...")
|
167
|
+
next
|
168
|
+
end
|
169
|
+
end
|
167
170
|
end
|
171
|
+
else
|
172
|
+
Logger.error("\nMedia not found")
|
173
|
+
end
|
168
174
|
end
|
175
|
+
|
176
|
+
Logger.info("\n\n=> Files pocessed #{files_processed_count}/#{source.files.size}") unless @options[:test]
|
177
|
+
end
|
169
178
|
end
|
179
|
+
end
|
170
180
|
end
|
data/lib/plexify/logger.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
# Logger class
|
3
|
+
class Logger
|
4
|
+
# print informations
|
5
|
+
def self.info(content)
|
6
|
+
print content
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
# print success in green
|
10
|
+
def self.success(content)
|
11
|
+
print content.colorize(:green)
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
14
|
+
# print errors in red
|
15
|
+
def self.error(content)
|
16
|
+
print content.colorize(:red)
|
18
17
|
end
|
18
|
+
end
|
19
19
|
end
|
data/lib/plexify/media.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
# Media class
|
3
|
+
# Will include more methods to test media data
|
4
|
+
class Media
|
5
|
+
attr_accessor :data
|
6
|
+
attr_accessor :found_title
|
7
|
+
attr_accessor :score
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
9
|
+
def initialize(data)
|
10
|
+
@data = data
|
12
11
|
end
|
12
|
+
end
|
13
13
|
end
|
@@ -1,88 +1,106 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
def initialize
|
5
|
-
@tmdb_api_key = nil
|
6
|
-
@tmdb_default_language = nil
|
7
|
-
load_tmdb
|
8
|
-
end
|
9
|
-
|
10
|
-
# Search for a media in the Tmdb media manager database.
|
11
|
-
def analyze(file)
|
12
|
-
name = File.basename(file.path, File.extname(file.path))
|
13
|
-
splitted_name = name.split(/\W/).map(&:downcase)
|
2
|
+
class MediaManager
|
3
|
+
include Amatch
|
14
4
|
|
15
|
-
|
5
|
+
# Initialize variables and load tmdb media manager
|
6
|
+
def initialize
|
7
|
+
@tmdb_api_key = nil
|
8
|
+
@tmdb_default_language = nil
|
9
|
+
load_tmdb
|
10
|
+
end
|
16
11
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
flattened_medias << media
|
23
|
-
end
|
24
|
-
end
|
12
|
+
# Search for a media in the Tmdb media manager database.
|
13
|
+
def analyze(file)
|
14
|
+
name = File.basename(file.path, File.extname(file.path))
|
15
|
+
splitted_name = name.split(/\W/).map(&:downcase)
|
16
|
+
found_medias = analyze_names(splitted_name, {})
|
25
17
|
|
26
|
-
|
18
|
+
medias = Array.new
|
19
|
+
found_medias.each_pair do |title, list|
|
20
|
+
for media in list
|
21
|
+
medias << media if medias.empty?
|
22
|
+
unless medias.map(&:data).map(&:id).include?(media.data.id)
|
23
|
+
media.found_title = title
|
24
|
+
medias << media
|
25
|
+
end
|
27
26
|
end
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
# Check differences betweend the file title
|
30
|
+
# and found medias names
|
31
|
+
title = name.gsub(/\W/, " ").downcase
|
32
|
+
for media in medias
|
33
|
+
name = media.data.name.downcase
|
34
|
+
media.score = title.hamming_similar(name)
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
config_file_path = File.join(File.dirname(__FILE__), "../../.plexify.yml")
|
45
|
-
config_file = File.open config_file_path
|
46
|
-
yaml_config = YAML::load(config_file)
|
47
|
-
|
48
|
-
tmdb_config = yaml_config["plexify"]["tmdb"]
|
49
|
-
@tmdb_api_key = tmdb_config["api_key"]
|
50
|
-
@tmdb_default_language = tmdb_config["default_language"]
|
51
|
-
end
|
37
|
+
# Compare title with original media name
|
38
|
+
# if the score is better, it selects it
|
39
|
+
for media in medias
|
40
|
+
name = media.data.original_name.downcase
|
41
|
+
score = title.hamming_similar(name)
|
42
|
+
media.score = score if score > media.score
|
43
|
+
end
|
52
44
|
|
53
|
-
|
54
|
-
|
45
|
+
# Return ten last medias sort by score
|
46
|
+
return medias.sort_by(&:score).reverse.delete_if {|m| medias.index(m) > 10 }
|
47
|
+
end
|
55
48
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
49
|
+
private
|
50
|
+
# Load tmdb media manager
|
51
|
+
def load_tmdb
|
52
|
+
load_plexify_config
|
53
|
+
Tmdb.api_key = @tmdb_api_key
|
54
|
+
Tmdb.default_language = @tmdb_default_language
|
55
|
+
end
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
57
|
+
# Load plexify config yaml file in the home directory
|
58
|
+
# The file structure is :
|
59
|
+
# plexify:
|
60
|
+
# tmdb:
|
61
|
+
# api_key: xxxx
|
62
|
+
# default_language: en
|
63
|
+
def load_plexify_config
|
64
|
+
config_file_path = File.join(File.dirname(__FILE__), "../../.plexify.yml")
|
65
|
+
config_file = File.open config_file_path
|
66
|
+
yaml_config = YAML::load(config_file)
|
72
67
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
68
|
+
tmdb_config = yaml_config["plexify"]["tmdb"]
|
69
|
+
@tmdb_api_key = tmdb_config["api_key"]
|
70
|
+
@tmdb_default_language = tmdb_config["default_language"]
|
71
|
+
end
|
77
72
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
73
|
+
# Analyze title name: iterates each words and find a related media
|
74
|
+
def analyze_names(list, medias)
|
75
|
+
title = String.new
|
76
|
+
|
77
|
+
list.each_with_index do |str, index|
|
78
|
+
title << " " if title.length > 0
|
79
|
+
title << str
|
80
|
+
found_medias = find(title)
|
82
81
|
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
if found_medias.empty?
|
83
|
+
if medias.any?
|
84
|
+
shift = list.shift
|
85
|
+
analyze_names(list, medias)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
tmp_medias = Array.new
|
89
|
+
found_medias.each { |m| tmp_medias << Media.new(m) }
|
90
|
+
medias[title] = tmp_medias
|
86
91
|
end
|
92
|
+
end
|
93
|
+
return medias
|
94
|
+
end
|
95
|
+
|
96
|
+
# Find a movie in the Tmdb database with a title and a limit of one item
|
97
|
+
def find(title)
|
98
|
+
TmdbMovie.find(:title => title, :limit => 5)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Find all movies in the Tmdb database with a title
|
102
|
+
def find_all(title)
|
103
|
+
TmdbMovie.find(:title => title)
|
87
104
|
end
|
105
|
+
end
|
88
106
|
end
|
@@ -1,71 +1,71 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
# Class MediaOrganizer
|
3
|
+
# It manage source and destination folders
|
4
|
+
class MediaOrganizer
|
5
|
+
attr_reader :source
|
6
|
+
attr_reader :destination
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
# Initialize
|
9
|
+
def initialize(source, destination)
|
10
|
+
@source = source
|
11
|
+
@destination = destination
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
# Move a file the his appropriate destination folder
|
15
|
+
def move_to_destination(file, media)
|
16
|
+
# Consider media is a movie
|
17
|
+
destination = @destination.movies_folder.path
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# Create an appropriate media folder
|
20
|
+
media_folder_name = "#{media.data.name} (#{Date.parse(media.data.released).year})"
|
21
|
+
media_folder_path = File.join(destination, media_folder_name)
|
22
|
+
file_basename = File.basename(file.path)
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Create the movie folder
|
25
|
+
FileUtils.mkdir(media_folder_path) unless File.exists?(media_folder_path)
|
26
|
+
media_folder = File.open(media_folder_path)
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
if media_folder
|
29
|
+
renamed_file_basename = media_folder_name + File.extname(file_basename)
|
30
|
+
renamed_file_path = File.join(media_folder.path, renamed_file_basename)
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
# Copy the file with a progress bar
|
33
|
+
Logger.info("Copying file to '#{renamed_file_path}'")
|
34
|
+
copy_with_progressbar(file.path, renamed_file_path)
|
35
|
+
Logger.success("File '#{renamed_file_path}' copied") if File.exists?(renamed_file_path)
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
Logger.info("\nDo you want to remove the source file '#{file_basename}' ? (y/n) ")
|
39
|
+
resp = STDIN.gets.chomp
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
if resp.eql?("y")
|
42
|
+
FileUtils.rm file.path
|
43
|
+
Logger.success("File '#{file_basename}' removed")
|
44
|
+
end
|
45
|
+
end
|
46
46
|
|
47
|
-
|
47
|
+
private
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
# Copy a file with a great progress bar
|
50
|
+
def copy_with_progressbar(source, destination)
|
51
|
+
source_file = File.new(source, "r")
|
52
|
+
destination_file = File.new(destination, "w")
|
53
|
+
source_size = File.size(source)
|
54
|
+
batch_bytes = ( source_size / 100 ).ceil
|
55
|
+
total = 0
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
p_bar = ProgressBar.create(:title => "Copying '#{File.basename(destination_file.path)}'", :format => '|%b>%i| %p%% %t')
|
58
|
+
buffer = source_file.sysread(batch_bytes)
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
buffer = source_file.sysread(batch_bytes)
|
68
|
-
end
|
60
|
+
while total < source_size do
|
61
|
+
destination_file.syswrite(buffer)
|
62
|
+
p_bar.progress = ((total*100)/source_size).ceil + 1
|
63
|
+
total += batch_bytes
|
64
|
+
if (source_size - total) < batch_bytes
|
65
|
+
batch_bytes = (source_size - total)
|
69
66
|
end
|
67
|
+
buffer = source_file.sysread(batch_bytes)
|
68
|
+
end
|
70
69
|
end
|
70
|
+
end
|
71
71
|
end
|
data/lib/plexify/prepare.rb
CHANGED
@@ -1,91 +1,91 @@
|
|
1
1
|
module Plexify
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
module Prepare
|
3
|
+
# Destination class implementation
|
4
|
+
class Destination
|
5
|
+
attr_accessor :folder
|
6
|
+
attr_accessor :movies_folder
|
7
|
+
attr_accessor :series_folder
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
# Plexify::Execute initialize method
|
10
|
+
def initialize(options)
|
11
|
+
@destination_path = options[:destination]
|
12
|
+
@movies_destination_path = options[:movies_destination]
|
13
|
+
@series_destination_path = options[:series_destination]
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
protected
|
25
|
-
# Prepare destination
|
26
|
-
def prepare_destination
|
27
|
-
# Create destination folder
|
28
|
-
destination_path = @destination_path
|
29
|
-
@folder = create_or_use_folder(destination_path)
|
16
|
+
def prepare
|
17
|
+
if @destination_path
|
18
|
+
prepare_destination
|
19
|
+
else
|
20
|
+
prepare_destinations
|
21
|
+
end
|
22
|
+
end
|
30
23
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
protected
|
25
|
+
# Prepare destination
|
26
|
+
def prepare_destination
|
27
|
+
# Create destination folder
|
28
|
+
destination_path = @destination_path
|
29
|
+
@folder = create_or_use_folder(destination_path)
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
series_path = @series_destination_path
|
31
|
+
# Create "Movies" and "TV Shows" folder
|
32
|
+
@movies_folder = create_or_use_folder(destination_path, "Movies")
|
33
|
+
@series_folder = create_or_use_folder(destination_path, "TV Shows")
|
34
|
+
end
|
41
35
|
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
# Prepare destinations
|
37
|
+
def prepare_destinations
|
38
|
+
# Create or use movies and series folders
|
39
|
+
movies_path = @movies_destination_path
|
40
|
+
series_path = @series_destination_path
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
|
42
|
+
@movies_folder = create_or_use_folder(movies_path) if movies_path
|
43
|
+
@series_folder = create_or_use_folder(series_path) if series_path
|
44
|
+
end
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
# Create or use a folder
|
47
|
+
def create_or_use_folder(*args)
|
48
|
+
path = args.first
|
53
49
|
|
54
|
-
|
50
|
+
if args.size > 1
|
51
|
+
name = args[1]
|
52
|
+
end
|
55
53
|
|
56
|
-
|
57
|
-
folder = File.open(folder_path)
|
58
|
-
else
|
59
|
-
Logger.info("\n=> Created missing '#{name ? name : File.basename(folder_path)}' folder in destination folder.")
|
60
|
-
FileUtils.mkdir_p folder_path
|
61
|
-
folder = File.open(folder_path) if File.exists?(folder_path)
|
62
|
-
end
|
54
|
+
folder_path = name ? File.join(path, name) : path
|
63
55
|
|
64
|
-
|
65
|
-
|
56
|
+
if File.exists?(folder_path) && File.directory?(folder_path)
|
57
|
+
folder = File.open(folder_path)
|
58
|
+
else
|
59
|
+
Logger.info("\n=> Created missing '#{name ? name : File.basename(folder_path)}' folder in destination folder.")
|
60
|
+
FileUtils.mkdir_p folder_path
|
61
|
+
folder = File.open(folder_path) if File.exists?(folder_path)
|
66
62
|
end
|
67
63
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
64
|
+
folder
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Source class implementation
|
69
|
+
class Source
|
70
|
+
attr_accessor :folder
|
71
|
+
attr_accessor :files
|
72
|
+
|
73
|
+
# Initialize
|
74
|
+
def initialize(options)
|
75
|
+
@source_path = options[:source]
|
76
|
+
@files = Array.new
|
77
|
+
end
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
# Prepare source folder
|
80
|
+
def prepare
|
81
|
+
@folder = File.new @source_path
|
82
82
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
end
|
83
|
+
if File.exists?(@folder.path) && File.directory?(@folder.path)
|
84
|
+
Dir.glob(@folder.path + "/**/*.{mkv,avi,mov}") do |file|
|
85
|
+
@files << File.open(file)
|
86
|
+
end
|
89
87
|
end
|
88
|
+
end
|
90
89
|
end
|
90
|
+
end
|
91
91
|
end
|
data/lib/plexify/version.rb
CHANGED
data/plexify.gemspec
CHANGED
@@ -14,9 +14,11 @@ Gem::Specification.new do |gem|
|
|
14
14
|
|
15
15
|
gem.add_dependency("ruby-tmdb", "= 0.2.1")
|
16
16
|
gem.add_dependency("colorize", "= 0.5.8")
|
17
|
-
gem.add_dependency("
|
17
|
+
gem.add_dependency("amatch", "= 0.2.10")
|
18
18
|
gem.add_dependency("ruby-progressbar", "= 1.0.2")
|
19
19
|
|
20
|
+
gem.add_development_dependency 'rspec', '~> 2.12.0'
|
21
|
+
|
20
22
|
gem.files = `git ls-files`.split($/)
|
21
23
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
24
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Plexify, "#Version" do
|
4
|
+
it 'should return correct version string' do
|
5
|
+
Plexify.version.should == "Plexify version #{Plexify::VERSION}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Plexify, "#Arguments" do
|
10
|
+
it 'should raise an error by passing bad arguments count' do
|
11
|
+
#TODO
|
12
|
+
end
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plexify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-tmdb
|
@@ -44,13 +44,13 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 0.5.8
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: amatch
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
51
51
|
- - '='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
53
|
+
version: 0.2.10
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.2.10
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: ruby-progressbar
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +75,22 @@ dependencies:
|
|
75
75
|
- - '='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: 1.0.2
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.12.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.12.0
|
78
94
|
description: Organize media folders to get ready with media managers
|
79
95
|
email:
|
80
96
|
- pfilstroff@gmail.com
|
@@ -99,6 +115,8 @@ files:
|
|
99
115
|
- lib/plexify/prepare.rb
|
100
116
|
- lib/plexify/version.rb
|
101
117
|
- plexify.gemspec
|
118
|
+
- spec/plexify_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
102
120
|
homepage: ''
|
103
121
|
licenses: []
|
104
122
|
post_install_message:
|
@@ -123,4 +141,6 @@ rubygems_version: 1.8.24
|
|
123
141
|
signing_key:
|
124
142
|
specification_version: 3
|
125
143
|
summary: Media managers files organizer
|
126
|
-
test_files:
|
144
|
+
test_files:
|
145
|
+
- spec/plexify_spec.rb
|
146
|
+
- spec/spec_helper.rb
|