libbbf 0.2.4__cp312-cp312-win32.whl
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.
- libbbf-0.2.4.dist-info/METADATA +143 -0
- libbbf-0.2.4.dist-info/RECORD +9 -0
- libbbf-0.2.4.dist-info/WHEEL +5 -0
- libbbf-0.2.4.dist-info/entry_points.txt +3 -0
- libbbf-0.2.4.dist-info/top_level.txt +2 -0
- libbbf.cp312-win32.pyd +0 -0
- libbbf_tools/__init__.py +0 -0
- libbbf_tools/bbf2cbx.py +130 -0
- libbbf_tools/cbx2bbf.py +209 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: libbbf
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Bound Book Format (BBF) tools and bindings
|
|
5
|
+
Home-page: https://github.com/ef1500/libbbf
|
|
6
|
+
Author: EF1500
|
|
7
|
+
Author-email: rosemilovelockofficial@proton.me
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: author-email
|
|
12
|
+
Dynamic: description
|
|
13
|
+
Dynamic: description-content-type
|
|
14
|
+
Dynamic: home-page
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# libbbf-python: Bound Book Format (BBF) Tools & Python Bindings
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/libbbf/)
|
|
21
|
+
[](https://pypi.org/project/libbbf/)
|
|
22
|
+
[](https://opensource.org/licenses/MIT)
|
|
23
|
+
|
|
24
|
+
## Project Overview
|
|
25
|
+
|
|
26
|
+
`libbbf-python` provides Python bindings and command-line tools for the Bound Book Format (BBF). The Bound Book Format is an archive format designed for digital books and comics. This package allows Python developers to programmatically create and read `.bbf` files, and provides convenient command-line utilities to convert between common archive formats (like CBZ/CBX) and BBF.
|
|
27
|
+
|
|
28
|
+
The core of `libbbf-python` is a `pybind11` wrapper around the high-performance C++ `libbbf` library, ensuring speed and native integration.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
* **`libbbf` Python Bindings**: Access the full `BBFBuilder` (for creating BBF files) and `BBFReader` (for reading and extracting BBF files) functionality directly in Python.
|
|
33
|
+
* **`cbx2bbf` Command-Line Tool**: Convert one or more CBZ/CBX archives (or directories of images) into a single `.bbf` file. Supports advanced features like custom page ordering, section markers (chapters, volumes), and metadata.
|
|
34
|
+
* **`bbf2cbx` Command-Line Tool**: Extract the contents of a `.bbf` file back into a `.cbz` archive or an image directory. Supports integrity verification, section-specific extraction, and range extraction using keywords.
|
|
35
|
+
* **High Performance**: Leverages C++ for core operations (file I/O, hashing, memory mapping) for optimal speed.
|
|
36
|
+
* **Integrity Checks**: Built-in XXH3 hashing for robust data verification during creation and extraction.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
You can install `libbbf-python` directly from PyPI:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install libbbf
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Note on C++ Compiler:**
|
|
47
|
+
For platforms where pre-built binary wheels are not available (e.g., specific Linux distributions or older Python versions), `pip` will attempt to build the package from source. This requires a C++ compiler (like GCC/Clang on Linux/macOS or MSVC on Windows) to be installed on your system.
|
|
48
|
+
|
|
49
|
+
## Command-Line Tools Usage
|
|
50
|
+
|
|
51
|
+
Once installed, two command-line tools will be available globally: `cbx2bbf` and `bbf2cbx`.
|
|
52
|
+
|
|
53
|
+
### `cbx2bbf`: Create BBF Files from CBZ/Images
|
|
54
|
+
|
|
55
|
+
**Usage:**
|
|
56
|
+
```cbx2bbf <inputs...> [options] --output <output.bbf>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Inputs:**
|
|
60
|
+
Can be individual image files (`.png`, `.avif`, `.jpg`), directories containing images, or `.cbz`/`.cbx` archives.
|
|
61
|
+
|
|
62
|
+
**Options:**
|
|
63
|
+
* `--output <output.bbf>`, `-o <output.bbf>`: **Required.** Specifies the output BBF filename.
|
|
64
|
+
* `--order <path.txt>`: Use a text file to define page order. Format: `filename:index` (e.g., `cover.png:1`, `credits.png:-1` for last page).
|
|
65
|
+
* `--sections <path.txt>`: Use a text file to define multiple sections. Format: `Name:Target[:Parent]`.
|
|
66
|
+
* `--section "Name:Target[:Parent]"`: Add a single section marker. Target can be a 1-based page index or a filename. Example: `--section "Chapter 1:001.png"` or `--section "Volume 1:my_comic.cbx:Series"`.
|
|
67
|
+
* `--meta "Key:Value"`: Add archival metadata (e.g., `--meta "Title:Akira"`).
|
|
68
|
+
|
|
69
|
+
**Examples:**
|
|
70
|
+
|
|
71
|
+
1. **Basic Conversion:**
|
|
72
|
+
```bash
|
|
73
|
+
cbx2bbf my_comic.cbz -o output.bbf
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
2. **Converting Multiple CBZs with Sections and Metadata:**
|
|
77
|
+
```bash
|
|
78
|
+
cbx2bbf vol1.cbz vol2.cbz \
|
|
79
|
+
--section "Volume 1:vol1.cbz" \
|
|
80
|
+
--section "Volume 2:vol2.cbz" \
|
|
81
|
+
--meta "Title:My Awesome Series" \
|
|
82
|
+
--meta "Author:Me" \
|
|
83
|
+
-o series.bbf
|
|
84
|
+
```
|
|
85
|
+
*(Note: `vol1.cbz` and `vol2.cbz` as targets will automatically map to the first page of those archives after sorting)*
|
|
86
|
+
|
|
87
|
+
3. **Converting a Directory of Images with a Custom Order File:**
|
|
88
|
+
```bash
|
|
89
|
+
# pages.txt content:
|
|
90
|
+
# cover.png:1
|
|
91
|
+
# page001.png:2
|
|
92
|
+
# ...
|
|
93
|
+
# credits.png:-1
|
|
94
|
+
|
|
95
|
+
cbx2bbf ./my_pages_folder/ --order pages.txt -o my_book.bbf
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `bbf2cbx`: Extract BBF Files
|
|
99
|
+
|
|
100
|
+
**Usage:**
|
|
101
|
+
```
|
|
102
|
+
bbf2cbx <input.bbf> [options] --output <output.cbz_or_dir>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Options:**
|
|
106
|
+
* `--output <output_path>`, `-o <output_path>`: **Required.** Output `.cbz` file or directory name.
|
|
107
|
+
* `--dir`: Extract to a directory instead of a `.cbz` archive.
|
|
108
|
+
* `--section "Name"`: Extract only a specific section defined in the BBF.
|
|
109
|
+
* `--rangekey "String"`: When extracting a section, find the end of extraction by matching this string against the *next* section's title. Useful for extracting "Vol 1" until "Vol 2" begins.
|
|
110
|
+
* `--verify`: Perform a full XXH3 integrity check on all assets and the directory hash before extraction.
|
|
111
|
+
* `--info`: Display book structure and metadata without extracting.
|
|
112
|
+
|
|
113
|
+
**Examples:**
|
|
114
|
+
|
|
115
|
+
1. **Extract entire BBF to CBZ with Verification:**
|
|
116
|
+
```bash
|
|
117
|
+
bbf2cbx my_book.bbf -o my_book_extracted.cbz --verify
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
2. **Extract a specific section to a folder:**
|
|
121
|
+
```bash
|
|
122
|
+
bbf2cbx series.bbf --section "Volume 1" --dir -o ./output/vol1/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
3. **Extract a range using `rangekey`:**
|
|
126
|
+
```bash
|
|
127
|
+
bbf2cbx series.bbf --section "Volume 1" --rangekey "Volume 2" -o vol1_only.cbz
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
4. **Display BBF Information:**
|
|
131
|
+
```bash
|
|
132
|
+
bbf2cbx series.bbf --info
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
Contributions, issues, and feature requests are welcome! For libbbf, free to check the [issues page](https://github.com/ef1500/libbbf/issues) (or create one if it doesn't exist yet).
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
142
|
+
|
|
143
|
+
---
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
libbbf.cp312-win32.pyd,sha256=NMLCloFzmYQc4OkOJtHVBjlBOJ2p5gX6fZo_-T6iP0M,240640
|
|
2
|
+
libbbf_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
libbbf_tools/bbf2cbx.py,sha256=HbPmcVOUSTgVnkPpXEJCTRYesIhcYnn0rA91oGC9XKI,4314
|
|
4
|
+
libbbf_tools/cbx2bbf.py,sha256=n9qsPojUqnm_2yFyBWB4q5_4J71_lZjVdz9L8LIYLTQ,8107
|
|
5
|
+
libbbf-0.2.4.dist-info/METADATA,sha256=WBPIrNkDde5UEM3JEJ8uWA-0TYRIrH8Fcfa5Re8NDR4,6123
|
|
6
|
+
libbbf-0.2.4.dist-info/WHEEL,sha256=3-JGBxlfJ7cpG1EEEM2PrRJYpl2Lix3zyWKiPlGiWWY,97
|
|
7
|
+
libbbf-0.2.4.dist-info/entry_points.txt,sha256=7qX-_x7iM2ygeZmkpL_bLEuJY1TlifTetaprHlK39hA,90
|
|
8
|
+
libbbf-0.2.4.dist-info/top_level.txt,sha256=DM4mwVaAZp8aBb8KOwCvyYCev1_RMhsj3lXjBWDnbJc,20
|
|
9
|
+
libbbf-0.2.4.dist-info/RECORD,,
|
libbbf.cp312-win32.pyd
ADDED
|
Binary file
|
libbbf_tools/__init__.py
ADDED
|
File without changes
|
libbbf_tools/bbf2cbx.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import zipfile
|
|
3
|
+
import libbbf
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
def find_section_end(sections, page_count, current_idx, range_key):
|
|
8
|
+
start_page = sections[current_idx]['startPage']
|
|
9
|
+
|
|
10
|
+
# Iterate through subsequent sections
|
|
11
|
+
for j in range(current_idx + 1, len(sections)):
|
|
12
|
+
next_sec = sections[j]
|
|
13
|
+
title = next_sec['title']
|
|
14
|
+
|
|
15
|
+
if not range_key:
|
|
16
|
+
if next_sec['startPage'] > start_page:
|
|
17
|
+
return next_sec['startPage']
|
|
18
|
+
else:
|
|
19
|
+
if range_key in title:
|
|
20
|
+
return next_sec['startPage']
|
|
21
|
+
|
|
22
|
+
return page_count
|
|
23
|
+
|
|
24
|
+
def main():
|
|
25
|
+
parser = argparse.ArgumentParser(description="Extract BBF to CBZ/Folder")
|
|
26
|
+
parser.add_argument("input_bbf", help="Input .bbf file")
|
|
27
|
+
parser.add_argument("--output", "-o", help="Output .cbz file (or directory if --dir)")
|
|
28
|
+
parser.add_argument("--extract", action="store_true", help="Enable extraction mode (legacy flag support)")
|
|
29
|
+
parser.add_argument("--dir", action="store_true", help="Extract to directory instead of CBZ")
|
|
30
|
+
parser.add_argument("--section", help="Extract only specific section by name")
|
|
31
|
+
parser.add_argument("--rangekey", help="String to match for end of range (e.g. 'Vol 2')")
|
|
32
|
+
parser.add_argument("--verify", action="store_true", help="Verify integrity before extraction")
|
|
33
|
+
parser.add_argument("--info", action="store_true", help="Show info and exit")
|
|
34
|
+
|
|
35
|
+
args = parser.parse_args()
|
|
36
|
+
|
|
37
|
+
reader = libbbf.BBFReader(args.input_bbf)
|
|
38
|
+
if not reader.is_valid:
|
|
39
|
+
print("Error: Invalid or corrupt BBF file.")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
|
|
42
|
+
# Info Mode
|
|
43
|
+
if args.info:
|
|
44
|
+
print(f"BBF Version: 1")
|
|
45
|
+
print(f"Pages: {reader.get_page_count()}")
|
|
46
|
+
print(f"Assets: {reader.get_asset_count()}")
|
|
47
|
+
print("\n[Sections]")
|
|
48
|
+
secs = reader.get_sections()
|
|
49
|
+
if not secs: print(" None.")
|
|
50
|
+
for s in secs:
|
|
51
|
+
print(f" - {s['title']:<20} (Start: {s['startPage']+1})")
|
|
52
|
+
print("\n[Metadata]")
|
|
53
|
+
for k, v in reader.get_metadata():
|
|
54
|
+
print(f" - {k}: {v}")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
# Verify Mode
|
|
58
|
+
if args.verify:
|
|
59
|
+
print("Verifying assets (XXH3)...")
|
|
60
|
+
if reader.verify():
|
|
61
|
+
print("Integrity OK.")
|
|
62
|
+
else:
|
|
63
|
+
print("Integrity Check FAILED.")
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
# Extraction Mode
|
|
67
|
+
if not args.output:
|
|
68
|
+
print("Error: Output filename required (-o)")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
sections = reader.get_sections()
|
|
72
|
+
total_pages = reader.get_page_count()
|
|
73
|
+
|
|
74
|
+
start_idx = 0
|
|
75
|
+
end_idx = total_pages
|
|
76
|
+
|
|
77
|
+
# Calculate Range
|
|
78
|
+
if args.section:
|
|
79
|
+
found_sec_idx = -1
|
|
80
|
+
for i, s in enumerate(sections):
|
|
81
|
+
if s['title'] == args.section:
|
|
82
|
+
found_sec_idx = i
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
if found_sec_idx == -1:
|
|
86
|
+
print(f"Error: Section '{args.section}' not found.")
|
|
87
|
+
sys.exit(1)
|
|
88
|
+
|
|
89
|
+
start_idx = sections[found_sec_idx]['startPage']
|
|
90
|
+
end_idx = find_section_end(sections, total_pages, found_sec_idx, args.rangekey)
|
|
91
|
+
|
|
92
|
+
print(f"Extracting Section: '{args.section}' (Pages {start_idx+1}-{end_idx})")
|
|
93
|
+
|
|
94
|
+
# Perform Extraction
|
|
95
|
+
is_zip = not args.dir
|
|
96
|
+
|
|
97
|
+
if is_zip:
|
|
98
|
+
zf = zipfile.ZipFile(args.output, 'w', zipfile.ZIP_DEFLATED)
|
|
99
|
+
else:
|
|
100
|
+
if not os.path.exists(args.output):
|
|
101
|
+
os.makedirs(args.output)
|
|
102
|
+
|
|
103
|
+
pad_len = len(str(total_pages))
|
|
104
|
+
|
|
105
|
+
count = 0
|
|
106
|
+
for i in range(start_idx, end_idx):
|
|
107
|
+
info = reader.get_page_info(i)
|
|
108
|
+
data = reader.get_page_data(i)
|
|
109
|
+
|
|
110
|
+
ext = ".png"
|
|
111
|
+
if info['type'] == 1: ext = ".avif"
|
|
112
|
+
elif info['type'] == 3: ext = ".jpg"
|
|
113
|
+
|
|
114
|
+
fname = f"p{str(i+1).zfill(pad_len)}{ext}"
|
|
115
|
+
|
|
116
|
+
if is_zip:
|
|
117
|
+
zf.writestr(fname, data)
|
|
118
|
+
else:
|
|
119
|
+
with open(os.path.join(args.output, fname), 'wb') as f:
|
|
120
|
+
f.write(data)
|
|
121
|
+
|
|
122
|
+
count += 1
|
|
123
|
+
if count % 10 == 0:
|
|
124
|
+
print(f"\rExtracted {count} pages...", end="")
|
|
125
|
+
|
|
126
|
+
if is_zip: zf.close()
|
|
127
|
+
print(f"\nDone. Extracted {count} pages.")
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
main()
|
libbbf_tools/cbx2bbf.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import zipfile
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
import libbbf
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PagePlan:
|
|
12
|
+
def __init__(self, path, filename, order=0):
|
|
13
|
+
self.path = path
|
|
14
|
+
self.filename = filename
|
|
15
|
+
self.order = order # 0=unspecified, >0=start, <0=end
|
|
16
|
+
|
|
17
|
+
def compare_pages(a):
|
|
18
|
+
if a.order > 0:
|
|
19
|
+
return (0, a.order)
|
|
20
|
+
elif a.order == 0:
|
|
21
|
+
return (1, a.filename)
|
|
22
|
+
else:
|
|
23
|
+
return (2, a.order)
|
|
24
|
+
|
|
25
|
+
def trim_quotes(s):
|
|
26
|
+
if not s: return ""
|
|
27
|
+
if len(s) >= 2 and s.startswith('"') and s.endswith('"'):
|
|
28
|
+
return s[1:-1]
|
|
29
|
+
return s
|
|
30
|
+
|
|
31
|
+
def main():
|
|
32
|
+
parser = argparse.ArgumentParser(description="Mux CBZ/images to BBF (bbfenc compatible)")
|
|
33
|
+
parser.add_argument("inputs", nargs="+", help="Input files (.cbz, images) or directories")
|
|
34
|
+
parser.add_argument("--output", "-o", help="Output .bbf file", default="out.bbf")
|
|
35
|
+
|
|
36
|
+
# Matching bbfenc options
|
|
37
|
+
parser.add_argument("--order", help="Text file defining page order (filename:index)")
|
|
38
|
+
parser.add_argument("--sections", help="Text file defining sections")
|
|
39
|
+
parser.add_argument("--section", action="append", help="Add section 'Name:Target[:Parent]'")
|
|
40
|
+
parser.add_argument("--meta", action="append", help="Add metadata 'Key:Value'")
|
|
41
|
+
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
|
|
44
|
+
# 1. Parse Order File
|
|
45
|
+
order_map = {}
|
|
46
|
+
if args.order and os.path.exists(args.order):
|
|
47
|
+
with open(args.order, 'r', encoding='utf-8') as f:
|
|
48
|
+
for line in f:
|
|
49
|
+
line = line.strip()
|
|
50
|
+
if not line: continue
|
|
51
|
+
if ':' in line:
|
|
52
|
+
fname, val = line.rsplit(':', 1)
|
|
53
|
+
order_map[trim_quotes(fname)] = int(val)
|
|
54
|
+
else:
|
|
55
|
+
order_map[trim_quotes(line)] = 0
|
|
56
|
+
|
|
57
|
+
manifest = []
|
|
58
|
+
|
|
59
|
+
# We need to extract CBZs to temp to get individual file paths for the Builder
|
|
60
|
+
# bbfenc processes directories and images. Since Python zipfile needs extraction
|
|
61
|
+
# to pass a path to the C++ fstream, we extract everything to a temp dir.
|
|
62
|
+
temp_dir = tempfile.mkdtemp(prefix="bbfmux_")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
print("Gathering inputs...")
|
|
66
|
+
for inp in args.inputs:
|
|
67
|
+
inp = trim_quotes(inp)
|
|
68
|
+
|
|
69
|
+
if os.path.isdir(inp):
|
|
70
|
+
# Directory input
|
|
71
|
+
for root, dirs, files in os.walk(inp):
|
|
72
|
+
for f in files:
|
|
73
|
+
if f.lower().endswith(('.png', '.avif', '.jpg', '.jpeg')):
|
|
74
|
+
full_path = os.path.join(root, f)
|
|
75
|
+
p = PagePlan(full_path, f)
|
|
76
|
+
if f in order_map: p.order = order_map[f]
|
|
77
|
+
manifest.append(p)
|
|
78
|
+
|
|
79
|
+
elif zipfile.is_zipfile(inp):
|
|
80
|
+
# CBZ input
|
|
81
|
+
print(f"Extracting {os.path.basename(inp)}...")
|
|
82
|
+
with zipfile.ZipFile(inp, 'r') as zf:
|
|
83
|
+
# Extract all valid images
|
|
84
|
+
for name in zf.namelist():
|
|
85
|
+
if name.lower().endswith(('.png', '.avif', '.jpg', '.jpeg')):
|
|
86
|
+
|
|
87
|
+
extracted_path = zf.extract(name, temp_dir)
|
|
88
|
+
fname = os.path.basename(name)
|
|
89
|
+
|
|
90
|
+
p = PagePlan(extracted_path, fname)
|
|
91
|
+
|
|
92
|
+
if fname in order_map: p.order = order_map[fname]
|
|
93
|
+
|
|
94
|
+
# Also check if the ZIP name itself has an order (less likely for pages)
|
|
95
|
+
zip_name = os.path.basename(inp)
|
|
96
|
+
if zip_name in order_map and len(manifest) == 0:
|
|
97
|
+
# TODO: I'll figure this out LATER!
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
manifest.append(p)
|
|
101
|
+
else:
|
|
102
|
+
#Single image file
|
|
103
|
+
fname = os.path.basename(inp)
|
|
104
|
+
p = PagePlan(inp, fname)
|
|
105
|
+
if fname in order_map: p.order = order_map[fname]
|
|
106
|
+
manifest.append(p)
|
|
107
|
+
|
|
108
|
+
#Sort Manifest
|
|
109
|
+
print(f"Sorting {len(manifest)} pages...")
|
|
110
|
+
manifest.sort(key=compare_pages)
|
|
111
|
+
|
|
112
|
+
file_to_page = {}
|
|
113
|
+
#allow --section="Vol 1":"chapter1.cbx" to work
|
|
114
|
+
input_file_start_map = {} # TODO: DO THIS LATER!
|
|
115
|
+
|
|
116
|
+
for idx, p in enumerate(manifest):
|
|
117
|
+
file_to_page[p.filename] = idx
|
|
118
|
+
# For now, we rely on exact filename matching as per bbfenc.
|
|
119
|
+
|
|
120
|
+
# Structure: Name:Target[:Parent]
|
|
121
|
+
sec_reqs = []
|
|
122
|
+
|
|
123
|
+
# From file
|
|
124
|
+
if args.sections and os.path.exists(args.sections):
|
|
125
|
+
with open(args.sections, 'r', encoding='utf-8') as f:
|
|
126
|
+
for line in f:
|
|
127
|
+
if not line.strip(): continue
|
|
128
|
+
parts = [trim_quotes(x) for x in line.strip().split(':')]
|
|
129
|
+
if len(parts) >= 2:
|
|
130
|
+
sec_reqs.append({
|
|
131
|
+
'name': parts[0],
|
|
132
|
+
'target': parts[1],
|
|
133
|
+
'parent': parts[2] if len(parts) > 2 else None
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
# From args
|
|
137
|
+
if args.section:
|
|
138
|
+
for s in args.section:
|
|
139
|
+
# Naive split on colon, might break if titles have colons,
|
|
140
|
+
# but bbfenc does simplistic parsing too.
|
|
141
|
+
parts = [trim_quotes(x) for x in s.split(':')]
|
|
142
|
+
if len(parts) >= 2:
|
|
143
|
+
sec_reqs.append({
|
|
144
|
+
'name': parts[0],
|
|
145
|
+
'target': parts[1],
|
|
146
|
+
'parent': parts[2] if len(parts) > 2 else None
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
#Initialize Builder
|
|
150
|
+
builder = libbbf.BBFBuilder(args.output)
|
|
151
|
+
|
|
152
|
+
# Write Pages
|
|
153
|
+
print("Writing pages to BBF...")
|
|
154
|
+
for p in manifest:
|
|
155
|
+
ext = os.path.splitext(p.filename)[1].lower()
|
|
156
|
+
ftype = 2 # PNG default
|
|
157
|
+
if ext == '.avif': ftype = 1
|
|
158
|
+
elif ext in ['.jpg', '.jpeg']: ftype = 3
|
|
159
|
+
|
|
160
|
+
if not builder.add_page(p.path, ftype):
|
|
161
|
+
print(f"Failed to add page: {p.path}")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
# Write Sections
|
|
165
|
+
section_name_to_idx = {}
|
|
166
|
+
# We need to process sections in order to resolve parents correctly if they refer to
|
|
167
|
+
# sections defined earlier in the list.
|
|
168
|
+
|
|
169
|
+
for i, req in enumerate(sec_reqs):
|
|
170
|
+
target = req['target']
|
|
171
|
+
name = req['name']
|
|
172
|
+
parent_name = req['parent']
|
|
173
|
+
|
|
174
|
+
page_index = 0
|
|
175
|
+
|
|
176
|
+
# Is target a number?
|
|
177
|
+
if target.lstrip('-').isdigit():
|
|
178
|
+
val = int(target)
|
|
179
|
+
page_index = max(0, val - 1) # 1-based to 0-based
|
|
180
|
+
else:
|
|
181
|
+
# It's a filename
|
|
182
|
+
if target in file_to_page:
|
|
183
|
+
page_index = file_to_page[target]
|
|
184
|
+
else:
|
|
185
|
+
print(f"Warning: Section target '{target}' not found in manifest. Defaulting to Pg 1.")
|
|
186
|
+
|
|
187
|
+
parent_idx = 0xFFFFFFFF
|
|
188
|
+
if parent_name and parent_name in section_name_to_idx:
|
|
189
|
+
parent_idx = section_name_to_idx[parent_name]
|
|
190
|
+
|
|
191
|
+
builder.add_section(name, page_index, parent_idx)
|
|
192
|
+
section_name_to_idx[name] = i # bbfenc uses index in the vector
|
|
193
|
+
|
|
194
|
+
# Write Metadata
|
|
195
|
+
if args.meta:
|
|
196
|
+
for m in args.meta:
|
|
197
|
+
if ':' in m:
|
|
198
|
+
k, v = m.split(':', 1)
|
|
199
|
+
builder.add_metadata(trim_quotes(k), trim_quotes(v))
|
|
200
|
+
|
|
201
|
+
print("Finalizing...")
|
|
202
|
+
builder.finalize()
|
|
203
|
+
print(f"Created {args.output}")
|
|
204
|
+
|
|
205
|
+
finally:
|
|
206
|
+
shutil.rmtree(temp_dir)
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
main()
|