tankobon 0.5.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.
@@ -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
+