plexify 0.1.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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.plexify.yml ADDED
@@ -0,0 +1,4 @@
1
+ plexify:
2
+ tmdb:
3
+ api_key: a1d640d24a78fb94c297cac5483ba31e
4
+ default_language: fr
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in plexify.gemspec
4
+ gemspec
5
+
6
+ gem "ruby-tmdb", ">= 0.2.1"
7
+ gem "colorize", ">= 0.5.8"
8
+ gem "text", ">= 1.2.1"
9
+ gem "ruby-progressbar", ">= 1.0.2"
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Pierre FILSTROFF
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,31 @@
1
+ # Plexify
2
+
3
+ Currently in development. Not working yet.
4
+
5
+ TODO: Write a gem description
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'plexify'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install plexify
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
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
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/plexify ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
+ require "plexify"
5
+
6
+ plexify = Plexify::Plexify.new(ARGV)
7
+ plexify.run!
@@ -0,0 +1,170 @@
1
+ module Plexify
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] 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
49
+
50
+ private
51
+ # Check missing arguments
52
+ def check_arguments
53
+ return if @args.size == 2
54
+
55
+ @missing_args << "-s [source]" if !@options[:source] && !@args[0]
56
+ @missing_args << "-d [destination]" if !@options[:destination] && !@options[:movies_destination] && !@options[:series_destination]
57
+ @missing_args << "--movies-destination [destination]" if !@options[:destination] && !@options[:movies_destination] && !@options[:series_destination]
58
+ @missing_args << "--series-destination [destination]" if !@options[:destination] && !@options[:series_destination] && !@options[:movies_destination]
59
+
60
+ if @missing_args.any?
61
+ raise OptionParser::MissingArgument
62
+ end
63
+ end
64
+
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"
68
+
69
+ opts.on('-s [source]', '--source [source]', 'Source folder which includes media files. Temp folder.') do |source|
70
+ @options[:source] = source
71
+ end
72
+
73
+ opts.on('-d [destination]', '--destination [dest]', 'Destination folder to put organized.') do |destination|
74
+ @options[:destination] = destination
75
+ end
76
+
77
+ opts.on('--movies-destination [dest]', 'Destination folder to put organized movies files.') do |movies_destination|
78
+ @options[:movies_destination] = movies_destination
79
+ end
80
+
81
+ opts.on('--series-destination [dest]', 'Destination folder to put organized series files.') do |series_destination|
82
+ @options[:series_destination] = series_destination
83
+ end
84
+
85
+ opts.on_tail('-t', '--test', 'Test files name searching in media databases: only print results.') do
86
+ @options[:test] = true
87
+ end
88
+
89
+ opts.on_tail('--accept-removing-all', :NONE, 'Accept removing source files. [NOT WORKING YET]') do
90
+ @options[:accept_removing_all] = true
91
+ end
92
+
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
102
+
103
+ opts.on_tail('--trace', :NONE, 'Show a full traceback on error') do
104
+ @options[:trace] = true
105
+ end
106
+ end
107
+
108
+ # Process result
109
+ def process_result
110
+
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
+
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
+ Logger.info("\nFound medias :")
136
+
137
+ # If the test argument is present, don't move files or prompt the user
138
+ if @options[:test]
139
+ for media in medias
140
+ Logger.success("\n#{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
141
+ end
142
+ else
143
+ Logger.success("\n0: The movie isn't in the list, go next without doing anything.")
144
+ medias.each_with_index do |media, index|
145
+ Logger.success("\n#{index+1}: #{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
146
+ end
147
+
148
+ Logger.success("\nChoose the movie: ")
149
+ resp = STDIN.gets.chomp.to_i-1
150
+
151
+ if resp >= 0 && resp <= medias.size
152
+ media = medias[resp]
153
+ Logger.success("\nMedia found : #{media.data.name} #{media.data.released ? "(#{Date.parse(media.data.released).year})" : nil}")
154
+ media_organizer.move_to_destination(file, media)
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
164
+ end
165
+
166
+ Logger.info("\n\n=> Files pocessed #{files_processed_count}/#{source.files.size}") unless @options[:test]
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,19 @@
1
+ module Plexify
2
+ # Logger class
3
+ class Logger
4
+ # print informations
5
+ def self.info(content)
6
+ print content
7
+ end
8
+
9
+ # print success in green
10
+ def self.success(content)
11
+ print content.colorize(:green)
12
+ end
13
+
14
+ # print errors in red
15
+ def self.error(content)
16
+ print content.colorize(:red)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Plexify
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
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,88 @@
1
+ module Plexify
2
+ class MediaManager
3
+ # Initialize variables and load tmdb media manager
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)
14
+
15
+ medias = analyse_names(splitted_name, {})
16
+
17
+ flattened_medias = []
18
+ medias.each_pair do |title, media|
19
+ flattened_medias << media if flattened_medias.empty?
20
+ unless flattened_medias.map(&:data).map(&:id).include?(media.data.id)
21
+ media.found_title = title
22
+ flattened_medias << media
23
+ end
24
+ end
25
+
26
+ return flattened_medias
27
+ end
28
+
29
+ private
30
+ # Load tmdb media manager
31
+ def load_tmdb
32
+ load_plexify_config
33
+ Tmdb.api_key = @tmdb_api_key
34
+ Tmdb.default_language = @tmdb_default_language
35
+ end
36
+
37
+ # Load plexify config yaml file in the home directory
38
+ # The file structure is :
39
+ # plexify:
40
+ # tmdb:
41
+ # api_key: xxxx
42
+ # default_language: en
43
+ def load_plexify_config
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
52
+
53
+ def analyse_names(list, medias)
54
+ title = String.new
55
+
56
+ list.each_with_index do |str, index|
57
+ title << " " if title.length > 0
58
+ title << str
59
+ media = find(title)
60
+
61
+ if media.empty?
62
+ if medias.any?
63
+ shift = list.shift
64
+ analyse_names(list, medias)
65
+ end
66
+ else
67
+ medias[title] = Media.new(media)
68
+ end
69
+ end
70
+ return medias
71
+ end
72
+
73
+ # Get Levenshtein distance
74
+ def levenshtein_distance(first, second)
75
+ Text::Levenshtein.distance(first, second)
76
+ end
77
+
78
+ # Find a movie in the Tmdb database with a title and a limit of one item
79
+ def find(title)
80
+ TmdbMovie.find(:title => title, :limit => 1)
81
+ end
82
+
83
+ # Find all movies in the Tmdb database with a title
84
+ def find_all(title)
85
+ TmdbMovie.find(:title => title)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,71 @@
1
+ module Plexify
2
+ # Class MediaOrganizer
3
+ # It manage source and destination folders
4
+ class MediaOrganizer
5
+ attr_reader :source
6
+ attr_reader :destination
7
+
8
+ # Initialize
9
+ def initialize(source, destination)
10
+ @source = source
11
+ @destination = destination
12
+ end
13
+
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
+
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
+
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
+
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
+
32
+ # Copy the file with a progress bar
33
+ Logger.info("\nCopying 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
+
38
+ Logger.info("\nDo you want to remove the source file '#{file_basename}' ? (y/n) ")
39
+ resp = STDIN.gets.chomp
40
+
41
+ if resp.eql?("y")
42
+ FileUtils.rm file.path
43
+ Logger.success("File '#{file_basename}' removed")
44
+ end
45
+ end
46
+
47
+ private
48
+
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
+
57
+ p_bar = ProgressBar.create(:title => "Copying '#{File.basename(source_file.path)}'", :format => '|%b>%i| %p%% %t')
58
+ buffer = source_file.sysread(batch_bytes)
59
+
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)
66
+ end
67
+ buffer = source_file.sysread(batch_bytes)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,91 @@
1
+ module Plexify
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
+
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
+
16
+ def prepare
17
+ if @destination_path
18
+ prepare_destination
19
+ else
20
+ prepare_destinations
21
+ end
22
+ end
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)
30
+
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
35
+
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
41
+
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
45
+
46
+ # Create or use a folder
47
+ def create_or_use_folder(*args)
48
+ path = args.first
49
+
50
+ if args.size > 1
51
+ name = args[1]
52
+ end
53
+
54
+ folder_path = name ? File.join(path, name) : path
55
+
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)
62
+ end
63
+
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
+
79
+ # Prepare source folder
80
+ def prepare
81
+ @folder = File.new @source_path
82
+
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
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module Plexify
2
+ VERSION = "0.1.0"
3
+ end
data/lib/plexify.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'fileutils'
2
+ require 'date'
3
+ require 'ruby-progressbar'
4
+ require 'optparse'
5
+ require 'colorize'
6
+ require 'ruby-tmdb'
7
+ require 'yaml'
8
+ require 'text'
9
+
10
+ Dir[File.dirname(__FILE__) + "/plexify/**/*"].each{ |file| require file }
11
+
12
+ module Plexify
13
+ class Plexify
14
+ def initialize(args)
15
+ @opts = Execute::Plexify.new(args)
16
+ end
17
+
18
+ # Run plexify !
19
+ def run!
20
+ @opts.parse!
21
+ end
22
+ end
23
+ end
data/plexify.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'plexify/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "plexify"
8
+ gem.version = Plexify::VERSION
9
+ gem.authors = ["Pierre FILSTROFF"]
10
+ gem.email = ["pfilstroff@gmail.com"]
11
+ gem.description = %q{Organize media folders to get ready with media managers}
12
+ gem.summary = %q{Media managers files organizer}
13
+ gem.homepage = ""
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
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: plexify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pierre FILSTROFF
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Organize media folders to get ready with media managers
15
+ email:
16
+ - pfilstroff@gmail.com
17
+ executables:
18
+ - plexify
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - .plexify.yml
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - bin/plexify
29
+ - lib/plexify.rb
30
+ - lib/plexify/execute.rb
31
+ - lib/plexify/logger.rb
32
+ - lib/plexify/media.rb
33
+ - lib/plexify/media_manager.rb
34
+ - lib/plexify/media_organizer.rb
35
+ - lib/plexify/prepare.rb
36
+ - lib/plexify/version.rb
37
+ - plexify.gemspec
38
+ homepage: ''
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 1.8.24
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Media managers files organizer
62
+ test_files: []