tankobon 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/tankobon +33 -0
- data/lib/tankobon.rb +21 -0
- data/lib/tankobon/application.rb +65 -0
- data/lib/tankobon/archive.rb +10 -0
- data/lib/tankobon/cli.rb +25 -0
- data/lib/tankobon/converter.rb +53 -0
- data/lib/tankobon/directory.rb +9 -0
- data/lib/tankobon/dsl.rb +9 -0
- data/lib/tankobon/ext/dir.rb +19 -0
- data/lib/tankobon/ext/file.rb +42 -0
- data/lib/tankobon/ext/string.rb +9 -0
- data/lib/tankobon/stageable.rb +28 -0
- data/lib/tankobon/stageddirectory.rb +71 -0
- data/lib/tankobon/transform.rb +48 -0
- data/lib/tankobon/version.rb +3 -0
- metadata +92 -0
data/bin/tankobon
ADDED
@@ -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
|
data/lib/tankobon.rb
ADDED
@@ -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
|
data/lib/tankobon/cli.rb
ADDED
@@ -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
|
data/lib/tankobon/dsl.rb
ADDED
@@ -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,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
|
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
|
+
|