@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 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;
@@ -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":"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"}
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":";;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;;;;;;;;;;EAS/C,WAAW,QAAQ,OAAO,gBAAgB;;iBAG3B,2BAA2B;EAC1C;;iBAGe,sBAAsB;EACrC;EACA;;iBAGe,qBAAqB;EACpC;;YAGU,qBAAqB;iBAEhB;EACf;EACA;;EAEA;;EAEA;;iBAGe;EACf;EACA;;EAEA;;iBAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA;EACA;;;;;;;;;iBAUe;EACf;EACA;;iBAGe,6BAA6B;;EAE5C;;iBAGe;EACf;EACA;EACA;;iBAGe;EACf;;EAEA;;iBAGe;EACf;EACA;;;iBAIe;EACf;EACA;EACA;;;;;;;;iBASe;EACf;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YAAY;EACZ,YAAY;EACZ,mBAAmB;EACnB,iBAAiB;EACjB,uBAAuB;EACvB,kBAAkB;EAClB,sBAAsB;EACtB,YAAY;EACZ,UAAU;EACV,gBAAgB;;EAEhB;;EAEA;;EAEA;;EAEA;EACA,QAAQ;;EAGR;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAGA,cAAc;;EAEd;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,yBAAyB;;EAEzB;;EAEA;;EAEA,oBAAoB;;EAEpB,SAAS;;EAET,qBAAqB;;EAErB,YAAY;;EAEZ;;EAEA;;EAGA,iBAAiB;;EAEjB,kBAAkB;;;;;KC1PR,sBAAsB,QAAQ;qBAE7B,iCAAiC;qBAEjC,kCAAkC;;qBAGlC,yBAAyB,OAAO;UAO5B;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,eAAe;;EAEf,QAAQ;;UAGO;EACf,aAAa;EACb,aAAa;EACb;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;UAGe;EACf;;EAEA;EACA;EACA;;EAEA;EACA;EACA,UAAU;;;;qBCxDC,kCAAkC;EACjC,YAAA;;qBAMD,2BAA2B;EAC1B,YAAA;;qBAMD,8BAA8B;EAC7B,YAAA;;qBAMD,8BAA8B;EAC7B,YAAA;;qBAMD,iCAAiC;EAChC,YAAA;;qBAMD,2BAA2B;EAC1B,YAAA;;;;wBCHQ,kBAAkB,OAAO,eAAe,QAAQ;wBAchD,MAAM,OAAO,eAAe;wBAI5B,MAAM,OAAO,eAAe;wBAI5B,MAAM,OAAO,eAAe;wBAI5B,OAAO,OAAO,eAAe;wBAI7B,KAAK,OAAO,eAAe;wBAI3B,MAAM,OAAO,eAAe;;;wBC/C5B,eACpB,OAAO,cACP,YAAY,QAAQ,yBACpB,UAAS,wBACR,QAAQ;;;iBChBM;;EAEf;;;;;;;;;wBAUoB,eAAe,OAAO,cAAc,iBAAiB,UAAS,wBAA6B;;;wBCsCjG,uBAAuB,UAAU;wBAuNjC,uBAAuB,cAAc,SAAS;;;wBC1C9C,wBAAwB,UAAU;wBA8KlC,wBAAwB,cAAc,SAAS;;;UChZ9C;EACf;EACA;;UAGe;EACf;EACA,QAAQ;;;;;;;;;wBA+BY,oBAAoB,cAAc,QAAQ,QAAQ,iBAAiB,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBCkCpF;KAWD,8BAA8B;;qBAG7B;;qBAGA;;qBAGA;;qBAmBA;;qBAeA;;qBAaA;;qBAcA;;qBA8CA;wBAEG,eAAe;wBAYf,cAAc;;wBAKd,aAAa,eAAe;;wBAK5B,WAAW,eAAe;wBAI1B,kBAAkB,mBAAmB;;;;wBC/LrC,cAAc,UAAU,eAAe,QAAQ;wBAI/C,cAAc,cAAc,QAAQ,QAAQ,iBAAiB;wBAYvD,iBACpB,OAAO,eACN;EAAU;EAAkB,SAAS;EAAgB;EAAe;;;;;;;;;wBA0CjD,oBACpB,OAAO,cACP,SAAS,iBACR;EAAU,QAAQ;EAAgB,UAAU;;wBA4CzB,qBACpB,OAAO,cACP,UAAU,eACV,QAAQ,gBACR,UAAS,qBACR,QAAQ;;;;;;;wBA4DW,oBACpB,OAAO,cACP,QAAQ,gBACR,UAAS,sBACR,QAAQ;;;wBC9MW,mBAAmB,OAAO,QAAQ,QAAQ,mBAAmB,UAAS,sBAA2B,QAAQ;wBAmCzG,qBACpB,OAAO,cACP,QAAQ,mBACR,UAAS,sBAAsB,sBAC9B,QAAQ;;;;;;;;;;wBCQW,kBAAkB,OAAO,SAAS;wBAmCxC,WAAW;wBAIX,gBAAgB,WAAW;wBAYrB,yBAAyB,OAAO,cAAc,oBAAoB;;;iBC3GvE;EACf;EACA;;iBAGe,kBAAkB;;EAEjC;;;wBAkBoB,cAAc,QAAQ,SAAS,QAAQ;;wBAUvC,oBAAoB,QAAQ,SAAS,QAAQ;;;qBCrCtD;wBAEG,aAAa;wBAMb,YAAY;;;wBCWN,mBAAmB,OAAO,cAAc,oBAAoB;;;;;;wBAkB5D,cAAc,OAAO,eAAe;;;;;;;;;;;;;;;;;wBCfpC,gCAAgC,OAAO,cAAc,UAAS,gBAAqB,QAAQ;;;wBCf3F,uBAAuB,OAAO,cAAc,UAAS,eAAoB,QAAQ;;;wBCHjF,iBAAiB,OAAO,eAAe;;;wBCEvC,iBAAiB,OAAO,cAAc,oBAAoB,QAAQ;;;iBCDvE;EACf;EACA;EACA,QAAQ;EACR;;;;;;;;;;;;;wBAcqB,mBAAmB,OAAO,eAAe,eAAe;;;iBCf9D;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;;;wBASoB,qBAAqB,OAAO,eAAe,QAAQ;;;wBCpBnD,mBAAmB,OAAO,cAAc,mBAAmB,UAAS,qBAA0B,QAAQ;;;;;;;;;;;;;;;;;;;;wBCqEtG,iBAAiB,kBAAkB,UAAS,0BAA+B,QAAQ;;;wBCVzF,8BAA8B,QAAQ;;;cCtBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAgCwI;IAAuB;IAAsB"}
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