simple-photo-gallery 2.0.2 → 2.0.3
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.js +171 -122
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -3,17 +3,75 @@ import process2 from 'process';
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { LogLevels, createConsola } from 'consola';
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
-
import
|
|
6
|
+
import fs6, { promises } from 'fs';
|
|
7
7
|
import path4 from 'path';
|
|
8
|
+
import { GalleryDataSchema } from '@simple-photo-gallery/common/src/gallery';
|
|
8
9
|
import { Buffer } from 'buffer';
|
|
9
10
|
import sharp from 'sharp';
|
|
10
|
-
import { z } from 'zod';
|
|
11
11
|
import ExifReader from 'exifreader';
|
|
12
|
+
import { encode } from 'blurhash';
|
|
12
13
|
import ffprobe from 'node-ffprobe';
|
|
13
14
|
|
|
15
|
+
// src/config/index.ts
|
|
16
|
+
var DEFAULT_THUMBNAIL_SIZE = 300;
|
|
17
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".tif", ".svg", ".avif"]);
|
|
18
|
+
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
|
|
19
|
+
var HEADER_IMAGE_LANDSCAPE_WIDTHS = [3840, 2560, 1920, 1280, 960, 640];
|
|
20
|
+
var HEADER_IMAGE_PORTRAIT_WIDTHS = [1080, 720, 480, 360];
|
|
21
|
+
async function resizeImage(image, outputPath, width, height, format = "avif") {
|
|
22
|
+
await image.resize(width, height, { withoutEnlargement: true }).toFormat(format).toFile(outputPath);
|
|
23
|
+
}
|
|
24
|
+
async function cropAndResizeImage(image, outputPath, width, height, format = "avif") {
|
|
25
|
+
await image.resize(width, height, {
|
|
26
|
+
fit: "cover",
|
|
27
|
+
withoutEnlargement: true
|
|
28
|
+
}).toFormat(format).toFile(outputPath);
|
|
29
|
+
}
|
|
30
|
+
async function getImageDescription(imagePath) {
|
|
31
|
+
try {
|
|
32
|
+
const tags = await ExifReader.load(imagePath);
|
|
33
|
+
if (tags.description?.description) return tags.description.description;
|
|
34
|
+
if (tags.ImageDescription?.description) return tags.ImageDescription.description;
|
|
35
|
+
if (tags.UserComment && typeof tags.UserComment === "object" && tags.UserComment !== null && "description" in tags.UserComment) {
|
|
36
|
+
return tags.UserComment.description;
|
|
37
|
+
}
|
|
38
|
+
if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;
|
|
39
|
+
if (tags["Caption/Abstract"]?.description) return tags["Caption/Abstract"].description;
|
|
40
|
+
if (tags.XPTitle?.description) return tags.XPTitle.description;
|
|
41
|
+
if (tags.XPComment?.description) return tags.XPComment.description;
|
|
42
|
+
} catch {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, size) {
|
|
47
|
+
const originalWidth = metadata.width || 0;
|
|
48
|
+
const originalHeight = metadata.height || 0;
|
|
49
|
+
if (originalWidth === 0 || originalHeight === 0) {
|
|
50
|
+
throw new Error("Invalid image dimensions");
|
|
51
|
+
}
|
|
52
|
+
const aspectRatio = originalWidth / originalHeight;
|
|
53
|
+
let width;
|
|
54
|
+
let height;
|
|
55
|
+
if (originalWidth > originalHeight) {
|
|
56
|
+
width = size;
|
|
57
|
+
height = Math.round(size / aspectRatio);
|
|
58
|
+
} else {
|
|
59
|
+
width = Math.round(size * aspectRatio);
|
|
60
|
+
height = size;
|
|
61
|
+
}
|
|
62
|
+
await resizeImage(image, outputPath, width, height);
|
|
63
|
+
await resizeImage(image, outputPathRetina, width * 2, height * 2);
|
|
64
|
+
return { width, height };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/modules/build/utils/index.ts
|
|
14
68
|
path4.dirname(new URL(import.meta.url).pathname);
|
|
15
69
|
async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
|
|
16
70
|
ui.start(`Creating social media card image`);
|
|
71
|
+
if (fs6.existsSync(ouputPath)) {
|
|
72
|
+
ui.success(`Social media card image already exists`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
17
75
|
const resizedImageBuffer = await sharp(headerPhotoPath).resize(1200, 631, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
|
|
18
76
|
const outputPath = ouputPath;
|
|
19
77
|
await sharp(resizedImageBuffer).toFile(outputPath);
|
|
@@ -32,66 +90,72 @@ async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPa
|
|
|
32
90
|
await sharp(finalImageBuffer).toFile(outputPath);
|
|
33
91
|
ui.success(`Created social media card image successfully`);
|
|
34
92
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
|
|
94
|
+
ui.start(`Creating optimized header images`);
|
|
95
|
+
const image = sharp(headerPhotoPath);
|
|
96
|
+
const landscapeYFactor = 3 / 4;
|
|
97
|
+
for (const width of HEADER_IMAGE_LANDSCAPE_WIDTHS) {
|
|
98
|
+
ui.debug(`Creating landscape header image ${width}`);
|
|
99
|
+
if (fs6.existsSync(path4.join(outputFolder, `header_landscape_${width}.avif`))) {
|
|
100
|
+
ui.debug(`Landscape header image ${width} AVIF already exists`);
|
|
101
|
+
} else {
|
|
102
|
+
await cropAndResizeImage(
|
|
103
|
+
image.clone(),
|
|
104
|
+
path4.join(outputFolder, `header_landscape_${width}.avif`),
|
|
105
|
+
width,
|
|
106
|
+
width * landscapeYFactor,
|
|
107
|
+
"avif"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (fs6.existsSync(path4.join(outputFolder, `header_landscape_${width}.jpg`))) {
|
|
111
|
+
ui.debug(`Landscape header image ${width} JPG already exists`);
|
|
112
|
+
} else {
|
|
113
|
+
await cropAndResizeImage(
|
|
114
|
+
image.clone(),
|
|
115
|
+
path4.join(outputFolder, `header_landscape_${width}.jpg`),
|
|
116
|
+
width,
|
|
117
|
+
width * landscapeYFactor,
|
|
118
|
+
"jpg"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const portraitYFactor = 4 / 3;
|
|
123
|
+
for (const width of HEADER_IMAGE_PORTRAIT_WIDTHS) {
|
|
124
|
+
ui.debug(`Creating portrait header image ${width}`);
|
|
125
|
+
if (fs6.existsSync(path4.join(outputFolder, `header_portrait_${width}.avif`))) {
|
|
126
|
+
ui.debug(`Portrait header image ${width} AVIF already exists`);
|
|
127
|
+
} else {
|
|
128
|
+
await cropAndResizeImage(
|
|
129
|
+
image.clone(),
|
|
130
|
+
path4.join(outputFolder, `header_portrait_${width}.avif`),
|
|
131
|
+
width,
|
|
132
|
+
width * portraitYFactor,
|
|
133
|
+
"avif"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (fs6.existsSync(path4.join(outputFolder, `header_portrait_${width}.jpg`))) {
|
|
137
|
+
ui.debug(`Portrait header image ${width} JPG already exists`);
|
|
138
|
+
} else {
|
|
139
|
+
await cropAndResizeImage(
|
|
140
|
+
image.clone(),
|
|
141
|
+
path4.join(outputFolder, `header_portrait_${width}.jpg`),
|
|
142
|
+
width,
|
|
143
|
+
width * portraitYFactor,
|
|
144
|
+
"jpg"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ui.success(`Created optimized header image successfully`);
|
|
149
|
+
}
|
|
86
150
|
function findGalleries(basePath, recursive) {
|
|
87
151
|
const galleryDirs = [];
|
|
88
152
|
const galleryJsonPath = path4.join(basePath, "gallery", "gallery.json");
|
|
89
|
-
if (
|
|
153
|
+
if (fs6.existsSync(galleryJsonPath)) {
|
|
90
154
|
galleryDirs.push(basePath);
|
|
91
155
|
}
|
|
92
156
|
if (recursive) {
|
|
93
157
|
try {
|
|
94
|
-
const entries =
|
|
158
|
+
const entries = fs6.readdirSync(basePath, { withFileTypes: true });
|
|
95
159
|
for (const entry of entries) {
|
|
96
160
|
if (entry.isDirectory() && entry.name !== "gallery") {
|
|
97
161
|
const subPath = path4.join(basePath, entry.name);
|
|
@@ -120,36 +184,11 @@ async function getFileMtime(filePath) {
|
|
|
120
184
|
const stats = await promises.stat(filePath);
|
|
121
185
|
return stats.mtime;
|
|
122
186
|
}
|
|
123
|
-
async function
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const tags = await ExifReader.load(imagePath);
|
|
129
|
-
if (tags.description?.description) return tags.description.description;
|
|
130
|
-
if (tags.ImageDescription?.description) return tags.ImageDescription.description;
|
|
131
|
-
if (tags.UserComment && typeof tags.UserComment === "object" && tags.UserComment !== null && "description" in tags.UserComment) {
|
|
132
|
-
return tags.UserComment.description;
|
|
133
|
-
}
|
|
134
|
-
if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;
|
|
135
|
-
if (tags["Caption/Abstract"]?.description) return tags["Caption/Abstract"].description;
|
|
136
|
-
if (tags.XPTitle?.description) return tags.XPTitle.description;
|
|
137
|
-
if (tags.XPComment?.description) return tags.XPComment.description;
|
|
138
|
-
} catch {
|
|
139
|
-
return void 0;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, height) {
|
|
143
|
-
const originalWidth = metadata.width || 0;
|
|
144
|
-
const originalHeight = metadata.height || 0;
|
|
145
|
-
if (originalWidth === 0 || originalHeight === 0) {
|
|
146
|
-
throw new Error("Invalid image dimensions");
|
|
147
|
-
}
|
|
148
|
-
const aspectRatio = originalWidth / originalHeight;
|
|
149
|
-
const width = Math.round(height * aspectRatio);
|
|
150
|
-
await resizeAndSaveThumbnail(image, outputPath, width, height);
|
|
151
|
-
await resizeAndSaveThumbnail(image, outputPathRetina, width * 2, height * 2);
|
|
152
|
-
return { width, height };
|
|
187
|
+
async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
|
|
188
|
+
const image = sharp(imagePath);
|
|
189
|
+
const { data, info } = await image.resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
190
|
+
const pixels = new Uint8ClampedArray(data.buffer);
|
|
191
|
+
return encode(pixels, info.width, info.height, componentX, componentY);
|
|
153
192
|
}
|
|
154
193
|
async function getVideoDimensions(filePath) {
|
|
155
194
|
const data = await ffprobe(filePath);
|
|
@@ -188,8 +227,8 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
|
|
|
188
227
|
if (code === 0) {
|
|
189
228
|
try {
|
|
190
229
|
const frameImage = sharp(tempFramePath);
|
|
191
|
-
await
|
|
192
|
-
await
|
|
230
|
+
await resizeImage(frameImage, outputPath, width, height);
|
|
231
|
+
await resizeImage(frameImage, outputPathRetina, width * 2, height * 2);
|
|
193
232
|
try {
|
|
194
233
|
await promises.unlink(tempFramePath);
|
|
195
234
|
} catch {
|
|
@@ -208,15 +247,10 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
|
|
|
208
247
|
});
|
|
209
248
|
}
|
|
210
249
|
|
|
211
|
-
// src/config/index.ts
|
|
212
|
-
var DEFAULT_THUMBNAIL_SIZE = 300;
|
|
213
|
-
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".tif", ".svg", ".avif"]);
|
|
214
|
-
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
|
|
215
|
-
|
|
216
250
|
// src/modules/thumbnails/index.ts
|
|
217
251
|
async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
|
|
218
252
|
const fileMtime = await getFileMtime(imagePath);
|
|
219
|
-
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp &&
|
|
253
|
+
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs6.existsSync(thumbnailPath)) {
|
|
220
254
|
return void 0;
|
|
221
255
|
}
|
|
222
256
|
const image = sharp(imagePath);
|
|
@@ -236,6 +270,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
236
270
|
thumbnailPathRetina,
|
|
237
271
|
thumbnailSize
|
|
238
272
|
);
|
|
273
|
+
const blurHash = await generateBlurHash(thumbnailPath);
|
|
239
274
|
return {
|
|
240
275
|
type: "image",
|
|
241
276
|
path: imagePath,
|
|
@@ -246,14 +281,15 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
246
281
|
path: thumbnailPath,
|
|
247
282
|
pathRetina: thumbnailPathRetina,
|
|
248
283
|
width: thumbnailDimensions.width,
|
|
249
|
-
height: thumbnailDimensions.height
|
|
284
|
+
height: thumbnailDimensions.height,
|
|
285
|
+
blurHash
|
|
250
286
|
},
|
|
251
287
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
252
288
|
};
|
|
253
289
|
}
|
|
254
290
|
async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
|
|
255
291
|
const fileMtime = await getFileMtime(videoPath);
|
|
256
|
-
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp &&
|
|
292
|
+
if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs6.existsSync(thumbnailPath)) {
|
|
257
293
|
return void 0;
|
|
258
294
|
}
|
|
259
295
|
const videoDimensions = await getVideoDimensions(videoPath);
|
|
@@ -265,6 +301,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
265
301
|
thumbnailSize,
|
|
266
302
|
verbose
|
|
267
303
|
);
|
|
304
|
+
const blurHash = await generateBlurHash(thumbnailPath);
|
|
268
305
|
return {
|
|
269
306
|
type: "video",
|
|
270
307
|
path: videoPath,
|
|
@@ -275,7 +312,8 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
|
|
|
275
312
|
path: thumbnailPath,
|
|
276
313
|
pathRetina: thumbnailPathRetina,
|
|
277
314
|
width: thumbnailDimensions.width,
|
|
278
|
-
height: thumbnailDimensions.height
|
|
315
|
+
height: thumbnailDimensions.height,
|
|
316
|
+
blurHash
|
|
279
317
|
},
|
|
280
318
|
lastMediaTimestamp: fileMtime.toISOString()
|
|
281
319
|
};
|
|
@@ -286,9 +324,9 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
|
|
|
286
324
|
const filePath = path4.resolve(path4.join(galleryJsonDir, mediaFile.path));
|
|
287
325
|
const fileName = path4.basename(filePath);
|
|
288
326
|
const fileNameWithoutExt = path4.parse(fileName).name;
|
|
289
|
-
const thumbnailFileName = `${fileNameWithoutExt}.
|
|
327
|
+
const thumbnailFileName = `${fileNameWithoutExt}.avif`;
|
|
290
328
|
const thumbnailPath = path4.join(thumbnailsPath, thumbnailFileName);
|
|
291
|
-
const thumbnailPathRetina = thumbnailPath.replace(".
|
|
329
|
+
const thumbnailPathRetina = thumbnailPath.replace(".avif", "@2x.avif");
|
|
292
330
|
const relativeThumbnailPath = path4.relative(galleryJsonDir, thumbnailPath);
|
|
293
331
|
const relativeThumbnailRetinaPath = path4.relative(galleryJsonDir, thumbnailPathRetina);
|
|
294
332
|
const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
|
|
@@ -297,6 +335,20 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
|
|
|
297
335
|
const updatedMediaFile = await (mediaFile.type === "image" ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));
|
|
298
336
|
if (!updatedMediaFile) {
|
|
299
337
|
ui.debug(` Skipping ${fileName} because it has already been processed`);
|
|
338
|
+
if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs6.existsSync(thumbnailPath)) {
|
|
339
|
+
try {
|
|
340
|
+
const blurHash = await generateBlurHash(thumbnailPath);
|
|
341
|
+
return {
|
|
342
|
+
...mediaFile,
|
|
343
|
+
thumbnail: {
|
|
344
|
+
...mediaFile.thumbnail,
|
|
345
|
+
blurHash
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
} catch (error) {
|
|
349
|
+
ui.debug(` Failed to generate BlurHash for ${fileName}:`, error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
300
352
|
return mediaFile;
|
|
301
353
|
}
|
|
302
354
|
updatedMediaFile.path = mediaFile.path;
|
|
@@ -312,11 +364,11 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
|
|
|
312
364
|
}
|
|
313
365
|
async function processGalleryThumbnails(galleryDir, ui) {
|
|
314
366
|
const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
|
|
315
|
-
const thumbnailsPath = path4.join(galleryDir, "gallery", "
|
|
367
|
+
const thumbnailsPath = path4.join(galleryDir, "gallery", "images");
|
|
316
368
|
ui.start(`Creating thumbnails: ${galleryDir}`);
|
|
317
369
|
try {
|
|
318
|
-
|
|
319
|
-
const galleryContent =
|
|
370
|
+
fs6.mkdirSync(thumbnailsPath, { recursive: true });
|
|
371
|
+
const galleryContent = fs6.readFileSync(galleryJsonPath, "utf8");
|
|
320
372
|
const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
|
|
321
373
|
const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;
|
|
322
374
|
let processedCount = 0;
|
|
@@ -326,7 +378,7 @@ async function processGalleryThumbnails(galleryDir, ui) {
|
|
|
326
378
|
}
|
|
327
379
|
processedCount += section.images.length;
|
|
328
380
|
}
|
|
329
|
-
|
|
381
|
+
fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
330
382
|
ui.success(`Created thumbnails for ${processedCount} media files`);
|
|
331
383
|
return processedCount;
|
|
332
384
|
} catch (error) {
|
|
@@ -373,7 +425,7 @@ function copyPhotos(galleryData, galleryDir, ui) {
|
|
|
373
425
|
const fileName = path4.basename(image.path);
|
|
374
426
|
const destPath = path4.join(galleryDir, fileName);
|
|
375
427
|
ui.debug(`Copying photo to ${destPath}`);
|
|
376
|
-
|
|
428
|
+
fs6.copyFileSync(sourcePath, destPath);
|
|
377
429
|
}
|
|
378
430
|
}
|
|
379
431
|
}
|
|
@@ -382,17 +434,14 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
|
|
|
382
434
|
ui.start(`Building gallery ${galleryDir}`);
|
|
383
435
|
await processGalleryThumbnails(galleryDir, ui);
|
|
384
436
|
const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
|
|
385
|
-
const galleryContent =
|
|
437
|
+
const galleryContent = fs6.readFileSync(galleryJsonPath, "utf8");
|
|
386
438
|
const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
|
|
387
|
-
const socialMediaCardImagePath = path4.join(galleryDir, "gallery", "
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
galleryData.title,
|
|
391
|
-
socialMediaCardImagePath,
|
|
392
|
-
ui
|
|
393
|
-
);
|
|
439
|
+
const socialMediaCardImagePath = path4.join(galleryDir, "gallery", "images", "social-media-card.jpg");
|
|
440
|
+
const headerImagePath = path4.resolve(path4.join(galleryDir, "gallery"), galleryData.headerImage);
|
|
441
|
+
await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);
|
|
394
442
|
galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${path4.relative(galleryDir, socialMediaCardImagePath)}`;
|
|
395
|
-
|
|
443
|
+
fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
444
|
+
await createOptimizedHeaderImage(headerImagePath, path4.join(galleryDir, "gallery", "images"), ui);
|
|
396
445
|
if (!baseUrl) {
|
|
397
446
|
const shouldCopyPhotos = galleryData.sections.some(
|
|
398
447
|
(section) => section.images.some((image) => !checkFileIsOneFolderUp(image.path))
|
|
@@ -405,7 +454,7 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
|
|
|
405
454
|
if (baseUrl) {
|
|
406
455
|
ui.debug("Updating gallery.json with baseUrl");
|
|
407
456
|
galleryData.mediaBaseUrl = baseUrl;
|
|
408
|
-
|
|
457
|
+
fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
409
458
|
}
|
|
410
459
|
ui.debug("Building gallery form template");
|
|
411
460
|
try {
|
|
@@ -419,12 +468,12 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
|
|
|
419
468
|
const outputDir = path4.join(galleryDir, "gallery");
|
|
420
469
|
const buildDir = path4.join(outputDir, "_build");
|
|
421
470
|
ui.debug(`Copying build output to ${outputDir}`);
|
|
422
|
-
|
|
471
|
+
fs6.cpSync(buildDir, outputDir, { recursive: true });
|
|
423
472
|
ui.debug("Moving index.html to gallery directory");
|
|
424
|
-
|
|
425
|
-
|
|
473
|
+
fs6.copyFileSync(path4.join(outputDir, "index.html"), path4.join(galleryDir, "index.html"));
|
|
474
|
+
fs6.rmSync(path4.join(outputDir, "index.html"));
|
|
426
475
|
ui.debug("Cleaning up build directory");
|
|
427
|
-
|
|
476
|
+
fs6.rmSync(buildDir, { recursive: true, force: true });
|
|
428
477
|
ui.success(`Gallery built successfully`);
|
|
429
478
|
}
|
|
430
479
|
async function build(options, ui) {
|
|
@@ -455,9 +504,9 @@ async function build(options, ui) {
|
|
|
455
504
|
async function cleanGallery(galleryDir, ui) {
|
|
456
505
|
let filesRemoved = 0;
|
|
457
506
|
const indexHtmlPath = path4.join(galleryDir, "index.html");
|
|
458
|
-
if (
|
|
507
|
+
if (fs6.existsSync(indexHtmlPath)) {
|
|
459
508
|
try {
|
|
460
|
-
|
|
509
|
+
fs6.rmSync(indexHtmlPath);
|
|
461
510
|
ui.debug(`Removed: ${indexHtmlPath}`);
|
|
462
511
|
filesRemoved++;
|
|
463
512
|
} catch (error) {
|
|
@@ -465,9 +514,9 @@ async function cleanGallery(galleryDir, ui) {
|
|
|
465
514
|
}
|
|
466
515
|
}
|
|
467
516
|
const galleryPath = path4.join(galleryDir, "gallery");
|
|
468
|
-
if (
|
|
517
|
+
if (fs6.existsSync(galleryPath)) {
|
|
469
518
|
try {
|
|
470
|
-
|
|
519
|
+
fs6.rmSync(galleryPath, { recursive: true, force: true });
|
|
471
520
|
ui.debug(`Removed directory: ${galleryPath}`);
|
|
472
521
|
filesRemoved++;
|
|
473
522
|
} catch (error) {
|
|
@@ -483,7 +532,7 @@ async function cleanGallery(galleryDir, ui) {
|
|
|
483
532
|
async function clean(options, ui) {
|
|
484
533
|
try {
|
|
485
534
|
const basePath = path4.resolve(options.gallery);
|
|
486
|
-
if (!
|
|
535
|
+
if (!fs6.existsSync(basePath)) {
|
|
487
536
|
ui.error(`Directory does not exist: ${basePath}`);
|
|
488
537
|
return;
|
|
489
538
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/modules/build/utils/index.ts","../src/types/index.ts","../src/utils/index.ts","../src/modules/thumbnails/utils/index.ts","../src/config/index.ts","../src/modules/thumbnails/index.ts","../src/modules/build/index.ts","../src/modules/clean/index.ts","../src/modules/init/utils/index.ts","../src/modules/init/index.ts","../src/index.ts"],"names":["path","fs","sharp","process","LogLevels","result"],"mappings":"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","file":"index.js","sourcesContent":["import { Buffer } from 'node:buffer';\nimport path from 'node:path';\n\nimport sharp from 'sharp';\n\nimport type { ConsolaInstance } from 'consola';\n\n/** __dirname workaround for ESM modules */\nconst __dirname = path.dirname(new URL(import.meta.url).pathname);\n\n/**\n * Helper function to resolve paths relative to current file\n * @param segments - Path segments to resolve relative to current directory\n * @returns Resolved absolute path\n */\nexport function resolveFromCurrentDir(...segments: string[]): string {\n return path.resolve(__dirname, ...segments);\n}\n\n/**\n * Creates a social media card image for a gallery\n * @param headerPhotoPath - Path to the header photo\n * @param title - Title of the gallery\n * @param ouputPath - Output path for the social media card image\n * @param ui - ConsolaInstance for logging\n */\nexport async function createGallerySocialMediaCardImage(\n headerPhotoPath: string,\n title: string,\n ouputPath: string,\n ui: ConsolaInstance,\n): Promise<void> {\n ui.start(`Creating social media card image`);\n\n // Read and resize the header image to 1200x631 using fit\n const resizedImageBuffer = await sharp(headerPhotoPath)\n .resize(1200, 631, { fit: 'cover' })\n .jpeg({ quality: 90 })\n .toBuffer();\n\n // Save the resized image as social media card\n const outputPath = ouputPath;\n await sharp(resizedImageBuffer).toFile(outputPath);\n\n // Create SVG with title and description\n const svgText = `\n <svg width=\"1200\" height=\"631\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <style>\n .title { font-family: Arial, sans-serif; font-size: 96px; font-weight: bold; fill: white; text-anchor: middle; }\n .description { font-family: Arial, sans-serif; font-size: 48px; fill: white; text-anchor: middle; }\n </style>\n </defs>\n <text x=\"600\" y=\"250\" class=\"title\">${title}</text>\n </svg>\n `;\n\n // Composite the text overlay on top of the resized image\n const finalImageBuffer = await sharp(resizedImageBuffer)\n .composite([{ input: Buffer.from(svgText), top: 0, left: 0 }])\n .jpeg({ quality: 90 })\n .toBuffer();\n\n // Save the final image with text overlay\n await sharp(finalImageBuffer).toFile(outputPath);\n\n ui.success(`Created social media card image successfully`);\n}\n","import { z } from 'zod';\n\n/** Zod schema for thumbnail metadata including path and dimensions */\nexport const ThumbnailSchema = z.object({\n path: z.string(),\n pathRetina: z.string(),\n width: z.number(),\n height: z.number(),\n});\n\n/** Zod schema for media file metadata including type, dimensions, and thumbnail info */\nexport const MediaFileSchema = z.object({\n type: z.enum(['image', 'video']),\n path: z.string(),\n alt: z.string().optional(),\n width: z.number(),\n height: z.number(),\n thumbnail: ThumbnailSchema.optional(),\n lastMediaTimestamp: z.string().optional(),\n});\n\n/** Zod schema for a gallery section containing title, description, and media files */\nexport const GallerySectionSchema = z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n images: z.array(MediaFileSchema),\n});\n\n/** Zod schema for sub-gallery metadata including title, header image, and path */\nexport const SubGallerySchema = z.object({\n title: z.string(),\n headerImage: z.string(),\n path: z.string(),\n});\n\n/** Zod schema for complete gallery data including metadata, sections, and sub-galleries */\nexport const GalleryDataSchema = z.object({\n title: z.string(),\n description: z.string(),\n url: z.string().optional(),\n headerImage: z.string(),\n thumbnailSize: z.number().optional(),\n metadata: z.object({\n image: z.string().optional(),\n imageWidth: z.number().optional(),\n imageHeight: z.number().optional(),\n ogUrl: z.string().optional(),\n ogType: z.string().optional(),\n ogSiteName: z.string().optional(),\n twitterSite: z.string().optional(),\n twitterCreator: z.string().optional(),\n author: z.string().optional(),\n keywords: z.string().optional(),\n canonicalUrl: z.string().optional(),\n language: z.string().optional(),\n robots: z.string().optional(),\n }),\n galleryOutputPath: z.string().optional(),\n mediaBaseUrl: z.string().optional(),\n sections: z.array(GallerySectionSchema),\n subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) }),\n});\n\n/** TypeScript type for thumbnail metadata */\nexport type Thumbnail = z.infer<typeof ThumbnailSchema>;\n\n/** TypeScript type for media file metadata */\nexport type MediaFile = z.infer<typeof MediaFileSchema>;\n\n/** TypeScript type for gallery section data */\nexport type GallerySection = z.infer<typeof GallerySectionSchema>;\n\n/** TypeScript type for sub-gallery metadata */\nexport type SubGallery = z.infer<typeof SubGallerySchema>;\n\n/** TypeScript type for complete gallery data structure */\nexport type GalleryData = z.infer<typeof GalleryDataSchema>;\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Finds all gallery directories that contain a gallery/gallery.json file.\n *\n * @param basePath - The base directory to search from\n * @param recursive - Whether to search subdirectories recursively\n * @returns Array of paths to directories containing gallery/gallery.json files\n */\nexport function findGalleries(basePath: string, recursive: boolean): string[] {\n const galleryDirs: string[] = [];\n\n // Check basePath itself\n const galleryJsonPath = path.join(basePath, 'gallery', 'gallery.json');\n if (fs.existsSync(galleryJsonPath)) {\n galleryDirs.push(basePath);\n }\n\n // If recursive, search all subdirectories\n if (recursive) {\n try {\n const entries = fs.readdirSync(basePath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'gallery') {\n const subPath = path.join(basePath, entry.name);\n const subResults = findGalleries(subPath, recursive);\n galleryDirs.push(...subResults);\n }\n }\n } catch {\n // Silently ignore errors when reading directories\n }\n }\n\n return galleryDirs;\n}\n\n/**\n * Handles file processing errors with appropriate user-friendly messages\n * @param error - The error that occurred during file processing\n * @param filename - Name of the file that caused the error\n * @param ui - ConsolaInstance for logging messages\n */\nexport function handleFileProcessingError(error: unknown, filename: string, ui: ConsolaInstance): void {\n if (error instanceof Error && (error.message.includes('ffprobe') || error.message.includes('ffmpeg'))) {\n // Handle ffmpeg error\n ui.warn(\n `Error processing ${filename}: ffprobe (part of ffmpeg) is required to process videos. Please install ffmpeg and ensure it is available in your PATH`,\n );\n } else if (error instanceof Error && error.message.includes('unsupported image format')) {\n // Handle unsupported image format error\n ui.warn(`Error processing ${filename}: unsupported image format`);\n } else {\n // Handle unknown error\n ui.warn(`Error processing ${filename}`);\n }\n\n ui.debug(error);\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\n\nimport ExifReader from 'exifreader';\nimport ffprobe from 'node-ffprobe';\nimport sharp from 'sharp';\n\nimport type { Dimensions } from '../types';\nimport type { Buffer } from 'node:buffer';\nimport type { Metadata, Sharp } from 'sharp';\n\n/**\n * Gets the last modification time of a file\n * @param filePath - Path to the file\n * @returns Promise resolving to the file's modification date\n */\nexport async function getFileMtime(filePath: string): Promise<Date> {\n const stats = await fs.stat(filePath);\n return stats.mtime;\n}\n\n/**\n * Utility function to resize and save thumbnail using Sharp\n * @param image - Sharp image instance\n * @param outputPath - Path where thumbnail should be saved\n * @param width - Target width for thumbnail\n * @param height - Target height for thumbnail\n */\nasync function resizeAndSaveThumbnail(image: Sharp, outputPath: string, width: number, height: number): Promise<void> {\n await image.resize(width, height, { withoutEnlargement: true }).jpeg({ quality: 90 }).toFile(outputPath);\n}\n\n/**\n * Extracts description from image EXIF data\n * @param metadata - Sharp metadata object containing EXIF data\n * @returns Promise resolving to image description or undefined if not found\n */\nexport async function getImageDescription(imagePath: string): Promise<string | undefined> {\n try {\n const tags = await ExifReader.load(imagePath);\n\n // Description\n if (tags.description?.description) return tags.description.description;\n\n // ImageDescription\n if (tags.ImageDescription?.description) return tags.ImageDescription.description;\n\n // UserComment\n if (\n tags.UserComment &&\n typeof tags.UserComment === 'object' &&\n tags.UserComment !== null &&\n 'description' in tags.UserComment\n ) {\n return (tags.UserComment as { description: string }).description;\n }\n\n // ExtDescrAccessibility\n if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;\n\n // Caption/Abstract\n if (tags['Caption/Abstract']?.description) return tags['Caption/Abstract'].description;\n\n // XP Title\n if (tags.XPTitle?.description) return tags.XPTitle.description;\n\n // XP Comment\n if (tags.XPComment?.description) return tags.XPComment.description;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates regular and retina thumbnails for an image while maintaining aspect ratio\n * @param image - Sharp image instance\n * @param metadata - Image metadata containing dimensions\n * @param outputPath - Path where thumbnail should be saved\n * @param outputPathRetina - Path where retina thumbnail should be saved\n * @param height - Target height for thumbnail\n * @returns Promise resolving to thumbnail dimensions\n */\nexport async function createImageThumbnails(\n image: Sharp,\n metadata: Metadata,\n outputPath: string,\n outputPathRetina: string,\n height: number,\n): Promise<Dimensions> {\n // Create thumbnail using sharp\n const originalWidth = metadata.width || 0;\n const originalHeight = metadata.height || 0;\n\n if (originalWidth === 0 || originalHeight === 0) {\n throw new Error('Invalid image dimensions');\n }\n\n // Calculate width maintaining aspect ratio\n const aspectRatio = originalWidth / originalHeight;\n const width = Math.round(height * aspectRatio);\n\n // Resize the image and create the thumbnails\n await resizeAndSaveThumbnail(image, outputPath, width, height);\n await resizeAndSaveThumbnail(image, outputPathRetina, width * 2, height * 2);\n\n // Return the dimensions of the thumbnail\n return { width, height };\n}\n\n/**\n * Gets video dimensions using ffprobe\n * @param filePath - Path to the video file\n * @returns Promise resolving to video dimensions\n * @throws Error if no video stream found or invalid dimensions\n */\nexport async function getVideoDimensions(filePath: string): Promise<Dimensions> {\n const data = await ffprobe(filePath);\n const videoStream = data.streams.find((stream) => stream.codec_type === 'video');\n\n if (!videoStream) {\n throw new Error('No video stream found');\n }\n\n const dimensions = {\n width: videoStream.width || 0,\n height: videoStream.height || 0,\n };\n\n if (dimensions.width === 0 || dimensions.height === 0) {\n throw new Error('Invalid video dimensions');\n }\n\n return dimensions;\n}\n\n/**\n * Creates regular and retina thumbnails for a video by extracting the first frame\n * @param inputPath - Path to the video file\n * @param videoDimensions - Original video dimensions\n * @param outputPath - Path where thumbnail should be saved\n * @param outputPathRetina - Path where retina thumbnail should be saved\n * @param height - Target height for thumbnail\n * @param verbose - Whether to enable verbose ffmpeg output\n * @returns Promise resolving to thumbnail dimensions\n */\nexport async function createVideoThumbnails(\n inputPath: string,\n videoDimensions: Dimensions,\n outputPath: string,\n outputPathRetina: string,\n height: number,\n verbose: boolean = false,\n): Promise<Dimensions> {\n // Calculate width maintaining aspect ratio\n const aspectRatio = videoDimensions.width / videoDimensions.height;\n const width = Math.round(height * aspectRatio);\n\n // Use ffmpeg to extract first frame as a temporary file, then process with sharp\n const tempFramePath = `${outputPath}.temp.png`;\n\n return new Promise((resolve, reject) => {\n // Extract first frame using ffmpeg\n const ffmpeg = spawn('ffmpeg', [\n '-i',\n inputPath,\n '-vframes',\n '1',\n '-y',\n '-loglevel',\n verbose ? 'error' : 'quiet',\n tempFramePath,\n ]);\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n // FFmpeg writes normal output to stderr, so we don't treat this as an error\n console.log(`ffmpeg: ${data.toString()}`);\n });\n\n ffmpeg.on('close', async (code: number) => {\n if (code === 0) {\n try {\n // Process the extracted frame with sharp\n const frameImage = sharp(tempFramePath);\n await resizeAndSaveThumbnail(frameImage, outputPath, width, height);\n await resizeAndSaveThumbnail(frameImage, outputPathRetina, width * 2, height * 2);\n\n // Clean up temporary file\n try {\n await fs.unlink(tempFramePath);\n } catch {\n // Ignore cleanup errors\n }\n\n resolve({ width, height });\n } catch (sharpError) {\n reject(new Error(`Failed to process extracted frame: ${sharpError}`));\n }\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', (error: Error) => {\n reject(new Error(`Failed to start ffmpeg: ${error.message}`));\n });\n });\n}\n","/** Default thumbnail size in pixels */\nexport const DEFAULT_THUMBNAIL_SIZE = 300;\n\n/** Set of supported image file extensions */\nexport const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.tiff', '.tif', '.svg', '.avif']);\n\n/** Set of supported video file extensions */\nexport const VIDEO_EXTENSIONS = new Set(['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', '.3gp']);\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { LogLevels, type ConsolaInstance } from 'consola';\nimport sharp from 'sharp';\n\nimport {\n createImageThumbnails,\n createVideoThumbnails,\n getImageDescription,\n getFileMtime,\n getVideoDimensions,\n} from './utils';\n\nimport { DEFAULT_THUMBNAIL_SIZE } from '../../config';\nimport { GalleryDataSchema, type MediaFile } from '../../types';\nimport { findGalleries, handleFileProcessingError } from '../../utils';\n\nimport type { ThumbnailOptions } from './types';\n\n/**\n * Processes an image file to create thumbnail and extract metadata\n * @param imagePath - Path to the image file\n * @param thumbnailPath - Path where thumbnail should be saved\n * @param thumbnailPathRetina - Path where retina thumbnail should be saved\n * @param thumbnailSize - Target size for thumbnail\n * @param lastMediaTimestamp - Optional timestamp to check if processing can be skipped\n * @returns Promise resolving to updated MediaFile or undefined if skipped\n */\nasync function processImage(\n imagePath: string,\n thumbnailPath: string,\n thumbnailPathRetina: string,\n thumbnailSize: number,\n lastMediaTimestamp?: Date,\n): Promise<MediaFile | undefined> {\n // Get the last media timestamp\n const fileMtime = await getFileMtime(imagePath);\n\n // Check if processing of the file can be skipped\n if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs.existsSync(thumbnailPath)) {\n return undefined;\n }\n\n // Load the image\n const image = sharp(imagePath);\n const metadata = await image.metadata();\n\n // Get the image dimensions\n const imageDimensions = {\n width: metadata.width || 0,\n height: metadata.height || 0,\n };\n\n if (imageDimensions.width === 0 || imageDimensions.height === 0) {\n throw new Error('Invalid image dimensions');\n }\n\n // Get the image description\n const description = await getImageDescription(imagePath);\n\n // Create the thumbnails\n const thumbnailDimensions = await createImageThumbnails(\n image,\n metadata,\n thumbnailPath,\n thumbnailPathRetina,\n thumbnailSize,\n );\n\n // Return the updated media file\n return {\n type: 'image',\n path: imagePath,\n alt: description,\n width: imageDimensions.width,\n height: imageDimensions.height,\n thumbnail: {\n path: thumbnailPath,\n pathRetina: thumbnailPathRetina,\n width: thumbnailDimensions.width,\n height: thumbnailDimensions.height,\n },\n lastMediaTimestamp: fileMtime.toISOString(),\n };\n}\n\n/**\n * Processes a video file to create thumbnail and extract metadata\n * @param videoPath - Path to the video file\n * @param thumbnailPath - Path where thumbnail should be saved\n * @param thumbnailPathRetina - Path where retina thumbnail should be saved\n * @param thumbnailSize - Target size for thumbnail\n * @param verbose - Whether to enable verbose output\n * @param lastMediaTimestamp - Optional timestamp to check if processing can be skipped\n * @returns Promise resolving to updated MediaFile or undefined if skipped\n */\nasync function processVideo(\n videoPath: string,\n thumbnailPath: string,\n thumbnailPathRetina: string,\n thumbnailSize: number,\n verbose: boolean,\n lastMediaTimestamp?: Date,\n): Promise<MediaFile | undefined> {\n // Get the last media timestamp\n const fileMtime = await getFileMtime(videoPath);\n\n // Check if processing of the file can be skipped\n if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs.existsSync(thumbnailPath)) {\n return undefined;\n }\n\n // Get the video dimensions\n const videoDimensions = await getVideoDimensions(videoPath);\n\n // Create the thumbnail\n const thumbnailDimensions = await createVideoThumbnails(\n videoPath,\n videoDimensions,\n thumbnailPath,\n thumbnailPathRetina,\n thumbnailSize,\n verbose,\n );\n\n return {\n type: 'video',\n path: videoPath,\n alt: undefined,\n width: videoDimensions.width,\n height: videoDimensions.height,\n thumbnail: {\n path: thumbnailPath,\n pathRetina: thumbnailPathRetina,\n width: thumbnailDimensions.width,\n height: thumbnailDimensions.height,\n },\n lastMediaTimestamp: fileMtime.toISOString(),\n };\n}\n\n/**\n * Processes a media file to generate thumbnails and update metadata\n * @param mediaFile - Media file to process\n * @param galleryDir - Gallery directory path\n * @param thumbnailsPath - Directory where thumbnails are stored\n * @param thumbnailSize - Target size for thumbnails\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to updated MediaFile\n */\nasync function processMediaFile(\n mediaFile: MediaFile,\n galleryDir: string,\n thumbnailsPath: string,\n thumbnailSize: number,\n ui: ConsolaInstance,\n): Promise<MediaFile> {\n try {\n // Resolve the path relative to the gallery.json file location, not the gallery directory\n const galleryJsonDir = path.join(galleryDir, 'gallery');\n const filePath = path.resolve(path.join(galleryJsonDir, mediaFile.path));\n\n const fileName = path.basename(filePath);\n const fileNameWithoutExt = path.parse(fileName).name;\n const thumbnailFileName = `${fileNameWithoutExt}.jpg`;\n const thumbnailPath = path.join(thumbnailsPath, thumbnailFileName);\n const thumbnailPathRetina = thumbnailPath.replace('.jpg', '@2x.jpg');\n const relativeThumbnailPath = path.relative(galleryJsonDir, thumbnailPath);\n const relativeThumbnailRetinaPath = path.relative(galleryJsonDir, thumbnailPathRetina);\n\n const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : undefined;\n const verbose = ui.level === LogLevels.debug;\n\n ui.debug(` Processing ${mediaFile.type}: ${fileName}`);\n\n const updatedMediaFile = await (mediaFile.type === 'image'\n ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp)\n : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));\n\n if (!updatedMediaFile) {\n ui.debug(` Skipping ${fileName} because it has already been processed`);\n return mediaFile;\n }\n\n updatedMediaFile.path = mediaFile.path;\n if (updatedMediaFile.thumbnail) {\n updatedMediaFile.thumbnail.path = relativeThumbnailPath;\n updatedMediaFile.thumbnail.pathRetina = relativeThumbnailRetinaPath;\n }\n\n return updatedMediaFile;\n } catch (error) {\n handleFileProcessingError(error, path.basename(mediaFile.path), ui);\n\n return mediaFile;\n }\n}\n\n/**\n * Processes all media files in a gallery to generate thumbnails\n * @param galleryDir - Directory containing the gallery\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to the number of files processed\n */\nexport async function processGalleryThumbnails(galleryDir: string, ui: ConsolaInstance): Promise<number> {\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n const thumbnailsPath = path.join(galleryDir, 'gallery', 'thumbnails');\n\n ui.start(`Creating thumbnails: ${galleryDir}`);\n\n try {\n // Ensure thumbnails directory exists\n fs.mkdirSync(thumbnailsPath, { recursive: true });\n\n // Read gallery.json\n const galleryContent = fs.readFileSync(galleryJsonPath, 'utf8');\n const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));\n\n const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;\n\n // Process all sections and their images\n let processedCount = 0;\n for (const section of galleryData.sections) {\n for (const [index, mediaFile] of section.images.entries()) {\n section.images[index] = await processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnailSize, ui);\n }\n\n processedCount += section.images.length;\n }\n\n // Write updated gallery.json\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n\n ui.success(`Created thumbnails for ${processedCount} media files`);\n\n return processedCount;\n } catch (error) {\n ui.error(`Error creating thumbnails for ${galleryDir}`);\n throw error;\n }\n}\n\n/**\n * Main thumbnails command implementation - generates thumbnails for all galleries\n * @param options - Options specifying gallery path and recursion settings\n * @param ui - ConsolaInstance for logging\n */\nexport async function thumbnails(options: ThumbnailOptions, ui: ConsolaInstance): Promise<void> {\n try {\n // Find all gallery directories\n const galleryDirs = findGalleries(options.gallery, options.recursive);\n if (galleryDirs.length === 0) {\n ui.error('No galleries found.');\n return;\n }\n\n // Process each gallery directory\n let totalGalleries = 0;\n let totalProcessed = 0;\n for (const galleryDir of galleryDirs) {\n const processed = await processGalleryThumbnails(galleryDir, ui);\n\n if (processed > 0) {\n ++totalGalleries;\n totalProcessed += processed;\n }\n }\n\n ui.box(\n `Created thumbnails for ${totalGalleries} ${totalGalleries === 1 ? 'gallery' : 'galleries'} with ${totalProcessed} media ${totalProcessed === 1 ? 'file' : 'files'}`,\n );\n } catch (error) {\n ui.error('Error creating thumbnails');\n throw error;\n }\n}\n","import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { LogLevels, type ConsolaInstance } from 'consola';\n\nimport { createGallerySocialMediaCardImage } from './utils';\n\nimport { type GalleryData, GalleryDataSchema } from '../../types';\nimport { findGalleries } from '../../utils';\nimport { processGalleryThumbnails } from '../thumbnails';\n\nimport type { BuildOptions } from './types';\n\n/**\n * Checks if a file path refers to a file one folder up from the current directory\n * @param filePath - The file path to check\n * @returns True if the file is exactly one folder up (../filename)\n */\nfunction checkFileIsOneFolderUp(filePath: string): boolean {\n const normalizedPath = path.normalize(filePath);\n const pathParts = normalizedPath.split(path.sep);\n return pathParts.length === 2 && pathParts[0] === '..';\n}\n\n/**\n * Copies photos from gallery subdirectory to main directory when needed\n * @param galleryData - Gallery data containing image paths\n * @param galleryDir - Base gallery directory\n * @param ui - ConsolaInstance for logging\n */\nfunction copyPhotos(galleryData: GalleryData, galleryDir: string, ui: ConsolaInstance): void {\n for (const section of galleryData.sections) {\n for (const image of section.images) {\n if (!checkFileIsOneFolderUp(image.path)) {\n const sourcePath = path.join(galleryDir, 'gallery', image.path);\n const fileName = path.basename(image.path);\n const destPath = path.join(galleryDir, fileName);\n\n ui.debug(`Copying photo to ${destPath}`);\n fs.copyFileSync(sourcePath, destPath);\n }\n }\n }\n}\n\n/**\n * Builds a single gallery by generating thumbnails and creating HTML output\n * @param galleryDir - Directory containing the gallery\n * @param templateDir - Directory containing the Astro template\n * @param ui - ConsolaInstance for logging\n * @param baseUrl - Optional base URL for hosting photos\n */\nasync function buildGallery(galleryDir: string, templateDir: string, ui: ConsolaInstance, baseUrl?: string): Promise<void> {\n ui.start(`Building gallery ${galleryDir}`);\n\n // Generate the thumbnails if needed\n await processGalleryThumbnails(galleryDir, ui);\n\n // Read the gallery.json file\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n const galleryContent = fs.readFileSync(galleryJsonPath, 'utf8');\n const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));\n const socialMediaCardImagePath = path.join(galleryDir, 'gallery', 'thumbnails', 'social-media-card.jpg');\n\n // Create the gallery social media card image\n await createGallerySocialMediaCardImage(\n path.resolve(path.join(galleryDir, 'gallery'), galleryData.headerImage),\n galleryData.title,\n socialMediaCardImagePath,\n ui,\n );\n galleryData.metadata.image =\n galleryData.metadata.image || `${galleryData.url || ''}/${path.relative(galleryDir, socialMediaCardImagePath)}`;\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n\n // Check if the photos need to be copied. Not needed if the baseUrl is provided.\n if (!baseUrl) {\n const shouldCopyPhotos = galleryData.sections.some((section) =>\n section.images.some((image) => !checkFileIsOneFolderUp(image.path)),\n );\n\n if (\n shouldCopyPhotos &&\n (await ui.prompt('All photos need to be copied. Are you sure you want to continue?', { type: 'confirm' }))\n ) {\n ui.debug('Copying photos');\n copyPhotos(galleryData, galleryDir, ui);\n }\n }\n\n // If the baseUrl is provided, update the gallery.json file\n if (baseUrl) {\n ui.debug('Updating gallery.json with baseUrl');\n galleryData.mediaBaseUrl = baseUrl;\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n }\n\n // Build the template\n ui.debug('Building gallery form template');\n try {\n // Set the environment variable for the gallery.json path that will be used by the template\n process.env.GALLERY_JSON_PATH = galleryJsonPath;\n process.env.GALLERY_OUTPUT_DIR = path.join(galleryDir, 'gallery');\n\n execSync('npx astro build', { cwd: templateDir, stdio: ui.level === LogLevels.debug ? 'inherit' : 'ignore' });\n } catch (error) {\n ui.error(`Build failed for ${galleryDir}`);\n throw error;\n }\n\n // Copy the build output to the output directory\n const outputDir = path.join(galleryDir, 'gallery');\n const buildDir = path.join(outputDir, '_build');\n ui.debug(`Copying build output to ${outputDir}`);\n fs.cpSync(buildDir, outputDir, { recursive: true });\n\n // Move the index.html to the gallery directory\n ui.debug('Moving index.html to gallery directory');\n fs.copyFileSync(path.join(outputDir, 'index.html'), path.join(galleryDir, 'index.html'));\n fs.rmSync(path.join(outputDir, 'index.html'));\n\n // Clean up the _build directory\n ui.debug('Cleaning up build directory');\n fs.rmSync(buildDir, { recursive: true, force: true });\n\n ui.success(`Gallery built successfully`);\n}\n\n/**\n * Main build command implementation - builds HTML galleries from gallery.json files\n * @param options - Options specifying gallery path, recursion, and base URL\n * @param ui - ConsolaInstance for logging\n */\nexport async function build(options: BuildOptions, ui: ConsolaInstance): Promise<void> {\n try {\n // Find all gallery directories\n const galleryDirs = findGalleries(options.gallery, options.recursive);\n if (galleryDirs.length === 0) {\n ui.error('No galleries found.');\n return;\n }\n\n // Get the astro theme directory from the default one\n const themePath = await import.meta.resolve('@simple-photo-gallery/theme-modern/package.json');\n const themeDir = path.dirname(new URL(themePath).pathname);\n\n // Process each gallery directory\n let totalGalleries = 0;\n for (const dir of galleryDirs) {\n const baseUrl = options.baseUrl ? `${options.baseUrl}${path.relative(options.gallery, dir)}` : undefined;\n await buildGallery(path.resolve(dir), themeDir, ui, baseUrl);\n\n ++totalGalleries;\n }\n\n ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? 'gallery' : 'galleries'} successfully`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('Cannot find package')) {\n ui.error('Theme package not found: @simple-photo-gallery/theme-modern/package.json');\n } else {\n ui.error('Error building gallery');\n }\n\n throw error;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { findGalleries } from '../../utils';\n\nimport type { CleanOptions } from './types';\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Clean gallery files from a single directory\n * @param galleryDir - Directory containing a gallery\n * @param ui - Consola instance for logging\n */\nasync function cleanGallery(galleryDir: string, ui: ConsolaInstance): Promise<void> {\n let filesRemoved = 0;\n\n // Remove index.html file from the gallery directory\n const indexHtmlPath = path.join(galleryDir, 'index.html');\n if (fs.existsSync(indexHtmlPath)) {\n try {\n fs.rmSync(indexHtmlPath);\n ui.debug(`Removed: ${indexHtmlPath}`);\n filesRemoved++;\n } catch (error) {\n ui.warn(`Failed to remove index.html: ${error}`);\n }\n }\n\n // Remove gallery directory and all its contents\n const galleryPath = path.join(galleryDir, 'gallery');\n if (fs.existsSync(galleryPath)) {\n try {\n fs.rmSync(galleryPath, { recursive: true, force: true });\n ui.debug(`Removed directory: ${galleryPath}`);\n filesRemoved++;\n } catch (error) {\n ui.warn(`Failed to remove gallery directory: ${error}`);\n }\n }\n\n if (filesRemoved > 0) {\n ui.success(`Cleaned gallery at: ${galleryDir}`);\n } else {\n ui.info(`No gallery files found at: ${galleryDir}`);\n }\n}\n\n/**\n * Clean command implementation\n * Removes all gallery-related files and directories\n */\nexport async function clean(options: CleanOptions, ui: ConsolaInstance): Promise<void> {\n try {\n const basePath = path.resolve(options.gallery);\n\n // Check if the base path exists\n if (!fs.existsSync(basePath)) {\n ui.error(`Directory does not exist: ${basePath}`);\n return;\n }\n\n // Find all gallery directories\n const galleryDirs = findGalleries(basePath, options.recursive);\n\n if (galleryDirs.length === 0) {\n ui.info('No galleries found to clean.');\n return;\n }\n\n // Clean each gallery directory\n for (const dir of galleryDirs) {\n await cleanGallery(dir, ui);\n }\n\n ui.box(`Successfully cleaned ${galleryDirs.length} ${galleryDirs.length === 1 ? 'gallery' : 'galleries'}`);\n } catch (error) {\n ui.error('Error cleaning galleries');\n throw error;\n }\n}\n","import path from 'node:path';\n\nimport { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '../../../config';\n\nimport type { MediaFileType } from '../types';\n\n/**\n * Determines the media file type based on file extension\n * @param fileName - Name of the file to check\n * @returns Media file type ('image' or 'video') or null if not supported\n */\nexport function getMediaFileType(fileName: string): MediaFileType | null {\n const ext = path.extname(fileName).toLowerCase();\n if (IMAGE_EXTENSIONS.has(ext)) return 'image';\n if (VIDEO_EXTENSIONS.has(ext)) return 'video';\n return null;\n}\n\n/**\n * Converts a folder name into a properly capitalized title\n * @param folderName - The folder name to convert\n * @returns Formatted title with proper capitalization\n */\nexport function capitalizeTitle(folderName: string): string {\n return folderName\n .replace('-', ' ')\n .replace('_', ' ')\n .split(' ')\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { capitalizeTitle, getMediaFileType } from './utils';\n\nimport type { GallerySettingsFromUser, ProcessDirectoryResult, ScanDirectoryResult, ScanOptions, SubGallery } from './types';\nimport type { MediaFile } from '../../types';\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Scans a directory for media files and subdirectories\n * @param dirPath - Path to the directory to scan\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to scan results with media files and subdirectories\n */\nasync function scanDirectory(dirPath: string, ui: ConsolaInstance): Promise<ScanDirectoryResult> {\n const mediaFiles: MediaFile[] = [];\n const subGalleryDirectories: string[] = [];\n\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n const fullPath = path.join(dirPath, entry.name);\n const mediaType = getMediaFileType(entry.name);\n\n if (mediaType) {\n const mediaFile: MediaFile = {\n type: mediaType,\n path: fullPath,\n width: 0,\n height: 0,\n };\n\n mediaFiles.push(mediaFile);\n }\n } else if (entry.isDirectory() && entry.name !== 'gallery') {\n subGalleryDirectories.push(path.join(dirPath, entry.name));\n }\n }\n } catch (error) {\n if (error instanceof Error && error.message.includes('ENOENT')) {\n ui.error(`Directory does not exist: ${dirPath}`);\n } else if (error instanceof Error && error.message.includes('ENOTDIR')) {\n ui.error(`Path is not a directory: ${dirPath}`);\n } else {\n ui.error(`Error scanning directory ${dirPath}:`, error);\n }\n\n throw error;\n }\n\n return { mediaFiles, subGalleryDirectories };\n}\n\n/**\n * Prompts the user for gallery settings through interactive CLI\n * @param galleryName - Name of the gallery directory\n * @param defaultImage - Default header image path\n * @param ui - ConsolaInstance for prompting and logging\n * @returns Promise resolving to user-provided gallery settings\n */\nasync function getGallerySettingsFromUser(\n galleryName: string,\n defaultImage: string,\n ui: ConsolaInstance,\n): Promise<GallerySettingsFromUser> {\n ui.info(`Enter gallery settings for the gallery in folder \"${galleryName}\"`);\n\n const title = await ui.prompt('Enter gallery title', { type: 'text', default: 'My Gallery', placeholder: 'My Gallery' });\n const description = await ui.prompt('Enter gallery description', {\n type: 'text',\n default: 'My gallery with fantastic photos.',\n placeholder: 'My gallery with fantastic photos.',\n });\n const url = await ui.prompt('Enter the URL where the gallery will be hosted (important for social media image)', {\n type: 'text',\n default: '',\n placeholder: '',\n });\n const headerImageName = await ui.prompt('Enter the name of the header image', {\n type: 'text',\n default: defaultImage,\n placeholder: defaultImage,\n });\n\n const headerImage = path.join('..', headerImageName);\n\n return { title, description, url, headerImage };\n}\n\n/**\n * Creates a gallery.json file with media files and settings\n * @param mediaFiles - Array of media files to include in gallery\n * @param galleryJsonPath - Path where gallery.json should be created\n * @param subGalleries - Array of sub-galleries to include\n * @param useDefaultSettings - Whether to use default settings or prompt user\n * @param ui - ConsolaInstance for prompting and logging\n */\nasync function createGalleryJson(\n mediaFiles: MediaFile[],\n galleryJsonPath: string,\n subGalleries: SubGallery[] = [],\n useDefaultSettings: boolean,\n ui: ConsolaInstance,\n): Promise<void> {\n const galleryDir = path.dirname(galleryJsonPath);\n\n // Convert media file paths to be relative to gallery.json\n const relativeMediaFiles = mediaFiles.map((file) => ({\n ...file,\n path: path.relative(galleryDir, file.path),\n }));\n\n // Convert subGallery header image paths to be relative to gallery.json\n const relativeSubGalleries = subGalleries.map((subGallery) => ({\n ...subGallery,\n headerImage: subGallery.headerImage ? path.relative(galleryDir, subGallery.headerImage) : '',\n }));\n\n let galleryData = {\n title: 'My Gallery',\n description: 'My gallery with fantastic photos.',\n headerImage: relativeMediaFiles[0]?.path || '',\n metadata: {},\n sections: [\n {\n images: relativeMediaFiles,\n },\n ],\n subGalleries: {\n title: 'Sub Galleries',\n galleries: relativeSubGalleries,\n },\n };\n\n if (!useDefaultSettings) {\n galleryData = {\n ...galleryData,\n ...(await getGallerySettingsFromUser(\n path.basename(path.join(galleryDir, '..')),\n path.basename(mediaFiles[0]?.path || ''),\n ui,\n )),\n };\n }\n\n await fs.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n}\n\n/**\n * Processes a directory and its subdirectories to create galleries\n * @param scanPath - Path to scan for media files\n * @param outputPath - Path where gallery should be created\n * @param recursive - Whether to process subdirectories recursively\n * @param useDefaultSettings - Whether to use default settings or prompt user\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to processing results\n */\nasync function processDirectory(\n scanPath: string,\n outputPath: string,\n recursive: boolean,\n useDefaultSettings: boolean,\n ui: ConsolaInstance,\n): Promise<ProcessDirectoryResult> {\n ui.start(`Scanning ${scanPath}`);\n\n let totalFiles = 0;\n let totalGalleries = 1;\n const subGalleries: SubGallery[] = [];\n\n // Scan current directory for media files\n const { mediaFiles, subGalleryDirectories } = await scanDirectory(scanPath, ui);\n totalFiles += mediaFiles.length;\n\n // Process subdirectories only if recursive mode is enabled\n if (recursive) {\n for (const subGalleryDir of subGalleryDirectories) {\n const result = await processDirectory(\n subGalleryDir,\n path.join(outputPath, path.basename(subGalleryDir)),\n recursive,\n useDefaultSettings,\n ui,\n );\n\n totalFiles += result.totalFiles;\n totalGalleries += result.totalGalleries;\n\n // If the result contains a valid subGallery, add it to the list\n if (result.subGallery) {\n subGalleries.push(result.subGallery);\n }\n }\n }\n\n // Create gallery.json if there are media files or subGalleries\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const galleryPath = path.join(outputPath, 'gallery');\n const galleryJsonPath = path.join(galleryPath, 'gallery.json');\n\n try {\n // Create output directory\n await fs.mkdir(galleryPath, { recursive: true });\n\n // Create gallery.json for this directory\n await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries, useDefaultSettings, ui);\n\n ui.success(\n `Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`,\n );\n } catch (error) {\n ui.error(`Error creating gallery.json at ${galleryJsonPath}`);\n throw error;\n }\n }\n\n // Return result with suGgallery info if this directory has media files\n const result: ProcessDirectoryResult = { totalFiles, totalGalleries };\n\n // If this directory has media files or subGalleries, create a subGallery in the result\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const dirName = path.basename(scanPath);\n result.subGallery = {\n title: capitalizeTitle(dirName),\n headerImage: mediaFiles[0]?.path || '',\n path: path.join('..', dirName),\n };\n }\n\n return result;\n}\n\n/**\n * Main init command implementation - scans directories and creates gallery.json files\n * @param options - Options specifying paths, recursion, and default settings\n * @param ui - ConsolaInstance for logging and user prompts\n */\nexport async function init(options: ScanOptions, ui: ConsolaInstance): Promise<void> {\n try {\n const scanPath = path.resolve(options.photos);\n const outputPath = options.gallery ? path.resolve(options.gallery) : scanPath;\n\n // Process the directory tree with the specified recursion setting\n const result = await processDirectory(scanPath, outputPath, options.recursive, options.default, ui);\n\n ui.box(\n `Created ${result.totalGalleries} ${result.totalGalleries === 1 ? 'gallery' : 'galleries'} with ${result.totalFiles} media ${result.totalFiles === 1 ? 'file' : 'files'}`,\n );\n } catch (error) {\n ui.error('Error initializing gallery');\n throw error;\n }\n}\n","#!/usr/bin/env node\n\nimport process from 'node:process';\n\nimport { Command } from 'commander';\nimport { createConsola, LogLevels, type ConsolaInstance } from 'consola';\n\nimport { build } from './modules/build';\nimport { clean } from './modules/clean';\nimport { init } from './modules/init';\nimport { thumbnails } from './modules/thumbnails';\n\n/** Command line interface program instance */\nconst program = new Command();\n\nprogram\n .name('gallery')\n .description('Simple Photo Gallery CLI')\n .version('0.0.1')\n .option('-v, --verbose', 'Verbose output (debug level)', false)\n .option('-q, --quiet', 'Minimal output (only warnings/errors)', false)\n .showHelpAfterError(true);\n\n/**\n * Creates a Consola UI instance with appropriate log level based on global options\n * @param globalOpts - Global command options containing verbose/quiet flags\n * @returns ConsolaInstance configured with appropriate log level and tag\n */\nfunction createConsolaUI(globalOpts: ReturnType<typeof program.opts>): ConsolaInstance {\n let level = LogLevels.info;\n\n if (globalOpts.quiet) {\n level = LogLevels.warn;\n } else if (globalOpts.verbose) {\n level = LogLevels.debug;\n }\n\n return createConsola({\n level,\n }).withTag('simple-photo-gallery');\n}\n\n/**\n * Higher-order function that wraps command handlers to provide ConsolaUI instance\n * @param handler - Command handler function that receives options and UI instance\n * @returns Wrapped handler function that creates UI and handles errors\n */\nfunction withConsolaUI<O>(handler: (opts: O, ui: ConsolaInstance) => Promise<void> | void) {\n return async (opts: O) => {\n const ui = createConsolaUI(program.opts());\n try {\n await handler(opts, ui);\n } catch (error) {\n ui.debug(error);\n\n process.exitCode = 1;\n }\n };\n}\n\nprogram\n .command('init')\n .description('Initialize a gallery by scaning a folder for images and videos')\n .option(\n '-p, --photos <path>',\n 'Path to the folder where the photos are stored. Default: current working directory',\n process.cwd(),\n )\n .option(\n '-g, --gallery <path>',\n 'Path to the directory where the gallery will be initialized. Default: same directory as the photos folder',\n )\n .option('-r, --recursive', 'Recursively create galleries from all photos subdirectories', false)\n .option('-d, --default', 'Use default gallery settings instead of asking the user', false)\n .action(withConsolaUI(init));\n\nprogram\n .command('thumbnails')\n .description('Create thumbnails for all media files in the gallery')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .action(withConsolaUI(thumbnails));\n\nprogram\n .command('build')\n .description('Build the HTML gallery in the specified directory')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .option('-b, --base-url <url>', 'Base URL where the photos are hosted')\n .action(withConsolaUI(build));\n\nprogram\n .command('clean')\n .description('Remove all gallery files and folders (index.html, gallery/)')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Clean subdirectories recursively', false)\n .action(withConsolaUI(clean));\n\nprogram.parse();\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/index.ts","../src/utils/image.ts","../src/modules/build/utils/index.ts","../src/utils/index.ts","../src/modules/thumbnails/utils/index.ts","../src/utils/blurhash.ts","../src/utils/video.ts","../src/modules/thumbnails/index.ts","../src/modules/build/index.ts","../src/modules/clean/index.ts","../src/modules/init/utils/index.ts","../src/modules/init/index.ts","../src/index.ts"],"names":["path","fs","sharp","GalleryDataSchema","process","LogLevels","result"],"mappings":"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","file":"index.js","sourcesContent":["/** Default thumbnail size in pixels */\nexport const DEFAULT_THUMBNAIL_SIZE = 300;\n\n/** Set of supported image file extensions */\nexport const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.tiff', '.tif', '.svg', '.avif']);\n\n/** Set of supported video file extensions */\nexport const VIDEO_EXTENSIONS = new Set(['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', '.3gp']);\n\n/** Set of supported header image landscape widths */\nexport const HEADER_IMAGE_LANDSCAPE_WIDTHS = [3840, 2560, 1920, 1280, 960, 640];\n\n/** Set of supported header image portrait widths */\nexport const HEADER_IMAGE_PORTRAIT_WIDTHS = [1080, 720, 480, 360];\n","import ExifReader from 'exifreader';\n\nimport type { Dimensions } from '../types';\nimport type { FormatEnum, Metadata, Sharp } from 'sharp';\n\n/**\n * Utility function to resize and save thumbnail using Sharp. The functions avoids upscaling the image and only reduces the size if necessary.\n * @param image - Sharp image instance\n * @param outputPath - Path where thumbnail should be saved\n * @param width - Target width for thumbnail\n * @param height - Target height for thumbnail\n */\nexport async function resizeImage(\n image: Sharp,\n outputPath: string,\n width: number,\n height: number,\n format: keyof FormatEnum = 'avif',\n): Promise<void> {\n // Resize the image without enlarging it\n await image.resize(width, height, { withoutEnlargement: true }).toFormat(format).toFile(outputPath);\n}\n\n/**\n * Crops and resizes an image to a target aspect ratio, avoiding upscaling the image.\n * @param image - Sharp image instance\n * @param outputPath - Path where the image should be saved\n * @param width - Target width for the image\n * @param height - Target height for the image\n */\nexport async function cropAndResizeImage(\n image: Sharp,\n outputPath: string,\n width: number,\n height: number,\n format: keyof FormatEnum = 'avif',\n): Promise<void> {\n // Apply resize with cover fit and without enlargement\n await image\n .resize(width, height, {\n fit: 'cover',\n withoutEnlargement: true,\n })\n .toFormat(format)\n .toFile(outputPath);\n}\n\n/**\n * Extracts description from image EXIF data\n * @param metadata - Sharp metadata object containing EXIF data\n * @returns Promise resolving to image description or undefined if not found\n */\nexport async function getImageDescription(imagePath: string): Promise<string | undefined> {\n try {\n const tags = await ExifReader.load(imagePath);\n\n // Description\n if (tags.description?.description) return tags.description.description;\n\n // ImageDescription\n if (tags.ImageDescription?.description) return tags.ImageDescription.description;\n\n // UserComment\n if (\n tags.UserComment &&\n typeof tags.UserComment === 'object' &&\n tags.UserComment !== null &&\n 'description' in tags.UserComment\n ) {\n return (tags.UserComment as { description: string }).description;\n }\n\n // ExtDescrAccessibility\n if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;\n\n // Caption/Abstract\n if (tags['Caption/Abstract']?.description) return tags['Caption/Abstract'].description;\n\n // XP Title\n if (tags.XPTitle?.description) return tags.XPTitle.description;\n\n // XP Comment\n if (tags.XPComment?.description) return tags.XPComment.description;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Creates regular and retina thumbnails for an image while maintaining aspect ratio\n * @param image - Sharp image instance\n * @param metadata - Image metadata containing dimensions\n * @param outputPath - Path where thumbnail should be saved\n * @param outputPathRetina - Path where retina thumbnail should be saved\n * @param size - Target size of the longer side of the thumbnail\n * @returns Promise resolving to thumbnail dimensions\n */\nexport async function createImageThumbnails(\n image: Sharp,\n metadata: Metadata,\n outputPath: string,\n outputPathRetina: string,\n size: number,\n): Promise<Dimensions> {\n // Get the original dimensions\n const originalWidth = metadata.width || 0;\n const originalHeight = metadata.height || 0;\n\n if (originalWidth === 0 || originalHeight === 0) {\n throw new Error('Invalid image dimensions');\n }\n\n // Calculate the new size maintaining aspect ratio\n const aspectRatio = originalWidth / originalHeight;\n\n let width: number;\n let height: number;\n\n if (originalWidth > originalHeight) {\n width = size;\n height = Math.round(size / aspectRatio);\n } else {\n width = Math.round(size * aspectRatio);\n height = size;\n }\n\n // Resize the image and create the thumbnails\n await resizeImage(image, outputPath, width, height);\n await resizeImage(image, outputPathRetina, width * 2, height * 2);\n\n // Return the dimensions of the thumbnail\n return { width, height };\n}\n","import { Buffer } from 'node:buffer';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport sharp from 'sharp';\n\nimport { HEADER_IMAGE_LANDSCAPE_WIDTHS, HEADER_IMAGE_PORTRAIT_WIDTHS } from '../../../config';\nimport { cropAndResizeImage } from '../../../utils/image';\n\nimport type { ConsolaInstance } from 'consola';\n\n/** __dirname workaround for ESM modules */\nconst __dirname = path.dirname(new URL(import.meta.url).pathname);\n\n/**\n * Helper function to resolve paths relative to current file\n * @param segments - Path segments to resolve relative to current directory\n * @returns Resolved absolute path\n */\nexport function resolveFromCurrentDir(...segments: string[]): string {\n return path.resolve(__dirname, ...segments);\n}\n\n/**\n * Creates a social media card image for a gallery\n * @param headerPhotoPath - Path to the header photo\n * @param title - Title of the gallery\n * @param ouputPath - Output path for the social media card image\n * @param ui - ConsolaInstance for logging\n */\nexport async function createGallerySocialMediaCardImage(\n headerPhotoPath: string,\n title: string,\n ouputPath: string,\n ui: ConsolaInstance,\n): Promise<void> {\n ui.start(`Creating social media card image`);\n\n if (fs.existsSync(ouputPath)) {\n ui.success(`Social media card image already exists`);\n return;\n }\n\n // Read and resize the header image to 1200x631 using fit\n const resizedImageBuffer = await sharp(headerPhotoPath)\n .resize(1200, 631, { fit: 'cover' })\n .jpeg({ quality: 90 })\n .toBuffer();\n\n // Save the resized image as social media card\n const outputPath = ouputPath;\n await sharp(resizedImageBuffer).toFile(outputPath);\n\n // Create SVG with title and description\n const svgText = `\n <svg width=\"1200\" height=\"631\" xmlns=\"http://www.w3.org/2000/svg\">\n <defs>\n <style>\n .title { font-family: Arial, sans-serif; font-size: 96px; font-weight: bold; fill: white; text-anchor: middle; }\n .description { font-family: Arial, sans-serif; font-size: 48px; fill: white; text-anchor: middle; }\n </style>\n </defs>\n <text x=\"600\" y=\"250\" class=\"title\">${title}</text>\n </svg>\n `;\n\n // Composite the text overlay on top of the resized image\n const finalImageBuffer = await sharp(resizedImageBuffer)\n .composite([{ input: Buffer.from(svgText), top: 0, left: 0 }])\n .jpeg({ quality: 90 })\n .toBuffer();\n\n // Save the final image with text overlay\n await sharp(finalImageBuffer).toFile(outputPath);\n\n ui.success(`Created social media card image successfully`);\n}\n\nexport async function createOptimizedHeaderImage(\n headerPhotoPath: string,\n outputFolder: string,\n ui: ConsolaInstance,\n): Promise<void> {\n ui.start(`Creating optimized header images`);\n\n const image = sharp(headerPhotoPath);\n\n // Create landscape header images\n const landscapeYFactor = 3 / 4;\n for (const width of HEADER_IMAGE_LANDSCAPE_WIDTHS) {\n ui.debug(`Creating landscape header image ${width}`);\n\n if (fs.existsSync(path.join(outputFolder, `header_landscape_${width}.avif`))) {\n ui.debug(`Landscape header image ${width} AVIF already exists`);\n } else {\n await cropAndResizeImage(\n image.clone(),\n path.join(outputFolder, `header_landscape_${width}.avif`),\n width,\n width * landscapeYFactor,\n 'avif',\n );\n }\n\n if (fs.existsSync(path.join(outputFolder, `header_landscape_${width}.jpg`))) {\n ui.debug(`Landscape header image ${width} JPG already exists`);\n } else {\n await cropAndResizeImage(\n image.clone(),\n path.join(outputFolder, `header_landscape_${width}.jpg`),\n width,\n width * landscapeYFactor,\n 'jpg',\n );\n }\n }\n\n // Create portrait header images\n const portraitYFactor = 4 / 3;\n for (const width of HEADER_IMAGE_PORTRAIT_WIDTHS) {\n ui.debug(`Creating portrait header image ${width}`);\n\n if (fs.existsSync(path.join(outputFolder, `header_portrait_${width}.avif`))) {\n ui.debug(`Portrait header image ${width} AVIF already exists`);\n } else {\n await cropAndResizeImage(\n image.clone(),\n path.join(outputFolder, `header_portrait_${width}.avif`),\n width,\n width * portraitYFactor,\n 'avif',\n );\n }\n\n if (fs.existsSync(path.join(outputFolder, `header_portrait_${width}.jpg`))) {\n ui.debug(`Portrait header image ${width} JPG already exists`);\n } else {\n await cropAndResizeImage(\n image.clone(),\n path.join(outputFolder, `header_portrait_${width}.jpg`),\n width,\n width * portraitYFactor,\n 'jpg',\n );\n }\n }\n\n ui.success(`Created optimized header image successfully`);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Finds all gallery directories that contain a gallery/gallery.json file.\n *\n * @param basePath - The base directory to search from\n * @param recursive - Whether to search subdirectories recursively\n * @returns Array of paths to directories containing gallery/gallery.json files\n */\nexport function findGalleries(basePath: string, recursive: boolean): string[] {\n const galleryDirs: string[] = [];\n\n // Check basePath itself\n const galleryJsonPath = path.join(basePath, 'gallery', 'gallery.json');\n if (fs.existsSync(galleryJsonPath)) {\n galleryDirs.push(basePath);\n }\n\n // If recursive, search all subdirectories\n if (recursive) {\n try {\n const entries = fs.readdirSync(basePath, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'gallery') {\n const subPath = path.join(basePath, entry.name);\n const subResults = findGalleries(subPath, recursive);\n galleryDirs.push(...subResults);\n }\n }\n } catch {\n // Silently ignore errors when reading directories\n }\n }\n\n return galleryDirs;\n}\n\n/**\n * Handles file processing errors with appropriate user-friendly messages\n * @param error - The error that occurred during file processing\n * @param filename - Name of the file that caused the error\n * @param ui - ConsolaInstance for logging messages\n */\nexport function handleFileProcessingError(error: unknown, filename: string, ui: ConsolaInstance): void {\n if (error instanceof Error && (error.message.includes('ffprobe') || error.message.includes('ffmpeg'))) {\n // Handle ffmpeg error\n ui.warn(\n `Error processing ${filename}: ffprobe (part of ffmpeg) is required to process videos. Please install ffmpeg and ensure it is available in your PATH`,\n );\n } else if (error instanceof Error && error.message.includes('unsupported image format')) {\n // Handle unsupported image format error\n ui.warn(`Error processing ${filename}: unsupported image format`);\n } else {\n // Handle unknown error\n ui.warn(`Error processing ${filename}`);\n }\n\n ui.debug(error);\n}\n","import { promises as fs } from 'node:fs';\n\n/**\n * Gets the last modification time of a file\n * @param filePath - Path to the file\n * @returns Promise resolving to the file's modification date\n */\nexport async function getFileMtime(filePath: string): Promise<Date> {\n const stats = await fs.stat(filePath);\n return stats.mtime;\n}\n","import { encode } from 'blurhash';\nimport sharp from 'sharp';\n\n/**\n * Generates a BlurHash from an image file or Sharp instance\n * @param imagePath - Path to image file or Sharp instance\n * @param componentX - Number of x components (default: 4)\n * @param componentY - Number of y components (default: 3)\n * @returns Promise resolving to BlurHash string\n */\nexport async function generateBlurHash(imagePath: string, componentX: number = 4, componentY: number = 3): Promise<string> {\n const image = sharp(imagePath);\n\n // Resize to small size for BlurHash computation to improve performance\n // BlurHash doesn't need high resolution\n const { data, info } = await image\n .resize(32, 32, { fit: 'inside' })\n .ensureAlpha()\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n // Convert to Uint8ClampedArray format expected by blurhash\n const pixels = new Uint8ClampedArray(data.buffer);\n\n // Generate BlurHash\n return encode(pixels, info.width, info.height, componentX, componentY);\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\n\nimport ffprobe from 'node-ffprobe';\nimport sharp from 'sharp';\n\nimport { resizeImage } from './image';\n\nimport type { Dimensions } from '../types';\nimport type { Buffer } from 'node:buffer';\n\n/**\n * Gets video dimensions using ffprobe\n * @param filePath - Path to the video file\n * @returns Promise resolving to video dimensions\n * @throws Error if no video stream found or invalid dimensions\n */\nexport async function getVideoDimensions(filePath: string): Promise<Dimensions> {\n const data = await ffprobe(filePath);\n const videoStream = data.streams.find((stream) => stream.codec_type === 'video');\n\n if (!videoStream) {\n throw new Error('No video stream found');\n }\n\n const dimensions = {\n width: videoStream.width || 0,\n height: videoStream.height || 0,\n };\n\n if (dimensions.width === 0 || dimensions.height === 0) {\n throw new Error('Invalid video dimensions');\n }\n\n return dimensions;\n}\n\n/**\n * Creates regular and retina thumbnails for a video by extracting the first frame\n * @param inputPath - Path to the video file\n * @param videoDimensions - Original video dimensions\n * @param outputPath - Path where thumbnail should be saved\n * @param outputPathRetina - Path where retina thumbnail should be saved\n * @param height - Target height for thumbnail\n * @param verbose - Whether to enable verbose ffmpeg output\n * @returns Promise resolving to thumbnail dimensions\n */\nexport async function createVideoThumbnails(\n inputPath: string,\n videoDimensions: Dimensions,\n outputPath: string,\n outputPathRetina: string,\n height: number,\n verbose: boolean = false,\n): Promise<Dimensions> {\n // Calculate width maintaining aspect ratio\n const aspectRatio = videoDimensions.width / videoDimensions.height;\n const width = Math.round(height * aspectRatio);\n\n // Use ffmpeg to extract first frame as a temporary file, then process with sharp\n const tempFramePath = `${outputPath}.temp.png`;\n\n return new Promise((resolve, reject) => {\n // Extract first frame using ffmpeg\n const ffmpeg = spawn('ffmpeg', [\n '-i',\n inputPath,\n '-vframes',\n '1',\n '-y',\n '-loglevel',\n verbose ? 'error' : 'quiet',\n tempFramePath,\n ]);\n\n ffmpeg.stderr.on('data', (data: Buffer) => {\n // FFmpeg writes normal output to stderr, so we don't treat this as an error\n console.log(`ffmpeg: ${data.toString()}`);\n });\n\n ffmpeg.on('close', async (code: number) => {\n if (code === 0) {\n try {\n // Process the extracted frame with sharp\n const frameImage = sharp(tempFramePath);\n await resizeImage(frameImage, outputPath, width, height);\n await resizeImage(frameImage, outputPathRetina, width * 2, height * 2);\n\n // Clean up temporary file\n try {\n await fs.unlink(tempFramePath);\n } catch {\n // Ignore cleanup errors\n }\n\n resolve({ width, height });\n } catch (sharpError) {\n reject(new Error(`Failed to process extracted frame: ${sharpError}`));\n }\n } else {\n reject(new Error(`ffmpeg exited with code ${code}`));\n }\n });\n\n ffmpeg.on('error', (error: Error) => {\n reject(new Error(`Failed to start ffmpeg: ${error.message}`));\n });\n });\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GalleryDataSchema, type MediaFile } from '@simple-photo-gallery/common/src/gallery';\nimport { LogLevels, type ConsolaInstance } from 'consola';\nimport sharp from 'sharp';\n\nimport { getFileMtime } from './utils';\n\nimport { DEFAULT_THUMBNAIL_SIZE } from '../../config';\nimport { findGalleries, handleFileProcessingError } from '../../utils';\nimport { generateBlurHash } from '../../utils/blurhash';\nimport { getImageDescription, createImageThumbnails } from '../../utils/image';\nimport { getVideoDimensions, createVideoThumbnails } from '../../utils/video';\n\nimport type { ThumbnailOptions } from './types';\n\n/**\n * Processes an image file to create thumbnail and extract metadata\n * @param imagePath - Path to the image file\n * @param thumbnailPath - Path where thumbnail should be saved\n * @param thumbnailPathRetina - Path where retina thumbnail should be saved\n * @param thumbnailSize - Target size for thumbnail\n * @param lastMediaTimestamp - Optional timestamp to check if processing can be skipped\n * @returns Promise resolving to updated MediaFile or undefined if skipped\n */\nasync function processImage(\n imagePath: string,\n thumbnailPath: string,\n thumbnailPathRetina: string,\n thumbnailSize: number,\n lastMediaTimestamp?: Date,\n): Promise<MediaFile | undefined> {\n // Get the last media timestamp\n const fileMtime = await getFileMtime(imagePath);\n\n // Check if processing of the file can be skipped\n if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs.existsSync(thumbnailPath)) {\n return undefined;\n }\n\n // Load the image\n const image = sharp(imagePath);\n const metadata = await image.metadata();\n\n // Get the image dimensions\n const imageDimensions = {\n width: metadata.width || 0,\n height: metadata.height || 0,\n };\n\n if (imageDimensions.width === 0 || imageDimensions.height === 0) {\n throw new Error('Invalid image dimensions');\n }\n\n // Get the image description\n const description = await getImageDescription(imagePath);\n\n // Create the thumbnails\n const thumbnailDimensions = await createImageThumbnails(\n image,\n metadata,\n thumbnailPath,\n thumbnailPathRetina,\n thumbnailSize,\n );\n\n // Generate BlurHash from the thumbnail\n const blurHash = await generateBlurHash(thumbnailPath);\n\n // Return the updated media file\n return {\n type: 'image',\n path: imagePath,\n alt: description,\n width: imageDimensions.width,\n height: imageDimensions.height,\n thumbnail: {\n path: thumbnailPath,\n pathRetina: thumbnailPathRetina,\n width: thumbnailDimensions.width,\n height: thumbnailDimensions.height,\n blurHash,\n },\n lastMediaTimestamp: fileMtime.toISOString(),\n };\n}\n\n/**\n * Processes a video file to create thumbnail and extract metadata\n * @param videoPath - Path to the video file\n * @param thumbnailPath - Path where thumbnail should be saved\n * @param thumbnailPathRetina - Path where retina thumbnail should be saved\n * @param thumbnailSize - Target size for thumbnail\n * @param verbose - Whether to enable verbose output\n * @param lastMediaTimestamp - Optional timestamp to check if processing can be skipped\n * @returns Promise resolving to updated MediaFile or undefined if skipped\n */\nasync function processVideo(\n videoPath: string,\n thumbnailPath: string,\n thumbnailPathRetina: string,\n thumbnailSize: number,\n verbose: boolean,\n lastMediaTimestamp?: Date,\n): Promise<MediaFile | undefined> {\n // Get the last media timestamp\n const fileMtime = await getFileMtime(videoPath);\n\n // Check if processing of the file can be skipped\n if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs.existsSync(thumbnailPath)) {\n return undefined;\n }\n\n // Get the video dimensions\n const videoDimensions = await getVideoDimensions(videoPath);\n\n // Create the thumbnail\n const thumbnailDimensions = await createVideoThumbnails(\n videoPath,\n videoDimensions,\n thumbnailPath,\n thumbnailPathRetina,\n thumbnailSize,\n verbose,\n );\n\n // Generate BlurHash from the thumbnail\n const blurHash = await generateBlurHash(thumbnailPath);\n\n return {\n type: 'video',\n path: videoPath,\n alt: undefined,\n width: videoDimensions.width,\n height: videoDimensions.height,\n thumbnail: {\n path: thumbnailPath,\n pathRetina: thumbnailPathRetina,\n width: thumbnailDimensions.width,\n height: thumbnailDimensions.height,\n blurHash,\n },\n lastMediaTimestamp: fileMtime.toISOString(),\n };\n}\n\n/**\n * Processes a media file to generate thumbnails and update metadata\n * @param mediaFile - Media file to process\n * @param galleryDir - Gallery directory path\n * @param thumbnailsPath - Directory where thumbnails are stored\n * @param thumbnailSize - Target size for thumbnails\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to updated MediaFile\n */\nasync function processMediaFile(\n mediaFile: MediaFile,\n galleryDir: string,\n thumbnailsPath: string,\n thumbnailSize: number,\n ui: ConsolaInstance,\n): Promise<MediaFile> {\n try {\n // Resolve the path relative to the gallery.json file location, not the gallery directory\n const galleryJsonDir = path.join(galleryDir, 'gallery');\n const filePath = path.resolve(path.join(galleryJsonDir, mediaFile.path));\n\n const fileName = path.basename(filePath);\n const fileNameWithoutExt = path.parse(fileName).name;\n const thumbnailFileName = `${fileNameWithoutExt}.avif`;\n const thumbnailPath = path.join(thumbnailsPath, thumbnailFileName);\n const thumbnailPathRetina = thumbnailPath.replace('.avif', '@2x.avif');\n const relativeThumbnailPath = path.relative(galleryJsonDir, thumbnailPath);\n const relativeThumbnailRetinaPath = path.relative(galleryJsonDir, thumbnailPathRetina);\n\n const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : undefined;\n const verbose = ui.level === LogLevels.debug;\n\n ui.debug(` Processing ${mediaFile.type}: ${fileName}`);\n\n const updatedMediaFile = await (mediaFile.type === 'image'\n ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp)\n : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));\n\n if (!updatedMediaFile) {\n ui.debug(` Skipping ${fileName} because it has already been processed`);\n\n // Check if we need to generate BlurHash for existing thumbnail\n if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs.existsSync(thumbnailPath)) {\n try {\n const blurHash = await generateBlurHash(thumbnailPath);\n return {\n ...mediaFile,\n thumbnail: {\n ...mediaFile.thumbnail,\n blurHash,\n },\n };\n } catch (error) {\n ui.debug(` Failed to generate BlurHash for ${fileName}:`, error);\n }\n }\n\n return mediaFile;\n }\n\n updatedMediaFile.path = mediaFile.path;\n if (updatedMediaFile.thumbnail) {\n updatedMediaFile.thumbnail.path = relativeThumbnailPath;\n updatedMediaFile.thumbnail.pathRetina = relativeThumbnailRetinaPath;\n }\n\n return updatedMediaFile;\n } catch (error) {\n handleFileProcessingError(error, path.basename(mediaFile.path), ui);\n\n return mediaFile;\n }\n}\n\n/**\n * Processes all media files in a gallery to generate thumbnails\n * @param galleryDir - Directory containing the gallery\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to the number of files processed\n */\nexport async function processGalleryThumbnails(galleryDir: string, ui: ConsolaInstance): Promise<number> {\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n const thumbnailsPath = path.join(galleryDir, 'gallery', 'images');\n\n ui.start(`Creating thumbnails: ${galleryDir}`);\n\n try {\n // Ensure thumbnails directory exists\n fs.mkdirSync(thumbnailsPath, { recursive: true });\n\n // Read gallery.json\n const galleryContent = fs.readFileSync(galleryJsonPath, 'utf8');\n const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));\n\n const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;\n\n // Process all sections and their images\n let processedCount = 0;\n for (const section of galleryData.sections) {\n for (const [index, mediaFile] of section.images.entries()) {\n section.images[index] = await processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnailSize, ui);\n }\n\n processedCount += section.images.length;\n }\n\n // Write updated gallery.json\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n\n ui.success(`Created thumbnails for ${processedCount} media files`);\n\n return processedCount;\n } catch (error) {\n ui.error(`Error creating thumbnails for ${galleryDir}`);\n throw error;\n }\n}\n\n/**\n * Main thumbnails command implementation - generates thumbnails for all galleries\n * @param options - Options specifying gallery path and recursion settings\n * @param ui - ConsolaInstance for logging\n */\nexport async function thumbnails(options: ThumbnailOptions, ui: ConsolaInstance): Promise<void> {\n try {\n // Find all gallery directories\n const galleryDirs = findGalleries(options.gallery, options.recursive);\n if (galleryDirs.length === 0) {\n ui.error('No galleries found.');\n return;\n }\n\n // Process each gallery directory\n let totalGalleries = 0;\n let totalProcessed = 0;\n for (const galleryDir of galleryDirs) {\n const processed = await processGalleryThumbnails(galleryDir, ui);\n\n if (processed > 0) {\n ++totalGalleries;\n totalProcessed += processed;\n }\n }\n\n ui.box(\n `Created thumbnails for ${totalGalleries} ${totalGalleries === 1 ? 'gallery' : 'galleries'} with ${totalProcessed} media ${totalProcessed === 1 ? 'file' : 'files'}`,\n );\n } catch (error) {\n ui.error('Error creating thumbnails');\n throw error;\n }\n}\n","import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { type GalleryData, GalleryDataSchema } from '@simple-photo-gallery/common/src/gallery';\nimport { LogLevels, type ConsolaInstance } from 'consola';\n\nimport { createGallerySocialMediaCardImage, createOptimizedHeaderImage } from './utils';\n\nimport { findGalleries } from '../../utils';\nimport { processGalleryThumbnails } from '../thumbnails';\n\nimport type { BuildOptions } from './types';\n\n/**\n * Checks if a file path refers to a file one folder up from the current directory\n * @param filePath - The file path to check\n * @returns True if the file is exactly one folder up (../filename)\n */\nfunction checkFileIsOneFolderUp(filePath: string): boolean {\n const normalizedPath = path.normalize(filePath);\n const pathParts = normalizedPath.split(path.sep);\n return pathParts.length === 2 && pathParts[0] === '..';\n}\n\n/**\n * Copies photos from gallery subdirectory to main directory when needed\n * @param galleryData - Gallery data containing image paths\n * @param galleryDir - Base gallery directory\n * @param ui - ConsolaInstance for logging\n */\nfunction copyPhotos(galleryData: GalleryData, galleryDir: string, ui: ConsolaInstance): void {\n for (const section of galleryData.sections) {\n for (const image of section.images) {\n if (!checkFileIsOneFolderUp(image.path)) {\n const sourcePath = path.join(galleryDir, 'gallery', image.path);\n const fileName = path.basename(image.path);\n const destPath = path.join(galleryDir, fileName);\n\n ui.debug(`Copying photo to ${destPath}`);\n fs.copyFileSync(sourcePath, destPath);\n }\n }\n }\n}\n\n/**\n * Builds a single gallery by generating thumbnails and creating HTML output\n * @param galleryDir - Directory containing the gallery\n * @param templateDir - Directory containing the Astro template\n * @param ui - ConsolaInstance for logging\n * @param baseUrl - Optional base URL for hosting photos\n */\nasync function buildGallery(galleryDir: string, templateDir: string, ui: ConsolaInstance, baseUrl?: string): Promise<void> {\n ui.start(`Building gallery ${galleryDir}`);\n\n // Generate the thumbnails if needed\n await processGalleryThumbnails(galleryDir, ui);\n\n // Read the gallery.json file\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n const galleryContent = fs.readFileSync(galleryJsonPath, 'utf8');\n const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));\n const socialMediaCardImagePath = path.join(galleryDir, 'gallery', 'images', 'social-media-card.jpg');\n const headerImagePath = path.resolve(path.join(galleryDir, 'gallery'), galleryData.headerImage);\n\n // Create the gallery social media card image\n await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);\n galleryData.metadata.image =\n galleryData.metadata.image || `${galleryData.url || ''}/${path.relative(galleryDir, socialMediaCardImagePath)}`;\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n\n // Create optimized header image\n await createOptimizedHeaderImage(headerImagePath, path.join(galleryDir, 'gallery', 'images'), ui);\n\n // Check if the photos need to be copied. Not needed if the baseUrl is provided.\n if (!baseUrl) {\n const shouldCopyPhotos = galleryData.sections.some((section) =>\n section.images.some((image) => !checkFileIsOneFolderUp(image.path)),\n );\n\n if (\n shouldCopyPhotos &&\n (await ui.prompt('All photos need to be copied. Are you sure you want to continue?', { type: 'confirm' }))\n ) {\n ui.debug('Copying photos');\n copyPhotos(galleryData, galleryDir, ui);\n }\n }\n\n // If the baseUrl is provided, update the gallery.json file\n if (baseUrl) {\n ui.debug('Updating gallery.json with baseUrl');\n galleryData.mediaBaseUrl = baseUrl;\n fs.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n }\n\n // Build the template\n ui.debug('Building gallery form template');\n try {\n // Set the environment variable for the gallery.json path that will be used by the template\n process.env.GALLERY_JSON_PATH = galleryJsonPath;\n process.env.GALLERY_OUTPUT_DIR = path.join(galleryDir, 'gallery');\n\n execSync('npx astro build', { cwd: templateDir, stdio: ui.level === LogLevels.debug ? 'inherit' : 'ignore' });\n } catch (error) {\n ui.error(`Build failed for ${galleryDir}`);\n throw error;\n }\n\n // Copy the build output to the output directory\n const outputDir = path.join(galleryDir, 'gallery');\n const buildDir = path.join(outputDir, '_build');\n ui.debug(`Copying build output to ${outputDir}`);\n fs.cpSync(buildDir, outputDir, { recursive: true });\n\n // Move the index.html to the gallery directory\n ui.debug('Moving index.html to gallery directory');\n fs.copyFileSync(path.join(outputDir, 'index.html'), path.join(galleryDir, 'index.html'));\n fs.rmSync(path.join(outputDir, 'index.html'));\n\n // Clean up the _build directory\n ui.debug('Cleaning up build directory');\n fs.rmSync(buildDir, { recursive: true, force: true });\n\n ui.success(`Gallery built successfully`);\n}\n\n/**\n * Main build command implementation - builds HTML galleries from gallery.json files\n * @param options - Options specifying gallery path, recursion, and base URL\n * @param ui - ConsolaInstance for logging\n */\nexport async function build(options: BuildOptions, ui: ConsolaInstance): Promise<void> {\n try {\n // Find all gallery directories\n const galleryDirs = findGalleries(options.gallery, options.recursive);\n if (galleryDirs.length === 0) {\n ui.error('No galleries found.');\n return;\n }\n\n // Get the astro theme directory from the default one\n const themePath = await import.meta.resolve('@simple-photo-gallery/theme-modern/package.json');\n const themeDir = path.dirname(new URL(themePath).pathname);\n\n // Process each gallery directory\n let totalGalleries = 0;\n for (const dir of galleryDirs) {\n const baseUrl = options.baseUrl ? `${options.baseUrl}${path.relative(options.gallery, dir)}` : undefined;\n await buildGallery(path.resolve(dir), themeDir, ui, baseUrl);\n\n ++totalGalleries;\n }\n\n ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? 'gallery' : 'galleries'} successfully`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('Cannot find package')) {\n ui.error('Theme package not found: @simple-photo-gallery/theme-modern/package.json');\n } else {\n ui.error('Error building gallery');\n }\n\n throw error;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { findGalleries } from '../../utils';\n\nimport type { CleanOptions } from './types';\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Clean gallery files from a single directory\n * @param galleryDir - Directory containing a gallery\n * @param ui - Consola instance for logging\n */\nasync function cleanGallery(galleryDir: string, ui: ConsolaInstance): Promise<void> {\n let filesRemoved = 0;\n\n // Remove index.html file from the gallery directory\n const indexHtmlPath = path.join(galleryDir, 'index.html');\n if (fs.existsSync(indexHtmlPath)) {\n try {\n fs.rmSync(indexHtmlPath);\n ui.debug(`Removed: ${indexHtmlPath}`);\n filesRemoved++;\n } catch (error) {\n ui.warn(`Failed to remove index.html: ${error}`);\n }\n }\n\n // Remove gallery directory and all its contents\n const galleryPath = path.join(galleryDir, 'gallery');\n if (fs.existsSync(galleryPath)) {\n try {\n fs.rmSync(galleryPath, { recursive: true, force: true });\n ui.debug(`Removed directory: ${galleryPath}`);\n filesRemoved++;\n } catch (error) {\n ui.warn(`Failed to remove gallery directory: ${error}`);\n }\n }\n\n if (filesRemoved > 0) {\n ui.success(`Cleaned gallery at: ${galleryDir}`);\n } else {\n ui.info(`No gallery files found at: ${galleryDir}`);\n }\n}\n\n/**\n * Clean command implementation\n * Removes all gallery-related files and directories\n */\nexport async function clean(options: CleanOptions, ui: ConsolaInstance): Promise<void> {\n try {\n const basePath = path.resolve(options.gallery);\n\n // Check if the base path exists\n if (!fs.existsSync(basePath)) {\n ui.error(`Directory does not exist: ${basePath}`);\n return;\n }\n\n // Find all gallery directories\n const galleryDirs = findGalleries(basePath, options.recursive);\n\n if (galleryDirs.length === 0) {\n ui.info('No galleries found to clean.');\n return;\n }\n\n // Clean each gallery directory\n for (const dir of galleryDirs) {\n await cleanGallery(dir, ui);\n }\n\n ui.box(`Successfully cleaned ${galleryDirs.length} ${galleryDirs.length === 1 ? 'gallery' : 'galleries'}`);\n } catch (error) {\n ui.error('Error cleaning galleries');\n throw error;\n }\n}\n","import path from 'node:path';\n\nimport { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '../../../config';\n\nimport type { MediaFileType } from '../types';\n\n/**\n * Determines the media file type based on file extension\n * @param fileName - Name of the file to check\n * @returns Media file type ('image' or 'video') or null if not supported\n */\nexport function getMediaFileType(fileName: string): MediaFileType | null {\n const ext = path.extname(fileName).toLowerCase();\n if (IMAGE_EXTENSIONS.has(ext)) return 'image';\n if (VIDEO_EXTENSIONS.has(ext)) return 'video';\n return null;\n}\n\n/**\n * Converts a folder name into a properly capitalized title\n * @param folderName - The folder name to convert\n * @returns Formatted title with proper capitalization\n */\nexport function capitalizeTitle(folderName: string): string {\n return folderName\n .replace('-', ' ')\n .replace('_', ' ')\n .split(' ')\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { capitalizeTitle, getMediaFileType } from './utils';\n\nimport type { GallerySettingsFromUser, ProcessDirectoryResult, ScanDirectoryResult, ScanOptions, SubGallery } from './types';\nimport type { MediaFile } from '@simple-photo-gallery/common/src/gallery';\nimport type { ConsolaInstance } from 'consola';\n\n/**\n * Scans a directory for media files and subdirectories\n * @param dirPath - Path to the directory to scan\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to scan results with media files and subdirectories\n */\nasync function scanDirectory(dirPath: string, ui: ConsolaInstance): Promise<ScanDirectoryResult> {\n const mediaFiles: MediaFile[] = [];\n const subGalleryDirectories: string[] = [];\n\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile()) {\n const fullPath = path.join(dirPath, entry.name);\n const mediaType = getMediaFileType(entry.name);\n\n if (mediaType) {\n const mediaFile: MediaFile = {\n type: mediaType,\n path: fullPath,\n width: 0,\n height: 0,\n };\n\n mediaFiles.push(mediaFile);\n }\n } else if (entry.isDirectory() && entry.name !== 'gallery') {\n subGalleryDirectories.push(path.join(dirPath, entry.name));\n }\n }\n } catch (error) {\n if (error instanceof Error && error.message.includes('ENOENT')) {\n ui.error(`Directory does not exist: ${dirPath}`);\n } else if (error instanceof Error && error.message.includes('ENOTDIR')) {\n ui.error(`Path is not a directory: ${dirPath}`);\n } else {\n ui.error(`Error scanning directory ${dirPath}:`, error);\n }\n\n throw error;\n }\n\n return { mediaFiles, subGalleryDirectories };\n}\n\n/**\n * Prompts the user for gallery settings through interactive CLI\n * @param galleryName - Name of the gallery directory\n * @param defaultImage - Default header image path\n * @param ui - ConsolaInstance for prompting and logging\n * @returns Promise resolving to user-provided gallery settings\n */\nasync function getGallerySettingsFromUser(\n galleryName: string,\n defaultImage: string,\n ui: ConsolaInstance,\n): Promise<GallerySettingsFromUser> {\n ui.info(`Enter gallery settings for the gallery in folder \"${galleryName}\"`);\n\n const title = await ui.prompt('Enter gallery title', { type: 'text', default: 'My Gallery', placeholder: 'My Gallery' });\n const description = await ui.prompt('Enter gallery description', {\n type: 'text',\n default: 'My gallery with fantastic photos.',\n placeholder: 'My gallery with fantastic photos.',\n });\n const url = await ui.prompt('Enter the URL where the gallery will be hosted (important for social media image)', {\n type: 'text',\n default: '',\n placeholder: '',\n });\n const headerImageName = await ui.prompt('Enter the name of the header image', {\n type: 'text',\n default: defaultImage,\n placeholder: defaultImage,\n });\n\n const headerImage = path.join('..', headerImageName);\n\n return { title, description, url, headerImage };\n}\n\n/**\n * Creates a gallery.json file with media files and settings\n * @param mediaFiles - Array of media files to include in gallery\n * @param galleryJsonPath - Path where gallery.json should be created\n * @param subGalleries - Array of sub-galleries to include\n * @param useDefaultSettings - Whether to use default settings or prompt user\n * @param ui - ConsolaInstance for prompting and logging\n */\nasync function createGalleryJson(\n mediaFiles: MediaFile[],\n galleryJsonPath: string,\n subGalleries: SubGallery[] = [],\n useDefaultSettings: boolean,\n ui: ConsolaInstance,\n): Promise<void> {\n const galleryDir = path.dirname(galleryJsonPath);\n\n // Convert media file paths to be relative to gallery.json\n const relativeMediaFiles = mediaFiles.map((file) => ({\n ...file,\n path: path.relative(galleryDir, file.path),\n }));\n\n // Convert subGallery header image paths to be relative to gallery.json\n const relativeSubGalleries = subGalleries.map((subGallery) => ({\n ...subGallery,\n headerImage: subGallery.headerImage ? path.relative(galleryDir, subGallery.headerImage) : '',\n }));\n\n let galleryData = {\n title: 'My Gallery',\n description: 'My gallery with fantastic photos.',\n headerImage: relativeMediaFiles[0]?.path || '',\n metadata: {},\n sections: [\n {\n images: relativeMediaFiles,\n },\n ],\n subGalleries: {\n title: 'Sub Galleries',\n galleries: relativeSubGalleries,\n },\n };\n\n if (!useDefaultSettings) {\n galleryData = {\n ...galleryData,\n ...(await getGallerySettingsFromUser(\n path.basename(path.join(galleryDir, '..')),\n path.basename(mediaFiles[0]?.path || ''),\n ui,\n )),\n };\n }\n\n await fs.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n}\n\n/**\n * Processes a directory and its subdirectories to create galleries\n * @param scanPath - Path to scan for media files\n * @param outputPath - Path where gallery should be created\n * @param recursive - Whether to process subdirectories recursively\n * @param useDefaultSettings - Whether to use default settings or prompt user\n * @param ui - ConsolaInstance for logging\n * @returns Promise resolving to processing results\n */\nasync function processDirectory(\n scanPath: string,\n outputPath: string,\n recursive: boolean,\n useDefaultSettings: boolean,\n ui: ConsolaInstance,\n): Promise<ProcessDirectoryResult> {\n ui.start(`Scanning ${scanPath}`);\n\n let totalFiles = 0;\n let totalGalleries = 1;\n const subGalleries: SubGallery[] = [];\n\n // Scan current directory for media files\n const { mediaFiles, subGalleryDirectories } = await scanDirectory(scanPath, ui);\n totalFiles += mediaFiles.length;\n\n // Process subdirectories only if recursive mode is enabled\n if (recursive) {\n for (const subGalleryDir of subGalleryDirectories) {\n const result = await processDirectory(\n subGalleryDir,\n path.join(outputPath, path.basename(subGalleryDir)),\n recursive,\n useDefaultSettings,\n ui,\n );\n\n totalFiles += result.totalFiles;\n totalGalleries += result.totalGalleries;\n\n // If the result contains a valid subGallery, add it to the list\n if (result.subGallery) {\n subGalleries.push(result.subGallery);\n }\n }\n }\n\n // Create gallery.json if there are media files or subGalleries\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const galleryPath = path.join(outputPath, 'gallery');\n const galleryJsonPath = path.join(galleryPath, 'gallery.json');\n\n try {\n // Create output directory\n await fs.mkdir(galleryPath, { recursive: true });\n\n // Create gallery.json for this directory\n await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries, useDefaultSettings, ui);\n\n ui.success(\n `Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`,\n );\n } catch (error) {\n ui.error(`Error creating gallery.json at ${galleryJsonPath}`);\n throw error;\n }\n }\n\n // Return result with suGgallery info if this directory has media files\n const result: ProcessDirectoryResult = { totalFiles, totalGalleries };\n\n // If this directory has media files or subGalleries, create a subGallery in the result\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const dirName = path.basename(scanPath);\n result.subGallery = {\n title: capitalizeTitle(dirName),\n headerImage: mediaFiles[0]?.path || '',\n path: path.join('..', dirName),\n };\n }\n\n return result;\n}\n\n/**\n * Main init command implementation - scans directories and creates gallery.json files\n * @param options - Options specifying paths, recursion, and default settings\n * @param ui - ConsolaInstance for logging and user prompts\n */\nexport async function init(options: ScanOptions, ui: ConsolaInstance): Promise<void> {\n try {\n const scanPath = path.resolve(options.photos);\n const outputPath = options.gallery ? path.resolve(options.gallery) : scanPath;\n\n // Process the directory tree with the specified recursion setting\n const result = await processDirectory(scanPath, outputPath, options.recursive, options.default, ui);\n\n ui.box(\n `Created ${result.totalGalleries} ${result.totalGalleries === 1 ? 'gallery' : 'galleries'} with ${result.totalFiles} media ${result.totalFiles === 1 ? 'file' : 'files'}`,\n );\n } catch (error) {\n ui.error('Error initializing gallery');\n throw error;\n }\n}\n","#!/usr/bin/env node\n\nimport process from 'node:process';\n\nimport { Command } from 'commander';\nimport { createConsola, LogLevels, type ConsolaInstance } from 'consola';\n\nimport { build } from './modules/build';\nimport { clean } from './modules/clean';\nimport { init } from './modules/init';\nimport { thumbnails } from './modules/thumbnails';\n\n/** Command line interface program instance */\nconst program = new Command();\n\nprogram\n .name('gallery')\n .description('Simple Photo Gallery CLI')\n .version('0.0.1')\n .option('-v, --verbose', 'Verbose output (debug level)', false)\n .option('-q, --quiet', 'Minimal output (only warnings/errors)', false)\n .showHelpAfterError(true);\n\n/**\n * Creates a Consola UI instance with appropriate log level based on global options\n * @param globalOpts - Global command options containing verbose/quiet flags\n * @returns ConsolaInstance configured with appropriate log level and tag\n */\nfunction createConsolaUI(globalOpts: ReturnType<typeof program.opts>): ConsolaInstance {\n let level = LogLevels.info;\n\n if (globalOpts.quiet) {\n level = LogLevels.warn;\n } else if (globalOpts.verbose) {\n level = LogLevels.debug;\n }\n\n return createConsola({\n level,\n }).withTag('simple-photo-gallery');\n}\n\n/**\n * Higher-order function that wraps command handlers to provide ConsolaUI instance\n * @param handler - Command handler function that receives options and UI instance\n * @returns Wrapped handler function that creates UI and handles errors\n */\nfunction withConsolaUI<O>(handler: (opts: O, ui: ConsolaInstance) => Promise<void> | void) {\n return async (opts: O) => {\n const ui = createConsolaUI(program.opts());\n try {\n await handler(opts, ui);\n } catch (error) {\n ui.debug(error);\n\n process.exitCode = 1;\n }\n };\n}\n\nprogram\n .command('init')\n .description('Initialize a gallery by scaning a folder for images and videos')\n .option(\n '-p, --photos <path>',\n 'Path to the folder where the photos are stored. Default: current working directory',\n process.cwd(),\n )\n .option(\n '-g, --gallery <path>',\n 'Path to the directory where the gallery will be initialized. Default: same directory as the photos folder',\n )\n .option('-r, --recursive', 'Recursively create galleries from all photos subdirectories', false)\n .option('-d, --default', 'Use default gallery settings instead of asking the user', false)\n .action(withConsolaUI(init));\n\nprogram\n .command('thumbnails')\n .description('Create thumbnails for all media files in the gallery')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .action(withConsolaUI(thumbnails));\n\nprogram\n .command('build')\n .description('Build the HTML gallery in the specified directory')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .option('-b, --base-url <url>', 'Base URL where the photos are hosted')\n .action(withConsolaUI(build));\n\nprogram\n .command('clean')\n .description('Remove all gallery files and folders (index.html, gallery/)')\n .option('-g, --gallery <path>', 'Path to the directory of the gallery. Default: current working directory', process.cwd())\n .option('-r, --recursive', 'Clean subdirectories recursively', false)\n .action(withConsolaUI(clean));\n\nprogram.parse();\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-photo-gallery",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Simple Photo Gallery CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vladimir Haltakov, Tomasz Rusin",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist"
|
|
14
14
|
],
|
|
15
|
-
"bin":
|
|
15
|
+
"bin": {
|
|
16
|
+
"simple-photo-gallery": "./dist/index.js",
|
|
17
|
+
"spg": "./dist/index.js"
|
|
18
|
+
},
|
|
16
19
|
"scripts": {
|
|
17
20
|
"gallery": "tsx src/index.ts",
|
|
18
21
|
"clean": "rm -rf dist",
|
|
@@ -27,7 +30,9 @@
|
|
|
27
30
|
"prepublish": "npm run build"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@simple-photo-gallery/
|
|
33
|
+
"@simple-photo-gallery/common": "1.0.0",
|
|
34
|
+
"@simple-photo-gallery/theme-modern": "2.0.1",
|
|
35
|
+
"blurhash": "^2.0.5",
|
|
31
36
|
"commander": "^12.0.0",
|
|
32
37
|
"consola": "^3.4.2",
|
|
33
38
|
"exifreader": "^4.32.0",
|