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
@@ -31,13 +31,16 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency 'rspec', '~> 3.2'
32
32
  spec.add_development_dependency 'rubocop'
33
33
  spec.add_development_dependency 'simplecov'
34
+ spec.add_development_dependency 'titleize', '~> 1.3'
35
+ spec.add_development_dependency 'vcr', '~> 4.0'
36
+ spec.add_development_dependency 'webmock', '~> 3.3'
34
37
 
35
38
  spec.add_runtime_dependency 'colored'
36
39
  spec.add_runtime_dependency 'fileutils'
37
- spec.add_runtime_dependency 'mime-types'
38
40
  spec.add_runtime_dependency 'net-scp', '~> 1.2'
41
+ spec.add_runtime_dependency 'ruby-filemagic', '~> 0.7.2' # Determine mime types
39
42
  spec.add_runtime_dependency 'themoviedb', '~> 1.0'
40
- spec.add_runtime_dependency 'titleize', '~> 1.3'
41
43
  spec.add_runtime_dependency 'trollop'
44
+ # spec.add_runtime_dependency 'tvdbr', '~> 0.2'
42
45
  end
43
46
  # rubocop:enable Metrics/BlockLength
Binary file
@@ -0,0 +1 @@
1
+ this is not a media file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ ---
2
+ :new_media_directories:
3
+ - "~/Videos"
@@ -5,6 +5,8 @@ require 'spec_helper'
5
5
  # rubocop:disable Metrics/BlockLength
6
6
  module MovieOrganizer
7
7
  RSpec.describe FileCopier, type: :lib do
8
+ include_context 'media_shared'
9
+
8
10
  let(:tmp_movie_src) { MovieOrganizer.root.join('spec', 'files', 'movies') }
9
11
  let(:tmp_dest) { MovieOrganizer.root.join('tmp', 'files', 'movies') }
10
12
  test_files = {}
@@ -25,9 +27,10 @@ module MovieOrganizer
25
27
  context 'with a local target' do
26
28
  test_files.each_pair do |src, dst|
27
29
  it "copies [#{src}] to [#{dst}]" do
30
+ expect(File.exist?(src)).to eq(true)
28
31
  expect(File.exist?(dst)).to eq(false)
29
- file_copier = FileCopier.new(src, dst, dry_run: false)
30
- file_copier.copy
32
+ file_copier = FileCopier.new(src, dst)
33
+ file_copier.copy!
31
34
  expect(File.exist?(dst)).to eq(true)
32
35
  end
33
36
  end
@@ -1,49 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/BlockLength
3
+ require 'spec_helper'
4
+
4
5
  module MovieOrganizer
5
6
  RSpec.describe MediaList, type: :lib do
6
7
  include_context 'media_shared'
7
8
 
8
- let(:media_list) { MediaList.new([tmpdir]) }
9
+ let(:media_list) { MediaList.new(Dir[file_fixture_path]) }
9
10
 
10
11
  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)
12
+ it 'only collects video files' do
13
+ good = media_list.file_collection.grep(/good/)
14
+ expect(good.count).to eq(5)
15
+ bad = media_list.file_collection.grep(/bad/)
16
+ expect(bad.count).to eq(0)
45
17
  end
46
18
  end
47
19
  end
48
20
  end
49
- # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ module MovieOrganizer
5
+ RSpec.describe Medium, type: :lib do
6
+ include_context 'media_shared'
7
+
8
+ context '::media_file?' do
9
+ Dir[file_fixture_path.join('bad.*')].each do |vid|
10
+ it "returns false for #{vid}" do
11
+ expect(Medium.media_file?(vid)).to eq(false)
12
+ end
13
+ end
14
+
15
+ Dir[file_fixture_path.join('good.*')].each do |vid|
16
+ it "returns true for #{vid}" do
17
+ expect(Medium.media_file?(vid)).to eq(true)
18
+ end
19
+ end
20
+ end
21
+
22
+ context '::build_instance', vcr: true do
23
+ Dir[file_fixture_path.join('movies', '*')].each do |vid|
24
+ it "returns a Movie instance type for #{vid}" do
25
+ expect(Medium).to receive(:media_file?).and_return(true)
26
+ instance = Medium.build_instance(vid)
27
+ expect(instance).to be_a(Movie)
28
+ end
29
+ end
30
+
31
+ Dir[file_fixture_path.join('tv_shows', '*')].each do |vid|
32
+ it "returns a TvShow instance type for #{vid}" do
33
+ expect(Medium).to receive(:media_file?).and_return(true)
34
+ instance = Medium.build_instance(vid)
35
+ expect(instance).to be_a(TvShow)
36
+ end
37
+ end
38
+ end
39
+
40
+ # context '.subtype' do
41
+ # tv_shows = {
42
+ # 'SnnEnn' => 'The.Walking.Dead.S04E08.HDTV.x264-2HD',
43
+ # 'nxn' => 'The.Walking.Dead.4x0.HDTV.x264-2HD',
44
+ # 'nnxn' => 'The.Walking.Dead.12x8.HDTV.x264-2HD',
45
+ # 'nnxnn' => 'The.Walking.Dead.12x08.HDTV.x264-2HD',
46
+ # 'nxnn' => 'The.Walking.Dead.2x18.HDTV.x264-2HD'
47
+ # }
48
+ #
49
+ # tv_shows.each_pair do |syntax, filename|
50
+ # it "returns a TvShow with season and episode syntax: #{syntax}" do
51
+ # filename = create_test_file(
52
+ # filename: filename,
53
+ # extension: 'mp4'
54
+ # ).first
55
+ # expect(Media.subtype(filename, default_options)).to be_a(TvShow)
56
+ # end
57
+ # end
58
+ #
59
+ # movies = [
60
+ # 'Coco.2017.1080p.BluRay.x264-[BOGUS.IT]',
61
+ # 'Beetlejuice',
62
+ # 'Justice.League.2017.1080p.BluRay.x264-[YTS.AM]',
63
+ # 'Jumanji.Welcome.To.The.Jungle.2017.1080p.WEBRip.x264-[YTS.AM].mp4'
64
+ # ]
65
+ #
66
+ # movies.each do |filename|
67
+ # it "returns a Movie for '#{filename}'" do
68
+ # expect(Tmdb::Movie).to receive(:find).at_least(1).times.and_return([1])
69
+ # filename = create_test_file(
70
+ # filename: filename
71
+ # ).first
72
+ # expect(Media.subtype(filename, default_options)).to be_a(Movie)
73
+ # end
74
+ # end
75
+ #
76
+ # videos = [
77
+ # 'IMG_2052',
78
+ # 'IMG_3322',
79
+ # 'VID_3322',
80
+ # 'VIDEO_3322',
81
+ # 'Our Summer Vacation',
82
+ # 'Tom and Jerry on the Beach'
83
+ # ]
84
+ #
85
+ # videos.each do |filename|
86
+ # it "returns a Video for '#{filename}'" do
87
+ # expect(Tmdb::Movie).to receive(:find).at_least(1).times.and_return([])
88
+ # filename = create_test_file(
89
+ # filename: filename
90
+ # ).first
91
+ # expect(Medium.subtype(filename, default_options)).to be_a(Video)
92
+ # end
93
+ # end
94
+ # end
95
+ end
96
+ end
97
+ # rubocop:enable Metrics/BlockLength
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # rubocop:disable Metrics/BlockLength
4
+ # rubocop:disable Metrics/ModuleLength
4
5
  module MovieOrganizer
5
- RSpec.describe Movie, type: :lib do
6
+ RSpec.describe Movie, type: :lib, vcr: true do
6
7
  include_context 'media_shared'
7
8
 
8
9
  movies = {
@@ -11,9 +12,9 @@ module MovieOrganizer
11
12
  name: 'Coco (2017).mp4'
12
13
  },
13
14
  'Beetlejuice' => {
14
- directory: 'Beetlejuice (1995)',
15
- year: '1995',
16
- name: 'Beetlejuice (1995).mp4'
15
+ directory: 'Beetlejuice (1988)',
16
+ year: '1988',
17
+ name: 'Beetlejuice (1988).mp4'
17
18
  },
18
19
  'Justice.League.2017.1080p.BluRay.x264-[YTS.AM]' => {
19
20
  year: '2017',
@@ -21,7 +22,7 @@ module MovieOrganizer
21
22
  },
22
23
  'Jumanji.Welcome.To.The.Jungle.2017.1080p.WEBRip.x264-[YTS.AM]' => {
23
24
  year: '2017',
24
- name: 'Jumanji Welcome To The Jungle (2017).mp4'
25
+ name: 'Jumanji: Welcome to the Jungle (2017).mp4'
25
26
  },
26
27
  'The.Prestige.2006.m720p.x264' => {
27
28
  year: '2006',
@@ -32,9 +33,9 @@ module MovieOrganizer
32
33
  name: 'Gone in Sixty Seconds (2000).mp4'
33
34
  },
34
35
  'Rain.Man.Br.YIFY' => {
35
- directory: 'Rain Man (2000)',
36
- year: '2000',
37
- name: 'Rain Man (2000).mp4'
36
+ directory: 'Rain Man (1988)',
37
+ year: '1988',
38
+ name: 'Rain Man (1988).mp4'
38
39
  },
39
40
  'Stealth (2005) BDRip 720p x264-muxed' => {
40
41
  year: '2005',
@@ -44,10 +45,10 @@ module MovieOrganizer
44
45
  year: '1973',
45
46
  name: 'The Train Robbers (1973).mp4'
46
47
  },
47
- 'waterhorse720p.x264' => {
48
+ 'the.water.horse.720p.x264' => {
48
49
  year: '2007',
49
50
  directory: 'The Water Horse (2007)',
50
- name: 'The Water Horse (2007)'
51
+ name: 'The Water Horse (2007).mp4'
51
52
  }
52
53
  }
53
54
 
@@ -58,7 +59,9 @@ module MovieOrganizer
58
59
  @filepath = create_test_file(filename: filename, extension: 'mp4', dirname: dirname).first
59
60
  @expected_filename = data[:name]
60
61
  @expected_year = data[:year]
61
- @movie = Movie.new(@filepath, default_options)
62
+ tmdb_instance = Movie.match?(@filepath)
63
+ @movie = Movie.new(@filepath, tmdb_instance)
64
+ @movie.process!
62
65
  end
63
66
 
64
67
  context '.processed_filename' do
@@ -71,7 +74,7 @@ module MovieOrganizer
71
74
 
72
75
  context '.year' do
73
76
  it 'returns the correct year' do
74
- expect(@movie.year).to eq(@expected_year)
77
+ expect(@movie.year).to eq(@expected_year.to_i)
75
78
  end
76
79
  end
77
80
  end
@@ -83,7 +86,10 @@ module MovieOrganizer
83
86
  filename: 'Foreign+Correspondent+(1940)+1080p', extension: 'mp4'
84
87
  ).first
85
88
  end
86
- let(:movie) { Movie.new(filename, default_options) }
89
+ let(:movie) do
90
+ tmdb_instance = Movie.match?(filename)
91
+ Movie.new(filename, tmdb_instance)
92
+ end
87
93
 
88
94
  context '#new' do
89
95
  it 'is a Movie' do
@@ -97,7 +103,7 @@ module MovieOrganizer
97
103
 
98
104
  context '.process!' do
99
105
  it 'moves the file to the configured location' do
100
- settings = Settings.new
106
+ settings = Settings.instance
101
107
  settings[:movies][:directory] = MovieOrganizer.root.join('tmp', 'files', 'movies').to_s
102
108
  settings.save
103
109
  movie.process!
@@ -108,3 +114,4 @@ module MovieOrganizer
108
114
  end
109
115
  end
110
116
  # rubocop:enable Metrics/BlockLength
117
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MovieOrganizer
4
+ # rubocop:disable Metrics/BlockLength
5
+ RSpec.describe TmdbInstance, type: :lib, vcr: true do
6
+ context '#movie?' do
7
+ it 'returns false if the title is not found' do
8
+ instance = TmdbInstance.new('unknown tv show')
9
+ expect(instance.movie?).to eq(false)
10
+ end
11
+
12
+ it 'returns self if the title is found' do
13
+ instance = TmdbInstance.new('the matrix', 1999)
14
+ expect(instance.movie?).to eq(instance)
15
+ end
16
+ end
17
+
18
+ context '#likely_match' do
19
+ context 'when passing a year' do
20
+ it 'returns the first match with a release date matching the year' do
21
+ instance = TmdbInstance.new('star wars', 2015)
22
+ instance.movie?
23
+ match = instance.likely_match
24
+ expect(match.title).to eq('Star Wars: The Force Awakens')
25
+ end
26
+ end
27
+
28
+ context 'when not passing a year' do
29
+ it 'returns the first match' do
30
+ instance = TmdbInstance.new('star wars')
31
+ instance.movie?
32
+ match = instance.likely_match
33
+ expect(match.title).to eq('Star Wars')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ # rubocop:enable Metrics/BlockLength
39
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MovieOrganizer
4
+ RSpec.describe TvdbInstance, type: :lib, vcr: true do
5
+ context '#tv_show?' do
6
+ it 'returns false if the title is not found' do
7
+ instance = TvdbInstance.new('unknown tv show')
8
+ expect(instance.tv_show?).to eq(false)
9
+ end
10
+
11
+ it 'returns self if the title is found' do
12
+ instance = TvdbInstance.new('the walking dead')
13
+ expect(instance.tv_show?).to eq(instance)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'spec_helper'
4
+
3
5
  # rubocop:disable Metrics/BlockLength
4
6
  module MovieOrganizer
5
7
  RSpec.describe Video, type: :lib do
@@ -11,11 +13,11 @@ module MovieOrganizer
11
13
  extension: 'mp4'
12
14
  ).first
13
15
  end
14
- let(:video) { Video.new(filename, default_options) }
16
+ let(:video) { Video.new(filename) }
15
17
 
16
18
  context '#new' do
17
19
  it 'returns a child of the Media class' do
18
- expect(video).to be_a(Media)
20
+ expect(video).to be_a(Medium)
19
21
  end
20
22
 
21
23
  it 'is a Video' do
@@ -35,8 +37,8 @@ module MovieOrganizer
35
37
 
36
38
  context '.process!' do
37
39
  it 'moves the file to the configured location' do
38
- fc = FileCopier.new('/tmp/bogus', '/tmp/bogus2', default_options)
39
- expect(fc).to receive(:copy).and_return(nil)
40
+ fc = FileCopier.new('/tmp/bogus', '/tmp/bogus2')
41
+ expect(fc).to receive(:copy!).and_return(nil)
40
42
  video.process!(fc)
41
43
  end
42
44
  end
@@ -49,18 +49,10 @@ RSpec.describe MovieOrganizer, type: :module do
49
49
  context '#source_directories' do
50
50
  context 'when configured' do
51
51
  it 'returns an array of strings' do
52
- settings = MovieOrganizer::Settings.new
52
+ settings = MovieOrganizer::Settings.instance
53
53
  expect(MovieOrganizer.source_directories(settings)).to be_an(Array)
54
54
  end
55
55
  end
56
-
57
- context 'when not yet configured' do
58
- it 'returns an array of strings' do
59
- blank = MovieOrganizer.root.join('spec', 'fixtures', '.blank_settings.yml')
60
- settings = MovieOrganizer::Settings.new(blank)
61
- expect(MovieOrganizer.source_directories(settings, '~/Videos')).to be_an(Array)
62
- end
63
- end
64
56
  end
65
57
  end
66
58
  # rubocop:enable Metrics/BlockLength
@@ -91,4 +91,9 @@ RSpec.configure do |config|
91
91
  # test failures related to randomization by passing the same `--seed` value
92
92
  # as the one that triggered the failure.
93
93
  Kernel.srand config.seed
94
+
95
+ config.before(:each) do
96
+ options = MovieOrganizer::Options.instance
97
+ options.send(:initialize_hash, dry_run: false, verbose: false, copy: true)
98
+ end
94
99
  end
@@ -50,3 +50,12 @@ shared_context 'media_shared' do
50
50
  end
51
51
  end
52
52
  # rubocop:enable Metrics/BlockLength
53
+
54
+ def file_fixture_path
55
+ MovieOrganizer.root.join('spec', 'files')
56
+ end
57
+
58
+ def stub_tvdb(return_value)
59
+ stub = double('TvdbInstance', :tv_show?)
60
+ allow(TvdbInstance).to receive(:new).and_return(stub)
61
+ end