cbz_tools 0.3.0 → 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.
- checksums.yaml +4 -4
- data/.vscode/settings.json +1 -1
- data/CHANGELOG.md +10 -0
- data/README.md +31 -1
- data/lib/cbz_tools/cbr_converter.rb +106 -0
- data/lib/cbz_tools/system_dependencies.rb +29 -0
- data/lib/cbz_tools/version.rb +1 -1
- data/lib/cbz_tools.rb +1 -0
- data/sig/cbz_tools.rbs +37 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30f58af31425d78057d97105cfe21164b6ec05263f0b86d2b2a0dca150792d5d
|
|
4
|
+
data.tar.gz: edd397b9588670cd49a3bcfe517f684ae9f684f4469a9725d1ac440f00da0fbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57e09be8a89fec63e2cb22b140c6500afa29a0363a1f0e7b1b6d5ef75b9a137e4f6da4b1012031d18b2875db856e73145ded2fabf8532a6080d9f80d9ff98894
|
|
7
|
+
data.tar.gz: 73deffc4ac234b2288b2b8c5898cd12dc005561b79a7b5941cfb052ace8766fef6227bf935b6835226381f679d0bfb5fd2f9553fc68fd8cb2bc5ce309e7bfd45
|
data/.vscode/settings.json
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-08-04
|
|
4
|
+
|
|
5
|
+
- `CbzTools::CbrConverter` now uses `unrar` directly — no extractor param required.
|
|
6
|
+
- **Breaking**: Removed `extractor:` param, `ExtractionError`, and `extractor` attribute.
|
|
7
|
+
- Runtime check raises `ArchiveError` if `unrar` is not in PATH.
|
|
8
|
+
|
|
9
|
+
## [0.4.0] - 2026-08-03
|
|
10
|
+
|
|
11
|
+
- Add `CbzTools::CbrConverter` for converting CBR archives to CBZ. The gem ships the orchestration (extract → repack → error model) but no default extractor — callers pass `extractor:` (anything responding to `call(cbr_path, destination_dir)` and raising `ExtractionError` on failure).
|
|
12
|
+
|
|
3
13
|
## [0.3.0] - 2026-08-03
|
|
4
14
|
|
|
5
15
|
- Add `CbzTools::ComicInfo` (parse/build ComicInfo.xml, `SCHEMA`); raises `ParseError` on bad XML.
|
data/README.md
CHANGED
|
@@ -18,7 +18,37 @@ gem install cbz_tools
|
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### Reading a CBZ archive
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
CbzTools::Reader.open("path/to/comic.cbz") do |reader|
|
|
25
|
+
reader.image_names # => ["page_001.jpg", "page_002.jpg", ...]
|
|
26
|
+
reader.first_image # => "<image bytes>"
|
|
27
|
+
reader.comic_info # => {title: "...", page_count: "32", ...}
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Editing ComicInfo.xml inside a CBZ
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
CbzTools::Reader.open("path/to/comic.cbz") do |reader|
|
|
35
|
+
reader.update_comic_info(title: "New Title", page_count: "32")
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Converting CBR to CBZ
|
|
40
|
+
|
|
41
|
+
`CbzTools::CbrConverter` converts CBR archives to CBZ using the `unrar`
|
|
42
|
+
command-line tool (which must be installed on your system).
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
CbzTools::CbrConverter.convert("path/to/comic.cbr", to: "path/to/comic.cbz")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Errors inherit from `CbzTools::Error`:
|
|
49
|
+
|
|
50
|
+
- `CbzTools::CbrConverter::ArchiveError` — the destination couldn't be written,
|
|
51
|
+
`overwrite: false` and the destination already exists, or `unrar` is not installed.
|
|
22
52
|
|
|
23
53
|
## Development
|
|
24
54
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "zip"
|
|
6
|
+
require "pathname"
|
|
7
|
+
require "open3"
|
|
8
|
+
|
|
9
|
+
require_relative "system_dependencies"
|
|
10
|
+
|
|
11
|
+
module CbzTools
|
|
12
|
+
# Converts a CBR (Comic Book RAR) archive into a CBZ (Comic Book ZIP)
|
|
13
|
+
# archive.
|
|
14
|
+
#
|
|
15
|
+
# Extraction is handled internally using the +unrar+ command-line tool,
|
|
16
|
+
# which must be installed on the system. Consumers do not need to
|
|
17
|
+
# install or configure any extraction backend.
|
|
18
|
+
#
|
|
19
|
+
# @example Single-shot conversion
|
|
20
|
+
# CbzTools::CbrConverter.convert("path/to/comic.cbr", to: "path/to/comic.cbz")
|
|
21
|
+
#
|
|
22
|
+
# @example Reusing one converter across many conversions
|
|
23
|
+
# converter = CbzTools::CbrConverter.new
|
|
24
|
+
# paths.each { |p| converter.convert(p, to: p.sub(/\.cbr\z/i, ".cbz")) }
|
|
25
|
+
class CbrConverter
|
|
26
|
+
class ArchiveError < Error
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @see #convert
|
|
30
|
+
#
|
|
31
|
+
# @param cbr_path [String] source CBR path
|
|
32
|
+
# @param to [String] destination CBZ path
|
|
33
|
+
# @param overwrite [Boolean] when false, raise {ArchiveError} if
|
|
34
|
+
# +to+ already exists; default true
|
|
35
|
+
# @param work_in [String, nil] work directory; when nil (default),
|
|
36
|
+
# a fresh +Dir.mktmpdir+ is created and cleaned up. When set, the
|
|
37
|
+
# converter uses that directory and leaves cleanup to the caller
|
|
38
|
+
# (useful when batching many conversions).
|
|
39
|
+
# @return [String] the destination CBZ path
|
|
40
|
+
# @raise [ArchiveError] when target cannot be written or unrar is unavailable
|
|
41
|
+
def self.convert(cbr_path, to:, overwrite: true, work_in: nil)
|
|
42
|
+
new.convert(cbr_path, to: to, overwrite: overwrite, work_in: work_in)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def convert(cbr_path, to:, overwrite: true, work_in: nil)
|
|
46
|
+
ensure_target_slot!(to, overwrite: overwrite)
|
|
47
|
+
File.delete(to) if overwrite && File.exist?(to)
|
|
48
|
+
|
|
49
|
+
in_workdir(work_in) do |temp_dir|
|
|
50
|
+
extract(cbr_path, temp_dir)
|
|
51
|
+
repack(temp_dir, to)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
to
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def extract(cbr_path, destination)
|
|
60
|
+
unrar = SystemDependencies.unrar!
|
|
61
|
+
_, stderr, status = Open3.capture3(unrar, "x", "-o+", cbr_path, "#{destination}/")
|
|
62
|
+
|
|
63
|
+
unless status.success?
|
|
64
|
+
raise ArchiveError, "unrar failed: #{stderr}"
|
|
65
|
+
end
|
|
66
|
+
rescue SystemDependencies::MissingExecutableError => e
|
|
67
|
+
raise ArchiveError, e.message
|
|
68
|
+
rescue => e
|
|
69
|
+
raise ArchiveError, "Failed to extract CBR: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ensure_target_slot!(to, overwrite:)
|
|
73
|
+
return if overwrite || !File.exist?(to)
|
|
74
|
+
|
|
75
|
+
raise ArchiveError, "Target #{to} already exists (pass overwrite: true to replace)"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def in_workdir(work_in, &block)
|
|
79
|
+
if work_in
|
|
80
|
+
FileUtils.mkdir_p(work_in)
|
|
81
|
+
yield work_in
|
|
82
|
+
else
|
|
83
|
+
Dir.mktmpdir(&block)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def repack(source_dir, target_path)
|
|
88
|
+
Zip::File.open(target_path, create: true) do |zipfile|
|
|
89
|
+
each_file_in(source_dir) { |file| add_to_zip(zipfile, source_dir, file) }
|
|
90
|
+
end
|
|
91
|
+
rescue Zip::Error, SystemCallError => e
|
|
92
|
+
raise ArchiveError, "Failed to write CBZ #{target_path}: #{e.class}: #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def each_file_in(source_dir)
|
|
96
|
+
Dir["#{source_dir}/**/*"].each do |path|
|
|
97
|
+
yield path unless File.directory?(path)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def add_to_zip(zipfile, source_dir, file_path)
|
|
102
|
+
relative = Pathname.new(file_path).relative_path_from(Pathname.new(source_dir)).to_s
|
|
103
|
+
zipfile.add(relative, file_path)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CbzTools
|
|
4
|
+
module SystemDependencies
|
|
5
|
+
class MissingExecutableError < StandardError; end
|
|
6
|
+
|
|
7
|
+
def self.find_executable(name)
|
|
8
|
+
extensions = Gem.win_platform? ? ENV.fetch("PATHEXT", "").split(";") : [""]
|
|
9
|
+
|
|
10
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
|
|
11
|
+
extensions.each do |extension|
|
|
12
|
+
path = File.join(directory, "#{name}#{extension}")
|
|
13
|
+
return path if File.file?(path) && File.executable?(path)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.unrar!
|
|
21
|
+
find_executable("unrar") ||
|
|
22
|
+
raise(
|
|
23
|
+
MissingExecutableError,
|
|
24
|
+
"`unrar` is required for CBR conversion but was not found in PATH. " \
|
|
25
|
+
"Install unrar using your system's package manager."
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/cbz_tools/version.rb
CHANGED
data/lib/cbz_tools.rb
CHANGED
data/sig/cbz_tools.rbs
CHANGED
|
@@ -4,6 +4,43 @@ module CbzTools
|
|
|
4
4
|
class Error < StandardError
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
+
class CbrConverter
|
|
8
|
+
class ExtractionError < CbzTools::Error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class ArchiveError < CbzTools::Error
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader extractor: untyped
|
|
15
|
+
|
|
16
|
+
def self.convert: (
|
|
17
|
+
String cbr_path,
|
|
18
|
+
to: String,
|
|
19
|
+
?overwrite: bool,
|
|
20
|
+
?work_in: String?,
|
|
21
|
+
?extractor: untyped
|
|
22
|
+
) -> String
|
|
23
|
+
|
|
24
|
+
def initialize: (?extractor: untyped) -> void
|
|
25
|
+
|
|
26
|
+
def convert: (
|
|
27
|
+
String cbr_path,
|
|
28
|
+
to: String,
|
|
29
|
+
?overwrite: bool,
|
|
30
|
+
?work_in: String?
|
|
31
|
+
) -> String
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def ensure_extractor!: -> void
|
|
36
|
+
|
|
37
|
+
def ensure_target_slot!: (String to, overwrite: bool) -> void
|
|
38
|
+
|
|
39
|
+
def in_workdir: (String? work_in) { (String) -> void } -> void
|
|
40
|
+
|
|
41
|
+
def repack: (String source_dir, String target_path) -> void
|
|
42
|
+
end
|
|
43
|
+
|
|
7
44
|
class Reader
|
|
8
45
|
IMAGE_EXTENSIONS: Array[String]
|
|
9
46
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cbz_tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Figueroa
|
|
@@ -51,9 +51,11 @@ files:
|
|
|
51
51
|
- LICENSE.txt
|
|
52
52
|
- README.md
|
|
53
53
|
- lib/cbz_tools.rb
|
|
54
|
+
- lib/cbz_tools/cbr_converter.rb
|
|
54
55
|
- lib/cbz_tools/comic_info.rb
|
|
55
56
|
- lib/cbz_tools/error.rb
|
|
56
57
|
- lib/cbz_tools/reader.rb
|
|
58
|
+
- lib/cbz_tools/system_dependencies.rb
|
|
57
59
|
- lib/cbz_tools/version.rb
|
|
58
60
|
- sig/cbz_tools.rbs
|
|
59
61
|
homepage: https://github.com/dmfigueroa/cbz_tools
|
|
@@ -77,7 +79,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
77
79
|
- - ">="
|
|
78
80
|
- !ruby/object:Gem::Version
|
|
79
81
|
version: '0'
|
|
80
|
-
requirements:
|
|
82
|
+
requirements:
|
|
83
|
+
- unrar executable available in PATH (for CBR conversion)
|
|
81
84
|
rubygems_version: 4.0.17
|
|
82
85
|
specification_version: 4
|
|
83
86
|
summary: Read, extract, and edit metadata of CBZ comic archives, and convert CBR to
|