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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -0
- data/CHANGELOG +10 -0
- data/Gemfile +2 -1
- data/README.md +27 -14
- data/lib/movie_organizer.rb +33 -14
- data/lib/movie_organizer/file_copier.rb +33 -19
- data/lib/movie_organizer/logger.rb +2 -0
- data/lib/movie_organizer/media_list.rb +1 -15
- data/lib/movie_organizer/medium.rb +176 -0
- data/lib/movie_organizer/movie.rb +50 -18
- data/lib/movie_organizer/options.rb +23 -0
- data/lib/movie_organizer/organizer.rb +29 -11
- data/lib/movie_organizer/settings.rb +3 -0
- data/lib/movie_organizer/string.rb +8 -0
- data/lib/movie_organizer/tmdb_instance.rb +58 -0
- data/lib/movie_organizer/tv_show.rb +51 -37
- data/lib/movie_organizer/tvdb_instance.rb +31 -0
- data/lib/movie_organizer/version.rb +1 -1
- data/lib/movie_organizer/video.rb +39 -11
- data/movie_organizer.gemspec +5 -2
- data/spec/files/Dunkirk.2017.BluRay.1080p.mp4 +0 -0
- data/spec/files/bad.flv +0 -0
- data/spec/files/bad.mp4 +1 -0
- data/spec/files/good.3gp +0 -0
- data/spec/files/good.mov +0 -0
- data/spec/files/good.mp4 +0 -0
- data/spec/files/good.ogv +0 -0
- data/spec/files/good.webm +0 -0
- data/spec/files/{The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4 → tv_shows/The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4} +0 -0
- data/spec/fixtures/.blank_settings.yml +3 -0
- data/spec/lib/movie_organizer/file_copier_spec.rb +5 -2
- data/spec/lib/movie_organizer/media_list_spec.rb +8 -37
- data/spec/lib/movie_organizer/medium_spec.rb +97 -0
- data/spec/lib/movie_organizer/movie_spec.rb +21 -14
- data/spec/lib/movie_organizer/tmdb_instance_spec.rb +39 -0
- data/spec/lib/movie_organizer/tvdb_instance_spec.rb +17 -0
- data/spec/lib/movie_organizer/video_spec.rb +6 -4
- data/spec/lib/movie_organizer_spec.rb +1 -9
- data/spec/spec_helper.rb +5 -0
- data/spec/support/shared_contexts/media_shared.rb +9 -0
- data/spec/support/vcr.rb +17 -0
- metadata +74 -21
- data/lib/movie_organizer/media.rb +0 -110
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5157291fa0d5ccda33cc68f1af1f09a0a58fd42474b8e676fe838c2c1bc77964
|
4
|
+
data.tar.gz: 18a25b3e745211e672de63c45d5fce5596f32078c2efe35f7f59fd91417e5c26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cc23bf91f29c382261e349a7cb8d2e765a933f3ab0afe038d5263fe2fa749bd9af6c6c8f4b8102e03a11036a5a4783771a96337b35dda5a4076a6d0418c72d4
|
7
|
+
data.tar.gz: fccf79f2d5390c38f98cf10865688aa9365a81d26d09f5edfdb72a036d8ab903a3a1e44db74cf6ca609262955435272a5844fb94876be242edde56b44bde27e3
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
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 '
|
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.
|
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
|
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
|
-
|
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>
|
71
|
-
-
|
72
|
-
-
|
73
|
-
-v, --verbose
|
74
|
-
-h, --help
|
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
|
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.
|
data/lib/movie_organizer.rb
CHANGED
@@ -27,7 +27,23 @@ module MovieOrganizer
|
|
27
27
|
#:nocov:
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.
|
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.
|
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.
|
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.
|
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.
|
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,
|
84
|
-
autoload :Logger,
|
85
|
-
autoload :
|
86
|
-
autoload :MediaList,
|
87
|
-
autoload :Movie,
|
88
|
-
autoload :
|
89
|
-
autoload :
|
90
|
-
autoload :
|
91
|
-
autoload :
|
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
|
8
|
-
attr_reader :username, :hostname, :remote_filename
|
7
|
+
attr_accessor :filename, :target_file
|
8
|
+
attr_reader :username, :hostname, :remote_filename
|
9
9
|
|
10
|
-
def initialize(filename, target_file
|
10
|
+
def initialize(filename, target_file)
|
11
11
|
@filename = filename
|
12
12
|
@target_file = target_file
|
13
|
-
@
|
14
|
-
@
|
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
|
-
|
25
|
-
FileUtils.
|
26
|
-
|
27
|
-
target_file
|
28
|
-
|
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
|
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
|
40
|
-
|
41
|
-
|
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(
|
83
|
+
ssh.exec!(create_remote_dir_cmd)
|
71
84
|
end
|
72
85
|
rescue Net::SSH::ConnectionTimeout, Errno::EHOSTUNREACH, Errno::EHOSTDOWN
|
73
|
-
|
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
|
-
|
95
|
+
Logger.instance.error("ConnectionTimeout: the host '#{hostname}' is unreachable.".red)
|
82
96
|
end
|
83
97
|
end
|
84
98
|
end
|
@@ -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
|
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
|