comicbook 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Brewfile +1 -0
- data/CHANGELOG.md +16 -1
- data/README.md +33 -0
- data/lib/comic_book/adapter.rb +4 -0
- data/lib/comic_book/cb.rb +8 -0
- data/lib/comic_book/cb7.rb +16 -0
- data/lib/comic_book/cba.rb +4 -0
- data/lib/comic_book/cbr.rb +14 -0
- data/lib/comic_book/cbt.rb +20 -0
- data/lib/comic_book/cbz.rb +14 -0
- data/lib/comic_book/cli.rb +121 -5
- data/lib/comic_book/cli_helpers.rb +1 -1
- data/lib/comic_book/pdf/extractor.rb +75 -0
- data/lib/comic_book/pdf.rb +45 -0
- data/lib/comic_book/version.rb +1 -1
- data/lib/comicbook.rb +11 -0
- metadata +37 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e9032e12765aa91a64364150391122a0324ee9f89b9987391f4708d1db7b0d6
|
|
4
|
+
data.tar.gz: de1dc67b4d78eb8ecf739b02381d78cf2a39518a6b12dd6bbef521dd77e1b8dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81a45ca97a476926a1d8216043069840e27f42c461175ebf0326cd0698e4cc0ea78f8627b3b932e23266ccc56081786eaec210cbbc07d1e5e452b11bcef94f4e
|
|
7
|
+
data.tar.gz: f073563d4dcc6253d140e6818f2c9e273dfdc8e6ce053a729b6cddddb115ef436d6afa80d3e4357cf2ff187112bb69f444032674a5a96db841a7df0664ff0f7e
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.2
|
data/Brewfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brew 'vips'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.3.0] - 2026-04-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- PDF to comic book conversion (PDF → .cb → .cbz/.cb7/.cbt) with configurable DPI (default 300)
|
|
8
|
+
- PDF adapter with extract and pages support using libvips
|
|
9
|
+
- `ruby-vips` gem dependency (lazy-loaded, only required for PDF features)
|
|
10
|
+
- `ComicBook#info` method to read ComicInfo.xml metadata from archives and folders
|
|
11
|
+
- `comicinfo` gem dependency for ComicInfo.xml parsing
|
|
12
|
+
- `ComicBook::Info` alias for `ComicInfo::Issue`
|
|
13
|
+
- `comicbook info` CLI subcommand with `--format` (verbose, terse, json, yaml), `--only`, and `--except` options
|
|
14
|
+
- `--dpi` CLI option for PDF extraction
|
|
15
|
+
- `--version`/`-v` CLI flag
|
|
16
|
+
- Install `libvips-dev` in CI workflow
|
|
17
|
+
|
|
18
|
+
## [0.2.0] - 2025-11-17
|
|
4
19
|
|
|
5
20
|
### Added
|
|
6
21
|
|
data/README.md
CHANGED
|
@@ -7,6 +7,8 @@ A Ruby library and CLI tool for managing comic books archives.
|
|
|
7
7
|
|
|
8
8
|
**`archive`** — to create a `.cb*` file (default: `.cbz`).
|
|
9
9
|
|
|
10
|
+
**`info`** — to read ComicInfo.xml metadata from a `.cb*` file.
|
|
11
|
+
|
|
10
12
|
Currently supported formats for `archive` and `extract`:
|
|
11
13
|
- CB7 — [7zip](https://en.wikipedia.org/wiki/7-Zip)
|
|
12
14
|
- CBT — [Tar](https://en.wikipedia.org/wiki/Tar_(computing))
|
|
@@ -14,6 +16,7 @@ Currently supported formats for `archive` and `extract`:
|
|
|
14
16
|
|
|
15
17
|
Currently supported formats for `extract` only:
|
|
16
18
|
- **CBR** — [RAR](https://en.wikipedia.org/wiki/WinRAR) is proprietary without an open source implementation license. Extracting support is provided using vendored [`unar`](https://theunarchiver.com/command-line) binaries because a large number of comic books are archived in .cbr/.rar format. No support for creating `.cbr` files will ever be added until RAR is open source (or reverse engineered).
|
|
19
|
+
- **PDF** — Extract PDF pages as images using [libvips](https://www.libvips.org/). Requires libvips installed on the system.
|
|
17
20
|
|
|
18
21
|
Planned formats for `extract` only:
|
|
19
22
|
- **CBA** — [ACE](https://en.wikipedia.org/wiki/WinAce) is both proprietary and very old/outdated/unsupported. ACE extracting support is provided for historical posterity and completeness.
|
|
@@ -46,12 +49,29 @@ comicbook extract path/to/archive.cbz --to path/to/output
|
|
|
46
49
|
# Extract only image files (exclude metadata like ComicInfo.xml)
|
|
47
50
|
comicbook extract path/to/archive.cbz --images-only
|
|
48
51
|
|
|
52
|
+
# Extract PDF at custom DPI (default: 300)
|
|
53
|
+
comicbook extract path/to/comic.pdf --dpi 600
|
|
54
|
+
|
|
49
55
|
# Create a comic book archive from a folder
|
|
50
56
|
comicbook archive path/to/folder
|
|
51
57
|
|
|
52
58
|
# Create archive at a specific destination
|
|
53
59
|
comicbook archive path/to/folder --to path/to/output.cbz
|
|
54
60
|
|
|
61
|
+
# Show ComicInfo.xml metadata
|
|
62
|
+
comicbook info path/to/archive.cbz
|
|
63
|
+
|
|
64
|
+
# Show info in different formats
|
|
65
|
+
comicbook info path/to/archive.cbz --format json
|
|
66
|
+
comicbook info path/to/archive.cbz --format yaml
|
|
67
|
+
comicbook info path/to/archive.cbz --format terse
|
|
68
|
+
|
|
69
|
+
# Show only specific fields
|
|
70
|
+
comicbook info path/to/archive.cbz --only title,writer,publisher
|
|
71
|
+
|
|
72
|
+
# Show all fields except specific ones
|
|
73
|
+
comicbook info path/to/archive.cbz --except summary,review
|
|
74
|
+
|
|
55
75
|
# Show help
|
|
56
76
|
comicbook --help
|
|
57
77
|
```
|
|
@@ -77,10 +97,23 @@ ComicBook.new('path/to/folder').archive to: 'path/to/output.cbz'
|
|
|
77
97
|
# Get pages from an archive
|
|
78
98
|
comic = ComicBook.new 'path/to/archive.cbz'
|
|
79
99
|
comic.pages # => [#<ComicBook::Page>, ...]
|
|
100
|
+
|
|
101
|
+
# Read ComicInfo.xml metadata
|
|
102
|
+
comic = ComicBook.new 'path/to/archive.cbz'
|
|
103
|
+
comic.info # => #<ComicInfo::Issue> or nil
|
|
104
|
+
comic.info.title # => "The Amazing Spider-Man"
|
|
105
|
+
comic.info.writer # => "Dan Slott, Christos Gage"
|
|
80
106
|
```
|
|
81
107
|
|
|
82
108
|
## Development
|
|
83
109
|
|
|
110
|
+
PDF support requires [libvips](https://www.libvips.org/). Install it with `brew bundle` (uses the included `Brewfile`) or manually:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
brew install vips # macOS
|
|
114
|
+
sudo apt install libvips-dev # Linux
|
|
115
|
+
```
|
|
116
|
+
|
|
84
117
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
85
118
|
|
|
86
119
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
data/lib/comic_book/adapter.rb
CHANGED
data/lib/comic_book/cb.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'comicinfo'
|
|
1
2
|
require_relative 'adapter'
|
|
2
3
|
require_relative 'cb/archiver'
|
|
3
4
|
require_relative 'cb/extractor'
|
|
@@ -8,6 +9,13 @@ class ComicBook
|
|
|
8
9
|
Archiver.new(path).archive options
|
|
9
10
|
end
|
|
10
11
|
|
|
12
|
+
def info
|
|
13
|
+
xml_path = File.join path, 'ComicInfo.xml'
|
|
14
|
+
return nil unless File.exist? xml_path
|
|
15
|
+
|
|
16
|
+
ComicInfo.load xml_path
|
|
17
|
+
end
|
|
18
|
+
|
|
11
19
|
def extract _options = {}
|
|
12
20
|
Extractor.new(path).extract
|
|
13
21
|
end
|
data/lib/comic_book/cb7.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'seven_zip_ruby'
|
|
2
|
+
require 'comicinfo'
|
|
2
3
|
require_relative 'adapter'
|
|
3
4
|
require_relative 'cb7/archiver'
|
|
4
5
|
require_relative 'cb7/extractor'
|
|
@@ -13,6 +14,21 @@ class ComicBook
|
|
|
13
14
|
Extractor.new(path).extract options
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def info
|
|
18
|
+
xml = nil
|
|
19
|
+
|
|
20
|
+
File.open(path, 'rb') do |file|
|
|
21
|
+
SevenZipRuby::Reader.open(file) do |reader|
|
|
22
|
+
entry = reader.entries.find { it.path == 'ComicInfo.xml' }
|
|
23
|
+
xml = reader.extract_data(entry.index) if entry
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
return nil unless xml
|
|
28
|
+
|
|
29
|
+
ComicInfo.load xml
|
|
30
|
+
end
|
|
31
|
+
|
|
16
32
|
def pages
|
|
17
33
|
entries = []
|
|
18
34
|
|
data/lib/comic_book/cba.rb
CHANGED
data/lib/comic_book/cbr.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'shellwords'
|
|
2
|
+
require 'comicinfo'
|
|
2
3
|
require_relative 'adapter'
|
|
3
4
|
require_relative 'cbr/extractor'
|
|
4
5
|
|
|
@@ -12,6 +13,19 @@ class ComicBook
|
|
|
12
13
|
Extractor.new(path).extract options
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
def info
|
|
17
|
+
entries = CLIHelpers.lsar_list path
|
|
18
|
+
return nil unless entries.include? 'ComicInfo.xml'
|
|
19
|
+
|
|
20
|
+
Dir.mktmpdir do |temp_dir|
|
|
21
|
+
CLIHelpers.unar_extract path, temp_dir
|
|
22
|
+
xml_path = File.join temp_dir, 'ComicInfo.xml'
|
|
23
|
+
return nil unless File.exist? xml_path
|
|
24
|
+
|
|
25
|
+
ComicInfo.load xml_path
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
15
29
|
def pages
|
|
16
30
|
CLIHelpers.lsar_list(path)
|
|
17
31
|
.select { image_file? it }
|
data/lib/comic_book/cbt.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'rubygems/package'
|
|
2
|
+
require 'comicinfo'
|
|
2
3
|
require_relative 'adapter'
|
|
3
4
|
require_relative 'cbt/archiver'
|
|
4
5
|
require_relative 'cbt/extractor'
|
|
@@ -13,6 +14,25 @@ class ComicBook
|
|
|
13
14
|
Extractor.new(path).extract options
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def info
|
|
18
|
+
xml = nil
|
|
19
|
+
|
|
20
|
+
File.open(path, 'rb') do |file|
|
|
21
|
+
Gem::Package::TarReader.new(file) do |reader|
|
|
22
|
+
reader.each do |entry|
|
|
23
|
+
next unless entry.full_name == 'ComicInfo.xml'
|
|
24
|
+
|
|
25
|
+
xml = entry.read
|
|
26
|
+
break
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return nil unless xml
|
|
32
|
+
|
|
33
|
+
ComicInfo.load xml
|
|
34
|
+
end
|
|
35
|
+
|
|
16
36
|
def pages
|
|
17
37
|
entries = []
|
|
18
38
|
|
data/lib/comic_book/cbz.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'zip'
|
|
2
|
+
require 'comicinfo'
|
|
2
3
|
require_relative 'adapter'
|
|
3
4
|
require_relative 'cbz/archiver'
|
|
4
5
|
require_relative 'cbz/extractor'
|
|
@@ -13,6 +14,19 @@ class ComicBook
|
|
|
13
14
|
Extractor.new(path).extract options
|
|
14
15
|
end
|
|
15
16
|
|
|
17
|
+
def info
|
|
18
|
+
xml = nil
|
|
19
|
+
|
|
20
|
+
Zip::File.open(path) do |zipfile|
|
|
21
|
+
entry = zipfile.find_entry('ComicInfo.xml')
|
|
22
|
+
xml = entry&.get_input_stream&.read
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
return nil unless xml
|
|
26
|
+
|
|
27
|
+
ComicInfo.load xml
|
|
28
|
+
end
|
|
29
|
+
|
|
16
30
|
def pages
|
|
17
31
|
entries = []
|
|
18
32
|
|
data/lib/comic_book/cli.rb
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
require 'optparse'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'yaml'
|
|
2
4
|
|
|
3
5
|
class ComicBook
|
|
4
6
|
class CLI
|
|
5
|
-
EXTRACT_FORMATS = %w[.cb .cb7 .cbr .cbt .cbz].freeze
|
|
7
|
+
EXTRACT_FORMATS = %w[.cb .cb7 .cbr .cbt .cbz .pdf].freeze
|
|
6
8
|
ARCHIVE_FORMATS = %w[.cb .cb7 .cbt .cbz].freeze
|
|
9
|
+
INFO_FORMATS = %w[.cb .cb7 .cbr .cbt .cbz].freeze
|
|
7
10
|
UNSUPPORTED_FORMATS = %w[.cba].freeze
|
|
8
11
|
|
|
12
|
+
# Fields that duplicate others in a less useful form
|
|
13
|
+
REDUNDANT_FIELDS = %i[
|
|
14
|
+
genre genres_raw_data
|
|
15
|
+
characters_raw_data
|
|
16
|
+
teams_raw_data
|
|
17
|
+
locations_raw_data
|
|
18
|
+
story_arc story_arcs_raw_data
|
|
19
|
+
story_arc_number story_arc_numbers_raw_data
|
|
20
|
+
web_urls
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
9
23
|
def self.start argv
|
|
10
24
|
new.start Array(argv)
|
|
11
25
|
end
|
|
@@ -13,6 +27,11 @@ class ComicBook
|
|
|
13
27
|
def start argv
|
|
14
28
|
argv = Array argv
|
|
15
29
|
|
|
30
|
+
if argv.include?('-v') || argv.include?('--version')
|
|
31
|
+
puts ComicBook::VERSION
|
|
32
|
+
return
|
|
33
|
+
end
|
|
34
|
+
|
|
16
35
|
if argv.empty? || argv.include?('-h') || argv.include?('--help')
|
|
17
36
|
show_help
|
|
18
37
|
return
|
|
@@ -22,6 +41,7 @@ class ComicBook
|
|
|
22
41
|
|
|
23
42
|
when 'extract' then extract(argv)
|
|
24
43
|
when 'archive' then archive(argv)
|
|
44
|
+
when 'info' then info(argv)
|
|
25
45
|
else
|
|
26
46
|
puts "Unknown command: #{command}"
|
|
27
47
|
show_help
|
|
@@ -36,20 +56,24 @@ class ComicBook
|
|
|
36
56
|
|
|
37
57
|
def show_help
|
|
38
58
|
puts <<~HELP
|
|
39
|
-
ComicBook CLI for .cb, .cb7, .cbt, .cbz, .cbr files
|
|
59
|
+
ComicBook CLI for .cb, .cb7, .cbt, .cbz, .cbr, .pdf files
|
|
60
|
+
Version #{ComicBook::VERSION}
|
|
40
61
|
|
|
41
62
|
Usage:
|
|
42
63
|
comicbook extract <file> [options]
|
|
43
64
|
comicbook archive <folder> [options]
|
|
44
|
-
comicbook
|
|
65
|
+
comicbook info <file> [options]
|
|
66
|
+
comicbook --help
|
|
45
67
|
|
|
46
68
|
Commands:
|
|
47
|
-
extract Extract comic book archive (.cb7, .cbr, .cbt, .cbz, .cb)
|
|
69
|
+
extract Extract comic book archive (.cb7, .cbr, .cbt, .cbz, .cb, .pdf)
|
|
48
70
|
archive Create comic book archive (.cb7, .cbt, .cbz, .cb)
|
|
71
|
+
info Show ComicInfo.xml metadata (.cb7, .cbr, .cbt, .cbz, .cb)
|
|
49
72
|
|
|
50
73
|
Extract Options:
|
|
51
74
|
--from Source file path (optional, first arg is default)
|
|
52
75
|
--to Destination path
|
|
76
|
+
--dpi DPI PDF render resolution (default: 300)
|
|
53
77
|
--images-only Extract only image files (exclude metadata, text, etc.)
|
|
54
78
|
--delete-original Delete source archive after extraction
|
|
55
79
|
|
|
@@ -58,20 +82,29 @@ class ComicBook
|
|
|
58
82
|
--to Destination path (extension determines format, default .cbz)
|
|
59
83
|
--delete-original Delete source folder after archiving
|
|
60
84
|
|
|
85
|
+
Info Options:
|
|
86
|
+
--from Source file path (optional, first arg is default)
|
|
87
|
+
--format FORMAT Output format: verbose (default), terse, json, yaml
|
|
88
|
+
--only FIELDS Only show these fields (comma-separated)
|
|
89
|
+
--except FIELDS Show all fields except these (comma-separated)
|
|
90
|
+
|
|
61
91
|
General Options:
|
|
62
|
-
--help, -h
|
|
92
|
+
--help, -h Show this help
|
|
93
|
+
--version, -v Show version
|
|
63
94
|
HELP
|
|
64
95
|
end
|
|
65
96
|
|
|
66
97
|
def extract argv
|
|
67
98
|
from_path = nil
|
|
68
99
|
to_path = nil
|
|
100
|
+
dpi = nil
|
|
69
101
|
images_only = false
|
|
70
102
|
delete_original = false
|
|
71
103
|
|
|
72
104
|
parser = OptionParser.new do |opts|
|
|
73
105
|
opts.on('--from PATH', 'Source file path') { from_path = it }
|
|
74
106
|
opts.on('--to PATH', 'Destination path') { to_path = it }
|
|
107
|
+
opts.on('--dpi DPI', Integer, 'PDF render DPI') { dpi = it }
|
|
75
108
|
opts.on('--images-only', 'Extract only images') { images_only = true }
|
|
76
109
|
opts.on('--delete-original', 'Delete source after extract') { delete_original = true }
|
|
77
110
|
end
|
|
@@ -85,6 +118,7 @@ class ComicBook
|
|
|
85
118
|
|
|
86
119
|
options.delete(:images_only) unless images_only
|
|
87
120
|
options.delete(:delete_original) unless delete_original
|
|
121
|
+
options[:dpi] = dpi if dpi
|
|
88
122
|
|
|
89
123
|
ComicBook.extract from_path, options
|
|
90
124
|
|
|
@@ -118,6 +152,88 @@ class ComicBook
|
|
|
118
152
|
puts "Archived #{from_path}#{" to #{to_path}" if to_path}"
|
|
119
153
|
end
|
|
120
154
|
|
|
155
|
+
def info argv
|
|
156
|
+
from_path = nil
|
|
157
|
+
output_format = 'verbose'
|
|
158
|
+
only_fields = nil
|
|
159
|
+
except_fields = nil
|
|
160
|
+
|
|
161
|
+
parser = OptionParser.new do |opts|
|
|
162
|
+
opts.on('--from PATH', 'Source file path') { from_path = it }
|
|
163
|
+
opts.on('--format FORMAT', 'Output format') { output_format = it }
|
|
164
|
+
opts.on('--only FIELDS', 'Only these fields') { only_fields = it.split(',').map(&:strip) }
|
|
165
|
+
opts.on('--except FIELDS', 'Exclude these fields') { except_fields = it.split(',').map(&:strip) }
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
remaining = parser.parse argv
|
|
169
|
+
from_path ||= remaining.first
|
|
170
|
+
|
|
171
|
+
validate_info_args! from_path, output_format
|
|
172
|
+
|
|
173
|
+
comic_info = ComicBook.new(from_path).info
|
|
174
|
+
|
|
175
|
+
raise ComicBook::Error, "No ComicInfo.xml found in #{from_path}" unless comic_info
|
|
176
|
+
|
|
177
|
+
data = comic_info.to_h
|
|
178
|
+
data = filter_fields data, only_fields, except_fields
|
|
179
|
+
|
|
180
|
+
puts format_info(data, output_format)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def validate_info_args! from_path, output_format
|
|
184
|
+
raise ComicBook::Error, 'Source file required' unless from_path
|
|
185
|
+
raise ComicBook::Error, "Source file not found: #{from_path}" unless File.exist?(from_path)
|
|
186
|
+
|
|
187
|
+
valid_formats = %w[verbose terse json yaml]
|
|
188
|
+
return if valid_formats.include? output_format
|
|
189
|
+
|
|
190
|
+
raise ComicBook::Error, "Invalid format: #{output_format} (valid: #{valid_formats.join(', ')})"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def filter_fields data, only_fields, except_fields
|
|
194
|
+
if only_fields
|
|
195
|
+
data.slice(*only_fields.map(&:to_sym))
|
|
196
|
+
elsif except_fields
|
|
197
|
+
data.except(*except_fields.map(&:to_sym))
|
|
198
|
+
else
|
|
199
|
+
data
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def format_info data, output_format
|
|
204
|
+
data = data.compact
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
'json' => format_json(data),
|
|
208
|
+
'yaml' => format_yaml(data),
|
|
209
|
+
'terse' => format_terse(data),
|
|
210
|
+
'verbose' => format_verbose(data)
|
|
211
|
+
}[output_format]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def format_json data
|
|
215
|
+
JSON.generate(data.transform_keys(&:to_s))
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def format_yaml data
|
|
219
|
+
data.transform_keys(&:to_s).to_yaml.delete_prefix("---\n")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def clean_for_display data
|
|
223
|
+
data.except(*REDUNDANT_FIELDS)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def format_terse data
|
|
227
|
+
clean_for_display(data).map { |key, value| "#{key}=#{value}" }.join(' | ')
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def format_verbose data
|
|
231
|
+
data = clean_for_display(data)
|
|
232
|
+
max_key_length = data.keys.map { it.to_s.length }.max || 0
|
|
233
|
+
|
|
234
|
+
data.map { |key, value| "#{key.to_s.ljust max_key_length} #{value}" }.join("\n")
|
|
235
|
+
end
|
|
236
|
+
|
|
121
237
|
def validate_extract_args! from_path, to_path
|
|
122
238
|
# from
|
|
123
239
|
raise ComicBook::Error, 'Source file required' unless from_path
|
|
@@ -21,7 +21,7 @@ class ComicBook
|
|
|
21
21
|
def check_linux_dependency! name
|
|
22
22
|
return if system("which #{name} > /dev/null 2>&1")
|
|
23
23
|
|
|
24
|
-
raise Error, "#{name} is not installed. Install
|
|
24
|
+
raise Error, "#{name} is not installed. Install with: sudo apt-get install unar (Ubuntu/Debian)"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def lsar_list archive_path
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
class ComicBook
|
|
2
|
+
class PDF < Adapter
|
|
3
|
+
class Extractor
|
|
4
|
+
DEFAULT_DPI = 300
|
|
5
|
+
|
|
6
|
+
def initialize pdf_path
|
|
7
|
+
@pdf_path = File.expand_path pdf_path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def extract options = {}
|
|
11
|
+
extension = options.fetch :extension, :cb
|
|
12
|
+
delete_original = options.fetch :delete_original, false
|
|
13
|
+
@dpi = options.fetch :dpi, DEFAULT_DPI
|
|
14
|
+
|
|
15
|
+
destination = options[:to] || determine_extract_path(extension)
|
|
16
|
+
create_destination_directory destination
|
|
17
|
+
render_pages destination
|
|
18
|
+
cleanup_pdf_file if delete_original
|
|
19
|
+
|
|
20
|
+
destination
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :pdf_path, :dpi
|
|
26
|
+
|
|
27
|
+
def determine_extract_path extension
|
|
28
|
+
base_name = File.basename pdf_path, '.*'
|
|
29
|
+
dir_name = File.dirname pdf_path
|
|
30
|
+
pdf_name = base_name
|
|
31
|
+
|
|
32
|
+
pdf_name << ".#{extension}" if extension
|
|
33
|
+
|
|
34
|
+
full_path = File.join dir_name, pdf_name
|
|
35
|
+
File.expand_path full_path
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_destination_directory destination
|
|
39
|
+
FileUtils.mkdir_p destination
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def page_count
|
|
43
|
+
require 'vips'
|
|
44
|
+
image = Vips::Image.new_from_file pdf_path
|
|
45
|
+
image.get 'n-pages'
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
raise unless e.class.name == 'Vips::Error' # rubocop:disable Style/ClassEqualityComparison
|
|
48
|
+
|
|
49
|
+
0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def render_pages destination
|
|
53
|
+
count = page_count
|
|
54
|
+
return if count.zero?
|
|
55
|
+
|
|
56
|
+
count.times do |page_number|
|
|
57
|
+
render_page destination, page_number
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def render_page destination, page_number
|
|
62
|
+
require 'vips'
|
|
63
|
+
image = Vips::Image.new_from_file pdf_path, page: page_number, dpi: dpi
|
|
64
|
+
file_name = format('page_%03d.jpg', page_number + 1)
|
|
65
|
+
file_path = File.join destination, file_name
|
|
66
|
+
|
|
67
|
+
image.jpegsave file_path
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def cleanup_pdf_file
|
|
71
|
+
File.delete pdf_path
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require_relative 'adapter'
|
|
2
|
+
require_relative 'pdf/extractor'
|
|
3
|
+
|
|
4
|
+
class ComicBook
|
|
5
|
+
class PDF < Adapter
|
|
6
|
+
def info
|
|
7
|
+
nil
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def archive _options = {}
|
|
11
|
+
raise Error, 'PDF archiving not supported (use extract to convert PDF pages to images)'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def extract options = {}
|
|
15
|
+
require_vips!
|
|
16
|
+
Extractor.new(path).extract options
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pages
|
|
20
|
+
require_vips!
|
|
21
|
+
image = Vips::Image.new_from_file path
|
|
22
|
+
count = image.get 'n-pages'
|
|
23
|
+
|
|
24
|
+
(1..count).map do |page_number|
|
|
25
|
+
name = format('page_%03d.jpg', page_number)
|
|
26
|
+
|
|
27
|
+
ComicBook::Page.new name, name
|
|
28
|
+
end
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
raise unless e.class.name == 'Vips::Error' # rubocop:disable Style/ClassEqualityComparison
|
|
31
|
+
|
|
32
|
+
[]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def require_vips!
|
|
38
|
+
require 'vips'
|
|
39
|
+
rescue LoadError
|
|
40
|
+
raise ComicBook::Error,
|
|
41
|
+
'PDF support requires libvips. ' \
|
|
42
|
+
'Install with: brew install vips (macOS) or apt install libvips-dev (Linux)'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/comic_book/version.rb
CHANGED
data/lib/comicbook.rb
CHANGED
|
@@ -6,11 +6,14 @@ require_relative 'comic_book/cba'
|
|
|
6
6
|
require_relative 'comic_book/cbr'
|
|
7
7
|
require_relative 'comic_book/cbt'
|
|
8
8
|
require_relative 'comic_book/cbz'
|
|
9
|
+
require_relative 'comic_book/pdf'
|
|
9
10
|
require_relative 'comic_book/cli_helpers'
|
|
10
11
|
|
|
11
12
|
class ComicBook
|
|
12
13
|
class Error < StandardError; end
|
|
13
14
|
|
|
15
|
+
Info = ComicInfo::Issue
|
|
16
|
+
|
|
14
17
|
IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .gif .bmp .webp].freeze
|
|
15
18
|
IMAGE_GLOB_PATTERN = '*.{jpg,jpeg,png,gif,bmp,webp}'.freeze
|
|
16
19
|
|
|
@@ -41,6 +44,12 @@ class ComicBook
|
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
def info
|
|
48
|
+
return CB.new(path).info if type == :folder
|
|
49
|
+
|
|
50
|
+
adapter.info
|
|
51
|
+
end
|
|
52
|
+
|
|
44
53
|
def archive options = {}
|
|
45
54
|
raise Error, 'Cannot archive a file' unless %i[folder cb].include?(type)
|
|
46
55
|
|
|
@@ -77,6 +86,7 @@ class ComicBook
|
|
|
77
86
|
when '.cbt' then :cbt
|
|
78
87
|
when '.cbr' then :cbr
|
|
79
88
|
when '.cba' then :cba
|
|
89
|
+
when '.pdf' then :pdf
|
|
80
90
|
else
|
|
81
91
|
raise Error, "Unsupported file type: #{File.extname(path)}"
|
|
82
92
|
end
|
|
@@ -111,6 +121,7 @@ class ComicBook
|
|
|
111
121
|
when :cbr then CBR.new path
|
|
112
122
|
when :cbt then CBT.new path
|
|
113
123
|
when :cbz then CBZ.new path
|
|
124
|
+
when :pdf then PDF.new path
|
|
114
125
|
else
|
|
115
126
|
raise Error, "No adapter available for type: #{type}"
|
|
116
127
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: comicbook
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shane Becker
|
|
@@ -9,6 +9,34 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: comicinfo
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ruby-vips
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.3'
|
|
12
40
|
- !ruby/object:Gem::Dependency
|
|
13
41
|
name: rubyzip
|
|
14
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -49,6 +77,7 @@ extra_rdoc_files: []
|
|
|
49
77
|
files:
|
|
50
78
|
- ".ruby-version"
|
|
51
79
|
- AGENT.md
|
|
80
|
+
- Brewfile
|
|
52
81
|
- CHANGELOG.md
|
|
53
82
|
- CODE_OF_CONDUCT.md
|
|
54
83
|
- LICENSE.md
|
|
@@ -75,6 +104,8 @@ files:
|
|
|
75
104
|
- lib/comic_book/cli.rb
|
|
76
105
|
- lib/comic_book/cli_helpers.rb
|
|
77
106
|
- lib/comic_book/page.rb
|
|
107
|
+
- lib/comic_book/pdf.rb
|
|
108
|
+
- lib/comic_book/pdf/extractor.rb
|
|
78
109
|
- lib/comic_book/vendor/linux/MPL-License-for-lsar-and-unar.txt
|
|
79
110
|
- lib/comic_book/vendor/macos/MPL-License-for-lsar-and-unar.txt
|
|
80
111
|
- lib/comic_book/vendor/macos/lsar
|
|
@@ -95,6 +126,10 @@ metadata:
|
|
|
95
126
|
source_code_uri: https://github.com/veganstraightedge/comicbook
|
|
96
127
|
changelog_uri: https://github.com/veganstraightedge/comicbook/blob/main/CHANGELOG.md
|
|
97
128
|
rubygems_mfa_required: 'true'
|
|
129
|
+
post_install_message: |
|
|
130
|
+
PDF support requires libvips:
|
|
131
|
+
macOS: brew install vips
|
|
132
|
+
Linux: sudo apt install libvips-dev
|
|
98
133
|
rdoc_options: []
|
|
99
134
|
require_paths:
|
|
100
135
|
- lib
|
|
@@ -109,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
109
144
|
- !ruby/object:Gem::Version
|
|
110
145
|
version: '0'
|
|
111
146
|
requirements: []
|
|
112
|
-
rubygems_version: 4.0.
|
|
147
|
+
rubygems_version: 4.0.6
|
|
113
148
|
specification_version: 4
|
|
114
149
|
summary: Ruby interface for working with comic book archive files
|
|
115
150
|
test_files: []
|