@gallop.software/studio 1.0.5 → 1.1.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.
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  getAllThumbnailPaths,
3
- getThumbnailPath
4
- } from "../chunk-FDWPNRNZ.mjs";
3
+ getThumbnailPath,
4
+ isProcessed
5
+ } from "../chunk-CIS6B4SP.mjs";
5
6
 
6
7
  // src/handlers/index.ts
7
8
  import { NextResponse as NextResponse5 } from "next/server";
@@ -15,7 +16,7 @@ import path5 from "path";
15
16
  import { promises as fs } from "fs";
16
17
  import path from "path";
17
18
  async function loadMeta() {
18
- const metaPath = path.join(process.cwd(), "_data", "studio.json");
19
+ const metaPath = path.join(process.cwd(), "_data", "_studio.json");
19
20
  try {
20
21
  const content = await fs.readFile(metaPath, "utf-8");
21
22
  return JSON.parse(content);
@@ -26,7 +27,7 @@ async function loadMeta() {
26
27
  async function saveMeta(meta) {
27
28
  const dataDir = path.join(process.cwd(), "_data");
28
29
  await fs.mkdir(dataDir, { recursive: true });
29
- const metaPath = path.join(dataDir, "studio.json");
30
+ const metaPath = path.join(dataDir, "_studio.json");
30
31
  const ordered = {};
31
32
  if (meta._cdns) {
32
33
  ordered._cdns = meta._cdns;
@@ -106,16 +107,18 @@ import { promises as fs2 } from "fs";
106
107
  import path3 from "path";
107
108
  import sharp from "sharp";
108
109
  import { encode } from "blurhash";
110
+ var FULL_MAX_WIDTH = 2560;
109
111
  var DEFAULT_SIZES = {
110
- small: { width: 300, suffix: "-sm" },
111
- medium: { width: 700, suffix: "-md" },
112
- large: { width: 1400, suffix: "-lg" }
112
+ small: { width: 300, suffix: "-sm", key: "sm" },
113
+ medium: { width: 700, suffix: "-md", key: "md" },
114
+ large: { width: 1400, suffix: "-lg", key: "lg" }
113
115
  };
114
116
  async function processImage(buffer, imageKey) {
115
117
  const sharpInstance = sharp(buffer);
116
118
  const metadata = await sharpInstance.metadata();
117
119
  const originalWidth = metadata.width || 0;
118
120
  const originalHeight = metadata.height || 0;
121
+ const ratio = originalHeight / originalWidth;
119
122
  const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
120
123
  const baseName = path3.basename(keyWithoutSlash, path3.extname(keyWithoutSlash));
121
124
  const ext = path3.extname(keyWithoutSlash).toLowerCase();
@@ -124,19 +127,34 @@ async function processImage(buffer, imageKey) {
124
127
  await fs2.mkdir(imagesPath, { recursive: true });
125
128
  const isPng = ext === ".png";
126
129
  const outputExt = isPng ? ".png" : ".jpg";
130
+ const entry = {
131
+ o: [originalWidth, originalHeight]
132
+ };
127
133
  const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
128
134
  const fullPath = path3.join(process.cwd(), "public", "images", fullFileName);
129
- if (isPng) {
130
- await sharp(buffer).png({ quality: 85 }).toFile(fullPath);
135
+ let fullWidth = originalWidth;
136
+ let fullHeight = originalHeight;
137
+ if (originalWidth > FULL_MAX_WIDTH) {
138
+ fullWidth = FULL_MAX_WIDTH;
139
+ fullHeight = Math.round(FULL_MAX_WIDTH * ratio);
140
+ if (isPng) {
141
+ await sharp(buffer).resize(fullWidth, fullHeight).png({ quality: 85 }).toFile(fullPath);
142
+ } else {
143
+ await sharp(buffer).resize(fullWidth, fullHeight).jpeg({ quality: 85 }).toFile(fullPath);
144
+ }
131
145
  } else {
132
- await sharp(buffer).jpeg({ quality: 85 }).toFile(fullPath);
146
+ if (isPng) {
147
+ await sharp(buffer).png({ quality: 85 }).toFile(fullPath);
148
+ } else {
149
+ await sharp(buffer).jpeg({ quality: 85 }).toFile(fullPath);
150
+ }
133
151
  }
152
+ entry.f = [fullWidth, fullHeight];
134
153
  for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
135
- const { width: maxWidth, suffix } = sizeConfig;
154
+ const { width: maxWidth, suffix, key } = sizeConfig;
136
155
  if (originalWidth <= maxWidth) {
137
156
  continue;
138
157
  }
139
- const ratio = originalHeight / originalWidth;
140
158
  const newHeight = Math.round(maxWidth * ratio);
141
159
  const sizeFileName = `${baseName}${suffix}${outputExt}`;
142
160
  const sizeFilePath = imageDir === "." ? sizeFileName : `${imageDir}/${sizeFileName}`;
@@ -146,14 +164,11 @@ async function processImage(buffer, imageKey) {
146
164
  } else {
147
165
  await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
148
166
  }
167
+ entry[key] = [maxWidth, newHeight];
149
168
  }
150
169
  const { data, info } = await sharp(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
151
- const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
152
- return {
153
- w: originalWidth,
154
- h: originalHeight,
155
- b: blurhash
156
- };
170
+ entry.b = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
171
+ return entry;
157
172
  }
158
173
 
159
174
  // src/handlers/utils/cdn.ts
@@ -400,7 +415,8 @@ async function handleList(request) {
400
415
  let thumbnail;
401
416
  let hasThumbnail = false;
402
417
  let fileSize;
403
- if (isImage && entry.p === 1) {
418
+ const entryIsProcessed = isProcessed(entry);
419
+ if (isImage && entryIsProcessed) {
404
420
  const thumbPath = getThumbnailPath(key, "sm");
405
421
  if (isPushedToCloud && entry.c !== void 0) {
406
422
  const cdnUrl = cdnUrls[entry.c];
@@ -443,12 +459,12 @@ async function handleList(request) {
443
459
  size: fileSize,
444
460
  thumbnail,
445
461
  hasThumbnail,
446
- isProcessed: entry.p === 1,
462
+ isProcessed: entryIsProcessed,
447
463
  cdnPushed: isPushedToCloud,
448
464
  cdnBaseUrl: fileCdnUrl,
449
465
  isRemote,
450
466
  isProtected: isInsideImagesFolder,
451
- dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
467
+ dimensions: entry.o ? { width: entry.o[0], height: entry.o[1] } : void 0
452
468
  });
453
469
  }
454
470
  }
@@ -480,7 +496,8 @@ async function handleSearch(request) {
480
496
  const isRemote = isPushedToCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);
481
497
  let thumbnail;
482
498
  let hasThumbnail = false;
483
- if (isImage && entry.p === 1) {
499
+ const entryIsProcessed = isProcessed(entry);
500
+ if (isImage && entryIsProcessed) {
484
501
  const thumbPath = getThumbnailPath(key, "sm");
485
502
  if (isPushedToCloud && entry.c !== void 0) {
486
503
  const cdnUrl = cdnUrls[entry.c];
@@ -514,11 +531,11 @@ async function handleSearch(request) {
514
531
  type: "file",
515
532
  thumbnail,
516
533
  hasThumbnail,
517
- isProcessed: entry.p === 1,
534
+ isProcessed: entryIsProcessed,
518
535
  cdnPushed: isPushedToCloud,
519
536
  cdnBaseUrl: fileCdnUrl,
520
537
  isRemote,
521
- dimensions: entry.w && entry.h ? { width: entry.w, height: entry.h } : void 0
538
+ dimensions: entry.o ? { width: entry.o[0], height: entry.o[1] } : void 0
522
539
  });
523
540
  }
524
541
  return NextResponse.json({ items });
@@ -949,7 +966,7 @@ async function handleMoveStream(request) {
949
966
  const fileCdnUrl = isInCloud && entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
950
967
  const isRemote = isInCloud && (!r2PublicUrl || fileCdnUrl !== r2PublicUrl);
951
968
  const isPushedToR2 = isInCloud && r2PublicUrl && fileCdnUrl === r2PublicUrl;
952
- const hasProcessedThumbnails = entry?.p === 1;
969
+ const hasProcessedThumbnails = isProcessed(entry);
953
970
  try {
954
971
  if (isRemote && isImage) {
955
972
  const remoteUrl = `${fileCdnUrl}${oldKey}`;
@@ -957,8 +974,7 @@ async function handleMoveStream(request) {
957
974
  await fs5.mkdir(path6.dirname(newAbsolutePath), { recursive: true });
958
975
  await fs5.writeFile(newAbsolutePath, buffer);
959
976
  const newEntry = {
960
- w: entry?.w,
961
- h: entry?.h,
977
+ o: entry?.o,
962
978
  b: entry?.b
963
979
  };
964
980
  delete meta[oldKey];
@@ -968,17 +984,13 @@ async function handleMoveStream(request) {
968
984
  const buffer = await downloadFromCdn(oldKey);
969
985
  await fs5.mkdir(path6.dirname(newAbsolutePath), { recursive: true });
970
986
  await fs5.writeFile(newAbsolutePath, buffer);
971
- const newEntry = {
972
- w: entry?.w,
973
- h: entry?.h,
987
+ let newEntry = {
988
+ o: entry?.o,
974
989
  b: entry?.b
975
990
  };
976
991
  if (hasProcessedThumbnails) {
977
992
  const processedEntry = await processImage(buffer, newKey);
978
- newEntry.w = processedEntry.w;
979
- newEntry.h = processedEntry.h;
980
- newEntry.b = processedEntry.b;
981
- newEntry.p = 1;
993
+ newEntry = { ...newEntry, ...processedEntry };
982
994
  }
983
995
  await uploadOriginalToCdn(newKey);
984
996
  if (hasProcessedThumbnails) {
@@ -1144,7 +1156,7 @@ async function handleSync(request) {
1144
1156
  })
1145
1157
  );
1146
1158
  urlsToPurge.push(`${publicUrl}${imageKey}`);
1147
- if (!isRemote && entry.p) {
1159
+ if (!isRemote && isProcessed(entry)) {
1148
1160
  for (const thumbPath of getAllThumbnailPaths(imageKey)) {
1149
1161
  const localPath = path7.join(process.cwd(), "public", thumbPath);
1150
1162
  try {
@@ -1240,7 +1252,6 @@ async function handleReprocess(request) {
1240
1252
  }
1241
1253
  }
1242
1254
  const updatedEntry = await processImage(buffer, imageKey);
1243
- updatedEntry.p = 1;
1244
1255
  if (isInOurR2) {
1245
1256
  updatedEntry.c = existingCdnIndex;
1246
1257
  await uploadToCdn(imageKey);
@@ -1297,7 +1308,7 @@ async function handleProcessAllStream() {
1297
1308
  for (const [key, entry] of getFileEntries(meta)) {
1298
1309
  const fileName = path7.basename(key);
1299
1310
  if (!isImageFile(fileName)) continue;
1300
- if (!entry.p) {
1311
+ if (!isProcessed(entry)) {
1301
1312
  imagesToProcess.push({ key, entry });
1302
1313
  } else {
1303
1314
  alreadyProcessed++;
@@ -1347,10 +1358,10 @@ async function handleProcessAllStream() {
1347
1358
  await fs6.writeFile(destPath, buffer);
1348
1359
  meta[key] = {
1349
1360
  ...entry,
1350
- w: 0,
1351
- h: 0,
1361
+ o: [0, 0],
1352
1362
  b: "",
1353
- p: 1
1363
+ f: [0, 0]
1364
+ // SVG has "full" to indicate processed
1354
1365
  };
1355
1366
  if (isRemote) {
1356
1367
  delete meta[key].c;
@@ -1359,7 +1370,6 @@ async function handleProcessAllStream() {
1359
1370
  const processedEntry = await processImage(buffer, key);
1360
1371
  meta[key] = {
1361
1372
  ...processedEntry,
1362
- p: 1,
1363
1373
  ...isInOurR2 ? { c: existingCdnIndex } : {}
1364
1374
  };
1365
1375
  }
@@ -1556,7 +1566,7 @@ async function handleScanStream() {
1556
1566
  if (isImage) {
1557
1567
  const ext = path8.extname(relativePath).toLowerCase();
1558
1568
  if (ext === ".svg") {
1559
- meta[imageKey] = { w: 0, h: 0, b: "" };
1569
+ meta[imageKey] = { o: [0, 0], b: "" };
1560
1570
  } else {
1561
1571
  try {
1562
1572
  const buffer = await fs7.readFile(fullPath);
@@ -1564,12 +1574,11 @@ async function handleScanStream() {
1564
1574
  const { data, info } = await sharp3(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1565
1575
  const blurhash = encode2(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
1566
1576
  meta[imageKey] = {
1567
- w: metadata.width || 0,
1568
- h: metadata.height || 0,
1577
+ o: [metadata.width || 0, metadata.height || 0],
1569
1578
  b: blurhash
1570
1579
  };
1571
1580
  } catch {
1572
- meta[imageKey] = { w: 0, h: 0 };
1581
+ meta[imageKey] = { o: [0, 0] };
1573
1582
  }
1574
1583
  }
1575
1584
  } else {
@@ -1586,7 +1595,7 @@ async function handleScanStream() {
1586
1595
  const expectedThumbnails = /* @__PURE__ */ new Set();
1587
1596
  const fileEntries = getFileEntries(meta);
1588
1597
  for (const [imageKey, entry] of fileEntries) {
1589
- if (entry.c === void 0 && entry.p === 1) {
1598
+ if (entry.c === void 0 && isProcessed(entry)) {
1590
1599
  for (const thumbPath of getAllThumbnailPaths(imageKey)) {
1591
1600
  expectedThumbnails.add(thumbPath);
1592
1601
  }
@@ -1719,8 +1728,7 @@ async function processRemoteImage(url) {
1719
1728
  const { data, info } = await sharp4(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1720
1729
  const blurhash = encode3(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
1721
1730
  return {
1722
- w: metadata.width || 0,
1723
- h: metadata.height || 0,
1731
+ o: [metadata.width || 0, metadata.height || 0],
1724
1732
  b: blurhash
1725
1733
  };
1726
1734
  }
@@ -1766,8 +1774,7 @@ async function handleImportUrls(request) {
1766
1774
  const cdnIndex = getOrAddCdnIndex(meta, base);
1767
1775
  const imageData = await processRemoteImage(url);
1768
1776
  setMetaEntry(meta, path9, {
1769
- w: imageData.w,
1770
- h: imageData.h,
1777
+ o: imageData.o,
1771
1778
  b: imageData.b,
1772
1779
  c: cdnIndex
1773
1780
  });