@murumets-ee/media 0.71.0 → 0.72.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.
Files changed (134) hide show
  1. package/dist/admin.d.mts.map +1 -1
  2. package/dist/admin.mjs +1 -1
  3. package/dist/backfill-D2Ubblh3.mjs +2 -0
  4. package/dist/backfill-D2Ubblh3.mjs.map +1 -0
  5. package/dist/backfill-trigger-BFL1L7_j.mjs +2 -0
  6. package/dist/backfill-trigger-BFL1L7_j.mjs.map +1 -0
  7. package/dist/backlog-D-H5VUQA.mjs +2 -0
  8. package/dist/backlog-D-H5VUQA.mjs.map +1 -0
  9. package/dist/client.d.mts +55 -7
  10. package/dist/client.d.mts.map +1 -1
  11. package/dist/client.mjs +1 -1
  12. package/dist/client.mjs.map +1 -1
  13. package/dist/crop-editor.d.mts +60 -0
  14. package/dist/crop-editor.d.mts.map +1 -0
  15. package/dist/crop-editor.mjs +2 -0
  16. package/dist/crop-editor.mjs.map +1 -0
  17. package/dist/crop-rect-B-AJ2pWm.mjs +2 -0
  18. package/dist/crop-rect-B-AJ2pWm.mjs.map +1 -0
  19. package/dist/crop-rect-C-SbjoxK.d.mts +59 -0
  20. package/dist/crop-rect-C-SbjoxK.d.mts.map +1 -0
  21. package/dist/crop.d.mts +2 -1
  22. package/dist/crop.mjs +1 -1
  23. package/dist/definitions-LJpgrdxd.mjs +2 -0
  24. package/dist/definitions-LJpgrdxd.mjs.map +1 -0
  25. package/dist/deps-CzHEhyJK.mjs +2 -0
  26. package/dist/deps-CzHEhyJK.mjs.map +1 -0
  27. package/dist/derive-media-type-C38ZA_Cl.mjs +2 -0
  28. package/dist/derive-media-type-C38ZA_Cl.mjs.map +1 -0
  29. package/dist/en-A8YzaZ51.mjs +2 -0
  30. package/dist/en-A8YzaZ51.mjs.map +1 -0
  31. package/dist/entity-CsDdjKz6.mjs +2 -0
  32. package/dist/entity-CsDdjKz6.mjs.map +1 -0
  33. package/dist/et-CXvu8U31.mjs +2 -0
  34. package/dist/et-CXvu8U31.mjs.map +1 -0
  35. package/dist/generate-variants-zyPjaGcy.mjs +2 -0
  36. package/dist/generate-variants-zyPjaGcy.mjs.map +1 -0
  37. package/dist/i18n.mjs +1 -1
  38. package/dist/i18n.mjs.map +1 -1
  39. package/dist/image-styles-settings.d.mts +8 -1
  40. package/dist/image-styles-settings.d.mts.map +1 -1
  41. package/dist/image-styles-settings.mjs +1 -1
  42. package/dist/image-styles-settings.mjs.map +1 -1
  43. package/dist/image-styles.d.mts +35 -21
  44. package/dist/image-styles.d.mts.map +1 -1
  45. package/dist/image-styles.mjs +1 -2
  46. package/dist/image-styles.mjs.map +1 -1
  47. package/dist/index.d.mts +16 -42
  48. package/dist/index.d.mts.map +1 -1
  49. package/dist/index.mjs +1 -1
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/media-config-DmcTxuDM.mjs +2 -0
  52. package/dist/media-config-DmcTxuDM.mjs.map +1 -0
  53. package/dist/owned-delete-Cn_KFFjS.mjs +2 -0
  54. package/dist/owned-delete-Cn_KFFjS.mjs.map +1 -0
  55. package/dist/picker.d.mts +1 -94
  56. package/dist/picker.d.mts.map +1 -1
  57. package/dist/picker.mjs +1 -2
  58. package/dist/picker.mjs.map +1 -1
  59. package/dist/plugin.d.mts +2 -1
  60. package/dist/plugin.d.mts.map +1 -1
  61. package/dist/plugin.mjs +1 -1
  62. package/dist/plugin.mjs.map +1 -1
  63. package/dist/process-image-Deedtkh4.mjs +2 -0
  64. package/dist/process-image-Deedtkh4.mjs.map +1 -0
  65. package/dist/processing.d.mts +201 -7
  66. package/dist/processing.d.mts.map +1 -1
  67. package/dist/processing.mjs +1 -1
  68. package/dist/public-resolver.d.mts +1 -1
  69. package/dist/public-resolver.mjs +1 -1
  70. package/dist/public-resolver.mjs.map +1 -1
  71. package/dist/query-client.d.mts +1 -1
  72. package/dist/query-client.mjs +1 -1
  73. package/dist/regenerate-variants-Cp3sNHkT.mjs +2 -0
  74. package/dist/regenerate-variants-Cp3sNHkT.mjs.map +1 -0
  75. package/dist/register-BSGkELj0.mjs +2 -0
  76. package/dist/register-BSGkELj0.mjs.map +1 -0
  77. package/dist/resolve-image-styles-pf7Nd3Zf.mjs +2 -0
  78. package/dist/resolve-image-styles-pf7Nd3Zf.mjs.map +1 -0
  79. package/dist/routes-BJ23Cp0g.mjs +2 -0
  80. package/dist/routes-BJ23Cp0g.mjs.map +1 -0
  81. package/dist/ru-Dj-ax8s_.mjs +2 -0
  82. package/dist/ru-Dj-ax8s_.mjs.map +1 -0
  83. package/dist/schedule-DREjg2ji.mjs +2 -0
  84. package/dist/schedule-DREjg2ji.mjs.map +1 -0
  85. package/dist/shapes-40dHtTb-.d.mts +90 -0
  86. package/dist/shapes-40dHtTb-.d.mts.map +1 -0
  87. package/dist/shapes-BlKW-0C1.mjs +2 -0
  88. package/dist/shapes-BlKW-0C1.mjs.map +1 -0
  89. package/dist/slot-CY4isqmN.mjs +2 -0
  90. package/dist/slot-CY4isqmN.mjs.map +1 -0
  91. package/dist/{types-CnORVkLZ.d.mts → types-CgkJF5dc.d.mts} +85 -5
  92. package/dist/types-CgkJF5dc.d.mts.map +1 -0
  93. package/dist/types-DwzblZfW.d.mts +97 -0
  94. package/dist/types-DwzblZfW.d.mts.map +1 -0
  95. package/dist/variant-key-Cki4n-42.mjs +2 -0
  96. package/dist/variant-key-Cki4n-42.mjs.map +1 -0
  97. package/dist/variant-plan-DV8hsMdz.mjs +2 -0
  98. package/dist/variant-plan-DV8hsMdz.mjs.map +1 -0
  99. package/package.json +21 -8
  100. package/dist/client-CSOH7Zun.mjs +0 -2
  101. package/dist/client-CSOH7Zun.mjs.map +0 -1
  102. package/dist/en-0YnQ_Qz-.mjs +0 -2
  103. package/dist/en-0YnQ_Qz-.mjs.map +0 -1
  104. package/dist/entity-CNxAERYG.mjs +0 -2
  105. package/dist/entity-CNxAERYG.mjs.map +0 -1
  106. package/dist/entity-DKFCdQJT.mjs +0 -2
  107. package/dist/entity-DKFCdQJT.mjs.map +0 -1
  108. package/dist/et-BIxGYWVK.mjs +0 -2
  109. package/dist/et-BIxGYWVK.mjs.map +0 -1
  110. package/dist/image-styles-settings-DEnu-V2E.mjs +0 -2
  111. package/dist/image-styles-settings-DEnu-V2E.mjs.map +0 -1
  112. package/dist/plugin-B_ZtfiAc.mjs +0 -2
  113. package/dist/plugin-B_ZtfiAc.mjs.map +0 -1
  114. package/dist/process-image-WByEHTXQ.mjs +0 -2
  115. package/dist/process-image-WByEHTXQ.mjs.map +0 -1
  116. package/dist/regenerate-variants-2Lmo5kf8.mjs +0 -2
  117. package/dist/regenerate-variants-2Lmo5kf8.mjs.map +0 -1
  118. package/dist/regenerate-variants-B9AXXN6N.mjs +0 -2
  119. package/dist/regenerate-variants-B9AXXN6N.mjs.map +0 -1
  120. package/dist/resolve-image-styles-Cakg1Qpp.mjs +0 -2
  121. package/dist/resolve-image-styles-Cakg1Qpp.mjs.map +0 -1
  122. package/dist/resolve-image-styles-iN9JbZYf.mjs +0 -2
  123. package/dist/resolve-image-styles-iN9JbZYf.mjs.map +0 -1
  124. package/dist/routes-C7Fx-055.mjs +0 -2
  125. package/dist/routes-C7Fx-055.mjs.map +0 -1
  126. package/dist/ru-UUTBfsMF.mjs +0 -2
  127. package/dist/ru-UUTBfsMF.mjs.map +0 -1
  128. package/dist/types-CnORVkLZ.d.mts.map +0 -1
  129. package/dist/usage-CVAqkS6h.mjs +0 -2
  130. package/dist/usage-CVAqkS6h.mjs.map +0 -1
  131. package/dist/variant-key-JBTJXPL1.mjs +0 -2
  132. package/dist/variant-key-JBTJXPL1.mjs.map +0 -1
  133. package/dist/variant-key-RKke2RfS.mjs +0 -2
  134. package/dist/variant-key-RKke2RfS.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"variant-key-RKke2RfS.mjs","names":[],"sources":["../src/process-image.ts","../src/variant-key.ts"],"sourcesContent":["/**\n * Server-side image processing via Sharp.\n *\n * Pure processing module — no DB or storage dependencies.\n * Takes a buffer and image styles, returns metadata + variant buffers.\n */\n\nimport sharp from 'sharp'\nimport type { FocalPoint } from './crop/focal-point.js'\nimport type { ImageStyle } from './types.js'\n\n/** Result of processing an image through Sharp */\nexport interface ProcessedImage {\n /** Original image width in pixels */\n width: number\n /** Original image height in pixels */\n height: number\n /** Generated variant buffers keyed by style name */\n variants: Map<string, ProcessedVariant>\n}\n\n/** A single processed variant */\nexport interface ProcessedVariant {\n buffer: Buffer\n format: string\n mimeType: string\n width: number\n height: number\n}\n\n/** MIME types that should NOT be processed (vectors, animations) */\nconst SKIP_MIME_TYPES = new Set(['image/svg+xml', 'image/gif'])\n\n/**\n * Check if a MIME type is eligible for Sharp processing.\n * Returns false for SVG, GIF, and non-image types.\n */\nexport function isProcessableImage(mimeType: string): boolean {\n return mimeType.startsWith('image/') && !SKIP_MIME_TYPES.has(mimeType)\n}\n\n/**\n * Extract image dimensions and generate resized variants.\n *\n * @param buffer - Original image file as a Buffer\n * @param styles - Named image style presets to generate\n * @returns Metadata (width/height) and variant buffers\n */\nexport async function processImage(\n buffer: Buffer,\n styles: Record<string, ImageStyle>,\n): Promise<ProcessedImage> {\n const { width, height } = displayDimensions(await sharp(buffer).metadata())\n\n const variants = new Map<string, ProcessedVariant>()\n\n const entries = Object.entries(styles)\n await Promise.all(\n entries.map(async ([name, style]) => {\n const fmt = style.format ?? 'webp'\n const quality = style.quality ?? 80\n const fit = style.fit ?? 'cover'\n\n // `.rotate()` first: without it an EXIF-rotated phone photo's variant is\n // written SIDEWAYS — the output format drops the orientation tag — while\n // the row's width/height (`displayDimensions`) and its focal point are in\n // the orientation a browser draws. Keys are unchanged; only the pixels\n // of a rotated original change, and they change to what was intended.\n const resized = sharp(buffer).rotate().resize({\n width: style.width,\n height: style.height,\n fit,\n withoutEnlargement: true,\n })\n\n const { data: variantBuffer, info } = await resized[fmt]({ quality }).toBuffer({\n resolveWithObject: true,\n })\n\n variants.set(name, {\n buffer: variantBuffer,\n format: fmt,\n mimeType: `image/${fmt}`,\n width: info.width,\n height: info.height,\n })\n }),\n )\n\n return { width, height, variants }\n}\n\n/**\n * Extract only image dimensions (no variant generation).\n * Useful for getting width/height when styles are empty.\n */\nexport async function getImageDimensions(\n buffer: Buffer,\n): Promise<{ width: number; height: number }> {\n return displayDimensions(await sharp(buffer).metadata())\n}\n\n/**\n * The size a browser DRAWS the image at — after EXIF orientation — not the\n * sensor's.\n *\n * `metadata().width/height` are the stored pixel grid, so a phone portrait\n * tagged orientation 6 reports 400×200 while every browser draws it 200×400\n * (`image-orientation: from-image` is the default). Those numbers become the\n * media row's `width`/`height`, which are the `<img>`'s CLS reservation AND the\n * image ratio the focal-point crop is computed against (images PR02) — a\n * swapped ratio positions the crop on the wrong axis. `autoOrient` is sharp's\n * own post-orientation size (0.33+).\n */\nfunction displayDimensions(meta: sharp.Metadata): { width: number; height: number } {\n return {\n width: meta.autoOrient?.width ?? meta.width ?? 0,\n height: meta.autoOrient?.height ?? meta.height ?? 0,\n }\n}\n\n/**\n * The longest edge the attention pass looks at. libvips scores saliency on a\n * ~32px grid, so a larger buffer buys no precision — it only costs decode time.\n */\nconst ATTENTION_EDGE = 512\n\n/** Below this on either side there is nothing to guess about — see {@link detectFocalPoint}. */\nconst MIN_ATTENTION_EDGE = 8\n\n/**\n * How far the two attention passes may disagree on an axis and still be the\n * same subject. libvips places its hotspot on a ~32-cell grid (1/32 ≈ 0.03), so\n * a real subject agrees to within a cell or two (measured ≤ 0.07); a FLAT axis\n * disagrees by the whole image (0 in one pass, 1 in the mirrored one).\n */\nconst AXIS_AGREEMENT = 0.25\n\n/**\n * Guess where the subject is, with sharp's `attention` strategy — the seed for\n * a new photograph's focal point, so an editor who opens nothing still gets\n * something other than a blind centre crop (images D002).\n *\n * ## Two passes, one of them mirrored\n *\n * When the saliency map is flat along an axis — a white product background, a\n * sky, a horizontal band, a plain texture — libvips reports the FIRST cell of\n * it: 0. Stored, that pins every shaped slot to the left or top edge, which is\n * worse than no guess. But a subject genuinely near the edge also reports 0 (a\n * disc at 5% from the corner does, measured), so \"0 means flat\" is not a rule.\n *\n * So the same buffer is scored twice: as-is, and flipped on both axes. A real\n * subject moves with the flip and the two answers agree once the second is\n * mirrored back; a flat axis reports 0 both times, i.e. 0 and 1 once mirrored.\n * Per axis: agree → their mean; disagree → that axis says nothing → 0.5. Both\n * axes flat → `null` (the caller stores it; it renders as the centre, D010).\n * The second pass runs on the already-decoded ≤512px raw buffer, so it costs a\n * saliency pass over ~0.25 MP, not a second decode.\n *\n * Also `null`: an image under {@link MIN_ATTENTION_EDGE} on either side — a\n * strip or an icon has no subject worth cropping around.\n *\n * ## Why not read `info.attentionX` off an ordinary resize\n *\n * Because it is WRONG whenever the same call resizes. sharp converts the\n * libvips hotspot with `attention_x * jpegShrinkOnLoad / scale`\n * (`src/pipeline.cc:505`, 0.34.5 and 0.35.3 alike), and those terms do not\n * describe the image smartcrop ran on: a disc drawn at x = 0.80 reported 0.50\n * from a JPEG and 0.25 from a PNG (images R012). The crop OFFSET beside it is\n * right; only the reported point is not.\n *\n * So each pass runs at SCALE 1: decode once to a bounded raw buffer, then crop\n * that buffer to its own size minus one pixel on the long axis. No\n * shrink-on-load and no scale, so the reported point is in that buffer's own\n * pixels and dividing by its size is the answer. A square image has no long\n * axis, but trimming either one still runs the strategy, and libvips takes the\n * hotspot over the whole saliency map whichever axis it crops.\n *\n * `.rotate()` first, so the point is in the orientation a browser DRAWS — an\n * EXIF-rotated phone photo would otherwise be seeded in sensor coordinates.\n *\n * ⚠️ Attention scores luminance frequency, saturation and skin tones. It is a\n * guess, not detection, and on a product shot the product can win (R012).\n */\nexport async function detectFocalPoint(buffer: Buffer): Promise<FocalPoint | null> {\n const { data, info } = await sharp(buffer)\n .rotate()\n .resize(ATTENTION_EDGE, ATTENTION_EDGE, { fit: 'inside', withoutEnlargement: true })\n .raw()\n .toBuffer({ resolveWithObject: true })\n const { width, height, channels } = info\n if (width < MIN_ATTENTION_EDGE || height < MIN_ATTENTION_EDGE) return null\n const raw = { raw: { width, height, channels } }\n const mirrored = await sharp(data, raw).flop().flip().raw().toBuffer()\n const [straight, flipped] = await Promise.all([\n attentionHotspot(data, width, height, channels),\n attentionHotspot(mirrored, width, height, channels),\n ])\n if (straight === null || flipped === null) return null\n const x = agreeingAxis(straight.x, 1 - flipped.x)\n const y = agreeingAxis(straight.y, 1 - flipped.y)\n if (x === null && y === null) return null\n return { x: clampFraction(x ?? 0.5), y: clampFraction(y ?? 0.5) }\n}\n\n/** One attention pass at scale 1, as fractions of the buffer. */\nasync function attentionHotspot(\n data: Buffer,\n width: number,\n height: number,\n channels: 1 | 2 | 3 | 4,\n): Promise<FocalPoint | null> {\n const landscape = width >= height\n const { info } = await sharp(data, { raw: { width, height, channels } })\n .resize(landscape ? width - 1 : width, landscape ? height : height - 1, {\n fit: 'cover',\n position: sharp.strategy.attention,\n })\n .toBuffer({ resolveWithObject: true })\n const { attentionX, attentionY } = info\n if (attentionX === undefined || attentionY === undefined) return null\n return { x: attentionX / width, y: attentionY / height }\n}\n\n/** The two passes' answer for one axis, or `null` when they disagree (a flat axis). */\nfunction agreeingAxis(straight: number, mirrored: number): number | null {\n return Math.abs(straight - mirrored) <= AXIS_AGREEMENT ? (straight + mirrored) / 2 : null\n}\n\n/** Round to four decimals and keep inside `[0, 1]`, so the stored value passes `isFocalPoint`. */\nfunction clampFraction(value: number): number {\n if (!Number.isFinite(value)) return 0.5\n return Math.min(Math.max(Math.round(value * 10000) / 10000, 0), 1)\n}\n","/**\n * Variant key derivation convention.\n *\n * Given an original storage key and a style name, produces a deterministic\n * variant key in the same directory.\n *\n * Convention:\n * Original: uploads/2026/02/{uuid}/photo.jpg\n * Variant: uploads/2026/02/{uuid}/thumbnail_photo.webp\n */\n\n/**\n * Derive a variant storage key from the original key + style name.\n *\n * @param originalKey - The original file's storage key\n * @param styleName - The image style name (e.g., 'thumbnail', 'medium')\n * @param format - The variant output format (default: 'webp')\n * @returns The derived variant key\n */\nexport function deriveVariantKey(originalKey: string, styleName: string, format = 'webp'): string {\n const lastSlash = originalKey.lastIndexOf('/')\n const dir = originalKey.substring(0, lastSlash)\n const filename = originalKey.substring(lastSlash + 1)\n const baseName = filename.replace(/\\.[^.]+$/, '')\n return `${dir}/${styleName}_${baseName}.${format}`\n}\n"],"mappings":"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"}