|
|
metabib extracts metadata from Flibusta/Librusec SQL dumps and book archives
into JSON Lines. It first builds cache manifests for database dumps and/or
archives, then merges those cached artifacts into final JSONL.
metabib is intentionally focused on current Flibusta/Librusec metadata
workflows: SQL dump metadata, main archive sets, supplemental usr archive sets,
and non-FB2 archive entries that should be carried through the same catalog
dataset. FB2-specific parsing and enrichment are applied when FB2 descriptions
are present. Unsupported areas include:
- dump schemas outside the current supported schemas;
- reader-specific INPX quirks that are not part of the generated formats;
- INPX daily updates.
Instead, metabib aims to provide an easily parsable source of truth for catalog
programs. INPX is useful as an interchange artifact, but it is far from optimal
as a primary metadata source because it carries reader-specific field and layout
constraints rather than representing a neutral catalog model.
metabib caches information extracted from SQL dumps and book archives into
manifest files so expensive extraction work can be reused later. Database dumps
and archives have separate manifests, which makes it possible to update
database-derived metadata without re-parsing the whole archive set. Cached
manifests and combined output records are JSON data with well-defined schemas,
making the resulting dataset easy to validate, transform, and consume from other
tools.
Schema definitions are maintained in docs/:
- dataset header:
metabib-dataset.schema.json; - dataset records:
metabib-dataset-record.schema.json; - source cache records:
metabib.schema.json; - archive cache manifests:
metabib-archive-manifest.schema.json; - database cache manifests:
metabib-database-manifest.schema.json.
Current schema versions:
- merged datasets use a
metabib.dataset/1header andmetabib.dataset_record/1rows; - manifest payload records use
metabib.record/1; - legacy FB2-only archive manifest headers may use
metabib.archive_manifest/1, which remains accepted so existing manifests do not need costly rebuilds; - generated archive manifest headers use
metabib.archive_manifest/2; this adds thescopefield and recordsfb2orusrarchive scope; - database manifest headers use
metabib.database_manifest/2.
metabib is organized around reusable processing passes:
fetchdownloads new daily archive updates and SQL dumps from a configured remote library profile;rollupfolds daily FB2 and USR update ZIPs into local archive ZIPs using size, rolling-duration, or UTC calendar-bucket finalization;cacheimports SQL dumps, queries database metadata, and builds reusable manifests for each selected source. Archive cache processing understands both FB2 and USR scopes: FB2 archives parse FictionBook descriptions from.fb2entries, while USR archives keep non-FB2 entries, pair supported.fbdsidecar metadata, and can inspect nested book containers when enabled;mergereads existing manifests and combines database-derived and archive-derived metadata into one provenance-aware dataset JSONL stream with ametabib.dataset/1header andmetabib.dataset_record/1rows;inspectsummarizes and validates merged dataset JSONL or locates individual records without producing another artifact;mhl-inpxconsumes the merged dataset JSONL to produce a MyHomeLib-compatible INPX without coupling the main extraction pipeline to INPX output constraints;flib-inpxconsumes the same merged dataset JSONL to produce a FLibrary-compatible INPX with extended fields and multiple flat series links;inpxconsumes the merged dataset JSONL to produce archive-backed INPX output for accepted archive entries withFOLDERandINSNOlocators, supports Go-template filters through--where, can split accepted rows into multiple.inpmembers through--split-by, and can write FLibrary-compatible additional artifacts when FB2-derived source data is available for the accepted book set.
Both current Flibusta and current Librusec SQL dump schemas are supported. The database cache pass autodetects the dump schema and records it in the database manifest so incompatible manifests are not reused accidentally.
The same transformation approach can support other derived artifacts later, including update lineages and differential update schemes.
When the cache pass processes SQL dumps in the default managed mode, metabib
discovers MariaDB binaries recursively in ./mariadb first, then in PATH,
starts a private local MariaDB server, imports *.sql dumps with the discovered
mariadb or mysql client, and stops the server with mariadb-admin or
mysqladmin when processing is done. It does not require a system database
service. If the optional admin client is unavailable, managed shutdown falls
back to signaling the private server process.
To use an existing MariaDB service instead of the managed local instance, set
database.dsn or database.managed: false in the configuration file.
The easiest portable setup for managed mode is to keep a local MariaDB unpacked
next to the metabib executable or project checkout.
This approach should allow metabib to run on any platform supported by Go that
also has recent MariaDB binaries available, whether those binaries come from the
system, a system package, or a separately compiled distribution for that
platform. Finding suitable MariaDB binaries for a particular platform is the
user's responsibility.
On Windows, download MariaDB from https://mariadb.org/download/, select the
ZIP archive package, and unzip it into a mariadb directory inside the metabib
directory. metabib will discover binaries such as mariadbd.exe,
mariadb-install-db.exe, mariadb.exe, and mariadb-admin.exe from that tree
automatically.
The same ZIP/tarball approach also works on Linux. On Linux it is often simpler to install the distribution package instead, for example:
sudo apt install mariadb-server -yIf you only want the binaries available for metabib managed mode and do not
want MariaDB running as a system service, disable the service after installing
it, for example:
sudo systemctl disable mariadbOn Synology, install the MariaDB 10 package and point metabib at the packaged
binaries explicitly, for example:
version: 1
processing:
manifests:
archive_dir: "/volume4/backup/library/manifests"
database:
server_path: "/volume4/@appstore/MariaDB10/usr/local/mariadb10.11/bin/mariadbd"
install_db_path: "/volume4/@appstore/MariaDB10/usr/local/mariadb10.11/bin/mariadb-install-db"
client_path: "/volume4/@appstore/MariaDB10/usr/local/mariadb10.11/bin/mariadb"
admin_path: "/volume4/@appstore/MariaDB10/usr/local/mariadb10.11/bin/mariadb-admin"Download new daily archive ZIPs and current SQL dumps using a configured remote library profile:
metabib fetch --library flibusta --to upd_flibusta --tosql flibusta_20260622 --continue
metabib fetch --library flibusta-all --to upd_flibusta --tosql flibusta_20260622 --continue
metabib fetch --library librusec --to upd_librusec --tosql librusec_20260713 --continue
metabib fetch --library librusec-usr --to upd_librusec_usr --tosql librusec_20260713 --continuefetch reads profiles from the fetch section of the YAML configuration,
tracks FB2 and USR high-water marks independently from existing range-named ZIPs
in --to, downloads only newer daily archive updates for the selected profile,
and decompresses downloaded *.sql.gz dumps into --tosql.
FB2 rollup archives such as fb2-000001-000100.zip, USR rollup archives such as
usr-000001-000100.zip, active .merging archives, and retained daily updates
count toward that family's local high-water mark. When --tosql is omitted, the
SQL output directory is generated from the library name and current UTC timestamp.
Use --nosql to download archive updates only.
FB2 and USR are maintained as separate update lineages. A newer FB2 archive or
fb2-*.merging file does not suppress USR downloads, and a newer USR archive or
usr-*.merging file does not suppress FB2 downloads. Combined profiles such as
flibusta-all and librusec-all classify each matched remote update first, then
compare it only with that lineage's high-water mark.
The default configuration includes these fetch profiles:
flibusta: Flibusta FB2 daily archives and SQL dumps.flibusta-usr: Flibusta non-FB2 daily archives and SQL dumps.flibusta-all: Flibusta FB2 plus non-FB2 daily archives and SQL dumps.librusec: Librusec FB2 daily archives and SQL dumps.librusec-usr: Librusec non-FB2 daily archives and SQL dumps.librusec-all: Librusec FB2 plus non-FB2 daily archives and SQL dumps.
USR selection uses regexp2 negative lookahead so any daily update extension
except fb2 is selected without maintaining an extension allowlist.
Exit code 0 means no new archive updates were downloaded, exit code 1 means
an error occurred, and exit code 2 means one or more new archive updates were
downloaded. Use code 2 to decide whether archive rollup or index/cache rebuild
work is needed.
Available fetch arguments:
--library NAME,-l NAME: fetch profile name from configuration. Default isflibusta.--to DIR,-o DIR: required destination directory for daily archive ZIPs.--tosql DIR: destination directory for decompressed SQL dump files.--nosql: skip SQL dump downloads.--retry N: download attempts per index or file. Default is3.--timeout SECONDS: per-request timeout. Default is20.--chunksize MB: download chunk size used while streaming files. Default is10.--continue: resume partial downloads when the server supports ranges.--sticky: ignore HTTP redirects and keep using the original host.
Roll downloaded daily update ZIPs into local FB2 and USR archives:
metabib rollup --archives flibusta --updates upd_flibustarollup keeps finalized archives and active .merging archives in --archives,
reads daily update ZIPs from each --updates directory, classifies updates with
rollup.update_patterns, and appends ZIP entries without recompressing them. If
no --updates directory is provided, rollup scans --archives for update ZIPs
as well. Generated archive names use the ID width of the existing .merging
archive or latest finalized archive in the same family; new archive directories
default to 10-digit IDs.
Daily update ZIPs are always preserved; retention and cleanup are separate
operational concerns.
Rolled-up archive names are always local range names such as
fb2-0000817672-0000818248.zip and usr-0000817672-0000818248.zip, including
when source updates are dated Librusec ZIPs.
Rollup also maintains these lineages independently. One invocation can consume a
mixed update directory and update both active archives, for example producing
fb2-0000886760-0000887123.merging and
usr-0000886760-0000887123.merging from the same --updates directory. Each
lineage has its own latest finalized archive, active .merging archive, and
overlap checks. Both lineages always use the same finalization policy.
rollup.update_patterns are regexp2 filename patterns. The first two capture
groups must be range begin and end, and family selects destination lineage
(fb2-* or usr-*). If one update file matches multiple patterns, rollup fails
with an ambiguity error so precedence is never hidden in code.
Rollup finalization is configured once under rollup.finalization and always
applies to both fb2 and usr. The default size policy preserves the original
behavior and finalizes active archives when their compressed size reaches the
per-lineage target in binary mebibytes:
rollup:
finalization:
policy: size
size:
target_mib:
fb2: 2048
usr: 4096Period policies ignore size targets. rolling finalizes an active .merging
archive after a whole-day or whole-week duration from the time that merge first
started accumulating entries:
rollup:
finalization:
policy: rolling
rolling:
duration: 14dAccepted rolling units are d and w; sub-day durations such as 24h are
rejected.
calendar finalizes when current UTC time leaves the stored bucket:
rollup:
finalization:
policy: calendar
calendar:
bucket: monthSupported UTC buckets are iso-week, iso-biweek, and month. ISO weeks start
Monday 00:00:00 UTC. ISO biweeks are weeks 1-2, 3-4, and so on; ISO week
53 is a single-week bucket.
Period policies store active merge timing in rollup-state.json inside
--archives. If a period policy sees an existing .merging archive without
matching state for that lineage, rollup fails instead of guessing when the merge
started. Stored state for all active lineages must use the same configured policy.
The size policy does not require this state file. When no .merging archive
exists for a lineage, period rollup starts that lineage cleanly and creates state
only after it publishes the first new active merge.
State file format:
{
"version": 1,
"lineages": {
"fb2": {
"active_merge": "fb2-0000000001-0000000100.merging",
"first_book": 1,
"last_book": 100,
"policy": "calendar",
"calendar": "month",
"bucket_start": "2026-08-01T00:00:00Z",
"bucket_end": "2026-09-01T00:00:00Z"
},
"usr": {
"active_merge": "usr-0000000001-0000000100.merging",
"first_book": 1,
"last_book": 100,
"policy": "calendar",
"calendar": "month",
"bucket_start": "2026-08-01T00:00:00Z",
"bucket_end": "2026-09-01T00:00:00Z"
}
}
}Only the fields for the selected policy are present for each active lineage.
active_merge, first_book, and last_book must match the .merging filename
for that lineage. When a lineage finalizes, its state entry is removed. When a
new active merge starts, rollup writes a new state entry atomically.
Finalization logs include both policy and reason; reasons are size,
rolling_deadline, or calendar_bucket_end.
By default, direct compressed copying does not validate entry payload CRCs. Set
rollup.validate_crc: true in the configuration to decompress each non-empty
numeric entry for CRC-32 validation before copying it. Validation can significantly
reduce performance, but entries are still copied in their original compressed form
without recompression.
Exit code 0 means no finalized archive was produced, exit code 1 means an
error occurred, and exit code 2 means one or more finalized fb2-*.zip or
usr-*.zip archives were created. Use code 2 to decide whether cache/index
rebuild work is needed.
Available rollup arguments:
--archives DIR,-a DIR: required directory for finalizedfb2-*.zipandusr-*.ziparchives plus activefb2-*.mergingandusr-*.mergingarchives.--updates DIR,-u DIR: directory containing daily update ZIPs; can be repeated. Defaults to--archiveswhen omitted.
cache creates portable manifest files for selected sources. It does not produce
the final merged dataset JSONL.
metabib cache \
--database-dumps /path/to/sql-dumps \
--archives /path/to/flibustaTo use an already imported database:
metabib cache --rebuild --no-import --database-dumps /path/to/sql-dumpsBy default, managed mode uses a fresh database for every run:
database:
managed: true
temporary: trueWith temporary: true, metabib initializes a new managed MariaDB datadir under
the OS temp directory and removes it on shutdown. Persistent managed datadirs are
reused between runs when temporary is set to false.
Use an existing MariaDB service instead of a managed one:
metabib --config metabib.yaml cache --rebuild --database-dumps /path/to/sql-dumpsBuild only archive manifests without starting MariaDB:
metabib cache --archives /path/to/flibustacache builds missing selected manifests by default. Existing manifests are
checked using source modification times; stale or invalid manifests fail unless
--rebuild is used. Use cache --check-md5 to additionally verify MD5 checksums
recorded in existing manifests.
Manifests are portable across directories and machines. Stored absolute paths are
kept as provenance, but manifest matching uses archive or dump file names,
recorded metadata, processing settings, timestamps for freshness, and optional
MD5 checksums when --check-md5 is enabled.
Archive manifest scope is selected before an archive manifest is validated or built:
- directories, backup entries such as
.org, and.fbdsidecar entries are ignored while classifying scope; - archive paths that look like USR input force
usrscope: the archive basename contains.usr-or.usr., starts withusr-, or any parent directory component isusr, ends with_usr, or ends with-usr; - otherwise, an archive with at least one
.fb2book entry isfb2scope; - otherwise, an archive with no
.fb2book entries isusrscope; - if an archive forced to
usrscope also contains.fb2book entries, those FB2 entries are ignored and a warning is logged; - if an archive selected as
fb2scope also contains non-FB2 book entries, those non-FB2 entries are ignored and a warning is logged.
New archive manifests are written as metabib.archive_manifest/2 with scope
set to fb2 or usr. Existing metabib.archive_manifest/1 files are treated as
legacy FB2 manifests and remain reusable when all other freshness checks pass.
When an archive manifest is rebuilt, the final Archive manifest created log
entry includes post-processing ignore counts: usr_fb2_entries_ignored for FB2
entries ignored in USR scope and fb2_non_fb2_entries_ignored for non-FB2 book
entries ignored in FB2 scope. The USR count includes both filename-detected
entries such as .fb2.zip and nested-inspection detections inside opaque
containers. Default logs have one final aggregate count per rebuilt archive.
Nested multi-volume archives in USR scope are not extracted across volumes. When
cache detects a complete nested multi-volume set, it emits one opaque record for
the root volume and skips continuation parts. The root record carries a
nested_archive_multivolume issue with volume_format, volume_count,
volume_entries, and volume_indexes details. Incomplete starts and orphan
continuations remain ordinary bad nested archive issues and are logged as
warnings. Supported detection patterns include RAR sets such as book.rar plus
book1.rar, book.part1.rar plus book.part2.rar, and
book_partiya_1_.rar plus book_partiya_2_.rar; 7z sets use the standard
book.7z.001, book.7z.002, ... scheme.
Set processing.fb2_body_fingerprints: true to calculate compact FB2 body and
section fingerprints while archive manifests are built. This requires
processing.parse_fb2: true. Archive manifests built with different fingerprint
settings, model, or section encoding are rejected and must be rebuilt.
By default, cache requires all SQL dump files to report the same dump date
before import. Use cache --allow-dump-date-mismatch to accept mixed dump dates;
per-file dump dates are still recorded, while the top-level manifest dump_date
is omitted.
For current Librusec dumps, only the tables required for FB2 metadata are imported. Unsupported or unrelated dump files in the SQL directory are ignored by the importer.
Database manifests also carry INPX-oriented author ambiguity metadata. Since the
database cache pass now covers both FB2 and non-FB2 catalog rows, this metadata is
stored in three scopes: all database books, FB2 books only, and USR/non-FB2 books
only. INPX generators select the scope that matches their --content mode so PDF
or other USR-only author collisions do not change FB2 INPX author names.
merge consumes existing cache manifests and writes one merged dataset JSONL
artifact for later inspection or INPX generation:
metabib merge \
--database-dumps /path/to/sql-dumps \
--archives /path/to/flibusta \
--output metabibMerge from archives only, database only, or both:
metabib merge --archives /path/to/flibusta --output archive-only
metabib merge --database-dumps /path/to/sql-dumps --output database-only
metabib merge --database-dumps /path/to/sql-dumps --archives /path/to/flibusta --output combinedmerge never starts MariaDB and never reads archives directly. It fails when a
selected manifest is missing, invalid, or stale. Use --check-md5 for full
source checksum verification, --allow-stale to warn and continue with stale
manifests, or --allow-missing to skip selected sources whose manifests do not
exist yet.
metabib merge \
--allow-missing \
--database-dumps /path/to/sql-dumps \
--archives /path/to/flibusta \
--archives /path/to/flibusta_usr \
--output combined--allow-missing omits missing sources from the dataset header and merged output.
It is mostly useful for debugging partial cache state; normal production runs
should build missing manifests with cache first. If every selected source
manifest is missing, merge still fails.
Archive-only merge does not require a database manifest. A database manifest is
required only when --database-dumps is selected, whether that is for
database-only output or for enriching archive records with database metadata.
Merged JSONL output is zstd-compressed by default, using the same compression
level as manifest files. Use --output-compression zstd, gz, zip, or none
to select a different output container. The --output value is an output prefix,
not a final file name: metabib merge --output all writes exactly one artifact,
such as all.jsonl.zst. Existing output files are replaced; when that happens,
metabib logs an overwrite warning.
The first JSONL value is a dataset header (metabib.dataset/1) with the database
dump date, archive entry layout, processing options, and declared ordering. Every
following value is a dataset record (metabib.dataset_record/1). INPX generation
requires this dataset shape and rejects metabib.record/1 input.
When a database manifest contains scoped INPX author ambiguity metadata, merge
copies it into the dataset header. Regenerate the database manifest and run
merge again after changes to author disambiguation logic; regenerating INPX from
an old merged JSONL cannot see new scoped metadata.
When archive manifests contain FB2 body fingerprints, merge records dataset-level
fingerprint coverage as none, partial, or complete and copies compact
per-book section fingerprints onto the FB2 artifact as fp. The value is a
base64url-no-padding encoded binary payload containing the synthetic root plus
compilation-relevant sections; sections with normalized word count below 100 are
omitted to keep manifests small.
Archive records are anchored by dataset archive ordinal and ZIP entry index.
Physical record order is never inferred from entry filenames or database book IDs.
When database enrichment is enabled, merge first treats a positive numeric entry
stem as matching evidence. If an exact filename alias with extension points at a
different database book, that alias wins because it describes the physical file
more precisely; merge keeps the numeric stem as an inferred archive catalog
identity and records a catalog_id_conflict issue. Otherwise merge uses the
numeric row, then tries a unique database filename alias when no numeric database
row exists. The selected database observation records the match method, and
conflicting evidence never changes the physical record locator.
Examples:
42.fb2first tries database book42throughnumeric_entry_stem; if present, database and FB2 claims share one archive-entry record while the archive locator remains the ZIP entry position.Some.Book.fb2can match a unique database filename alias; the database book ID becomes a catalog identity claim, not the physical record position.notes.fb2with no database match remains a valid archive-only record and has no invented catalog identity.1968.pdfcan infer numeric archive catalog identity1968, but an exact1968.pdfdatabase alias for another book wins the database match.
Database filename aliases are normalized with surrounding whitespace removed and
case folded before they are used for matching. Exact filenames with extensions are
kept as aliases. Bare nonnumeric stems are not indexed when an extension is known,
because title-like stems such as Megan_Lindholm_The_Wizard_of_the_Pigeons can
refer to several formats. Bare numeric stems are indexed only when the stem equals
the database book ID; 1968.pdf remains an exact alias, but bare 1968 is not an
alias for some other book whose title or filename happens to be 1968.
If several normalized aliases still point at different database books, merge tries to resolve the collision with database lifecycle metadata before declaring the alias ambiguous. The index still stores the database book that owns the alias; joined metadata is only tie-break evidence and is preserved as a relation.
- an active (
deleted=0) book wins over a deleted one; - if both candidates have the same deleted state, an alias owner with joined-book resolution wins over an otherwise unlinked alias owner;
- otherwise the alias is removed from the filename index and a debug log reports
Ambiguous database filename ignored.
Ambiguous or conflicting filename evidence never creates, removes, or reorders
archive entries. If archive entry 844654.pdf numerically matches database book
844654, but an exact filename alias points to book 844910, merge keeps one
physical 844654.pdf archive-entry record, matches it to 844910, and records a
catalog_id_conflict issue preserving the numeric stem evidence. If 844910.pdf
is also present as another archive entry, it is emitted separately as its own
record. INPX output therefore preserves both physical files unless later filters
explicitly drop one.
Use inspect for quick checks and debugging of merged dataset JSONL artifacts:
metabib inspect --input combined
metabib inspect --input combined --archives
metabib inspect --input combined --validate
metabib inspect --input combined --issues
metabib inspect --input combined --book-id 12345
metabib inspect --input combined --archive archive-0001 --index 42
metabib inspect --input combined --file 12345.fb2 --json--input accepts the same prefix or exact dataset path as INPX commands. For
example, --input combined discovers exactly one of combined.jsonl,
combined.jsonl.zst, combined.jsonl.gz, or combined.jsonl.zip.
With no mode flag, inspect reads the dataset header and prints its schema,
record count, source totals, processing settings, and other summary metadata. It
does not scan the record stream in this mode.
Inspect summary output includes FB2 body fingerprint coverage when present. Record
lookup output includes the optional artifact fp payload in both text and JSON
modes.
Inspect summary output also includes INPX author ambiguity counts. Use --verbose
to print the actual ambiguous DB author maps for all, FB2-only, and USR-only
scopes. This is the quickest way to explain why an INPX author got a suffix such
as [#17376] or [писатель].
Available modes and options:
--archives: list archive source IDs, ordinals, entry counts, names, and path hints. IDs such asarchive-0001can then be used with--archive.--validate: stream the full dataset and validate ordering, schemas, provenance references, source declarations, and archive indexes without writing any derived artifact. Successful output includes the number of records read plus issue totals by stage and code.--issues: stream the full dataset and list records withissues, including record number, locator, artifact names, and structured issue payloads. Use--jsonfor machine-readable issue records.--book-id ID: return the first record matching a primary locator, Flibusta catalog identity, or database observation with that book ID.--archive ID --index INDEX: return the record at the zero-based entry index in the specified dataset archive source.--file NAME: return the first record whose artifact name or archive occurrence entry matchesNAME; matching is case-insensitive.--decode-fp: when returning a record, also decode compact artifactfppayloads into root-first section rows with depth, leaf flag, and MD5 hex key.--json: emit the selected summary, archive list, validation result, or record as machine-readable JSON.
Only one of --archives, --issues, --book-id, --archive/--index, and
--file may be used at a time. --validate cannot be combined with any of those
modes, while --json can be used with every mode. A lookup that finds no matching
record exits with status 4; other failures use status 1.
Archive source IDs are local to one merged dataset. Use archive names, path hints, and checksums for long-term correlation across regenerated datasets.
All INPX generators treat filename fields as lookup keys. FILE and EXT
preserve physical archive-entry spelling more strictly than display fields:
non-breaking spaces, literal percent signs, tabs, and other non-structural bytes
are left unchanged. Only characters that would break the INPX row format are
escaped with a visible tilde sequence: field separator 0x04 becomes ~04,
carriage return becomes ~0D, and line feed becomes ~0A. This is a narrow
metabib convention rather than full URL percent-encoding; INPX consumers need
matching decode support to resolve such archive entries during import or
extraction. Literal ~04, ~0D, and ~0A in archive names are ambiguous escape
sequences; generators warn when such names are encountered.
Build a MyHomeLib-compatible "historical" INPX from merged dataset JSONL:
metabib mhl-inpx --input all --output flibustamhl-inpx consumes the merged dataset JSONL; it does not read SQL dumps, start
MariaDB, or parse archives directly. Database metadata, FB2 metadata, and
sidecar-derived metadata are read from normalized claims when present.
When the merge input is database-only and has no archives, mhl-inpx writes the
records into online.inp. When archive metadata is present, online.inp is not
created and archive-less records are ignored.
Available mhl-inpx arguments:
--input PREFIX,-i PREFIX: required input prefix or exact dataset path.metabib mhl-inpx --input alldiscovers exactly one ofall.jsonl,all.jsonl.zst,all.jsonl.gz, orall.jsonl.zip.--output PREFIX,-o PREFIX: required output prefix. The dump date from the dataset header is appended automatically, so--output flibustawrites a file named likeflibusta_20260603.inpx.--content MODE: content selection. Supported values arefb2,usr, andall. Default isfb2. Archive records are classified by their logical artifact name first; physical archive member names stay in occurrence entries. For example, an opaque.zipentry inspected as PDF has artifact name.pdfand occurrence entry.zip, so it is not selected as FB2 even when matched database metadata saysfile_type=fb2. If nested archive inspection is disabled, opaque containers keep their container extension, such as.zip, and still do not fall back to databasefile_typefor archive content selection. Databasefile_typeremains the fallback for database-only records.--format MODE: INPX record layout. Supported values are2xandruks. Default is2x.ruksappends MD5 and replacement fields when available.--sequence MODE: database sequence selection. Supported values areauthor,publisher, andignore. Default isauthor.--prefer-fb2 MODE: how FB2 metadata is used relative to database metadata for authors and sequences. Supported values areignore,merge,complement, andreplace. Default iscomplement: database authors and sequence data are preferred when present, and FB2 metadata fills missing values. Usereplacewhen FB2 author order should win.
Build a FLibrary-compatible INPX from the same merged dataset JSONL:
metabib flib-inpx --input all --output flibustaflib-inpx consumes only merged dataset JSONL. It does not read SQL dumps or
archives directly, emits no dummy records, and always writes structure.info with
FLibrary extensions such as FOLDER, YEAR, and SOURCELIB.
Database-only FLibrary INPX generation follows the same online.inp rule as
mhl-inpx: it is created only when the dataset header contains no archives.
When a book has multiple selected sequences, flib-inpx writes repeated .inp
rows for the same FOLDER + FILE + EXT, one row per flat SERIES/SERNO link.
FLibrary imports those rows as multiple series relations for one physical book.
Available flib-inpx arguments:
--input PREFIX,-i PREFIX: required input prefix, discovered the same way asmhl-inpx.--output PREFIX,-o PREFIX: required output prefix. The dump date is appended automatically, so--output flibustawrites a file named likeflibusta_20260603.inpx.--content MODE: content selection. Supported values arefb2,usr, andall. Default isfb2. Archive records are classified by their logical artifact name first; physical archive member names stay in occurrence entries. For example, an opaque.zipentry inspected as PDF has artifact name.pdfand occurrence entry.zip, so it is not selected as FB2 even when matched database metadata saysfile_type=fb2. If nested archive inspection is disabled, opaque containers keep their container extension, such as.zip, and still do not fall back to databasefile_typefor archive content selection. Databasefile_typeremains the fallback for database-only records.--prefer-fb2 MODE: sequence source preference. Supported values areignore,merge,complement, andreplace. Default iscomplement.--sequence MODE: selected sequence class. Supported values areauthor,publisher,all, andignore. Default isauthor.--fb2-flatten MODE: FB2 nested sequence flattening. Supported values areall,leaf,path, andpath-leaf. Default isall.--source-lib VALUE:SOURCELIBfield value. Default is the dataset header library name.--additional: also write supported FLibrary additional artifacts next to the INPX output. Database-only inputs have no archive-derived additional source data, so this flag is ignored with a warning for those datasets.
With --additional, flib-inpx writes prefix-annotations.zip from FB2
annotations and FBD sidecar annotations for accepted archive records, including
USR records selected by --content usr or --content all. FB2 annotations are
preferred when both FB2 and FBD claims are present. When the input dataset has FB2
body fingerprints and compilations are detected, it also writes
prefix-compilations.zip containing compact compilations.json. Partial
fingerprint coverage is accepted with a warning; datasets without fingerprints
skip the compilations artifact.
FLibrary-specific settings that are not command-line arguments live under
inpx.flibrary:
inpx:
flibrary:
sequence_dedup: case-insensitive
fb2_path_separator: " / "sequence_dedup supports case-insensitive and case-sensitive.
fb2_path_separator is used by --fb2-flatten path and path-leaf.
Build an archive-backed filtered/split INPX from the same merged dataset JSONL:
metabib inpx --input all --output ru --where '{{eq .Lang "ru"}}'inpx is archive-only. It rejects database-only datasets because its output uses
explicit FOLDER and INSNO fields to point at source archive entries. When
archive metadata is present, archive-less records are skipped. Filtered records
are omitted entirely, and no dummy records are emitted.
Content selection happens before template filtering. The inpx default is
--content all, while --content fb2 keeps only FB2 records and --content usr
keeps non-FB2 records. Archive records are classified by logical artifact name
before database file_type; physical archive member names stay in occurrence
entries. If nested archive inspection is disabled, opaque containers keep their
container extension for content selection. Database file_type is still used for
database-only records.
Filtering then happens after INPX normalization. Templates see final output
fields, including canonicalized language values such as ru, not raw source
values such as RU or russian. Canonicalization only normalizes existing
language values; it does not infer language when both database and FB2 language
claims are absent. Use {{ne .Lang ""}} to exclude missing-language records, or
{{default "unknown" .Lang}} to route them into an explicit split bucket.
Available inpx arguments:
--input PREFIX,-i PREFIX: required input prefix, discovered the same way asmhl-inpx.--output PREFIX,-o PREFIX: required output prefix. The dump date is appended automatically, so--output flibustawrites a file named likeflibusta_20260603.inpx.--content MODE: content selection. Supported values arefb2,usr, andall. Default isall.--where TEMPLATE: keep rows when the Go template renderstrue,1,yes, oron; drop rows when it renders empty output,false,0,no, oroff.--where-file FILE: load the filter template from a file. Mutually exclusive with--where.--split-by TEMPLATE: write accepted books to<key>.inpentries using a Go template. Without this flag, all accepted rows go tobooks.inp.--split-by-file FILE: load the split template from a file. Mutually exclusive with--split-by.--prefer-fb2 MODE,--sequence MODE, and--fb2-flatten MODE: same sequence-source and FB2 flattening semantics asflib-inpx.--additional: write FLibrary-compatible additional artifacts for accepted books only. Annotation artifacts use FB2 annotations first, then FBD sidecar annotations for USR records.
Filter and split templates use Go text/template with slim-sprig functions plus
oneOf, containsValue, and rangeName helpers:
metabib inpx --input all --output by-lang --split-by '{{.Lang}}'
metabib inpx --input all --output ru-sf --where '{{and (eq .Lang "ru") (containsValue .Genres "sf_history")}}'
metabib inpx --input all --output by-lang-state --split-by '{{if .Deleted}}deleted-{{default "unknown" .Lang}}{{else}}{{default "unknown" .Lang}}{{end}}'
metabib inpx --input all --output by-libid --split-by '{{rangeName .LibID 10000 "other"}}'
metabib inpx --input all --output chunks --split-by '{{rangeName .AcceptedBook 10000 "other"}}'Available filter and split template fields:
.InputRecord: 1-based ordinal of the streamed dataset record. Available to--whereand--split-by..AcceptedBook: 1-based ordinal of the accepted book. It is set for--split-byand is0while--whereis evaluated..AcceptedRow: 1-based ordinal of the accepted output row. It is set for--split-byand is0while--whereis evaluated..BookRow: 1-based ordinal of the current sequence row within the book..Author: rendered INPXAUTHORfield..Genre: rendered INPXGENREfield, with colon-separated genre codes..Title: rendered INPXTITLEfield..Series: rendered INPXSERIESfield for the current row..SerNo: rendered INPXSERNOfield for the current row..File: rendered INPXFILEfield..Size: rendered INPXSIZEfield..LibID: rendered INPXLIBIDfield..Deleted: boolean deletion state derived from the INPXDELsource value..Ext: rendered INPXEXTfield..Date: rendered INPXDATEfield..InsNo: integer source archive entry position written to INPXINSNO..Folder: rendered INPXFOLDERfield..Lang: rendered canonical INPXLANGfield..LibRate: rendered INPXLIBRATEfield..Keywords: rendered INPXKEYWORDSfield..Year: rendered INPXYEARfield..Authors: selected structured authors for the book..Genres: selected genre codes as a string slice..Sequences: selected sequences for the book; each item has.Name,.Number, and.Source..HasDatabase: true when the record has database observation..HasFB2: true when the record has FB2 observation..ArchiveID: dataset archive source ID..ArchiveName: source archive file name.
Each .Authors item has .FirstName, .MiddleName, .LastName, .NickName,
and .ID. .ID is the flibusta.person identity when present.
rangeName VALUE SIZE FALLBACK returns zero-padded inclusive range names.
Pass any positive numeric value, such as .AcceptedBook or .LibID. Invalid,
empty, or non-positive values return FALLBACK; invalid bucket sizes fail the
run.
Examples:
{{rangeName .AcceptedBook 10000 "other"}}: split by accepted-book ordinal.{{rangeName .LibID 10000 "other"}}: split by numericLIBIDrange..LibID = "103995"with size10000becomes0000100001-0000110000.inp.
--split-by does not sort rows. Splitting by .Lang is safe because language is
single-valued. Splitting by genres or other multi-valued fields is lossy unless
the template explicitly chooses one value, such as {{index .Genres 0}}. One
book is never split across multiple .inp entries; if it writes multiple
sequence rows, all accepted rows for that book go to the split key from the first
accepted row.
Because templates are flexible, every run logs loaded, written, filtered, skipped, and per-split counts so accidental lossy filters are visible without debug logging.
Shared INPX settings live under the inpx section of the YAML configuration.
Some fields are used by every INPX generator, while MHL-specific options such as
quick_fix and limits also live there for historical compatibility.
inpx:
disambiguate_authors: true
comment_template: "\ufeff{{ .DatabaseName }} FB2 - {{ .DisplayDate }}\r\n{{ .DatabaseName }}_{{ .DumpDate }}\r\n65536\r\nЛокальные архивы библиотеки {{ .DatabaseName }} (FB2) {{ .DisplayDate }}"
version_template: "{{ .DumpDate }}\r\n"
language:
canonicalize: true
aliases:
sp: es
gr: el
un: und
fallback_locales:
- en
- ru
- bg
ignore_patterns:
- '^\?+$'
context_rules:
- from: ba
to: krc
when_any_source_language:
- krc
- balkar
- from: xa
to: xal
when_any_source_language:
- xalcomment_template and version_template are Go text/template values rendered
when collection.info and version.info are written. Available values are
.DatabaseName, .DumpDate, and .DisplayDate; slim-sprig template functions
are available. Keep the leading \ufeff BOM when the target reader expects a BOM
at the start of collection.info.
INPX generators apply database author disambiguation when
inpx.disambiguate_authors is enabled. Database cache manifest creation records
DB authors whose cleansed, non-truncated LastName,FirstName,MiddleName value
collides with a different database contributor ID. The manifest stores collision
groups for all database books, FB2 books only, and USR/non-FB2 books only. INPX
generation selects the matching group set by --content: fb2 uses only FB2
collisions, usr uses only USR collisions, and all uses all collisions.
When a selected DB author belongs to an ambiguous group, the exported INPX
last-name field receives a stable suffix. A unique database nickname becomes the
preferred suffix, for example Новиков [писатель],Александр,Васильевич:. If no
unique nickname is available, the suffix falls back to the Flibusta person ID,
for example Абрамов [#17376],Александр,Иванович:. Only DB authors with catalog
person identities are disambiguated; FB2-only authors are not changed because they
do not have reliable database contributor IDs.
Scoped disambiguation prevents non-FB2 catalog rows from changing default FB2
INPX output. For example, if Коллектив авторов is ambiguous only because a PDF
row introduces another DB contributor ID, flib-inpx --content fb2 keeps FB2 rows
as Коллектив авторов,,:. flib-inpx --content all can still suffix that author
because the all-content INPX needs unique names across both FB2 and USR records.
To inspect author ambiguity metadata in a merged dataset, run:
metabib inspect --input all --verboseIf author disambiguation metadata changes, rebuild the database manifest before regenerating merged JSONL and INPX output:
metabib cache --database-dumps /path/to/sql-dumps --rebuild
metabib merge --database-dumps /path/to/sql-dumps --archives /path/to/flibusta --output all
metabib flib-inpx --input all --output flibustaAll INPX generators sanitize author name components before joining them into the
rendered INPX AUTHOR field. Internal ASCII commas and colons are replaced with
fullwidth characters (, and :) so they cannot be confused with INPX author
delimiters; leading and trailing spaces, commas, and colons are trimmed,
whitespace is collapsed, and invalid replacement characters make that component
empty. Generic inpx template fields such as .Author use this rendered value;
structured .Authors[] values exposed to templates remain the raw selected
author claims.
INPX generators canonicalize language values at generation time by default.
Raw merged dataset JSONL remains unchanged. Database language has priority over
FB2 language, except ignored placeholder values are treated as absent so FB2 can
be used as a fallback. Resolved output is the base language subtag, so values such
as RU, en-US, and sr-Latn become ru, en, and sr. Explicit aliases
handle known noisy values such as sp -> es, gr -> el, and un -> und.
aliases are matched case-insensitively after whitespace collapse. Full-value
aliases are checked before comma splitting, so phrase aliases containing commas
are supported. If no full-value alias matches, the raw language value is split on
commas and parts are tried from left to right until one resolves. fallback_locales
are BCP 47 display locales used for localized language-name matching after
same-record context does not match. ignore_patterns are regular expressions for
values that should be skipped entirely. context_rules apply only when another
source-language observation on the same record supports the correction.
Unresolved canonicalization attempts are emitted raw and logged as warnings with
book ID, source field, observation, original value, candidate value, locator,
artifact, source languages, and context language. With global --verbose,
ignored values and successful canonicalizations that change the output are logged
with the same identifying details.
Existing INPX output is replaced only after the new archive is fully written. If
an existing file is overwritten, metabib logs a warning. During generation,
metabib logs the selected dataset input, record loading progress, one live
message per created .inp member, and final aggregate INPX statistics.
Manifest cache files are zstd-compressed JSONL payloads named .manifest.zst,
for example lib.manifest.zst or database.manifest.zst. When archive
manifests are stored in a central processing.manifests.archive_dir, the first
archive with a basename keeps the usual manifest name. Later archives with the
same basename get a source-qualified manifest name and a warning is logged.
Use global --verbose to enable detailed progress reporting.
Dump the default configuration to a file before customizing paths, logging, fetch profiles, processing options, or INPX templates:
metabib dumpconfig --default metabib.yamldumpconfig --default writes the embedded YAML template after expanding runtime
defaults such as the executable directory, OS-specific database socket or TCP
settings, free local ports on Windows, and CPU-based worker counts. The output is
not based on values from a loaded --config file.
Dump the effective configuration after applying defaults and a config file:
metabib --config metabib.yaml dumpconfig effective.yamlOmit the destination to write YAML to stdout:
metabib dumpconfig --default
metabib --config metabib.yaml dumpconfigConfiguration files are validated strictly: unknown YAML fields are rejected, and
omitted fields keep their defaults from the embedded template. Pass the file with
the global --config FILE option, before the subcommand:
metabib --config metabib.yaml cache \
--database-dumps /path/to/sql-dumps \
--archives /path/to/flibustascripts/fb2_flibusta.sh is an example automation script for the common Flibusta
FB2 workflow. It is not required by metabib; use it as a starting point for
site-specific scheduling, paths, cleanup, and INPX output choices. The metabib
executable is expected to be in the same directory as the script; if
metabib.yaml exists there, it is passed to every metabib invocation.
Run the full update workflow:
scripts/fb2_flibusta.sh /volume4/backup/library full mhl
scripts/fb2_flibusta.sh /volume4/backup/library full flib
scripts/fb2_flibusta.sh /volume4/backup/library full bothRun indexing only from existing local archives and the latest existing SQL dump
directory matching <library-root>/flibusta_*:
scripts/fb2_flibusta.sh /volume4/backup/library reindex bothBoth modes accept an optional user account whose home directory should be used as the working directory. This is useful for Synology Task Scheduler setups:
scripts/fb2_flibusta.sh /volume4/backup/library full both myuserThe full mode runs fetch, rollup, cache, merge, and the selected INPX
exporter. It exits early when no new daily archives are downloaded or when rollup
does not finalize a new archive. The reindex mode skips download and rollup and
reruns cache, merge, and the selected INPX exporter from already available
data.
The script writes generated INPX files with non-overlapping output prefixes:
mhlmode writesinpx/flibusta_mhl_<dump-date>.inpx.flibmode writesinpx/flibusta_flib_<dump-date>.inpxand passes--source-lib flibusta --additionalso FLibrary receives the original source library name and additional artifacts are generated next to the INPX.bothmode writes both files from the same merged JSONL input.
In flib and both modes, additional FLibrary outputs use the same prefix as
the INPX: flibusta_flib_<dump-date>-annotations.zip is written when annotation
data is available, and flibusta_flib_<dump-date>-compilations.zip is written
when FB2 body fingerprints detect compilations.
After FLibrary additional artifacts are written, the script updates stable links
under inpx/flib-etc/: annotations.zip points to the current annotations ZIP,
and compilations.zip points to the current compilations ZIP when one was
generated. Stale symlinks are removed when an optional artifact is absent.
After a successful download, full mode keeps the five newest SQL dump
directories and also keeps the newest SQL dump directory that already contains
database.manifest.zst, so reindexing can reuse the latest database cache even
when it is older than the newest downloads.
Expected library layout under <library-root>:
flibusta/: finalized local FB2 archives and active.mergingarchive.upd_flibusta/: downloaded daily update archives.flibusta_<timestamp>/: downloaded SQL dumps.inpx/: generated INPX files and merged dataset JSONL artifacts.
The script writes a console log next to itself named like
flibusta_full_mhl_20260622_103000.log or
flibusta_reindex_both_20260622_103000.log.
For a single combined script and metabib debug log, configure logging in the
same-directory metabib.yaml like this:
logging:
console:
level: debug
file:
level: noneWith that configuration, the script log includes phase separators, metabib
debug messages, and MariaDB process/client output. The script leaves application
file logging to metabib logging configuration.