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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +75 -0
- data/CHANGELOG +3 -0
- data/Gemfile +9 -0
- data/Guardfile +37 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +14 -0
- data/bin/movie_organizer +9 -0
- data/lib/movie_organizer.rb +58 -0
- data/lib/movie_organizer/file_copier.rb +69 -0
- data/lib/movie_organizer/logger.rb +47 -0
- data/lib/movie_organizer/media.rb +95 -0
- data/lib/movie_organizer/media_list.rb +31 -0
- data/lib/movie_organizer/movie.rb +41 -0
- data/lib/movie_organizer/organizer.rb +68 -0
- data/lib/movie_organizer/settings.rb +47 -0
- data/lib/movie_organizer/string.rb +13 -0
- data/lib/movie_organizer/tv_show.rb +96 -0
- data/lib/movie_organizer/version.rb +5 -0
- data/lib/movie_organizer/video.rb +47 -0
- data/movie_organizer.gemspec +43 -0
- data/spec/files/The.Walking.Dead.S04E08.HDTV.x264-2HD.mp4 +0 -0
- data/spec/files/movies/Dunkirk.2017.BluRay.1080p/Dunkirk.2017.BluRay.1080p.mp4 +0 -0
- 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
- data/spec/files/short_video.mp4 +0 -0
- data/spec/fixtures/.blank_settings.yml +0 -0
- data/spec/fixtures/.movie_organizer.yml +19 -0
- data/spec/fixtures/.no_source_directories.yml +15 -0
- data/spec/lib/movie_organizer/file_copier_spec.rb +38 -0
- data/spec/lib/movie_organizer/logger_spec.rb +34 -0
- data/spec/lib/movie_organizer/media_list_spec.rb +49 -0
- data/spec/lib/movie_organizer/media_spec.rb +64 -0
- data/spec/lib/movie_organizer/movie_spec.rb +54 -0
- data/spec/lib/movie_organizer/organizer_spec.rb +35 -0
- data/spec/lib/movie_organizer/tv_show_spec.rb +80 -0
- data/spec/lib/movie_organizer_spec.rb +66 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/support/filename_mappings.yml +10 -0
- data/spec/support/shared_contexts/media_shared.rb +50 -0
- data/tmdb-logo-primary-green.png +0 -0
- metadata +331 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
lib = File.expand_path('../lib', __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require 'movie_organizer/version'
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/BlockLength
|
9
|
+
Gem::Specification.new do |spec|
|
10
|
+
spec.name = 'movie_organizer'
|
11
|
+
spec.version = MovieOrganizer::VERSION
|
12
|
+
spec.authors = ['Chris Blackburn']
|
13
|
+
spec.email = ['87a1779b@opayq.com']
|
14
|
+
spec.summary = 'Automatically organize movies, tv shows and home videos.'
|
15
|
+
spec.description = spec.summary
|
16
|
+
spec.homepage = 'https://github.com/midwire/movie_organizer'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.required_ruby_version = '>= 2.3.0'
|
20
|
+
spec.files = `git ls-files -z`.split("\x0")
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
26
|
+
spec.add_development_dependency 'faker'
|
27
|
+
spec.add_development_dependency 'midwire_common', '~> 1.1'
|
28
|
+
spec.add_development_dependency 'pry', '~> 0.10'
|
29
|
+
spec.add_development_dependency 'pry-nav', '~> 0.2'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'simplecov'
|
34
|
+
|
35
|
+
spec.add_runtime_dependency 'colored'
|
36
|
+
spec.add_runtime_dependency 'dotenv', '~> 1.0'
|
37
|
+
spec.add_runtime_dependency 'mime-types'
|
38
|
+
spec.add_runtime_dependency 'net-scp', '~> 1.2'
|
39
|
+
spec.add_runtime_dependency 'streamio-ffmpeg'
|
40
|
+
spec.add_runtime_dependency 'titleize', '~> 1.3'
|
41
|
+
spec.add_runtime_dependency 'trollop'
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/BlockLength
|
Binary file
|
File without changes
|
File without changes
|
Binary file
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
:eztv_host: https://eztv.ag/
|
3
|
+
:space_warning: 20GB
|
4
|
+
:tv_shows:
|
5
|
+
:directory: "/Volumes/Genesis/TV Series"
|
6
|
+
:my_shows:
|
7
|
+
- Arrow
|
8
|
+
- Fear the Walking Dead
|
9
|
+
- Last Man Standing
|
10
|
+
- Star Trek
|
11
|
+
- The Flash 2014
|
12
|
+
- The Last Ship
|
13
|
+
- The Walking Dead
|
14
|
+
- Vikings
|
15
|
+
- The Blacklist
|
16
|
+
:movies:
|
17
|
+
:directory: "/Users/cblackburn/Code/ruby/Gems/movie_organizer/tmp/files/movies"
|
18
|
+
:new_media_directories:
|
19
|
+
- "/Users/djohnson/Downloads"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
:eztv_host: https://eztv.ag/
|
3
|
+
:space_warning: 20GB
|
4
|
+
:tv_shows:
|
5
|
+
:directory: "/Volumes/Genesis/TV Series"
|
6
|
+
:my_shows:
|
7
|
+
- Fear the Walking Dead
|
8
|
+
- Last Man Standing
|
9
|
+
- Star Trek
|
10
|
+
- The Last Ship
|
11
|
+
- The Walking Dead
|
12
|
+
- Vikings
|
13
|
+
- The Blacklist
|
14
|
+
:movies:
|
15
|
+
:directory: "/Volumes/Qo'noS/Media/- Feature Films 3"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
# rubocop:disable Metrics/BlockLength
|
6
|
+
module MovieOrganizer
|
7
|
+
RSpec.describe FileCopier, type: :lib do
|
8
|
+
let(:tmp_movie_src) { MovieOrganizer.root.join('spec', 'files', 'movies') }
|
9
|
+
let(:tmp_dest) { MovieOrganizer.root.join('tmp', 'files', 'movies') }
|
10
|
+
test_files = {}
|
11
|
+
Dir[MovieOrganizer.root.join('spec', 'files', 'movies', '**', '*')].each do |src|
|
12
|
+
next if File.directory?(src)
|
13
|
+
dir, filename = src.split('/')[-2..999]
|
14
|
+
dest = MovieOrganizer.root.join('tmp', 'files', 'movies').join(dir, filename)
|
15
|
+
test_files[src] = dest.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
before(:each) do
|
19
|
+
test_files.each_pair do |_src, dst|
|
20
|
+
FileUtils.rm_rf(File.dirname(dst))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context '#copy' do
|
25
|
+
context 'with a local target' do
|
26
|
+
test_files.each_pair do |src, dst|
|
27
|
+
it "copies [#{src}] to [#{dst}]" do
|
28
|
+
expect(File.exist?(dst)).to eq(false)
|
29
|
+
file_copier = FileCopier.new(src, dst, dry_run: false)
|
30
|
+
file_copier.copy
|
31
|
+
expect(File.exist?(dst)).to eq(true)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module MovieOrganizer
|
4
|
+
RSpec.describe Logger, type: :lib do
|
5
|
+
|
6
|
+
let(:buffer) { StringIO.new }
|
7
|
+
let(:default_logger) { ::Logger.new(buffer) }
|
8
|
+
let(:logger) { Logger.send(:new, default_logger) }
|
9
|
+
let(:data) { { key: 'value' } }
|
10
|
+
|
11
|
+
context '.log_exception' do
|
12
|
+
it 'logs a formatted exception' do
|
13
|
+
exception = StandardError.new('bogus')
|
14
|
+
exception.set_backtrace(caller)
|
15
|
+
expect(buffer.string).to be_empty
|
16
|
+
logger.log_exception(exception, data)
|
17
|
+
expect(buffer.string).to match(/StandardError\s+:\s+bogus/)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context '.method_missing' do
|
22
|
+
it 'does not call bad_method' do
|
23
|
+
expect { logger.bogus }.to raise_error(NoMethodError)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context '.respond_to?' do
|
28
|
+
it 'does not respond to bad_method' do
|
29
|
+
expect(logger.respond_to?(:bad_method)).to eq(false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
module MovieOrganizer
|
5
|
+
RSpec.describe MediaList, type: :lib do
|
6
|
+
include_context 'media_shared'
|
7
|
+
|
8
|
+
let(:media_list) { MediaList.new([tmpdir]) }
|
9
|
+
|
10
|
+
context '#new' do
|
11
|
+
%w(m4v mov mkv mp4 avi).each do |extension|
|
12
|
+
it "includes #{extension} files" do
|
13
|
+
create_test_file(count: 3, extension: extension)
|
14
|
+
expect(media_list.file_collection.count).to eq(3)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context '.video?' do
|
20
|
+
it 'returns true if passed filename has a video extension' do
|
21
|
+
expect(media_list.video?('bogus.mp4')).to eq(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns false if passed filename isn't a video extension" do
|
25
|
+
expect(media_list.video?('bogus.mp3')).to eq(false)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '.subtitle?' do
|
30
|
+
it 'returns true if passed filename has a subtitle extension' do
|
31
|
+
expect(media_list.subtitle?('bogus.sub')).to eq(true)
|
32
|
+
expect(media_list.subtitle?('bogus.srt')).to eq(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns false if passed filename isn't a subtitle extension" do
|
36
|
+
expect(media_list.subtitle?('bogus.mp3')).to eq(false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context '.media?' do
|
41
|
+
it 'returns true if passed filename has a media extension' do
|
42
|
+
expect(media_list.media?('bogus.srt')).to eq(true)
|
43
|
+
expect(media_list.media?('bogus.mov')).to eq(true)
|
44
|
+
expect(media_list.media?('bogus.mp3')).to eq(false)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
module MovieOrganizer
|
5
|
+
RSpec.describe Media, type: :lib do
|
6
|
+
include_context 'media_shared'
|
7
|
+
|
8
|
+
context '.subtype' do
|
9
|
+
tv_shows = {
|
10
|
+
'SnnEnn' => 'The.Walking.Dead.S04E08.HDTV.x264-2HD',
|
11
|
+
'nxn' => 'The.Walking.Dead.4x0.HDTV.x264-2HD',
|
12
|
+
'nnxn' => 'The.Walking.Dead.12x8.HDTV.x264-2HD',
|
13
|
+
'nnxnn' => 'The.Walking.Dead.12x08.HDTV.x264-2HD',
|
14
|
+
'nxnn' => 'The.Walking.Dead.2x18.HDTV.x264-2HD'
|
15
|
+
}
|
16
|
+
|
17
|
+
tv_shows.each_pair do |syntax, filename|
|
18
|
+
it "returns a TvShow with season and episode syntax: #{syntax}" do
|
19
|
+
filename = create_test_file(
|
20
|
+
filename: filename,
|
21
|
+
extension: 'mp4'
|
22
|
+
).first
|
23
|
+
expect(Media.subtype(filename, default_options)).to be_a(TvShow)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
movies = [
|
28
|
+
'Coco.2017.1080p.BluRay.x264-[BOGUS.IT]',
|
29
|
+
'Beetlejuice',
|
30
|
+
'Justice.League.2017.1080p.BluRay.x264-[YTS.AM]'
|
31
|
+
]
|
32
|
+
|
33
|
+
movies.each do |filename|
|
34
|
+
it "returns a Movie for '#{filename}'" do
|
35
|
+
expect(Tmdb::Movie).to receive(:find).at_least(1).times.and_return([1])
|
36
|
+
filename = create_test_file(
|
37
|
+
filename: filename
|
38
|
+
).first
|
39
|
+
expect(Media.subtype(filename, default_options)).to be_a(Movie)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
videos = [
|
44
|
+
'IMG_2052',
|
45
|
+
'IMG_3322',
|
46
|
+
'VID_3322',
|
47
|
+
'VIDEO_3322',
|
48
|
+
'Our Summer Vacation',
|
49
|
+
'Tom and Jerry on the Beach'
|
50
|
+
]
|
51
|
+
|
52
|
+
videos.each do |filename|
|
53
|
+
it "returns a Video for '#{filename}'" do
|
54
|
+
expect(Tmdb::Movie).to receive(:find).at_least(1).times.and_return([])
|
55
|
+
filename = create_test_file(
|
56
|
+
filename: filename
|
57
|
+
).first
|
58
|
+
expect(Media.subtype(filename, default_options)).to be_a(Video)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
module MovieOrganizer
|
5
|
+
RSpec.describe Movie, type: :lib do
|
6
|
+
include_context 'media_shared'
|
7
|
+
|
8
|
+
let(:filename) do
|
9
|
+
create_test_file(
|
10
|
+
filename: 'Foreign+Correspondent+(1940)+1080p', extension: 'mp4'
|
11
|
+
).first
|
12
|
+
end
|
13
|
+
let(:movie) { Movie.new(filename, default_options) }
|
14
|
+
|
15
|
+
context '#new' do
|
16
|
+
it 'returns a child of the Media class' do
|
17
|
+
expect(movie).to be_a(Media)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'is a Movie' do
|
21
|
+
expect(movie).to be_a(Movie)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets filename accessor' do
|
25
|
+
expect(movie.filename).to eq(filename)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '.processed_filename' do
|
30
|
+
it 'correctly processes the filename' do
|
31
|
+
expect(
|
32
|
+
movie.processed_filename
|
33
|
+
).to eq('Foreign Correspondent (1940).mp4')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context '.year' do
|
38
|
+
it 'returns the year' do
|
39
|
+
expect(movie.year).to eq('1940')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context '.process!' do
|
44
|
+
it 'moves the file to the configured location' do
|
45
|
+
settings = Settings.new
|
46
|
+
settings[:movies][:directory] = MovieOrganizer.root.join('tmp', 'files', 'movies').to_s
|
47
|
+
settings.save
|
48
|
+
movie.process!
|
49
|
+
expect(File.exist?(filename)).to eq(true)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module MovieOrganizer
|
2
|
+
RSpec.describe Organizer, type: :lib do
|
3
|
+
include_context 'media_shared'
|
4
|
+
|
5
|
+
let(:organizer) { Organizer.instance }
|
6
|
+
|
7
|
+
context '#instance' do
|
8
|
+
it "calls 'initialize'" do
|
9
|
+
expect(Organizer).to receive(:new)
|
10
|
+
organizer
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'instantiates the logger' do
|
14
|
+
organizer.logger
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context '.start' do
|
19
|
+
let(:args) do
|
20
|
+
{
|
21
|
+
source_dir: tmpdir, help: false, source_dir_given: true
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
create_test_file(filename: 'Beetlejuice', extension: 'mp4')
|
27
|
+
end
|
28
|
+
|
29
|
+
# it 'collects the arguments' do
|
30
|
+
# expect(organizer).to receive(:collect_args).and_return(args)
|
31
|
+
# organizer.start
|
32
|
+
# end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
module MovieOrganizer
|
5
|
+
RSpec.describe TvShow, type: :lib do
|
6
|
+
include_context 'media_shared'
|
7
|
+
|
8
|
+
let(:filename) do
|
9
|
+
create_test_file(
|
10
|
+
filename: 'The.Walking.Dead.S05E02.720p.HDTV.x264-KILLERS',
|
11
|
+
extension: 'mkv'
|
12
|
+
).first
|
13
|
+
end
|
14
|
+
let(:tv_show) { TvShow.new(filename, default_options) }
|
15
|
+
|
16
|
+
context '#new' do
|
17
|
+
it 'returns a child of the Media class' do
|
18
|
+
expect(tv_show).to be_a(Media)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'is a TvShow' do
|
22
|
+
expect(tv_show).to be_a(TvShow)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'sets filename accessor' do
|
26
|
+
expect(tv_show.filename).to eq(filename)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context '.processed_filename' do
|
31
|
+
file = File.join(MovieOrganizer.root, 'spec', 'support', 'filename_mappings.yml')
|
32
|
+
|
33
|
+
context 'correctly maps the following with preserve_episode_name == false' do
|
34
|
+
mapped = YAML.load_file(file)['tvshows_no_preserve']
|
35
|
+
mapped.each do |from, to|
|
36
|
+
it "maps '#{from}' to [#{to}]" do
|
37
|
+
tv_show = TvShow.new(
|
38
|
+
from,
|
39
|
+
default_options.merge(preserve_episode_name: false)
|
40
|
+
)
|
41
|
+
expect(
|
42
|
+
tv_show.processed_filename
|
43
|
+
).to eq(to)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'correctly maps the following with preserve_episode_name == true' do
|
49
|
+
mapped = YAML.load_file(file)['tvshows_preserve']
|
50
|
+
mapped.each do |from, to|
|
51
|
+
it "maps '#{from}' to [#{to}]" do
|
52
|
+
tv_show = TvShow.new(
|
53
|
+
from,
|
54
|
+
default_options.merge(preserve_episode_name: true)
|
55
|
+
)
|
56
|
+
expect(
|
57
|
+
tv_show.processed_filename
|
58
|
+
).to eq(to)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context '.process!' do
|
65
|
+
it 'moves the file to the configured location' do
|
66
|
+
settings = Settings.new
|
67
|
+
binding.pry if settings.nil?
|
68
|
+
target_dir = File.join(
|
69
|
+
settings[:tv_shows][:directory],
|
70
|
+
tv_show.title,
|
71
|
+
"Season #{tv_show.season.sub(/^0+/, '')}"
|
72
|
+
)
|
73
|
+
expect(FileUtils).to receive(:mkdir_p).with(target_dir, noop: true).and_return(nil)
|
74
|
+
expect(FileUtils).to receive(:move).and_return(nil)
|
75
|
+
tv_show.process!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# rubocop:enable Metrics/BlockLength
|