@clearmist-labs/comic-archive-handler 1.5.0 → 1.7.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
@@ -30,6 +30,12 @@ export interface ImageConvertOptions {
30
30
  jpeg?: JpegOptions;
31
31
  png?: PngOptions;
32
32
  }
33
+ /**
34
+ * How many images an archive operation converts at once. A function is called
35
+ * again before each conversion starts, so the limit can change while an archive
36
+ * is being processed. Values below 1 are treated as 1.
37
+ */
38
+ export type ImageConcurrency = number | (() => number);
33
39
  /** Shared by every archive-producing operation. */
34
40
  export interface ArchiveWriteOptions {
35
41
  /**
@@ -49,6 +55,8 @@ export interface ConvertArchiveOptions extends ArchiveWriteOptions {
49
55
  image?: {
50
56
  format: ImageOutputFormat;
51
57
  options?: ImageConvertOptions;
58
+ /** How many images to convert at once. Defaults to `1`. */
59
+ concurrency?: ImageConcurrency;
52
60
  };
53
61
  /**
54
62
  * Replaces the source's embedded comic metadata in the output. The source's
@@ -469,7 +477,21 @@ export declare function removeComicMetadata(input: ArchiveInput, schema: Metadat
469
477
  //#endregion
470
478
  //#region src/images/convert.d.ts
471
479
  export declare function convertImageBuffer(image: Buffer, format: ImageOutputFormat, options?: ImageConvertOptions): Promise<Buffer>;
472
- export declare function convertArchiveImages(input: ArchiveInput, format: ImageOutputFormat, options?: ImageConvertOptions & ArchiveWriteOptions): Promise<Buffer | void>;
480
+ export declare function convertArchiveImages(input: ArchiveInput, format: ImageOutputFormat, options?: ImageConvertOptions & ArchiveWriteOptions & {
481
+ concurrency?: ImageConcurrency;
482
+ }): Promise<Buffer | void>;
483
+ //#endregion
484
+ //#region src/images/concurrency.d.ts
485
+ /**
486
+ * Sets how many libvips threads process each image, and returns the value now
487
+ * in effect. The setting is process-wide and applies to every image operation
488
+ * this package performs (conversion, dimensions, perceptual hashing). Callers
489
+ * that already parallelize across images (one worker thread per core, say)
490
+ * should pass `1` so each image doesn't also fan out across every core.
491
+ * sharp's default is the CPU core count, except on glibc Linux without
492
+ * jemalloc, where it is already `1`.
493
+ */
494
+ export declare function setImageConcurrency(threads: number): number;
473
495
  //#endregion
474
496
  //#region src/images/phash.d.ts
475
497
  /**
@@ -489,7 +511,13 @@ export interface ImageDimensions {
489
511
  width: number;
490
512
  height: number;
491
513
  }
492
- /** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't an image `sharp` can read. */
514
+ export interface ImageInfo extends ImageDimensions {
515
+ /** The format detected from the image's contents (not its file extension), such as `jpeg`, `png`, `webp`, or `bmp`. */
516
+ type: string;
517
+ }
518
+ /** Reads an image's format and pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
519
+ export declare function readImageInfo(buffer: Buffer): Promise<ImageInfo | null>;
520
+ /** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
493
521
  export declare function readImageDimensions(buffer: Buffer): Promise<ImageDimensions | null>;
494
522
  //#endregion
495
523
  //#region src/images/isImage.d.ts
@@ -552,6 +580,27 @@ export interface ReadArchiveEntriesResult {
552
580
  */
553
581
  export declare function readArchiveEntries(input: ArchiveInput): AsyncGenerator<ReadArchiveEntriesResult>;
554
582
  //#endregion
583
+ //#region src/readArchiveImageInfo.d.ts
584
+ export interface ArchiveImageInfo {
585
+ /** The entry's file name, without its directory. */
586
+ name: string;
587
+ /** The entry's full path inside the archive. */
588
+ path: string;
589
+ /** Pixel width, or `null` when the entry's contents aren't a readable image. */
590
+ width: number | null;
591
+ /** Pixel height, or `null` when the entry's contents aren't a readable image. */
592
+ height: number | null;
593
+ /** The format detected from the entry's contents (not its extension), or `null` when the contents aren't a readable image. */
594
+ type: string | null;
595
+ }
596
+ /**
597
+ * Returns the name, path, dimensions, and format of every image entry in an
598
+ * archive, in archive iteration order. Image entries are identified by
599
+ * extension (see `isImagePath`); non-image entries are skipped without being
600
+ * read. Everything is gathered in a single pass over the archive.
601
+ */
602
+ export declare function readArchiveImageInfo(input: ArchiveInput): Promise<ArchiveImageInfo[]>;
603
+ //#endregion
555
604
  //#region src/removeArchiveEntry.d.ts
556
605
  export declare function removeArchiveEntry(input: ArchiveInput, entryPath: string, options?: RemoveEntryOptions): Promise<Buffer | void>;
557
606
  //#endregion
@@ -582,12 +631,15 @@ export declare function renderBenchmarkReportMarkdown(result: BenchmarkArchiveRe
582
631
  declare const comicArchiveHandler: {
583
632
  convertImageBuffer: typeof convertImageBuffer;
584
633
  convertArchiveImages: typeof convertArchiveImages;
634
+ setImageConcurrency: typeof setImageConcurrency;
585
635
  computeImagePHash: typeof computeImagePHash;
586
636
  phashToHex: typeof phashToHex;
587
637
  computeArchiveImagePHash: typeof computeArchiveImagePHash;
588
638
  hammingDistance: typeof hammingDistance;
589
639
  IMAGE_EXTENSIONS: readonly ["jpg", "jpeg", "png", "gif", "webp", "bmp", "tiff", "tif"];
590
640
  isImagePath: typeof isImagePath;
641
+ readImageDimensions: typeof readImageDimensions;
642
+ readImageInfo: typeof readImageInfo;
591
643
  sha256ArchiveEntry: typeof sha256ArchiveEntry;
592
644
  sha256Archive: typeof sha256Archive;
593
645
  renameArchiveImagesSequentially: typeof renameArchiveImagesSequentially;
@@ -595,6 +647,7 @@ declare const comicArchiveHandler: {
595
647
  listArchiveFiles: typeof listArchiveFiles;
596
648
  readArchiveEntry: typeof readArchiveEntry;
597
649
  readArchiveEntries: typeof readArchiveEntries;
650
+ readArchiveImageInfo: typeof readArchiveImageInfo;
598
651
  removeArchiveEntry: typeof removeArchiveEntry;
599
652
  benchmarkArchive: typeof benchmarkArchive;
600
653
  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/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/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/concurrency.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
@@ -814,6 +814,43 @@ function getAdapter(type) {
814
814
  return adapter;
815
815
  }
816
816
  //#endregion
817
+ //#region src/internal/convertEntriesConcurrently.ts
818
+ function resolveLimit(concurrency) {
819
+ const value = typeof concurrency === "function" ? concurrency() : concurrency;
820
+ return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : 1;
821
+ }
822
+ /**
823
+ * Maps source entries to write entries, running up to `concurrency` image
824
+ * conversions at once while preserving entry order. Each image's bytes are
825
+ * read before the source iterator advances, so adapters whose entry streams
826
+ * are only valid until the next entry keep working. A pass-through entry is
827
+ * yielded only after every earlier conversion has been yielded, and before the
828
+ * source advances, since its content is read lazily by the writer. A function
829
+ * `concurrency` is re-read before each new conversion starts, so the limit can
830
+ * follow the caller's load while the archive is being converted.
831
+ */
832
+ async function* convertEntriesConcurrently(source, shouldConvert, convert, passThrough, concurrency = 1) {
833
+ const pending = [];
834
+ try {
835
+ for await (const entry of source) {
836
+ if (shouldConvert(entry)) {
837
+ const task = convert(entry, await streamToBuffer(entry.openReadStream()));
838
+ task.catch(() => void 0);
839
+ pending.push(task);
840
+ while (pending.length >= resolveLimit(concurrency)) yield await pending.shift();
841
+ continue;
842
+ }
843
+ const output = passThrough(entry);
844
+ if (!output) continue;
845
+ while (pending.length > 0) yield await pending.shift();
846
+ yield output;
847
+ }
848
+ while (pending.length > 0) yield await pending.shift();
849
+ } finally {
850
+ await Promise.allSettled(pending);
851
+ }
852
+ }
853
+ //#endregion
817
854
  //#region src/images/isImage.ts
818
855
  const IMAGE_EXTENSIONS = [
819
856
  "jpg",
@@ -862,22 +899,20 @@ function replaceExtension$1(entryPath, format) {
862
899
  }
863
900
  async function convertArchiveImages(input, format, options = {}) {
864
901
  const adapter = getAdapter(await detectArchiveType(input));
865
- const { tempDir, output, ...imageOptions } = options;
866
- async function* entries() {
867
- for await (const entry of adapter.listEntries(input, { tempDir })) if (isImagePath(entry.path) && getExtension(entry.path) !== (format === "jpg" ? "jpg" : format)) {
868
- const converted = await convertImageBuffer(await streamToBuffer(entry.openReadStream()), format, imageOptions);
869
- yield {
870
- path: replaceExtension$1(entry.path, format),
871
- size: converted.length,
872
- content: Readable.from(converted)
873
- };
874
- } else yield {
875
- path: entry.path,
876
- size: entry.size,
877
- content: entry.openReadStream()
902
+ const { tempDir, output, concurrency, ...imageOptions } = options;
903
+ const entries = convertEntriesConcurrently(adapter.listEntries(input, { tempDir }), (entry) => isImagePath(entry.path) && getExtension(entry.path) !== (format === "jpg" ? "jpg" : format), async (entry, buffer) => {
904
+ const converted = await convertImageBuffer(buffer, format, imageOptions);
905
+ return {
906
+ path: replaceExtension$1(entry.path, format),
907
+ size: converted.length,
908
+ content: Readable.from(converted)
878
909
  };
879
- }
880
- return withOutput(output, (destination) => adapter.write(entries(), destination, { tempDir }));
910
+ }, (entry) => ({
911
+ path: entry.path,
912
+ size: entry.size,
913
+ content: entry.openReadStream()
914
+ }), concurrency);
915
+ return withOutput(output, (destination) => adapter.write(entries, destination, { tempDir }));
881
916
  }
882
917
  //#endregion
883
918
  //#region src/removeArchiveEntry.ts
@@ -1900,21 +1935,19 @@ async function convertArchive(input, targetType, options = {}) {
1900
1935
  const image = options.image;
1901
1936
  const metadata = options.metadata;
1902
1937
  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()
1938
+ const imageExtension = image?.format === "jpg" ? "jpg" : image?.format;
1939
+ yield* convertEntriesConcurrently(sourceAdapter.listEntries(input, { tempDir: options.tempDir }), (entry) => Boolean(image) && isImagePath(entry.path) && getExtension(entry.path) !== imageExtension, async (entry, buffer) => {
1940
+ const converted = await convertImageBuffer(buffer, image.format, image.options);
1941
+ return {
1942
+ path: replaceExtension(entry.path, imageExtension),
1943
+ size: converted.length,
1944
+ content: Readable.from(converted)
1916
1945
  };
1917
- }
1946
+ }, (entry) => metadata && METADATA_FILE_NAMES.has(entry.path.split("/").pop() ?? "") ? void 0 : {
1947
+ path: entry.path,
1948
+ size: entry.size,
1949
+ content: entry.openReadStream()
1950
+ }, image?.concurrency);
1918
1951
  if (metadata && targetType !== "asar") for (const schema of Object.keys(metadata)) {
1919
1952
  const value = metadata[schema];
1920
1953
  if (!value) continue;
@@ -1953,6 +1986,20 @@ async function extractArchive(input, destDir, options = {}) {
1953
1986
  return written;
1954
1987
  }
1955
1988
  //#endregion
1989
+ //#region src/images/concurrency.ts
1990
+ /**
1991
+ * Sets how many libvips threads process each image, and returns the value now
1992
+ * in effect. The setting is process-wide and applies to every image operation
1993
+ * this package performs (conversion, dimensions, perceptual hashing). Callers
1994
+ * that already parallelize across images (one worker thread per core, say)
1995
+ * should pass `1` so each image doesn't also fan out across every core.
1996
+ * sharp's default is the CPU core count, except on glibc Linux without
1997
+ * jemalloc, where it is already `1`.
1998
+ */
1999
+ function setImageConcurrency(threads) {
2000
+ return sharp.concurrency(threads);
2001
+ }
2002
+ //#endregion
1956
2003
  //#region src/images/phash.ts
1957
2004
  const HASH_SIZE = 32;
1958
2005
  const BLOCK_SIZE = 8;
@@ -2023,18 +2070,39 @@ async function computeArchiveImagePHash(input, entryPath) {
2023
2070
  }
2024
2071
  //#endregion
2025
2072
  //#region src/images/dimensions.ts
2026
- /** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't an image `sharp` can read. */
2027
- async function readImageDimensions(buffer) {
2073
+ const BMP_HEADER_SIZE = 26;
2074
+ /** `sharp` can't read BMP, so read a BMP's dimensions straight from its `BITMAPINFOHEADER`. Height is negative for top-down bitmaps. */
2075
+ function readBmpInfo(buffer) {
2076
+ if (buffer.length < BMP_HEADER_SIZE || buffer.toString("latin1", 0, 2) !== "BM") return null;
2077
+ const width = buffer.readInt32LE(18);
2078
+ const height = Math.abs(buffer.readInt32LE(22));
2079
+ return width > 0 && height > 0 ? {
2080
+ width,
2081
+ height,
2082
+ type: "bmp"
2083
+ } : null;
2084
+ }
2085
+ /** Reads an image's format and pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
2086
+ async function readImageInfo(buffer) {
2028
2087
  try {
2029
- const { width, height } = await sharp(buffer).metadata();
2088
+ const { width, height, format } = await sharp(buffer).metadata();
2030
2089
  return width !== void 0 && height !== void 0 ? {
2031
2090
  width,
2032
- height
2091
+ height,
2092
+ type: format
2033
2093
  } : null;
2034
2094
  } catch {
2035
- return null;
2095
+ return readBmpInfo(buffer);
2036
2096
  }
2037
2097
  }
2098
+ /** Reads an image's pixel dimensions from its header without decoding it; `null` when the buffer isn't a readable image. */
2099
+ async function readImageDimensions(buffer) {
2100
+ const info = await readImageInfo(buffer);
2101
+ return info ? {
2102
+ width: info.width,
2103
+ height: info.height
2104
+ } : null;
2105
+ }
2038
2106
  //#endregion
2039
2107
  //#region src/hashing/asarContent.ts
2040
2108
  /**
@@ -2190,6 +2258,30 @@ async function* readArchiveEntries(input) {
2190
2258
  }
2191
2259
  }
2192
2260
  //#endregion
2261
+ //#region src/readArchiveImageInfo.ts
2262
+ /**
2263
+ * Returns the name, path, dimensions, and format of every image entry in an
2264
+ * archive, in archive iteration order. Image entries are identified by
2265
+ * extension (see `isImagePath`); non-image entries are skipped without being
2266
+ * read. Everything is gathered in a single pass over the archive.
2267
+ */
2268
+ async function readArchiveImageInfo(input) {
2269
+ const adapter = getAdapter(await detectArchiveType(input));
2270
+ const images = [];
2271
+ for await (const entry of adapter.listEntries(input)) {
2272
+ if (!isImagePath(entry.path)) continue;
2273
+ const info = await readImageInfo(await streamToBuffer(entry.openReadStream()));
2274
+ images.push({
2275
+ name: path.posix.basename(entry.path.replaceAll("\\", "/")),
2276
+ path: entry.path,
2277
+ width: info?.width ?? null,
2278
+ height: info?.height ?? null,
2279
+ type: info?.type ?? null
2280
+ });
2281
+ }
2282
+ return images;
2283
+ }
2284
+ //#endregion
2193
2285
  //#region src/benchmark/timing.ts
2194
2286
  /** Runs `run` `iterations` times and returns the average duration in milliseconds. */
2195
2287
  async function averageDuration(iterations, run) {
@@ -2422,12 +2514,15 @@ const comicArchiveHandler = {
2422
2514
  ...metadata_exports,
2423
2515
  convertImageBuffer,
2424
2516
  convertArchiveImages,
2517
+ setImageConcurrency,
2425
2518
  computeImagePHash,
2426
2519
  phashToHex,
2427
2520
  computeArchiveImagePHash,
2428
2521
  hammingDistance,
2429
2522
  IMAGE_EXTENSIONS,
2430
2523
  isImagePath,
2524
+ readImageDimensions,
2525
+ readImageInfo,
2431
2526
  sha256ArchiveEntry,
2432
2527
  sha256Archive,
2433
2528
  renameArchiveImagesSequentially,
@@ -2435,6 +2530,7 @@ const comicArchiveHandler = {
2435
2530
  listArchiveFiles,
2436
2531
  readArchiveEntry,
2437
2532
  readArchiveEntries,
2533
+ readArchiveImageInfo,
2438
2534
  removeArchiveEntry,
2439
2535
  benchmarkArchive,
2440
2536
  renderBenchmarkReportMarkdown,
@@ -2443,6 +2539,6 @@ const comicArchiveHandler = {
2443
2539
  ARCHIVE_TYPE_EXTENSIONS
2444
2540
  };
2445
2541
  //#endregion
2446
- 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, readImageDimensions, removeArchiveEntry, removeComicMetadata, renameArchiveImagesSequentially, renderBenchmarkReportMarkdown, resourceId, resourceName, sha256Archive, sha256ArchiveEntry, splitCommaList, stripNonEssentialFiles, validateMetadataXml, xmlToMetadata };
2542
+ 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, setImageConcurrency, sha256Archive, sha256ArchiveEntry, splitCommaList, stripNonEssentialFiles, validateMetadataXml, xmlToMetadata };
2447
2543
 
2448
2544
  //# sourceMappingURL=index.mjs.map