movie_organizer 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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +75 -0
  5. data/CHANGELOG +3 -0
  6. data/Gemfile +9 -0
  7. data/Guardfile +37 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +81 -0
  10. data/Rakefile +14 -0
  11. data/bin/movie_organizer +9 -0
  12. data/lib/movie_organizer.rb +58 -0
  13. data/lib/movie_organizer/file_copier.rb +69 -0
  14. data/lib/movie_organizer/logger.rb +47 -0
  15. data/lib/movie_organizer/media.rb +95 -0
  16. data/lib/movie_organizer/media_list.rb +31 -0
  17. data/lib/movie_organizer/movie.rb +41 -0
  18. data/lib/movie_organizer/organizer.rb +68 -0
  19. data/lib/movie_organizer/settings.rb +47 -0
  20. data/lib/movie_organizer/string.rb +13 -0
  21. data/lib/movie_organizer/tv_show.rb +96 -0
  22. data/lib/movie_organizer/version.rb +5 -0
  23. data/lib/movie_organizer/video.rb +47 -0
  24. data/movie_organizer.gemspec +43 -0
  25. data/spec/files/The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4 +0 -0
  26. data/spec/files/movies/Dunkirk.2017.BluRay.1080p/Dunkirk.2017.BluRay.1080p.mp4 +0 -0
  27. data/spec/files/movies/The Matrix (1999) [BluRay] [1080p]/The Matrix (1999) [BluRay] [1080p].mp4 b/data/spec/files/movies/The Matrix (1999) [BluRay] [1080p]/The Matrix (1999) [BluRay] → [1080p].mp4 +0 -0
  28. data/spec/files/short_video.mp4 +0 -0
  29. data/spec/fixtures/.blank_settings.yml +0 -0
  30. data/spec/fixtures/.movie_organizer.yml +19 -0
  31. data/spec/fixtures/.no_source_directories.yml +15 -0
  32. data/spec/lib/movie_organizer/file_copier_spec.rb +38 -0
  33. data/spec/lib/movie_organizer/logger_spec.rb +34 -0
  34. data/spec/lib/movie_organizer/media_list_spec.rb +49 -0
  35. data/spec/lib/movie_organizer/media_spec.rb +64 -0
  36. data/spec/lib/movie_organizer/movie_spec.rb +54 -0
  37. data/spec/lib/movie_organizer/organizer_spec.rb +35 -0
  38. data/spec/lib/movie_organizer/tv_show_spec.rb +80 -0
  39. data/spec/lib/movie_organizer_spec.rb +66 -0
  40. data/spec/spec_helper.rb +94 -0
  41. data/spec/support/filename_mappings.yml +10 -0
  42. data/spec/support/shared_contexts/media_shared.rb +50 -0
  43. data/tmdb-logo-primary-green.png +0 -0
  44. metadata +331 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 56db1180e21190c3099cc45f75066d9a27c9fb34d74364ed64c87c22f229da18
4
+ data.tar.gz: 34a39c981eb1cc29a7fdff7bb7bbd53ea464b9d0ebf973685bfe716e5da824af
5
+ SHA512:
6
+ metadata.gz: 9fbd33cf4a225627e99925e09f5d4a0cb3e2f13cc24cc2ef7da4485547079f07996ab6531491d8c1025d7394b0766d501e58a2ab312fbf09919efcccd8fd2616
7
+ data.tar.gz: 3db9a4aecbf719ceb5185f2ad00f78c853bc13df6c0ce400d4c8f954baa75ae9a98a1a0eb5b8c74dfb45170a0fe0b4c6a944000824c60c634a25ecfb2fb91b55
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .env*
16
+ /stubs
17
+ /vendor
18
+ /spec/.DS_Store
19
+ /.DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format doc
@@ -0,0 +1,75 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ # Include gemspec and Rakefile
5
+ Include:
6
+ - '**/*.gemspec'
7
+ - '**/*.podspec'
8
+ - '**/*.jbuilder'
9
+ - '**/*.rake'
10
+ - '**/Gemfile'
11
+ - '**/Rakefile'
12
+ - '**/Capfile'
13
+ - '**/Guardfile'
14
+ - '**/Podfile'
15
+ - '**/Thorfile'
16
+ - '**/Vagrantfile'
17
+ Exclude:
18
+ - 'vendor/**/*'
19
+ - 'stubs/**/*'
20
+ - 'spec/support/shared_contexts/*'
21
+
22
+ # Checks formatting of special comments
23
+ CommentAnnotation:
24
+ Keywords:
25
+ - TODO
26
+ - FIXME
27
+ - OPTIMIZE
28
+ - HACK
29
+ - REVIEW
30
+
31
+ ########################################
32
+ # Style Cops
33
+
34
+ Style/Documentation:
35
+ Enabled: false
36
+
37
+ Style/FileName:
38
+ Enabled: false
39
+
40
+ Style/AlignParameters:
41
+ EnforcedStyle: with_fixed_indentation
42
+
43
+ Style/RegexpLiteral:
44
+ Enabled: false
45
+
46
+ Style/EmptyLinesAroundBlockBody:
47
+ Enabled: false
48
+
49
+ Style/RaiseArgs:
50
+ Enabled: false
51
+
52
+ Style/DoubleNegation:
53
+ Enabled: false
54
+
55
+ Style/PerlBackrefs:
56
+ Enabled: false
57
+
58
+ ########################################
59
+ # Lint Cops
60
+
61
+ Lint/Eval:
62
+ Enabled: false
63
+
64
+ ########################################
65
+ # Metrics Cops
66
+
67
+ Metrics/LineLength:
68
+ Max: 110
69
+
70
+ Metrics/MethodLength:
71
+ CountComments: false # count full line comments?
72
+ Max: 20
73
+
74
+ Metrics/ClassLength:
75
+ Max: 120
@@ -0,0 +1,3 @@
1
+ *0.0.1* (March 08, 2015)
2
+
3
+ * Initial Commit
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
+
6
+ gem 'themoviedb', github: 'midwire/themoviedb'
7
+
8
+ # Specify your gem's dependencies in movie_organizer.gemspec
9
+ gemspec
@@ -0,0 +1,37 @@
1
+ # rubocop:disable all
2
+ # clearing :on
3
+
4
+ guard :bundler do
5
+ require 'guard/bundler'
6
+ require 'guard/bundler/verify'
7
+ helper = Guard::Bundler::Verify.new
8
+
9
+ files = ['Gemfile']
10
+ files += Dir['*.gemspec'] if files.any? { |f| helper.uses_gemspec?(f) }
11
+
12
+ # Assume files are symlinked from somewhere
13
+ files.each { |file| watch(helper.real_path(file)) }
14
+ end
15
+
16
+ CMD = 'bundle exec rspec -f doc --color'
17
+
18
+ guard :rspec, cmd: CMD do
19
+ require 'guard/rspec/dsl'
20
+ dsl = Guard::RSpec::Dsl.new(self)
21
+
22
+ # RSpec files
23
+ rspec = dsl.rspec
24
+ watch(rspec.spec_helper) { rspec.spec_dir }
25
+ watch(rspec.spec_support) { rspec.spec_dir }
26
+ watch(rspec.spec_files)
27
+
28
+ # Ruby files
29
+ ruby = dsl.ruby
30
+ dsl.watch_spec_files_for(ruby.lib_files)
31
+ end
32
+
33
+ # guard :rubocop, cli: ['-D'] do
34
+ # watch(%r{.+\.rb$})
35
+ # watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
36
+ # end
37
+ # rubocop:enable all
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Chris Blackburn
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.
@@ -0,0 +1,81 @@
1
+ # MovieOrganizer
2
+
3
+ Automatically organize movies, tv shows and home videos.
4
+
5
+ I use [Plex](https://www.plex.tv/) as the centerpiece of my home entertainment center, which requires that I rip my BluRay movies and TV shows, and copy them into a certain location so my [Plex server](https://www.plex.tv/downloads/) can recognize them.
6
+
7
+ MovieOrganizer makes the job of organizing my ripped movies, tv shows and home videos as simple as:
8
+
9
+ ```bash
10
+ movie_organizer
11
+
12
+ Starting MovieOrganizer...
13
+ Processing [/Users/midwire/Downloads/The Matrix (1990).mp4] - MovieOrganizer::Movie
14
+ target dir: [ssh://username@plex_server/media/movies/The Matrix (1990)]
15
+ target file: [ssh://username@plex_server/media/movies/The Matrix (1990)/The Matrix (1990).mp4]
16
+
17
+ ...
18
+ ```
19
+
20
+ ## Features
21
+
22
+ * Copies media to remote hosts
23
+ * Can differentiate between most Movies, TV Shows and Home Video if they are named properly, and copy each to the appropriately configured directory
24
+ * Uses [The Movie Database](https://www.themoviedb.org) for movie identification
25
+
26
+ ## Caveats
27
+
28
+ Note that this is beta software. I wrote it as a hack because I got tired of constantly copying my media over to Plex by hand. Having said that, it works but there are edge cases that are probably not covered. If you find a bug or problem, please file an issue and I'll address it ASAP.
29
+
30
+ ## Installation
31
+
32
+ $ gem install movie_organizer
33
+
34
+ ## Configuration
35
+
36
+ Sign up for an account at [The Movie Database](https://www.themoviedb.org/) and get an API key.
37
+
38
+ Create a file in your home directory named `.movie_organizer.yml` as follows:
39
+
40
+ ```yaml
41
+ ---
42
+ :space_warning: 20GB
43
+ :new_media_directories:
44
+ - "/Users/midwire/media_rips" # <- new media directory
45
+ :tv_shows:
46
+ :directory: "/Volumes/Genesis/TV Series" # <- a local directory
47
+ :movies:
48
+ :tmdb_key: df08efec9f01985d401a3cfedf5628a2 # <- use your own API key (this one is fake)
49
+ :directory: ssh://plex_admin@plex.local/media/media1/movies # <- remote directory
50
+ :videos:
51
+ :directory: "ssh://plex_admin@plex.local/media/media1/Family Videos" # <- remote directory
52
+ ```
53
+
54
+ **NOTE:** If you want to use a remote host, you will need to configure passwordless logins using your ssh-key, which is outside the scope of this document. If you don't know how, please do a search for
55
+
56
+ Remote hosts are specified in this format:
57
+
58
+ ```bash
59
+ ssh://username@hostname/path/to/remote/destination
60
+ ```
61
+
62
+ ## Usage
63
+
64
+ You should be able to simply run `movie_organizer`
65
+
66
+ Here are the command line options:
67
+
68
+ ```bash
69
+ Options:
70
+ -s, --source-dir=<s> Source directories containing media files. Colon (:) separated. (Default: /Users/midwire/media_rips)
71
+ -d, --dry-run Do not actually move or copy files
72
+ -p, --preserve-episode-name Preserve episode names if they exist (experimental)
73
+ -v, --verbose Be verbose with output
74
+ -h, --help Show this message
75
+ ```
76
+
77
+ ## Attribution
78
+
79
+ ### The Movie Database
80
+
81
+ <img src="tmdb-logo-primary-green.png" alt="TMDB Logo" style="float:left; padding: 0 10px 0 0;"/> We are profoundly pleased to have access to [The Movie Database](https://www.themoviedb.org) API. This product uses the TMDb API but is not endorsed or certified by TMDb.
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task default: :spec
8
+
9
+ begin
10
+ require 'midwire_common/rake_tasks'
11
+ rescue LoadError
12
+ puts ">>> Could not load 'midwire_common' gem."
13
+ exit
14
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'movie_organizer'
7
+ require 'movie_organizer/organizer'
8
+
9
+ MovieOrganizer::Organizer.instance.start
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Create a .test.env and a .development.env for your different local
4
+ # environments
5
+ require 'dotenv'
6
+ require 'colored'
7
+ require 'readline'
8
+
9
+ paths = %W(.env .env.#{ENV['APP_ENV']}).map { |name| "#{Dir.pwd}/#{name}" }
10
+ Dotenv.load(*paths).each { |k, v| ENV[k] = v }
11
+
12
+ require 'movie_organizer/version'
13
+ require 'midwire_common/string'
14
+
15
+ module MovieOrganizer
16
+ def self.root
17
+ Pathname.new(File.dirname(__FILE__)).parent
18
+ end
19
+
20
+ def self.current_environment
21
+ ENV.fetch('APP_ENV', 'development')
22
+ end
23
+
24
+ def self.config_file(filename = '.movie_organizer.yml')
25
+ return root.join('spec', 'fixtures', filename) if current_environment == 'test'
26
+ #:nocov:
27
+ home = ENV.fetch('HOME')
28
+ file = ENV.fetch('MO_CONFIG_FILE', File.join(home, '.movie_organizer.yml'))
29
+ FileUtils.touch(file)
30
+ file
31
+ #:nocov:
32
+ end
33
+
34
+ def self.source_directories(settings = Settings.new, test_response = nil)
35
+ settings[:new_media_directories] || begin
36
+ strings = prompt_for('Media source directories (separated by a colon)', test_response)
37
+ strings.split(':')
38
+ end
39
+ end
40
+
41
+ #:nocov:
42
+ def self.prompt_for(message = '', test_response = nil)
43
+ prompt = "#{message.dup}\n? "
44
+ return test_response if test_response
45
+ Readline.readline(prompt, true).squeeze(' ').strip
46
+ end
47
+ #:nocov:
48
+
49
+ autoload :FileCopier, 'movie_organizer/file_copier'
50
+ autoload :Logger, 'movie_organizer/logger'
51
+ autoload :Media, 'movie_organizer/media'
52
+ autoload :MediaList, 'movie_organizer/media_list'
53
+ autoload :Movie, 'movie_organizer/movie'
54
+ autoload :Organizer, 'movie_organizer/organizer'
55
+ autoload :Settings, 'movie_organizer/settings'
56
+ autoload :TvShow, 'movie_organizer/tv_show'
57
+ autoload :Video, 'movie_organizer/video'
58
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MovieOrganizer
4
+ class FileCopier
5
+ attr_accessor :filename, :target_file, :options
6
+ attr_reader :username, :hostname, :remote_filename
7
+
8
+ def initialize(filename, target_file, options)
9
+ @filename = filename
10
+ @target_file = target_file
11
+ @options = options
12
+ end
13
+
14
+ def copy
15
+ ssh? ? remote_copy : local_copy
16
+ end
17
+
18
+ private
19
+
20
+ def local_copy
21
+ FileUtils.mkdir_p(File.dirname(target_file))
22
+ FileUtils.copy(
23
+ filename,
24
+ target_file,
25
+ noop: options[:dry_run]
26
+ )
27
+ end
28
+
29
+ def remote_copy
30
+ parse_target
31
+ return do_dry_run if options[:dry_run]
32
+ Net::SSH.start(hostname, username) do |ssh|
33
+ ssh.exec("mkdir -p '#{target_dir}'")
34
+ end
35
+ Net::SCP.start(hostname, username) do |scp|
36
+ scp.upload!(filename, remote_filename)
37
+ end
38
+ end
39
+
40
+ def do_dry_run
41
+ puts("Would remotely execute: [#{"mkdir -p '#{target_dir}'"}] on #{hostname}")
42
+ puts("Would execute: [#{"scp '#{filename}' '#{remote_filename}'"}]")
43
+ end
44
+
45
+ def target_dir
46
+ @target_dir ||= begin
47
+ parts = @remote_filename.split('/')
48
+ parts[0..parts.length - 2].join('/').to_s
49
+ end
50
+ end
51
+
52
+ def parse_target
53
+ return nil if @parse_target
54
+ @parse_target = true
55
+ temp ||= target_file.to_s.split('/')[2..99]
56
+ md = temp.join('/').match(/([\w\-\.]+)@([^\/]+)(\/.+)$/)
57
+ @username = md[1]
58
+ @hostname = md[2]
59
+ @remote_filename = md[3]
60
+ if @username.nil? || @hostname.nil? || @remote_filename.nil?
61
+ fail 'SSH path not formatted properly. Use [ssh://username@hostname/absolute/path]'
62
+ end
63
+ end
64
+
65
+ def ssh?
66
+ target_file.match?(/^ssh:/)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,47 @@
1
+ require 'singleton'
2
+ require 'logger'
3
+
4
+ module MovieOrganizer
5
+ class Logger
6
+ include Singleton
7
+
8
+ attr_accessor :log_provider
9
+
10
+ def initialize(provider = default_logger)
11
+ @log_provider = provider
12
+ end
13
+
14
+ def log_exception(e, data = {})
15
+ msg = "EXCEPTION : #{e.class.name} : #{e.message}"
16
+ msg += "\n data : #{data.inspect}" if data && !data.empty?
17
+ msg += "\n #{e.backtrace[0, 6].join("\n ")}"
18
+ log_provider.error(msg)
19
+ end
20
+
21
+ def method_missing(meth, *args, &block)
22
+ if log_provider.respond_to?(meth)
23
+ log_provider.send(meth, *args, &block)
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def respond_to?(meth, include_private = false)
30
+ if log_provider.respond_to?(meth)
31
+ true
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def default_logger
40
+ logger = ::Logger.new(STDOUT)
41
+ logger.formatter = proc do |_, _, _, msg|
42
+ "#{msg}\n"
43
+ end
44
+ logger
45
+ end
46
+ end
47
+ end