moviesort 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/bin/moviesort +6 -0
- data/features/sorting_tv_shows.feature +22 -0
- data/features/step_definitions/sorting_tv_shows_steps.rb +28 -0
- data/features/support/env.rb +15 -0
- data/lib/moviesort/command_line.rb +30 -0
- data/lib/moviesort/sorter.rb +18 -0
- data/lib/moviesort/tv_show.rb +53 -0
- data/test/sorter_test.rb +25 -0
- data/test/test_helper.rb +4 -0
- data/test/tv_show_test.rb +50 -0
- metadata +69 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'jeweler'
|
2
|
+
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "moviesort"
|
5
|
+
gemspec.summary = "Small command-line utility for sorting downloaded TV shows and movies"
|
6
|
+
gemspec.email = "luke@lukeredpath.co.uk"
|
7
|
+
gemspec.authors = ["Luke Redpath"]
|
8
|
+
end
|
9
|
+
|
10
|
+
Jeweler::GemcutterTasks.new
|
11
|
+
|
12
|
+
task :release => "gemcutter:release"
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/moviesort
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Feature: Sorting a folder of TV show episodes
|
2
|
+
In order to keep my TV shows organised
|
3
|
+
As a person trying to keep their media server in order
|
4
|
+
I want to be able to sort my downloaded TV episodes into folders
|
5
|
+
|
6
|
+
Scenario: First episode of a new TV show
|
7
|
+
Given a folder "/movies/new" containing the file "The Sopranos - S01E01 - Pilot.avi"
|
8
|
+
When I run Moviesort with the options "--source /movies/new --target /movies/tv"
|
9
|
+
Then the movie should be moved to "/movies/tv/The Sopranos/Season 1/The.Sopranos.S01E01.avi"
|
10
|
+
|
11
|
+
Scenario: First episode of a new season of an existing TV show
|
12
|
+
Given a folder "/movies/new" containing the file "The Sopranos - S06E01 - Members Only.avi"
|
13
|
+
And a folder "/movies/tv/The Sopranos" containing the folder "Season 1"
|
14
|
+
When I run Moviesort with the options "--source /movies/new --target /movies/tv"
|
15
|
+
Then the movie should be moved to "/movies/tv/The Sopranos/Season 6/The.Sopranos.S06E01.avi"
|
16
|
+
And the folder "/movies/tv/The Sopranos/Season 1" should exist
|
17
|
+
|
18
|
+
Scenario: Another episode of an existing season of an existing TV show
|
19
|
+
Given a folder "/movies/new" containing the file "The Sopranos - S01E02 - 46 Long.avi"
|
20
|
+
And a folder "/movies/tv/The Sopranos/Season 1" containing the file "The.Sopranos.S01E01.avi"
|
21
|
+
When I run Moviesort with the options "--source /movies/new --target /movies/tv"
|
22
|
+
Then the movie should be moved to "/movies/tv/The Sopranos/Season 1/The.Sopranos.S01E02.avi"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'moviesort/command_line'
|
2
|
+
|
3
|
+
### givens
|
4
|
+
|
5
|
+
Given /^a folder "([^\"]*)" containing the file "([^\"]*)"$/ do |parent, file|
|
6
|
+
FileUtils.mkdir_p(parent)
|
7
|
+
FileUtils.touch(File.join(parent, file))
|
8
|
+
end
|
9
|
+
|
10
|
+
Given /^a folder "([^\"]*)" containing the folder "([^\"]*)"$/ do |parent, folder|
|
11
|
+
FileUtils.mkdir_p(File.join(parent, folder))
|
12
|
+
end
|
13
|
+
|
14
|
+
## whens
|
15
|
+
|
16
|
+
When /^I run Moviesort with the options "([^\"]*)"$/ do |options|
|
17
|
+
Moviesort::CommandLine.parse(options).run
|
18
|
+
end
|
19
|
+
|
20
|
+
## thens
|
21
|
+
|
22
|
+
Then /^the movie should be moved to "([^\"]*)"$/ do |path|
|
23
|
+
assert File.exist?(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
Then /^the folder "([^\"]*)" should exist$/ do |dir|
|
27
|
+
assert File.directory?(dir)
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'test/unit/assertions'
|
2
|
+
require 'fakefs/safe'
|
3
|
+
|
4
|
+
World(Test::Unit::Assertions)
|
5
|
+
|
6
|
+
Before do
|
7
|
+
FakeFS.activate!
|
8
|
+
end
|
9
|
+
|
10
|
+
After do
|
11
|
+
FakeFS::FileSystem.clear
|
12
|
+
FakeFS.deactivate!
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[.. .. lib]))
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'trollop'
|
2
|
+
require 'moviesort/sorter'
|
3
|
+
|
4
|
+
module Moviesort
|
5
|
+
class CommandLine
|
6
|
+
def self.parse(options)
|
7
|
+
new(options.split)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(argv = ARGV)
|
11
|
+
@options = Trollop.options(argv) do
|
12
|
+
banner <<-EOS.gsub(/^ {8}/, '')
|
13
|
+
Moviesort is a script to sort and file away your TV shows and movies
|
14
|
+
|
15
|
+
Usage: moviesort [options]
|
16
|
+
EOS
|
17
|
+
|
18
|
+
opt :source, "Source directory", :default => '.'
|
19
|
+
opt :target, "Target directory", :default => '.'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
sorter = Sorter::TV.new(@options[:target])
|
25
|
+
Dir[File.join(@options[:source], '*.avi')].each do |file|
|
26
|
+
sorter.sort_file(file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'moviesort/tv_show'
|
2
|
+
|
3
|
+
module Moviesort
|
4
|
+
module Sorter
|
5
|
+
class TV
|
6
|
+
def initialize(output_dir)
|
7
|
+
@output_dir = output_dir
|
8
|
+
end
|
9
|
+
|
10
|
+
def sort_file(path)
|
11
|
+
tv_show = Moviesort::TVShow.parse(File.basename(path))
|
12
|
+
target_path = File.join(@output_dir, tv_show.target_path)
|
13
|
+
FileUtils.mkdir_p(target_path)
|
14
|
+
FileUtils.mv(path, File.join(target_path, tv_show.target_filename))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Moviesort
|
4
|
+
class TVShow
|
5
|
+
attr_reader :show, :season, :episode
|
6
|
+
|
7
|
+
def initialize(filename, show, season, episode)
|
8
|
+
@show = show
|
9
|
+
@season = season
|
10
|
+
@episode = episode
|
11
|
+
@extension = File.extname(filename)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(filename)
|
15
|
+
parser = Parser.new(filename)
|
16
|
+
new(filename, *parser.parse)
|
17
|
+
end
|
18
|
+
|
19
|
+
def target_path
|
20
|
+
Pathname.new(File.join(show, "Season #{season}"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def target_filename
|
24
|
+
"#{@show.gsub(/\W/, '.')}.S%.2iE%.2i#{@extension}" % [season, episode]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
class Parser
|
30
|
+
def initialize(filename)
|
31
|
+
@filename = sanitize(filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse
|
35
|
+
patterns = [
|
36
|
+
/(.*)\sS(\d+)E(\d+)/i,
|
37
|
+
/(.*)\s(\d{1,2})x(\d+)/,
|
38
|
+
/(.*)\s(\d{1})(\d{2})/
|
39
|
+
]
|
40
|
+
match = patterns.map { |regex| @filename.match(regex) }.compact.first
|
41
|
+
extract_data_from_pattern_match(match)
|
42
|
+
end
|
43
|
+
|
44
|
+
def extract_data_from_pattern_match(match)
|
45
|
+
[match[1], *match[2..3].map { |s| s.to_i }]
|
46
|
+
end
|
47
|
+
|
48
|
+
def sanitize(filename)
|
49
|
+
filename.gsub(/\./, ' ').gsub(/-/, ' ').squeeze(' ')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/test/sorter_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'moviesort/sorter'
|
3
|
+
|
4
|
+
class SorterTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@output_path = "/output"
|
8
|
+
@download_path = "/downloads"
|
9
|
+
FileUtils.mkpath(@output_path)
|
10
|
+
FileUtils.mkpath(@download_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
context "TV show sorter" do
|
14
|
+
setup do
|
15
|
+
@sorter = Moviesort::Sorter::TV.new(@output_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
should "move a given TV show to its target path under the root path" do
|
19
|
+
FileUtils.touch("/downloads/The Sopranos - S02E04.avi")
|
20
|
+
@sorter.sort_file("/downloads/The Sopranos - S02E04.avi")
|
21
|
+
assert File.exist?("/output/The Sopranos/Season 2/The.Sopranos.S02E04.avi")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'moviesort/tv_show'
|
3
|
+
|
4
|
+
class TvShowTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
context "A TV show" do
|
7
|
+
setup do
|
8
|
+
@tv_show = Moviesort::TVShow.new("The.Sopranos.102.avi", "The Sopranos", season = 1, episode = 2)
|
9
|
+
end
|
10
|
+
|
11
|
+
should "have a target path based on the show name and season" do
|
12
|
+
assert_equal Pathname.new("The Sopranos/Season 1"), @tv_show.target_path
|
13
|
+
end
|
14
|
+
|
15
|
+
should "have a filename based on the show name and season with the original file extension" do
|
16
|
+
assert_equal "The.Sopranos.S01E02.avi", @tv_show.target_filename
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "When parsing" do
|
21
|
+
should "handle file in the format SHOW NAME - SXXEXX.xxx" do
|
22
|
+
@tv_show = Moviesort::TVShow.parse("The Sopranos - S01E02.avi")
|
23
|
+
assert_equal "The Sopranos", @tv_show.show
|
24
|
+
assert_equal 1, @tv_show.season
|
25
|
+
assert_equal 2, @tv_show.episode
|
26
|
+
end
|
27
|
+
|
28
|
+
should "handle file in the format SHOW NAME - sXXeXX.xxx" do
|
29
|
+
@tv_show = Moviesort::TVShow.parse("The Sopranos - s01e02.avi")
|
30
|
+
assert_equal "The Sopranos", @tv_show.show
|
31
|
+
assert_equal 1, @tv_show.season
|
32
|
+
assert_equal 2, @tv_show.episode
|
33
|
+
end
|
34
|
+
|
35
|
+
should "handle file in the format SHOW NAME - SxE.xxx" do
|
36
|
+
@tv_show = Moviesort::TVShow.parse("The Sopranos - 1x02.avi")
|
37
|
+
assert_equal "The Sopranos", @tv_show.show
|
38
|
+
assert_equal 1, @tv_show.season
|
39
|
+
assert_equal 2, @tv_show.episode
|
40
|
+
end
|
41
|
+
|
42
|
+
should "handle file in the format SHOW.NAME.XYY.xxx" do
|
43
|
+
@tv_show = Moviesort::TVShow.parse("The.Sopranos.102.avi")
|
44
|
+
assert_equal "The Sopranos", @tv_show.show
|
45
|
+
assert_equal 1, @tv_show.season
|
46
|
+
assert_equal 2, @tv_show.episode
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: moviesort
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Redpath
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-14 00:00:00 +00:00
|
13
|
+
default_executable: moviesort
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: luke@lukeredpath.co.uk
|
18
|
+
executables:
|
19
|
+
- moviesort
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- Rakefile
|
27
|
+
- VERSION
|
28
|
+
- bin/moviesort
|
29
|
+
- features/sorting_tv_shows.feature
|
30
|
+
- features/step_definitions/sorting_tv_shows_steps.rb
|
31
|
+
- features/support/env.rb
|
32
|
+
- lib/moviesort/command_line.rb
|
33
|
+
- lib/moviesort/sorter.rb
|
34
|
+
- lib/moviesort/tv_show.rb
|
35
|
+
- test/sorter_test.rb
|
36
|
+
- test/test_helper.rb
|
37
|
+
- test/tv_show_test.rb
|
38
|
+
has_rdoc: true
|
39
|
+
homepage:
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options:
|
44
|
+
- --charset=UTF-8
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.5
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: Small command-line utility for sorting downloaded TV shows and movies
|
66
|
+
test_files:
|
67
|
+
- test/sorter_test.rb
|
68
|
+
- test/test_helper.rb
|
69
|
+
- test/tv_show_test.rb
|