@clearmist-labs/comic-archive-handler 1.4.0 → 1.6.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.
- package/dist/index.d.mts +47 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +137 -50
- package/dist/index.mjs.map +1 -1
- package/docs/API.md +51 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -50,6 +50,15 @@ export interface ConvertArchiveOptions extends ArchiveWriteOptions {
|
|
|
50
50
|
format: ImageOutputFormat;
|
|
51
51
|
options?: ImageConvertOptions;
|
|
52
52
|
};
|
|
53
|
+
/**
|
|
54
|
+
* Replaces the source's embedded comic metadata in the output. The source's
|
|
55
|
+
* `ComicInfo.xml`/`MetronInfo.xml` entries (and, for an asar source, its
|
|
56
|
+
* header metadata) are dropped, then each schema given here is written the
|
|
57
|
+
* way the target format stores it: an asar header key, or a root-level
|
|
58
|
+
* `{schema}.xml` entry for every other format. When omitted, entries are
|
|
59
|
+
* copied as-is and an asar source's header metadata is not carried over.
|
|
60
|
+
*/
|
|
61
|
+
metadata?: Partial<Record<MetadataSchema, ComicMetadata>>;
|
|
53
62
|
}
|
|
54
63
|
export interface AddMetadataOptions extends ArchiveWriteOptions {
|
|
55
64
|
overwrite?: boolean;
|
|
@@ -475,6 +484,20 @@ export declare function phashToHex(hash: bigint): string;
|
|
|
475
484
|
export declare function hammingDistance(a: bigint, b: bigint): number;
|
|
476
485
|
export declare function computeArchiveImagePHash(input: ArchiveInput, entryPath: string): Promise<bigint>;
|
|
477
486
|
//#endregion
|
|
487
|
+
//#region src/images/dimensions.d.ts
|
|
488
|
+
export interface ImageDimensions {
|
|
489
|
+
width: number;
|
|
490
|
+
height: number;
|
|
491
|
+
}
|
|
492
|
+
export interface ImageInfo extends ImageDimensions {
|
|
493
|
+
/** The format detected from the image's contents (not its file extension), such as `jpeg`, `png`, `webp`, or `bmp`. */
|
|
494
|
+
type: string;
|
|
495
|
+
}
|
|
496
|
+
/** Reads an image's format and pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
|
|
497
|
+
export declare function readImageInfo(buffer: Buffer): Promise<ImageInfo | null>;
|
|
498
|
+
/** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
|
|
499
|
+
export declare function readImageDimensions(buffer: Buffer): Promise<ImageDimensions | null>;
|
|
500
|
+
//#endregion
|
|
478
501
|
//#region src/images/isImage.d.ts
|
|
479
502
|
export declare const IMAGE_EXTENSIONS: readonly ["jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", "tif"];
|
|
480
503
|
export declare function getExtension(entryPath: string): string;
|
|
@@ -535,6 +558,27 @@ export interface ReadArchiveEntriesResult {
|
|
|
535
558
|
*/
|
|
536
559
|
export declare function readArchiveEntries(input: ArchiveInput): AsyncGenerator<ReadArchiveEntriesResult>;
|
|
537
560
|
//#endregion
|
|
561
|
+
//#region src/readArchiveImageInfo.d.ts
|
|
562
|
+
export interface ArchiveImageInfo {
|
|
563
|
+
/** The entry's file name, without its directory. */
|
|
564
|
+
name: string;
|
|
565
|
+
/** The entry's full path inside the archive. */
|
|
566
|
+
path: string;
|
|
567
|
+
/** Pixel width, or `null` when the entry's contents aren't a readable image. */
|
|
568
|
+
width: number | null;
|
|
569
|
+
/** Pixel height, or `null` when the entry's contents aren't a readable image. */
|
|
570
|
+
height: number | null;
|
|
571
|
+
/** The format detected from the entry's contents (not its extension), or `null` when the contents aren't a readable image. */
|
|
572
|
+
type: string | null;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Returns the name, path, dimensions, and format of every image entry in an
|
|
576
|
+
* archive, in archive iteration order. Image entries are identified by
|
|
577
|
+
* extension (see `isImagePath`); non-image entries are skipped without being
|
|
578
|
+
* read. Everything is gathered in a single pass over the archive.
|
|
579
|
+
*/
|
|
580
|
+
export declare function readArchiveImageInfo(input: ArchiveInput): Promise<ArchiveImageInfo[]>;
|
|
581
|
+
//#endregion
|
|
538
582
|
//#region src/removeArchiveEntry.d.ts
|
|
539
583
|
export declare function removeArchiveEntry(input: ArchiveInput, entryPath: string, options?: RemoveEntryOptions): Promise<Buffer | void>;
|
|
540
584
|
//#endregion
|
|
@@ -571,6 +615,8 @@ declare const comicArchiveHandler: {
|
|
|
571
615
|
hammingDistance: typeof hammingDistance;
|
|
572
616
|
IMAGE_EXTENSIONS: readonly ["jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", "tif"];
|
|
573
617
|
isImagePath: typeof isImagePath;
|
|
618
|
+
readImageDimensions: typeof readImageDimensions;
|
|
619
|
+
readImageInfo: typeof readImageInfo;
|
|
574
620
|
sha256ArchiveEntry: typeof sha256ArchiveEntry;
|
|
575
621
|
sha256Archive: typeof sha256Archive;
|
|
576
622
|
renameArchiveImagesSequentially: typeof renameArchiveImagesSequentially;
|
|
@@ -578,6 +624,7 @@ declare const comicArchiveHandler: {
|
|
|
578
624
|
listArchiveFiles: typeof listArchiveFiles;
|
|
579
625
|
readArchiveEntry: typeof readArchiveEntry;
|
|
580
626
|
readArchiveEntries: typeof readArchiveEntries;
|
|
627
|
+
readArchiveImageInfo: typeof readArchiveImageInfo;
|
|
581
628
|
removeArchiveEntry: typeof removeArchiveEntry;
|
|
582
629
|
benchmarkArchive: typeof benchmarkArchive;
|
|
583
630
|
renderBenchmarkReportMarkdown: typeof renderBenchmarkReportMarkdown;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/benchmark/types.ts","../src/errors.ts","../src/detect.ts","../src/convertArchive.ts","../src/extractArchive.ts","../src/metadata/comicInfo.ts","../src/metadata/metronInfo.ts","../src/metadata/validate.ts","../src/metadata/schema.ts","../src/metadata/index.ts","../src/images/convert.ts","../src/images/phash.ts","../src/images/isImage.ts","../src/hashing/sha256.ts","../src/rename.ts","../src/strip.ts","../src/listFiles.ts","../src/readArchiveEntry.ts","../src/readArchiveEntries.ts","../src/removeArchiveEntry.ts","../src/benchmark/benchmarkArchive.ts","../src/benchmark/report.ts","../src/index.ts"],"mappings":";;YAEY;;YAGA,eAAe;YAEf;YAEA;iBAEK;;EAEf;;EAEA;;EAEA;;iBAGe;;EAEf;;iBAGe;;EAEf;;EAEA;;EAEA;;iBAGe;EACf,OAAO;EACP,OAAO;EACP,MAAM;;;iBAIS;;;;;;;EAOf;;;;;EAKA,kBAAkB;;iBAGH,8BAA8B;EAC7C;IAAU,QAAQ;IAAmB,UAAU
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/benchmark/types.ts","../src/errors.ts","../src/detect.ts","../src/convertArchive.ts","../src/extractArchive.ts","../src/metadata/comicInfo.ts","../src/metadata/metronInfo.ts","../src/metadata/validate.ts","../src/metadata/schema.ts","../src/metadata/index.ts","../src/images/convert.ts","../src/images/phash.ts","../src/images/dimensions.ts","../src/images/isImage.ts","../src/hashing/sha256.ts","../src/rename.ts","../src/strip.ts","../src/listFiles.ts","../src/readArchiveEntry.ts","../src/readArchiveEntries.ts","../src/readArchiveImageInfo.ts","../src/removeArchiveEntry.ts","../src/benchmark/benchmarkArchive.ts","../src/benchmark/report.ts","../src/index.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -14,10 +14,10 @@ import * as os from "node:os";
|
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import sevenBin from "7zip-bin-full";
|
|
16
16
|
import sharp from "sharp";
|
|
17
|
-
import { pipeline } from "node:stream/promises";
|
|
18
17
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
19
18
|
import { validateXML } from "xmllint-wasm";
|
|
20
19
|
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { pipeline } from "node:stream/promises";
|
|
21
21
|
import { createHash } from "node:crypto";
|
|
22
22
|
//#region src/errors.ts
|
|
23
23
|
var UnsupportedOperationError = class extends Error {
|
|
@@ -644,6 +644,13 @@ const asarAdapter = {
|
|
|
644
644
|
}
|
|
645
645
|
const outputFile = path.join(tempDir, "output.asar");
|
|
646
646
|
await createPackage(stagingDir, outputFile);
|
|
647
|
+
const comicMetadata = options?.comicMetadata;
|
|
648
|
+
if (comicMetadata && Object.keys(comicMetadata).length > 0) {
|
|
649
|
+
await writeAsarHeaderPatch(outputFile, (header) => {
|
|
650
|
+
header.comicMetadata = comicMetadata;
|
|
651
|
+
}, { output: destination });
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
647
654
|
await new Promise((resolve, reject) => {
|
|
648
655
|
const readStream = fs.createReadStream(outputFile);
|
|
649
656
|
readStream.on("error", reject);
|
|
@@ -873,54 +880,6 @@ async function convertArchiveImages(input, format, options = {}) {
|
|
|
873
880
|
return withOutput(output, (destination) => adapter.write(entries(), destination, { tempDir }));
|
|
874
881
|
}
|
|
875
882
|
//#endregion
|
|
876
|
-
//#region src/convertArchive.ts
|
|
877
|
-
function replaceExtension(entryPath, format) {
|
|
878
|
-
return `${entryPath.replace(/\.[^./\\]+$/, "")}.${format}`;
|
|
879
|
-
}
|
|
880
|
-
async function convertArchive(input, targetType, options = {}) {
|
|
881
|
-
const sourceType = await detectArchiveType(input);
|
|
882
|
-
if (sourceType === "unknown") throw new ArchiveFormatError("Could not determine the source archive type.");
|
|
883
|
-
const sourceAdapter = getAdapter(sourceType);
|
|
884
|
-
const targetAdapter = getAdapter(targetType);
|
|
885
|
-
const image = options.image;
|
|
886
|
-
async function* entries() {
|
|
887
|
-
for await (const entry of sourceAdapter.listEntries(input, { tempDir: options.tempDir })) if (image && isImagePath(entry.path) && getExtension(entry.path) !== (image.format === "jpg" ? "jpg" : image.format)) {
|
|
888
|
-
const converted = await convertImageBuffer(await streamToBuffer(entry.openReadStream()), image.format, image.options);
|
|
889
|
-
yield {
|
|
890
|
-
path: replaceExtension(entry.path, image.format === "jpg" ? "jpg" : image.format),
|
|
891
|
-
size: converted.length,
|
|
892
|
-
content: Readable.from(converted)
|
|
893
|
-
};
|
|
894
|
-
} else yield {
|
|
895
|
-
path: entry.path,
|
|
896
|
-
size: entry.size,
|
|
897
|
-
content: entry.openReadStream()
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
return withOutput(options.output, (destination) => targetAdapter.write(entries(), destination, { tempDir: options.tempDir }));
|
|
901
|
-
}
|
|
902
|
-
//#endregion
|
|
903
|
-
//#region src/extractArchive.ts
|
|
904
|
-
/**
|
|
905
|
-
* Extracts every entry in an archive to real files under `destDir`,
|
|
906
|
-
* preserving relative paths (`destDir` is created if missing). Throws
|
|
907
|
-
* `ArchiveFormatError` for an undetectable/unknown format, or
|
|
908
|
-
* `UnsupportedOperationError` for ACE. Returns the archive-relative entry
|
|
909
|
-
* paths that were written, in archive iteration order.
|
|
910
|
-
*/
|
|
911
|
-
async function extractArchive(input, destDir, options = {}) {
|
|
912
|
-
const adapter = getAdapter(await detectArchiveType(input));
|
|
913
|
-
await fs.promises.mkdir(destDir, { recursive: true });
|
|
914
|
-
const written = [];
|
|
915
|
-
for await (const entry of adapter.listEntries(input, { tempDir: options.tempDir })) {
|
|
916
|
-
const target = resolveSafeEntryPath(destDir, entry.path);
|
|
917
|
-
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
918
|
-
await pipeline(entry.openReadStream(), fs.createWriteStream(target));
|
|
919
|
-
written.push(entry.path);
|
|
920
|
-
}
|
|
921
|
-
return written;
|
|
922
|
-
}
|
|
923
|
-
//#endregion
|
|
924
883
|
//#region src/removeArchiveEntry.ts
|
|
925
884
|
async function removeArchiveEntry(input, entryPath, options = {}) {
|
|
926
885
|
const adapter = getAdapter(await detectArchiveType(input));
|
|
@@ -1928,6 +1887,72 @@ async function removeComicMetadata(input, schema, options = {}) {
|
|
|
1928
1887
|
throw new ArchiveFormatError(`Archive does not contain ${fileName}.`);
|
|
1929
1888
|
}
|
|
1930
1889
|
//#endregion
|
|
1890
|
+
//#region src/convertArchive.ts
|
|
1891
|
+
const METADATA_FILE_NAMES = /* @__PURE__ */ new Set(["ComicInfo.xml", "MetronInfo.xml"]);
|
|
1892
|
+
function replaceExtension(entryPath, format) {
|
|
1893
|
+
return `${entryPath.replace(/\.[^./\\]+$/, "")}.${format}`;
|
|
1894
|
+
}
|
|
1895
|
+
async function convertArchive(input, targetType, options = {}) {
|
|
1896
|
+
const sourceType = await detectArchiveType(input);
|
|
1897
|
+
if (sourceType === "unknown") throw new ArchiveFormatError("Could not determine the source archive type.");
|
|
1898
|
+
const sourceAdapter = getAdapter(sourceType);
|
|
1899
|
+
const targetAdapter = getAdapter(targetType);
|
|
1900
|
+
const image = options.image;
|
|
1901
|
+
const metadata = options.metadata;
|
|
1902
|
+
async function* entries() {
|
|
1903
|
+
for await (const entry of sourceAdapter.listEntries(input, { tempDir: options.tempDir })) {
|
|
1904
|
+
if (metadata && METADATA_FILE_NAMES.has(entry.path.split("/").pop() ?? "")) continue;
|
|
1905
|
+
if (image && isImagePath(entry.path) && getExtension(entry.path) !== (image.format === "jpg" ? "jpg" : image.format)) {
|
|
1906
|
+
const converted = await convertImageBuffer(await streamToBuffer(entry.openReadStream()), image.format, image.options);
|
|
1907
|
+
yield {
|
|
1908
|
+
path: replaceExtension(entry.path, image.format === "jpg" ? "jpg" : image.format),
|
|
1909
|
+
size: converted.length,
|
|
1910
|
+
content: Readable.from(converted)
|
|
1911
|
+
};
|
|
1912
|
+
} else yield {
|
|
1913
|
+
path: entry.path,
|
|
1914
|
+
size: entry.size,
|
|
1915
|
+
content: entry.openReadStream()
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
if (metadata && targetType !== "asar") for (const schema of Object.keys(metadata)) {
|
|
1919
|
+
const value = metadata[schema];
|
|
1920
|
+
if (!value) continue;
|
|
1921
|
+
const xml = Buffer.from(metadataToXml(value, schema), "utf8");
|
|
1922
|
+
yield {
|
|
1923
|
+
path: `${schema}.xml`,
|
|
1924
|
+
size: xml.length,
|
|
1925
|
+
content: Readable.from(xml)
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return withOutput(options.output, (destination) => targetAdapter.write(entries(), destination, {
|
|
1930
|
+
tempDir: options.tempDir,
|
|
1931
|
+
...metadata && targetType === "asar" ? { comicMetadata: metadata } : {}
|
|
1932
|
+
}));
|
|
1933
|
+
}
|
|
1934
|
+
//#endregion
|
|
1935
|
+
//#region src/extractArchive.ts
|
|
1936
|
+
/**
|
|
1937
|
+
* Extracts every entry in an archive to real files under `destDir`,
|
|
1938
|
+
* preserving relative paths (`destDir` is created if missing). Throws
|
|
1939
|
+
* `ArchiveFormatError` for an undetectable/unknown format, or
|
|
1940
|
+
* `UnsupportedOperationError` for ACE. Returns the archive-relative entry
|
|
1941
|
+
* paths that were written, in archive iteration order.
|
|
1942
|
+
*/
|
|
1943
|
+
async function extractArchive(input, destDir, options = {}) {
|
|
1944
|
+
const adapter = getAdapter(await detectArchiveType(input));
|
|
1945
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
1946
|
+
const written = [];
|
|
1947
|
+
for await (const entry of adapter.listEntries(input, { tempDir: options.tempDir })) {
|
|
1948
|
+
const target = resolveSafeEntryPath(destDir, entry.path);
|
|
1949
|
+
await fs.promises.mkdir(path.dirname(target), { recursive: true });
|
|
1950
|
+
await pipeline(entry.openReadStream(), fs.createWriteStream(target));
|
|
1951
|
+
written.push(entry.path);
|
|
1952
|
+
}
|
|
1953
|
+
return written;
|
|
1954
|
+
}
|
|
1955
|
+
//#endregion
|
|
1931
1956
|
//#region src/images/phash.ts
|
|
1932
1957
|
const HASH_SIZE = 32;
|
|
1933
1958
|
const BLOCK_SIZE = 8;
|
|
@@ -1997,6 +2022,41 @@ async function computeArchiveImagePHash(input, entryPath) {
|
|
|
1997
2022
|
throw new MetadataNotFoundError(`No entry named "${entryPath}" was found in the archive.`);
|
|
1998
2023
|
}
|
|
1999
2024
|
//#endregion
|
|
2025
|
+
//#region src/images/dimensions.ts
|
|
2026
|
+
const BMP_HEADER_SIZE = 26;
|
|
2027
|
+
/** `sharp` can't read BMP, so read a BMP's dimensions straight from its `BITMAPINFOHEADER`. Height is negative for top-down bitmaps. */
|
|
2028
|
+
function readBmpInfo(buffer) {
|
|
2029
|
+
if (buffer.length < BMP_HEADER_SIZE || buffer.toString("latin1", 0, 2) !== "BM") return null;
|
|
2030
|
+
const width = buffer.readInt32LE(18);
|
|
2031
|
+
const height = Math.abs(buffer.readInt32LE(22));
|
|
2032
|
+
return width > 0 && height > 0 ? {
|
|
2033
|
+
width,
|
|
2034
|
+
height,
|
|
2035
|
+
type: "bmp"
|
|
2036
|
+
} : null;
|
|
2037
|
+
}
|
|
2038
|
+
/** Reads an image's format and pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
|
|
2039
|
+
async function readImageInfo(buffer) {
|
|
2040
|
+
try {
|
|
2041
|
+
const { width, height, format } = await sharp(buffer).metadata();
|
|
2042
|
+
return width !== void 0 && height !== void 0 ? {
|
|
2043
|
+
width,
|
|
2044
|
+
height,
|
|
2045
|
+
type: format
|
|
2046
|
+
} : null;
|
|
2047
|
+
} catch {
|
|
2048
|
+
return readBmpInfo(buffer);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
/** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
|
|
2052
|
+
async function readImageDimensions(buffer) {
|
|
2053
|
+
const info = await readImageInfo(buffer);
|
|
2054
|
+
return info ? {
|
|
2055
|
+
width: info.width,
|
|
2056
|
+
height: info.height
|
|
2057
|
+
} : null;
|
|
2058
|
+
}
|
|
2059
|
+
//#endregion
|
|
2000
2060
|
//#region src/hashing/asarContent.ts
|
|
2001
2061
|
/**
|
|
2002
2062
|
* Locates the byte range of an asar archive's content region (everything
|
|
@@ -2151,6 +2211,30 @@ async function* readArchiveEntries(input) {
|
|
|
2151
2211
|
}
|
|
2152
2212
|
}
|
|
2153
2213
|
//#endregion
|
|
2214
|
+
//#region src/readArchiveImageInfo.ts
|
|
2215
|
+
/**
|
|
2216
|
+
* Returns the name, path, dimensions, and format of every image entry in an
|
|
2217
|
+
* archive, in archive iteration order. Image entries are identified by
|
|
2218
|
+
* extension (see `isImagePath`); non-image entries are skipped without being
|
|
2219
|
+
* read. Everything is gathered in a single pass over the archive.
|
|
2220
|
+
*/
|
|
2221
|
+
async function readArchiveImageInfo(input) {
|
|
2222
|
+
const adapter = getAdapter(await detectArchiveType(input));
|
|
2223
|
+
const images = [];
|
|
2224
|
+
for await (const entry of adapter.listEntries(input)) {
|
|
2225
|
+
if (!isImagePath(entry.path)) continue;
|
|
2226
|
+
const info = await readImageInfo(await streamToBuffer(entry.openReadStream()));
|
|
2227
|
+
images.push({
|
|
2228
|
+
name: path.posix.basename(entry.path.replaceAll("\\", "/")),
|
|
2229
|
+
path: entry.path,
|
|
2230
|
+
width: info?.width ?? null,
|
|
2231
|
+
height: info?.height ?? null,
|
|
2232
|
+
type: info?.type ?? null
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
return images;
|
|
2236
|
+
}
|
|
2237
|
+
//#endregion
|
|
2154
2238
|
//#region src/benchmark/timing.ts
|
|
2155
2239
|
/** Runs `run` `iterations` times and returns the average duration in milliseconds. */
|
|
2156
2240
|
async function averageDuration(iterations, run) {
|
|
@@ -2389,6 +2473,8 @@ const comicArchiveHandler = {
|
|
|
2389
2473
|
hammingDistance,
|
|
2390
2474
|
IMAGE_EXTENSIONS,
|
|
2391
2475
|
isImagePath,
|
|
2476
|
+
readImageDimensions,
|
|
2477
|
+
readImageInfo,
|
|
2392
2478
|
sha256ArchiveEntry,
|
|
2393
2479
|
sha256Archive,
|
|
2394
2480
|
renameArchiveImagesSequentially,
|
|
@@ -2396,6 +2482,7 @@ const comicArchiveHandler = {
|
|
|
2396
2482
|
listArchiveFiles,
|
|
2397
2483
|
readArchiveEntry,
|
|
2398
2484
|
readArchiveEntries,
|
|
2485
|
+
readArchiveImageInfo,
|
|
2399
2486
|
removeArchiveEntry,
|
|
2400
2487
|
benchmarkArchive,
|
|
2401
2488
|
renderBenchmarkReportMarkdown,
|
|
@@ -2404,6 +2491,6 @@ const comicArchiveHandler = {
|
|
|
2404
2491
|
ARCHIVE_TYPE_EXTENSIONS
|
|
2405
2492
|
};
|
|
2406
2493
|
//#endregion
|
|
2407
|
-
export { ARCHIVE_TYPE_EXTENSIONS, ArchiveFormatError, BENCHMARK_IMAGE_FORMATS, COMIC_INFO_AGE_RATING_VALUES, COMIC_INFO_CREDIT_ROLES, COMIC_INFO_MANGA_VALUES, COMIC_INFO_PAGE_TYPE_VALUES, COMIC_INFO_YES_NO_VALUES, FilesystemAccessError, IMAGE_EXTENSIONS, METRON_AGE_RATING_VALUES, METRON_FORMAT_VALUES, METRON_INFORMATION_SOURCE_VALUES, METRON_ROLE_VALUES, MetadataNotFoundError, NoImagesFoundError, SevenZipUnavailableError, UnsupportedOperationError, WRITABLE_ARCHIVE_TYPES, addMetadataToArchive, benchmarkArchive, comicInfoXmlToMetadata, computeArchiveImagePHash, computeImagePHash, convertArchive, convertArchiveImages, convertImageBuffer, comicArchiveHandler as default, detectArchiveType, extractArchive, getExtension, hammingDistance, hasComicMetadata, is7z, isAce, isAsar, isImagePath, isRar, isTar, isZip, joinCommaList, joinResourceNames, listArchiveFiles, metadataToComicInfoXml, metadataToMetronInfoXml, metadataToXml, metronInfoXmlToMetadata, phashToHex, readArchiveEntries, readArchiveEntry, readArchiveMetadata, removeArchiveEntry, removeComicMetadata, renameArchiveImagesSequentially, renderBenchmarkReportMarkdown, resourceId, resourceName, sha256Archive, sha256ArchiveEntry, splitCommaList, stripNonEssentialFiles, validateMetadataXml, xmlToMetadata };
|
|
2494
|
+
export { ARCHIVE_TYPE_EXTENSIONS, ArchiveFormatError, BENCHMARK_IMAGE_FORMATS, COMIC_INFO_AGE_RATING_VALUES, COMIC_INFO_CREDIT_ROLES, COMIC_INFO_MANGA_VALUES, COMIC_INFO_PAGE_TYPE_VALUES, COMIC_INFO_YES_NO_VALUES, FilesystemAccessError, IMAGE_EXTENSIONS, METRON_AGE_RATING_VALUES, METRON_FORMAT_VALUES, METRON_INFORMATION_SOURCE_VALUES, METRON_ROLE_VALUES, MetadataNotFoundError, NoImagesFoundError, SevenZipUnavailableError, UnsupportedOperationError, WRITABLE_ARCHIVE_TYPES, addMetadataToArchive, benchmarkArchive, comicInfoXmlToMetadata, computeArchiveImagePHash, computeImagePHash, convertArchive, convertArchiveImages, convertImageBuffer, comicArchiveHandler as default, detectArchiveType, extractArchive, getExtension, hammingDistance, hasComicMetadata, is7z, isAce, isAsar, isImagePath, isRar, isTar, isZip, joinCommaList, joinResourceNames, listArchiveFiles, metadataToComicInfoXml, metadataToMetronInfoXml, metadataToXml, metronInfoXmlToMetadata, phashToHex, readArchiveEntries, readArchiveEntry, readArchiveImageInfo, readArchiveMetadata, readImageDimensions, readImageInfo, removeArchiveEntry, removeComicMetadata, renameArchiveImagesSequentially, renderBenchmarkReportMarkdown, resourceId, resourceName, sha256Archive, sha256ArchiveEntry, splitCommaList, stripNonEssentialFiles, validateMetadataXml, xmlToMetadata };
|
|
2408
2495
|
|
|
2409
2496
|
//# sourceMappingURL=index.mjs.map
|