movie_organizer 0.1.11 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +3 -0
  4. data/CHANGELOG +10 -0
  5. data/Gemfile +2 -1
  6. data/README.md +27 -14
  7. data/lib/movie_organizer.rb +33 -14
  8. data/lib/movie_organizer/file_copier.rb +33 -19
  9. data/lib/movie_organizer/logger.rb +2 -0
  10. data/lib/movie_organizer/media_list.rb +1 -15
  11. data/lib/movie_organizer/medium.rb +176 -0
  12. data/lib/movie_organizer/movie.rb +50 -18
  13. data/lib/movie_organizer/options.rb +23 -0
  14. data/lib/movie_organizer/organizer.rb +29 -11
  15. data/lib/movie_organizer/settings.rb +3 -0
  16. data/lib/movie_organizer/string.rb +8 -0
  17. data/lib/movie_organizer/tmdb_instance.rb +58 -0
  18. data/lib/movie_organizer/tv_show.rb +51 -37
  19. data/lib/movie_organizer/tvdb_instance.rb +31 -0
  20. data/lib/movie_organizer/version.rb +1 -1
  21. data/lib/movie_organizer/video.rb +39 -11
  22. data/movie_organizer.gemspec +5 -2
  23. data/spec/files/Dunkirk.2017.BluRay.1080p.mp4 +0 -0
  24. data/spec/files/bad.flv +0 -0
  25. data/spec/files/bad.mp4 +1 -0
  26. data/spec/files/good.3gp +0 -0
  27. data/spec/files/good.mov +0 -0
  28. data/spec/files/good.mp4 +0 -0
  29. data/spec/files/good.ogv +0 -0
  30. data/spec/files/good.webm +0 -0
  31. data/spec/files/{The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4 → tv_shows/The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4} +0 -0
  32. data/spec/fixtures/.blank_settings.yml +3 -0
  33. data/spec/lib/movie_organizer/file_copier_spec.rb +5 -2
  34. data/spec/lib/movie_organizer/media_list_spec.rb +8 -37
  35. data/spec/lib/movie_organizer/medium_spec.rb +97 -0
  36. data/spec/lib/movie_organizer/movie_spec.rb +21 -14
  37. data/spec/lib/movie_organizer/tmdb_instance_spec.rb +39 -0
  38. data/spec/lib/movie_organizer/tvdb_instance_spec.rb +17 -0
  39. data/spec/lib/movie_organizer/video_spec.rb +6 -4
  40. data/spec/lib/movie_organizer_spec.rb +1 -9
  41. data/spec/spec_helper.rb +5 -0
  42. data/spec/support/shared_contexts/media_shared.rb +9 -0
  43. data/spec/support/vcr.rb +17 -0
  44. metadata +74 -21
  45. data/lib/movie_organizer/media.rb +0 -110
  46. data/spec/lib/movie_organizer/media_spec.rb +0 -65
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e79b66679e3034ba041b6a74648f9588579e177bb6aeeba31f6ea4dc55648ec
4
- data.tar.gz: e01ff240650b55d4237431b30fb1988ffb640a129fa5261448ab0dd4fee6add7
3
+ metadata.gz: 5157291fa0d5ccda33cc68f1af1f09a0a58fd42474b8e676fe838c2c1bc77964
4
+ data.tar.gz: 18a25b3e745211e672de63c45d5fce5596f32078c2efe35f7f59fd91417e5c26
5
5
  SHA512:
6
- metadata.gz: f52967cf08b3d97695c63960cea76713ac99d55154ffe8c9674153a9a5907ed1057e676fce98169ff76c4a84353ee1185bfb8a3d3f28779d0342135aa087cb10
7
- data.tar.gz: 245370271ea233c8c3f43da77860534b5b6527dcd835238454fddcfcf45233f6cf9345b2e078b2b4603aeb421dc2fc7bb21bf89e14fd64cf33d4ddfb586700a6
6
+ metadata.gz: 4cc23bf91f29c382261e349a7cb8d2e765a933f3ab0afe038d5263fe2fa749bd9af6c6c8f4b8102e03a11036a5a4783771a96337b35dda5a4076a6d0418c72d4
7
+ data.tar.gz: fccf79f2d5390c38f98cf10865688aa9365a81d26d09f5edfdb72a036d8ab903a3a1e44db74cf6ca609262955435272a5844fb94876be242edde56b44bde27e3
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ mkmf.log
18
18
  /spec/.DS_Store
19
19
  /.DS_Store
20
20
  .DS_Store
21
+ /spec/fixtures/vcr_cassettes
@@ -73,3 +73,6 @@ Metrics/MethodLength:
73
73
 
74
74
  Metrics/ClassLength:
75
75
  Max: 120
76
+
77
+ Metrics/AbcSize:
78
+ Max: 20
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ *1.0.1* (April 17, 2018)
2
+
3
+ * Temporarily cache the lookup instances for 3rd party service - for reuse during runs
4
+ * Refine SSH/SCP remote moving/copying
5
+ * Encapsulate media determination in each type
6
+ * Remove some unused dependencies
7
+ * Refactor all but TV Show specs
8
+ * Add Options singleton
9
+ * Fix some logging issues
10
+
1
11
  *0.1.11* (March 27, 2018)
2
12
 
3
13
  * Fix bug where it wouldn't always autoload net-scp.
data/Gemfile CHANGED
@@ -3,7 +3,8 @@
3
3
  source 'https://rubygems.org'
4
4
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
- # gem 'themoviedb', github: 'midwire/themoviedb'
6
+ # gem 'tvdbr', github: 'midwire/tvdbr'
7
+ gem 'tvdbr', path: '../tvdbr'
7
8
 
8
9
  # Specify your gem's dependencies in movie_organizer.gemspec
9
10
  gemspec
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # MovieOrganizer
2
2
 
3
- Automatically organize movies, tv shows and home videos.
3
+ Automatically organize movies, tv shows ~~and home videos~~. **NOTE: home videos are temporarily disabled for now**
4
4
 
5
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
6
 
7
- MovieOrganizer makes the job of organizing my ripped movies, tv shows and home videos as simple as:
7
+ MovieOrganizer makes the job of organizing my ripped movies, tv shows ~~and home videos~~ as simple as:
8
8
 
9
9
  ```bash
10
10
  movie_organizer
@@ -20,12 +20,13 @@ Processing [/Users/midwire/Downloads/The Matrix (1990).mp4] - MovieOrganizer::Mo
20
20
  ## Features
21
21
 
22
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
23
+ * Can differentiate between most Movies, TV Shows ~~and Home Video~~ if they are named properly, and copy each to the appropriately configured directory. See [Plex Media Preparation](https://support.plex.tv/articles/categories/media-preparation/)
24
24
  * Uses [The Movie Database](https://www.themoviedb.org) for movie identification
25
+ * Uses [The TV DB](https://www.thetvdb.com/) for TV show identification
25
26
 
26
27
  ## Caveats
27
28
 
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
+ 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. It does a decent job of recognizing and grooming media filenames but it is not perfect. There are edge cases that are not covered for really wonky filenames. If you find a bug or problem, please file an issue and I'll address it ASAP.
29
30
 
30
31
  ## Installation
31
32
 
@@ -34,24 +35,27 @@ Note that this is beta software. I wrote it as a hack because I got tired of con
34
35
  ## Configuration
35
36
 
36
37
  Sign up for an account at [The Movie Database](https://www.themoviedb.org/) and get an API key.
38
+ Sign up for an account at [The TV DB](https://www.thetvdb.com/) and get an API key.
37
39
 
38
40
  Create a file in your home directory named `.movie_organizer.yml` as follows:
39
41
 
40
42
  ```yaml
41
43
  ---
42
- :space_warning: 20GB
43
44
  :new_media_directories:
44
- - "/Users/midwire/media_rips" # <- new media directory
45
+ - "/Users/midwire/media_rips" # <- new media (source) directory
45
46
  :tv_shows:
46
47
  :directory: "/Volumes/Genesis/TV Series" # <- a local directory
47
48
  :movies:
48
49
  :tmdb_key: df08efec9f01985d401a3cfedf5628a2 # <- use your own API key (this one is fake)
49
50
  :directory: ssh://plex_admin@plex.local/media/media1/movies # <- remote directory
50
51
  :videos:
52
+ :tvdb_key: df08efec9f01985d401a3cfedf5628a2 # <- use your own API key (this one is fake)
51
53
  :directory: "ssh://plex_admin@plex.local/media/media1/Family Videos" # <- remote directory
52
54
  ```
53
55
 
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
56
+ **NOTE:** If your Plex media is on 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 `ssh public key authentication`.
57
+
58
+ You can also set your api keys, `TMDB_KEY` and `TVDB_KEY` as environment variables, in which case they will be used instead of the corresponding settings in your `.movie_organizer.yml` file. In other words, the environment variables override the .yml file. Useful for testing.
55
59
 
56
60
  Remote hosts are specified in this format:
57
61
 
@@ -61,21 +65,30 @@ ssh://username@hostname/path/to/remote/destination
61
65
 
62
66
  ## Usage
63
67
 
64
- You should be able to simply run `movie_organizer`
68
+ If you have the yaml configuration settings configured correctly, you should be able to simply run `movie_organizer`. However, it tries to prompt for most missing settings.
65
69
 
66
70
  Here are the command line options:
67
71
 
68
72
  ```bash
69
73
  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
74
+ -s, --source-dir=<s> Source directories containing media files. Colon (:) separated.
75
+ -c, --copy Copy instead of Move files
76
+ -d, --dry-run Do not actually move or copy files
77
+ -v, --verbose Be verbose with output
78
+ -h, --help Show this message
75
79
  ```
76
80
 
81
+ ## TODO
82
+
83
+ * Consolidate and better manage regular expressions
84
+ * Handle multi-part movies and videos `Beetlejuice (1996)-part1.mp4`, `Beetlejuice (1996)-part2.mp4`, etc.
85
+
77
86
  ## Attribution
78
87
 
79
88
  ### The Movie Database
80
89
 
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.
90
+ <img src="tmdb-logo-primary-green.png" alt="TMDB Logo" style="float:left; padding: 0 10px 0 0;"/> We are 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.
91
+
92
+ ### The TV DB
93
+
94
+ We are also pleased to have access to [The TV DB](https://www.tvdb.com) API. Please help by contributing TV Show information and artwork.
@@ -27,7 +27,23 @@ module MovieOrganizer
27
27
  #:nocov:
28
28
  end
29
29
 
30
- def self.source_directories(settings = Settings.new, test_response = nil)
30
+ def self.options
31
+ MovieOrganizer::Options.instance
32
+ end
33
+
34
+ def self.verbose_puts(string)
35
+ Logger.instance.info(" #{string}") if options[:verbose]
36
+ end
37
+
38
+ def self.os
39
+ if RUBY_PLATFORM.match?(/cygwin|mswin|mingw|bccwin|wince|emx/)
40
+ :retarded
41
+ else
42
+ :normal
43
+ end
44
+ end
45
+
46
+ def self.source_directories(settings = Settings.instance, test_response = nil)
31
47
  settings[:new_media_directories] || begin
32
48
  strings = prompt_for('Media source directories (separated by a colon)', test_response)
33
49
  settings[:new_media_directories] = strings.split(':')
@@ -36,7 +52,7 @@ module MovieOrganizer
36
52
  end
37
53
  end
38
54
 
39
- def self.tmdb_key(settings = Settings.new, test_response = nil)
55
+ def self.tmdb_key(settings = Settings.instance, test_response = nil)
40
56
  return settings[:movies][:tmdb_key] if settings[:movies] && settings[:movies][:tmdb_key]
41
57
  settings[:movies] ||= {}
42
58
  settings[:movies][:tmdb_key] =
@@ -45,7 +61,7 @@ module MovieOrganizer
45
61
  settings[:movies][:tmdb_key]
46
62
  end
47
63
 
48
- def self.movie_directory(settings = Settings.new, test_response = nil)
64
+ def self.movie_directory(settings = Settings.instance, test_response = nil)
49
65
  return settings[:movies][:directory] if settings[:movies] && settings[:movies][:directory]
50
66
  settings[:movies] ||= {}
51
67
  settings[:movies][:directory] =
@@ -54,7 +70,7 @@ module MovieOrganizer
54
70
  settings[:movies][:directory]
55
71
  end
56
72
 
57
- def self.tv_shows_directory(settings = Settings.new, test_response = nil)
73
+ def self.tv_shows_directory(settings = Settings.instance, test_response = nil)
58
74
  return settings[:tv_shows][:directory] if settings[:tv_shows] && settings[:tv_shows][:directory]
59
75
  settings[:tv_shows] ||= {}
60
76
  settings[:tv_shows][:directory] =
@@ -63,7 +79,7 @@ module MovieOrganizer
63
79
  settings[:tv_shows][:directory]
64
80
  end
65
81
 
66
- def self.video_directory(settings = Settings.new, test_response = nil)
82
+ def self.video_directory(settings = Settings.instance, test_response = nil)
67
83
  return settings[:videos][:directory] if settings[:videos] && settings[:videos][:directory]
68
84
  settings[:videos] ||= {}
69
85
  settings[:videos][:directory] =
@@ -80,13 +96,16 @@ module MovieOrganizer
80
96
  end
81
97
  #:nocov:
82
98
 
83
- autoload :FileCopier, 'movie_organizer/file_copier'
84
- autoload :Logger, 'movie_organizer/logger'
85
- autoload :Media, 'movie_organizer/media'
86
- autoload :MediaList, 'movie_organizer/media_list'
87
- autoload :Movie, 'movie_organizer/movie'
88
- autoload :Organizer, 'movie_organizer/organizer'
89
- autoload :Settings, 'movie_organizer/settings'
90
- autoload :TvShow, 'movie_organizer/tv_show'
91
- autoload :Video, 'movie_organizer/video'
99
+ autoload :FileCopier, 'movie_organizer/file_copier'
100
+ autoload :Logger, 'movie_organizer/logger'
101
+ autoload :Medium, 'movie_organizer/medium'
102
+ autoload :MediaList, 'movie_organizer/media_list'
103
+ autoload :Movie, 'movie_organizer/movie'
104
+ autoload :Options, 'movie_organizer/options'
105
+ autoload :Organizer, 'movie_organizer/organizer'
106
+ autoload :Settings, 'movie_organizer/settings'
107
+ autoload :TmdbInstance, 'movie_organizer/tmdb_instance'
108
+ autoload :TvdbInstance, 'movie_organizer/tvdb_instance'
109
+ autoload :TvShow, 'movie_organizer/tv_show'
110
+ autoload :Video, 'movie_organizer/video'
92
111
  end
@@ -4,41 +4,46 @@ require 'net/scp'
4
4
 
5
5
  module MovieOrganizer
6
6
  class FileCopier
7
- attr_accessor :filename, :target_file, :options
8
- attr_reader :username, :hostname, :remote_filename, :logger
7
+ attr_accessor :filename, :target_file
8
+ attr_reader :username, :hostname, :remote_filename
9
9
 
10
- def initialize(filename, target_file, options)
10
+ def initialize(filename, target_file)
11
11
  @filename = filename
12
12
  @target_file = target_file
13
- @options = options
14
- @logger = Logger.instance
13
+ @dry_run = MovieOrganizer.options[:dry_run]
14
+ @verbose = MovieOrganizer.options[:verbose]
15
15
  end
16
16
 
17
- def copy
17
+ def copy!
18
18
  ssh? ? remote_copy : local_copy
19
19
  end
20
20
 
21
21
  private
22
22
 
23
23
  def local_copy
24
- FileUtils.mkdir_p(File.dirname(target_file))
25
- FileUtils.copy(
26
- filename,
27
- target_file,
28
- noop: options[:dry_run]
29
- )
24
+ dir = File.dirname(target_file)
25
+ FileUtils.mkdir_p(dir) unless File.exist?(dir)
26
+ if File.exist?(target_file)
27
+ Logger.instance.info(" already exists: [#{target_file.green.bold}]")
28
+ return true
29
+ end
30
+ if MovieOrganizer.options[:copy]
31
+ FileUtils.copy(filename, target_file, noop: @dry_run, verbose: @verbose)
32
+ else
33
+ FileUtils.move(filename, target_file, noop: @dry_run, verbose: @verbose)
34
+ end
30
35
  end
31
36
 
32
37
  def remote_copy
33
38
  parse_target
34
- return do_dry_run if options[:dry_run]
39
+ return do_remote_dry_run if @dry_run
35
40
  create_remote_dir
36
41
  copy_file_to_remote
37
42
  end
38
43
 
39
- def do_dry_run
40
- puts("Would remotely execute: [#{"mkdir -p '#{target_dir}'"}] on #{hostname}")
41
- puts("Would execute: [#{"scp '#{filename}' '#{remote_filename}'"}]")
44
+ def do_remote_dry_run
45
+ Logger.instance.info("Would remotely execute: [#{create_remote_dir_cmd}] on #{hostname}")
46
+ Logger.instance.info("Would execute: [#{copy_file_to_remote_cmd}]")
42
47
  end
43
48
 
44
49
  def target_dir
@@ -65,20 +70,29 @@ module MovieOrganizer
65
70
  target_file.match?(/^ssh:/)
66
71
  end
67
72
 
73
+ def create_remote_dir_cmd
74
+ "mkdir -p \"#{target_dir}\""
75
+ end
76
+
77
+ def copy_file_to_remote_cmd
78
+ "scp '#{filename}' '#{remote_filename}'"
79
+ end
80
+
68
81
  def create_remote_dir
69
82
  Net::SSH.start(hostname, username, timeout: 5) do |ssh|
70
- ssh.exec("mkdir -p '#{target_dir}'")
83
+ ssh.exec!(create_remote_dir_cmd)
71
84
  end
72
85
  rescue Net::SSH::ConnectionTimeout, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
73
- logger.error("ConnectionTimeout: the host '#{hostname}' is unreachable.".red)
86
+ Logger.instance.error("ConnectionTimeout: the host '#{hostname}' is unreachable.".red)
74
87
  end
75
88
 
76
89
  def copy_file_to_remote
77
90
  Net::SCP.start(hostname, username) do |scp|
78
91
  scp.upload!(filename, remote_filename)
79
92
  end
93
+ FileUtils.rm(filename, noop: @dry_run) unless MovieOrganizer.options[:copy]
80
94
  rescue Net::SSH::ConnectionTimeout, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
81
- logger.error("ConnectionTimeout: the host '#{hostname}' is unreachable.".red)
95
+ Logger.instance.error("ConnectionTimeout: the host '#{hostname}' is unreachable.".red)
82
96
  end
83
97
  end
84
98
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'singleton'
2
4
  require 'logger'
3
5
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'mime/types'
4
-
5
3
  module MovieOrganizer
6
4
  class MediaList
7
5
  attr_accessor :file_collection
@@ -11,21 +9,9 @@ module MovieOrganizer
11
9
  @file_collection = []
12
10
  directories.each do |directory|
13
11
  Dir["#{directory}/**/*"].each do |entry|
14
- file_collection << entry if media?(entry)
12
+ file_collection << entry if Medium.media_file?(entry)
15
13
  end
16
14
  end
17
15
  end
18
-
19
- def media?(filename)
20
- video?(filename) || subtitle?(filename)
21
- end
22
-
23
- def video?(filename)
24
- MIME::Types.of(filename).map(&:media_type).include?('video')
25
- end
26
-
27
- def subtitle?(filename)
28
- !MIME::Types.of(filename).map(&:content_type).grep(/(subtitle$|subrip$)/).empty?
29
- end
30
16
  end
31
17
  end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'filemagic'
4
+ require 'themoviedb'
5
+
6
+ module MovieOrganizer
7
+ # Medium is a class factory for it's derived classes
8
+ class Medium
9
+ attr_reader :filename, :target
10
+
11
+ MIME_TYPES = %w[
12
+ video/mp4
13
+ video/ogg
14
+ video/webm
15
+ video/mpeg
16
+ video/quicktime
17
+ video/x-matroska
18
+ video/x-msvideo
19
+ ].freeze
20
+ OCTET_STREAM_EXTENSIONS = %w[.3gp .mp4].freeze
21
+
22
+ class << self
23
+ # Determine if a file is a processable media file
24
+ #
25
+ # @return [Boolean] true if file is processable, false if not
26
+ def media_file?(filepath)
27
+ return false if File.directory?(filepath)
28
+
29
+ MovieOrganizer.verbose_puts("checking: #{filepath}")
30
+
31
+ mime = mime_type(filepath)
32
+
33
+ return true if
34
+ mime == 'application/octet-stream' &&
35
+ OCTET_STREAM_EXTENSIONS.include?(File.extname(filepath))
36
+
37
+ MIME_TYPES.include?(mime)
38
+ end
39
+
40
+ # Build the proper instance of a child class. This method does not check if the file is
41
+ # of the proper mime-type
42
+ #
43
+ # @param filepath [String] full path to the file
44
+ # @return [Object] instance of the corresponding class type
45
+ def build_instance(filepath)
46
+ return nil unless media_file?(filepath)
47
+
48
+ tvdb_instance = TvShow.match?(filepath)
49
+ return TvShow.new(filepath, tvdb_instance) if tvdb_instance
50
+
51
+ tmdb_instance = Movie.match?(filepath)
52
+ return Movie.new(filepath, tmdb_instance) if tmdb_instance
53
+
54
+ # return Video.new(filepath) if Video.match?(filepath)
55
+
56
+ nil
57
+ end
58
+
59
+ private
60
+
61
+ # rubocop:disable Metrics/AbcSize
62
+ # rubocop:disable Metrics/MethodLength
63
+ def sanitize(str)
64
+ cleanstr = str.dup
65
+ cleanstr.gsub!(/-\s*-/, '')
66
+ cleanstr.gsub!(/\[?1080p\]?/, '')
67
+ cleanstr.gsub!(/m?\[?720p\]?/, '')
68
+ cleanstr.gsub!(/\[[^\]]+\]/, '')
69
+ cleanstr.gsub!(/ECI/, '')
70
+ cleanstr.gsub!(/EXTENDED/, '')
71
+ cleanstr.gsub!(/UNRATED/, '')
72
+ cleanstr.gsub!(/ETRG/, '')
73
+ cleanstr.gsub!(/VPPV/, '')
74
+ cleanstr.gsub!(/HQ/, '')
75
+ cleanstr.gsub!(/x264/, '')
76
+ cleanstr.gsub!(/AAC/, '')
77
+ cleanstr.gsub!(/[\.\s]B[dr]Rip[\.\s]/i, '')
78
+ cleanstr.gsub!(/\.Br\./i, '')
79
+ cleanstr.gsub!(/BluRay/i, '')
80
+ cleanstr.gsub!(/HDTV/i, '')
81
+ cleanstr.gsub!(/WEBRip/i, '')
82
+ cleanstr.gsub!(/(Deceit)?\.?YIFY/, '')
83
+ cleanstr.gsub!(/-?xvid-?/i, '')
84
+ cleanstr.gsub!(/-?maxspeed/i, '')
85
+ cleanstr.gsub!(/www\.torentz\.3xforum\.ro\.avi/i, '')
86
+ cleanstr.gsub!(/-lol/i, '')
87
+ cleanstr.gsub!(/\+HI/i, '')
88
+ cleanstr.gsub!(/muxed/i, '')
89
+ cleanstr.gsub!(/\(dvd\)/i, '')
90
+ cleanstr.gsub!(/dvdscr/i, '')
91
+ cleanstr.gsub!(/mkv/i, '')
92
+ cleanstr.gsub!(/aqos/i, '')
93
+ cleanstr.gsub!(/ac3/i, '')
94
+ cleanstr.gsub!(/hive/i, '')
95
+ cleanstr.gsub!(/-?cm8/i, '')
96
+ cleanstr.gsub!(/[\d\.]+mb/i, '')
97
+ cleanstr.gsub!(/[\d\.]+gb/i, '')
98
+ cleanstr.gsub!(/\s\s+/, ' ')
99
+ cleanstr.tr!('_', ' ') # underscores
100
+ cleanstr.gsub!(/[\.\+]/, ' ')
101
+ cleanstr.strip!
102
+ cleanstr
103
+ end
104
+ # rubocop:enable Metrics/MethodLength
105
+ # rubocop:enable Metrics/AbcSize
106
+
107
+ def basename(filepath)
108
+ File.basename(filepath, extname(filepath))
109
+ end
110
+
111
+ def extname(filepath)
112
+ File.extname(filepath)
113
+ end
114
+
115
+ def dirname(filepath)
116
+ File.dirname(filepath)
117
+ end
118
+
119
+ def mime_type(filepath)
120
+ file_magic ||= FileMagic.mime
121
+ mime_str = file_magic.file(filepath.to_s)
122
+ mime_str.split(/;/).first
123
+ end
124
+ end
125
+
126
+ def initialize(filename)
127
+ @filename = filename
128
+ end
129
+
130
+ def basename
131
+ self.class.send(:basename, filename)
132
+ end
133
+
134
+ def extname
135
+ self.class.send(:extname, filename)
136
+ end
137
+
138
+ def dirname
139
+ self.class.send(:dirname, filename)
140
+ end
141
+
142
+ def sanitized_basename
143
+ self.class.send(:sanitize, basename)
144
+ end
145
+
146
+ def sanitize(str)
147
+ self.class.send(:sanitize, str)
148
+ end
149
+
150
+ # Determine the supposed year in the filename or directory to match against
151
+ # the release date for a potential movie
152
+ #
153
+ # @return [String] Year derived from the filename or directory
154
+ def derived_year
155
+ @derived_year ||= begin
156
+ md = basename.match(/\((\d\d\d\d)\)|(19\d\d)|(20\d\d)/)
157
+ # try dirname if filename has no year
158
+ md = dirname.match(/\((\d\d\d\d)\)|(19\d\d)|(20\d\d)/) if md.nil?
159
+ md ? md.captures.compact.first : nil
160
+ end
161
+ end
162
+
163
+ def year
164
+ release_date&.year
165
+ end
166
+
167
+ # Groom the filename to reflect a proper naming scheme.
168
+ # Copy file to the configured destination.
169
+ #
170
+ def groom
171
+ process!
172
+ fc = FileCopier.new(filename, target)
173
+ fc.copy!
174
+ end
175
+ end
176
+ end