tankobon 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.dirname(File.dirname(__FILE__)), "lib")
3
+
4
+ require "rubygems"
5
+ require "tankobon"
6
+ require "trollop"
7
+
8
+ opts = Trollop::options do
9
+ version "tankobon #{Tankobon::VERSION} (c) 2010 Stefano Pigozzi"
10
+ banner <<-EOS
11
+ This program lets you batch rename, resize and change colorspace for manga scans
12
+ in order to make them optimal for consumption on ebook readers.
13
+
14
+ Usage:
15
+ tankobon [options] [<filename>]
16
+ where [<filename>] is the recipe file to make your Tankobons and [options] are:
17
+ EOS
18
+ end
19
+
20
+ begin
21
+ if ARGV.length.eql? 1 then
22
+ recipe do
23
+ eval(File.new(File.expand_path(ARGV[0])).read,
24
+ binding, __FILE__, __LINE__)
25
+ end
26
+ exit
27
+ else
28
+ Tankobon::CLI.message "Please provide a recipe file."
29
+ end
30
+ rescue Interrupt => e
31
+ Tankobon::CLI.message "\nRecived an interrupt. Quitting."
32
+ exit
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ require 'tankobon/ext/string'
5
+ require 'tankobon/ext/dir'
6
+ require 'tankobon/ext/file'
7
+ require 'tankobon/transform'
8
+ require 'tankobon/converter'
9
+ require 'tankobon/version'
10
+
11
+ module Tankobon
12
+ autoload :Application, 'tankobon/application'
13
+ autoload :Archive, 'tankobon/archive'
14
+ autoload :CLI, 'tankobon/cli'
15
+ autoload :Directory, 'tankobon/directory'
16
+ autoload :Stageable, 'tankobon/stageable'
17
+ autoload :StagedDirectory, 'tankobon/stageddirectory'
18
+ end
19
+
20
+ require 'tankobon/dsl'
21
+ include Tankobon::DSL
@@ -0,0 +1,65 @@
1
+ module Tankobon
2
+ class Application
3
+
4
+ def initialize
5
+ no_batch
6
+ end
7
+
8
+ def batch(name)
9
+ if name then
10
+ @batch = name
11
+ @archive = batched_class(Archive)
12
+ @directory = batched_class(Directory)
13
+ else
14
+ no_batch
15
+ end
16
+ self
17
+ end
18
+
19
+ def with_base(base, ary)
20
+ with(ary.map{|x| File.join(base, x)})
21
+ end
22
+
23
+ def with(ary)
24
+ @staged_ary = ary.map do |elem|
25
+ obj = File.archive?(elem) ? @archive : @directory
26
+ obj.new(elem).to_stage
27
+ end
28
+ self
29
+ end
30
+
31
+ def sanitize(transforms = [SanitizeTransform, SequenceTransform])
32
+ staged_elems.each do |sd|
33
+ transforms.each {|tx| sd.rename_images(&tx.new)}
34
+ sd.mv_images_to_root
35
+ sd.clean
36
+ end
37
+ self
38
+ end
39
+
40
+ def convert(converters = [KindleDXConverter])
41
+ staged_elems.each do |sd|
42
+ converters.each {|tx| sd.convert_images(&tx.new)}
43
+ end
44
+ self
45
+ end
46
+
47
+ private
48
+ def batched_class(klass)
49
+ Class.new(klass) do
50
+ def stage_root; super.stage_root + @batch; end
51
+ end
52
+ end
53
+
54
+ def no_batch
55
+ @batch = nil
56
+ @archive = Archive
57
+ @directory = Directory
58
+ end
59
+
60
+ def staged_elems
61
+ raise "You called sanitize or convert before with." unless @staged_ary
62
+ @batch ? File.dirname(@staged_ary[0]) : @staged_ary
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,10 @@
1
+ module Tankobon
2
+ class Archive < Stageable
3
+ def to_stage
4
+ super do
5
+ FileUtils.mkdir_p(stage) unless stage.exist?
6
+ %x{cd "#{stage}" && 7za x -r "#{stageable}"}
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ module Tankobon
2
+ class CLI
3
+ def self.message(string)
4
+ puts string
5
+ end
6
+
7
+ def self.progress(progress)
8
+ cr = "\r"
9
+ clear = "\e[0K"
10
+ reset = cr + clear
11
+ print "#{reset}"
12
+ print "#{progress}"
13
+ $stdout.flush
14
+ end
15
+
16
+ def self.done()
17
+ cr = "\r"
18
+ clear = "\e[0K"
19
+ reset = cr + clear
20
+
21
+ print " Done.\n"
22
+ $stdout.flush
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,53 @@
1
+ module Tankobon
2
+ class Converter
3
+ def initialize
4
+ utils = [:identify, :convert]
5
+ utils.each do |util|
6
+ raise "Fix your ImageMagick installation, #{util} not in your path." if
7
+ %x[which #{util}].strip == ""
8
+ end
9
+ end
10
+
11
+ def to_proc
12
+ method(:convert).to_proc
13
+ end
14
+
15
+ def size
16
+ "#{width}x#{height}"
17
+ end
18
+
19
+ def will_rotate?(file)
20
+ w, h = %x[identify -format "%wx%h" "#{file}"]
21
+ .split('x').map{|x| x.to_i}
22
+ w > h
23
+ end
24
+
25
+ def rotation(file)
26
+ will_rotate?(file) ? "-rotate 90" : ""
27
+ end
28
+
29
+ def convert(file)
30
+ %x[convert "#{file}" -format #{format} #{rotation(file)} \
31
+ -colorspace #{colorspace} -resize #{size} -background white \
32
+ -gravity center -extent #{size} "#{converted_name(file)}"
33
+ ]
34
+ clean(file)
35
+ end
36
+
37
+ protected
38
+ def clean(file)
39
+ File.delete(file) unless file == converted_name(file)
40
+ end
41
+
42
+ def converted_name(file)
43
+ File.join(File.dirname(file), "#{File.splitbase(file)[0]}.#{format}")
44
+ end
45
+ end
46
+
47
+ class KindleDXConverter < Converter
48
+ def width; 824; end
49
+ def height; 1200; end
50
+ def format; "jpg"; end
51
+ def colorspace; "Gray"; end
52
+ end
53
+ end
@@ -0,0 +1,9 @@
1
+ module Tankobon
2
+ class Directory < Stageable
3
+ def to_stage
4
+ super do
5
+ FileUtils.cp_r(@stageable, stage)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Tankobon
2
+ module DSL
3
+ def recipe (&block)
4
+ app = Tankobon::Application.new
5
+ block.arity < 1 ? app.instance_eval(&block) : block.call(app) if
6
+ block_given?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class Dir
2
+ def self.xplore(dir, &block)
3
+ block = Proc.new {|f| puts "got: #{f}"} if block.nil?
4
+ Dir.foreach(dir) do |f|
5
+ next if f[0] == '.'
6
+ if File.directory? File.join(dir, f)
7
+ Dir.xplore(File.join(dir, f), &block)
8
+ block.call(File.join(dir, f))
9
+ else
10
+ block.call(File.join(dir, f))
11
+ end
12
+ end
13
+ end
14
+
15
+ def self.empty?(dir)
16
+ entries = Dir.entries(dir)
17
+ entries.length < 3 and entries.include? '.' and entries.include? '..'
18
+ end
19
+ end
@@ -0,0 +1,42 @@
1
+ class File
2
+ def self.xform(file_name, &block)
3
+ base_name, _ = File.splitbase(file_name)
4
+ block = Proc.new {|name| name} if not block_given?
5
+ rnbase(file_name, block.call(base_name))
6
+ end
7
+
8
+ def self.bubble_mv(root_dir, file)
9
+ return if root_dir.eql? File.dirname(file)
10
+ new_name = "#{File.basename(File.dirname(file))}-#{File.basename(file)}"
11
+ new_full_name = File.join(
12
+ File.dirname(File.dirname(file)),
13
+ new_name)
14
+ File.rename(file, new_full_name)
15
+ self.bubble_mv(root_dir, new_full_name)
16
+ end
17
+
18
+ def self.rnbase(file, new_name)
19
+ File.rename(file,
20
+ File.join(File.dirname(file), "#{new_name}#{File.extname(file)}"))
21
+ end
22
+
23
+ def self.splitbase(file)
24
+ [File.basename(file, ".*"), File.extname(file)]
25
+ end
26
+
27
+ def self.image?(file)
28
+ image_extensions.include? File.extname(file).tail
29
+ end
30
+
31
+ def self.image_extensions
32
+ ['jpg', 'jpeg', 'png', 'gif']
33
+ end
34
+
35
+ def self.archive?(file)
36
+ archive_extensions.include? File.extname(file).tail
37
+ end
38
+
39
+ def self.archive_extensions
40
+ ['zip', 'rar', 'cbz', 'cbr']
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def tail
3
+ self[1, length]
4
+ end
5
+
6
+ def head
7
+ self[0]
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+
3
+ module Tankobon
4
+ class Stageable
5
+ def initialize(stageable)
6
+ @stageable = Pathname.new(stageable)
7
+ end
8
+
9
+ def stage_root
10
+ Pathname.new("~/tankobon2").expand_path
11
+ end
12
+
13
+ def stage
14
+ stage_root + @stageable.basename('.*')
15
+ end
16
+
17
+ def stageable
18
+ @stageable.expand_path
19
+ end
20
+
21
+ def to_stage(&block)
22
+ FileUtils.mkdir_p(stage.dirname) unless stage.dirname.exist?
23
+ FileUtils.rm_r(stage) if stage.exist?
24
+ yield if block_given?
25
+ StagedDirectory.new(stage)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ module Tankobon
2
+ class StagedDirectory
3
+ attr_reader :stage
4
+
5
+ def initialize(stage)
6
+ @stage = Pathname.new(stage)
7
+ end
8
+
9
+ def rename(what, &block)
10
+ send(what).each do |file|
11
+ File.xform(file, &block)
12
+ end
13
+ self
14
+ end
15
+
16
+ def mv_images_to(directory)
17
+ images.each do |file|
18
+ FileUtils.mv(file, Pathname.new(directory) + File.basename(file))
19
+ end
20
+ self
21
+ end
22
+
23
+ def mv_images_to_root
24
+ mv_images_to stage
25
+ self
26
+ end
27
+
28
+ def clean
29
+ all_in_root.each do |file|
30
+ FileUtils.rm_r(file) unless File.image?(file)
31
+ end
32
+ self
33
+ end
34
+
35
+ def all
36
+ join_stage stage_glob(File.join("**", "*")).sort.reverse
37
+ end
38
+
39
+ def images
40
+ images_wildcard = "*.{#{File.image_extensions.join(",")}}"
41
+ join_stage stage_glob(File.join("**", images_wildcard)).sort
42
+ end
43
+
44
+ def all_in_root
45
+ join_stage stage_glob("*").sort
46
+ end
47
+
48
+ def convert_images(&conversion)
49
+ images.each do |file|
50
+ yield(file) if block_given?
51
+ end
52
+ end
53
+
54
+ def method_missing(name, *args, &block)
55
+ if name =~ /^rename_(\w+)$/
56
+ rename($1.to_sym, &block)
57
+ elsif
58
+ super
59
+ end
60
+ end
61
+
62
+ private
63
+ def stage_glob(pattern)
64
+ Dir.chdir(stage){ Dir.glob(pattern) }
65
+ end
66
+
67
+ def join_stage(ary)
68
+ ary.map{|x| stage + x}.map(&:to_s)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,48 @@
1
+ module Tankobon
2
+ class Transform
3
+ def to_proc
4
+ method(:transform).to_proc
5
+ end
6
+
7
+ def transform(input)
8
+ input
9
+ end
10
+ end
11
+
12
+ class SequenceTransform < Transform
13
+ def initialize(seq=-1, padding=5)
14
+ @seq = seq
15
+ @padding = padding
16
+ end
17
+
18
+ def pad(num)
19
+ "%0#{@padding}d" % num
20
+ end
21
+
22
+ def transform(input)
23
+ pad(@seq += 1)
24
+ end
25
+ end
26
+
27
+ class SanitizeTransform < Transform
28
+ def initialize(padding=5)
29
+ @padding = padding
30
+ end
31
+
32
+ def pad(num)
33
+ "%0#{@padding}d-" % num
34
+ end
35
+
36
+ def transform(input)
37
+ if not input =~ /[0-9]+/ then
38
+ "#{pad(0)}#{input}"
39
+ else
40
+ input.gsub(/([0-9]+)/){pad($1.to_i)}
41
+ .gsub(/([^0-9\-]+)/){""}
42
+ .gsub(/(\-+)/){"-"}
43
+ .gsub(/(\-$)/){""}
44
+ .gsub(/(^\-)/){""}
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Tankobon
2
+ VERSION = "0.5.0" unless defined?(::Tankobon::VERSION)
3
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tankobon
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Stefano Pigozzi
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-05-07 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: trollop
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 16
31
+ - 0
32
+ version: 1.16.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Script for resizing manga scans for the e-book readers
36
+ email: stefano.pigozzi@gmail.com
37
+ executables:
38
+ - tankobon
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - bin/tankobon
45
+ - lib/tankobon/application.rb
46
+ - lib/tankobon/archive.rb
47
+ - lib/tankobon/cli.rb
48
+ - lib/tankobon/converter.rb
49
+ - lib/tankobon/directory.rb
50
+ - lib/tankobon/dsl.rb
51
+ - lib/tankobon/ext/dir.rb
52
+ - lib/tankobon/ext/file.rb
53
+ - lib/tankobon/ext/string.rb
54
+ - lib/tankobon/stageable.rb
55
+ - lib/tankobon/stageddirectory.rb
56
+ - lib/tankobon/transform.rb
57
+ - lib/tankobon/version.rb
58
+ - lib/tankobon.rb
59
+ has_rdoc: true
60
+ homepage: https://github.com/pigoz/tankobon
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.7
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Converts manga scans
91
+ test_files: []
92
+