moviesort 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ #!/usr/bin/env ruby
2
+ lib_path = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. lib]))
3
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
4
+
5
+ require 'moviesort/command_line'
6
+ Moviesort::CommandLine.new.run
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[.. lib]))
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'fakefs'
@@ -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