simple-photo-gallery 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -34,8 +34,8 @@ This is a free, open-source tool that you can use to generate galleries and self
34
34
  The fastest way to create a gallery is to use `npx` in your photos folder:
35
35
 
36
36
  ```bash
37
- npx simple-photo-gallery init
38
- npx simple-photo-gallery build
37
+ npx simple-photo-gallery@latest init
38
+ npx simple-photo-gallery@latest build
39
39
  ```
40
40
 
41
41
  This will:
package/dist/index.js CHANGED
@@ -3,40 +3,21 @@ 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 fs4, { promises } from 'fs';
6
+ import fs6, { promises } from 'fs';
7
7
  import path4 from 'path';
8
+ import { z } from 'zod';
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
 
14
- path4.dirname(new URL(import.meta.url).pathname);
15
- async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
16
- ui.start(`Creating social media card image`);
17
- const resizedImageBuffer = await sharp(headerPhotoPath).resize(1200, 631, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
18
- const outputPath = ouputPath;
19
- await sharp(resizedImageBuffer).toFile(outputPath);
20
- const svgText = `
21
- <svg width="1200" height="631" xmlns="http://www.w3.org/2000/svg">
22
- <defs>
23
- <style>
24
- .title { font-family: Arial, sans-serif; font-size: 96px; font-weight: bold; fill: white; text-anchor: middle; }
25
- .description { font-family: Arial, sans-serif; font-size: 48px; fill: white; text-anchor: middle; }
26
- </style>
27
- </defs>
28
- <text x="600" y="250" class="title">${title}</text>
29
- </svg>
30
- `;
31
- const finalImageBuffer = await sharp(resizedImageBuffer).composite([{ input: Buffer.from(svgText), top: 0, left: 0 }]).jpeg({ quality: 90 }).toBuffer();
32
- await sharp(finalImageBuffer).toFile(outputPath);
33
- ui.success(`Created social media card image successfully`);
34
- }
35
15
  var ThumbnailSchema = z.object({
36
16
  path: z.string(),
37
17
  pathRetina: z.string(),
38
18
  width: z.number(),
39
- height: z.number()
19
+ height: z.number(),
20
+ blurHash: z.string().optional()
40
21
  });
41
22
  var MediaFileSchema = z.object({
42
23
  type: z.enum(["image", "video"]),
@@ -57,41 +38,178 @@ var SubGallerySchema = z.object({
57
38
  headerImage: z.string(),
58
39
  path: z.string()
59
40
  });
41
+ var GalleryMetadataSchema = z.object({
42
+ image: z.string().optional(),
43
+ imageWidth: z.number().optional(),
44
+ imageHeight: z.number().optional(),
45
+ ogUrl: z.string().optional(),
46
+ ogType: z.string().optional(),
47
+ ogSiteName: z.string().optional(),
48
+ twitterSite: z.string().optional(),
49
+ twitterCreator: z.string().optional(),
50
+ author: z.string().optional(),
51
+ keywords: z.string().optional(),
52
+ canonicalUrl: z.string().optional(),
53
+ language: z.string().optional(),
54
+ robots: z.string().optional()
55
+ });
60
56
  var GalleryDataSchema = z.object({
61
57
  title: z.string(),
62
58
  description: z.string(),
63
59
  url: z.string().optional(),
64
60
  headerImage: z.string(),
65
61
  thumbnailSize: z.number().optional(),
66
- metadata: z.object({
67
- image: z.string().optional(),
68
- imageWidth: z.number().optional(),
69
- imageHeight: z.number().optional(),
70
- ogUrl: z.string().optional(),
71
- ogType: z.string().optional(),
72
- ogSiteName: z.string().optional(),
73
- twitterSite: z.string().optional(),
74
- twitterCreator: z.string().optional(),
75
- author: z.string().optional(),
76
- keywords: z.string().optional(),
77
- canonicalUrl: z.string().optional(),
78
- language: z.string().optional(),
79
- robots: z.string().optional()
80
- }),
62
+ metadata: GalleryMetadataSchema,
81
63
  galleryOutputPath: z.string().optional(),
82
64
  mediaBaseUrl: z.string().optional(),
83
65
  sections: z.array(GallerySectionSchema),
84
66
  subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
85
67
  });
68
+
69
+ // src/config/index.ts
70
+ var DEFAULT_THUMBNAIL_SIZE = 300;
71
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".tif", ".svg", ".avif"]);
72
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
73
+ var HEADER_IMAGE_LANDSCAPE_WIDTHS = [3840, 2560, 1920, 1280, 960, 640];
74
+ var HEADER_IMAGE_PORTRAIT_WIDTHS = [1080, 720, 480, 360];
75
+ async function resizeImage(image, outputPath, width, height, format = "avif") {
76
+ await image.resize(width, height, { withoutEnlargement: true }).toFormat(format).toFile(outputPath);
77
+ }
78
+ async function cropAndResizeImage(image, outputPath, width, height, format = "avif") {
79
+ await image.resize(width, height, {
80
+ fit: "cover",
81
+ withoutEnlargement: true
82
+ }).toFormat(format).toFile(outputPath);
83
+ }
84
+ async function getImageDescription(imagePath) {
85
+ try {
86
+ const tags = await ExifReader.load(imagePath);
87
+ if (tags.description?.description) return tags.description.description;
88
+ if (tags.ImageDescription?.description) return tags.ImageDescription.description;
89
+ if (tags.UserComment && typeof tags.UserComment === "object" && tags.UserComment !== null && "description" in tags.UserComment) {
90
+ return tags.UserComment.description;
91
+ }
92
+ if (tags.ExtDescrAccessibility?.description) return tags.ExtDescrAccessibility.description;
93
+ if (tags["Caption/Abstract"]?.description) return tags["Caption/Abstract"].description;
94
+ if (tags.XPTitle?.description) return tags.XPTitle.description;
95
+ if (tags.XPComment?.description) return tags.XPComment.description;
96
+ } catch {
97
+ return void 0;
98
+ }
99
+ }
100
+ async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, size) {
101
+ const originalWidth = metadata.width || 0;
102
+ const originalHeight = metadata.height || 0;
103
+ if (originalWidth === 0 || originalHeight === 0) {
104
+ throw new Error("Invalid image dimensions");
105
+ }
106
+ const aspectRatio = originalWidth / originalHeight;
107
+ let width;
108
+ let height;
109
+ if (originalWidth > originalHeight) {
110
+ width = size;
111
+ height = Math.round(size / aspectRatio);
112
+ } else {
113
+ width = Math.round(size * aspectRatio);
114
+ height = size;
115
+ }
116
+ await resizeImage(image, outputPath, width, height);
117
+ await resizeImage(image, outputPathRetina, width * 2, height * 2);
118
+ return { width, height };
119
+ }
120
+
121
+ // src/modules/build/utils/index.ts
122
+ path4.dirname(new URL(import.meta.url).pathname);
123
+ async function createGallerySocialMediaCardImage(headerPhotoPath, title, ouputPath, ui) {
124
+ ui.start(`Creating social media card image`);
125
+ if (fs6.existsSync(ouputPath)) {
126
+ ui.success(`Social media card image already exists`);
127
+ return;
128
+ }
129
+ const resizedImageBuffer = await sharp(headerPhotoPath).resize(1200, 631, { fit: "cover" }).jpeg({ quality: 90 }).toBuffer();
130
+ const outputPath = ouputPath;
131
+ await sharp(resizedImageBuffer).toFile(outputPath);
132
+ const svgText = `
133
+ <svg width="1200" height="631" xmlns="http://www.w3.org/2000/svg">
134
+ <defs>
135
+ <style>
136
+ .title { font-family: Arial, sans-serif; font-size: 96px; font-weight: bold; fill: white; text-anchor: middle; }
137
+ .description { font-family: Arial, sans-serif; font-size: 48px; fill: white; text-anchor: middle; }
138
+ </style>
139
+ </defs>
140
+ <text x="600" y="250" class="title">${title}</text>
141
+ </svg>
142
+ `;
143
+ const finalImageBuffer = await sharp(resizedImageBuffer).composite([{ input: Buffer.from(svgText), top: 0, left: 0 }]).jpeg({ quality: 90 }).toBuffer();
144
+ await sharp(finalImageBuffer).toFile(outputPath);
145
+ ui.success(`Created social media card image successfully`);
146
+ }
147
+ async function createOptimizedHeaderImage(headerPhotoPath, outputFolder, ui) {
148
+ ui.start(`Creating optimized header images`);
149
+ const image = sharp(headerPhotoPath);
150
+ const landscapeYFactor = 3 / 4;
151
+ for (const width of HEADER_IMAGE_LANDSCAPE_WIDTHS) {
152
+ ui.debug(`Creating landscape header image ${width}`);
153
+ if (fs6.existsSync(path4.join(outputFolder, `header_landscape_${width}.avif`))) {
154
+ ui.debug(`Landscape header image ${width} AVIF already exists`);
155
+ } else {
156
+ await cropAndResizeImage(
157
+ image.clone(),
158
+ path4.join(outputFolder, `header_landscape_${width}.avif`),
159
+ width,
160
+ width * landscapeYFactor,
161
+ "avif"
162
+ );
163
+ }
164
+ if (fs6.existsSync(path4.join(outputFolder, `header_landscape_${width}.jpg`))) {
165
+ ui.debug(`Landscape header image ${width} JPG already exists`);
166
+ } else {
167
+ await cropAndResizeImage(
168
+ image.clone(),
169
+ path4.join(outputFolder, `header_landscape_${width}.jpg`),
170
+ width,
171
+ width * landscapeYFactor,
172
+ "jpg"
173
+ );
174
+ }
175
+ }
176
+ const portraitYFactor = 4 / 3;
177
+ for (const width of HEADER_IMAGE_PORTRAIT_WIDTHS) {
178
+ ui.debug(`Creating portrait header image ${width}`);
179
+ if (fs6.existsSync(path4.join(outputFolder, `header_portrait_${width}.avif`))) {
180
+ ui.debug(`Portrait header image ${width} AVIF already exists`);
181
+ } else {
182
+ await cropAndResizeImage(
183
+ image.clone(),
184
+ path4.join(outputFolder, `header_portrait_${width}.avif`),
185
+ width,
186
+ width * portraitYFactor,
187
+ "avif"
188
+ );
189
+ }
190
+ if (fs6.existsSync(path4.join(outputFolder, `header_portrait_${width}.jpg`))) {
191
+ ui.debug(`Portrait header image ${width} JPG already exists`);
192
+ } else {
193
+ await cropAndResizeImage(
194
+ image.clone(),
195
+ path4.join(outputFolder, `header_portrait_${width}.jpg`),
196
+ width,
197
+ width * portraitYFactor,
198
+ "jpg"
199
+ );
200
+ }
201
+ }
202
+ ui.success(`Created optimized header image successfully`);
203
+ }
86
204
  function findGalleries(basePath, recursive) {
87
205
  const galleryDirs = [];
88
206
  const galleryJsonPath = path4.join(basePath, "gallery", "gallery.json");
89
- if (fs4.existsSync(galleryJsonPath)) {
207
+ if (fs6.existsSync(galleryJsonPath)) {
90
208
  galleryDirs.push(basePath);
91
209
  }
92
210
  if (recursive) {
93
211
  try {
94
- const entries = fs4.readdirSync(basePath, { withFileTypes: true });
212
+ const entries = fs6.readdirSync(basePath, { withFileTypes: true });
95
213
  for (const entry of entries) {
96
214
  if (entry.isDirectory() && entry.name !== "gallery") {
97
215
  const subPath = path4.join(basePath, entry.name);
@@ -120,36 +238,11 @@ async function getFileMtime(filePath) {
120
238
  const stats = await promises.stat(filePath);
121
239
  return stats.mtime;
122
240
  }
123
- async function resizeAndSaveThumbnail(image, outputPath, width, height) {
124
- await image.resize(width, height, { withoutEnlargement: true }).jpeg({ quality: 90 }).toFile(outputPath);
125
- }
126
- async function getImageDescription(imagePath) {
127
- try {
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 };
241
+ async function generateBlurHash(imagePath, componentX = 4, componentY = 3) {
242
+ const image = sharp(imagePath);
243
+ const { data, info } = await image.resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
244
+ const pixels = new Uint8ClampedArray(data.buffer);
245
+ return encode(pixels, info.width, info.height, componentX, componentY);
153
246
  }
154
247
  async function getVideoDimensions(filePath) {
155
248
  const data = await ffprobe(filePath);
@@ -188,8 +281,8 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
188
281
  if (code === 0) {
189
282
  try {
190
283
  const frameImage = sharp(tempFramePath);
191
- await resizeAndSaveThumbnail(frameImage, outputPath, width, height);
192
- await resizeAndSaveThumbnail(frameImage, outputPathRetina, width * 2, height * 2);
284
+ await resizeImage(frameImage, outputPath, width, height);
285
+ await resizeImage(frameImage, outputPathRetina, width * 2, height * 2);
193
286
  try {
194
287
  await promises.unlink(tempFramePath);
195
288
  } catch {
@@ -208,15 +301,10 @@ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, out
208
301
  });
209
302
  }
210
303
 
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
304
  // src/modules/thumbnails/index.ts
217
305
  async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
218
306
  const fileMtime = await getFileMtime(imagePath);
219
- if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs4.existsSync(thumbnailPath)) {
307
+ if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs6.existsSync(thumbnailPath)) {
220
308
  return void 0;
221
309
  }
222
310
  const image = sharp(imagePath);
@@ -236,6 +324,7 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
236
324
  thumbnailPathRetina,
237
325
  thumbnailSize
238
326
  );
327
+ const blurHash = await generateBlurHash(thumbnailPath);
239
328
  return {
240
329
  type: "image",
241
330
  path: imagePath,
@@ -246,14 +335,15 @@ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumb
246
335
  path: thumbnailPath,
247
336
  pathRetina: thumbnailPathRetina,
248
337
  width: thumbnailDimensions.width,
249
- height: thumbnailDimensions.height
338
+ height: thumbnailDimensions.height,
339
+ blurHash
250
340
  },
251
341
  lastMediaTimestamp: fileMtime.toISOString()
252
342
  };
253
343
  }
254
344
  async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
255
345
  const fileMtime = await getFileMtime(videoPath);
256
- if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs4.existsSync(thumbnailPath)) {
346
+ if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs6.existsSync(thumbnailPath)) {
257
347
  return void 0;
258
348
  }
259
349
  const videoDimensions = await getVideoDimensions(videoPath);
@@ -265,6 +355,7 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
265
355
  thumbnailSize,
266
356
  verbose
267
357
  );
358
+ const blurHash = await generateBlurHash(thumbnailPath);
268
359
  return {
269
360
  type: "video",
270
361
  path: videoPath,
@@ -275,7 +366,8 @@ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumb
275
366
  path: thumbnailPath,
276
367
  pathRetina: thumbnailPathRetina,
277
368
  width: thumbnailDimensions.width,
278
- height: thumbnailDimensions.height
369
+ height: thumbnailDimensions.height,
370
+ blurHash
279
371
  },
280
372
  lastMediaTimestamp: fileMtime.toISOString()
281
373
  };
@@ -286,9 +378,9 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
286
378
  const filePath = path4.resolve(path4.join(galleryJsonDir, mediaFile.path));
287
379
  const fileName = path4.basename(filePath);
288
380
  const fileNameWithoutExt = path4.parse(fileName).name;
289
- const thumbnailFileName = `${fileNameWithoutExt}.jpg`;
381
+ const thumbnailFileName = `${fileNameWithoutExt}.avif`;
290
382
  const thumbnailPath = path4.join(thumbnailsPath, thumbnailFileName);
291
- const thumbnailPathRetina = thumbnailPath.replace(".jpg", "@2x.jpg");
383
+ const thumbnailPathRetina = thumbnailPath.replace(".avif", "@2x.avif");
292
384
  const relativeThumbnailPath = path4.relative(galleryJsonDir, thumbnailPath);
293
385
  const relativeThumbnailRetinaPath = path4.relative(galleryJsonDir, thumbnailPathRetina);
294
386
  const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
@@ -297,6 +389,20 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
297
389
  const updatedMediaFile = await (mediaFile.type === "image" ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));
298
390
  if (!updatedMediaFile) {
299
391
  ui.debug(` Skipping ${fileName} because it has already been processed`);
392
+ if (mediaFile.thumbnail && !mediaFile.thumbnail.blurHash && fs6.existsSync(thumbnailPath)) {
393
+ try {
394
+ const blurHash = await generateBlurHash(thumbnailPath);
395
+ return {
396
+ ...mediaFile,
397
+ thumbnail: {
398
+ ...mediaFile.thumbnail,
399
+ blurHash
400
+ }
401
+ };
402
+ } catch (error) {
403
+ ui.debug(` Failed to generate BlurHash for ${fileName}:`, error);
404
+ }
405
+ }
300
406
  return mediaFile;
301
407
  }
302
408
  updatedMediaFile.path = mediaFile.path;
@@ -312,11 +418,11 @@ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnail
312
418
  }
313
419
  async function processGalleryThumbnails(galleryDir, ui) {
314
420
  const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
315
- const thumbnailsPath = path4.join(galleryDir, "gallery", "thumbnails");
421
+ const thumbnailsPath = path4.join(galleryDir, "gallery", "images");
316
422
  ui.start(`Creating thumbnails: ${galleryDir}`);
317
423
  try {
318
- fs4.mkdirSync(thumbnailsPath, { recursive: true });
319
- const galleryContent = fs4.readFileSync(galleryJsonPath, "utf8");
424
+ fs6.mkdirSync(thumbnailsPath, { recursive: true });
425
+ const galleryContent = fs6.readFileSync(galleryJsonPath, "utf8");
320
426
  const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
321
427
  const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;
322
428
  let processedCount = 0;
@@ -326,7 +432,7 @@ async function processGalleryThumbnails(galleryDir, ui) {
326
432
  }
327
433
  processedCount += section.images.length;
328
434
  }
329
- fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
435
+ fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
330
436
  ui.success(`Created thumbnails for ${processedCount} media files`);
331
437
  return processedCount;
332
438
  } catch (error) {
@@ -373,7 +479,7 @@ function copyPhotos(galleryData, galleryDir, ui) {
373
479
  const fileName = path4.basename(image.path);
374
480
  const destPath = path4.join(galleryDir, fileName);
375
481
  ui.debug(`Copying photo to ${destPath}`);
376
- fs4.copyFileSync(sourcePath, destPath);
482
+ fs6.copyFileSync(sourcePath, destPath);
377
483
  }
378
484
  }
379
485
  }
@@ -382,17 +488,14 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
382
488
  ui.start(`Building gallery ${galleryDir}`);
383
489
  await processGalleryThumbnails(galleryDir, ui);
384
490
  const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
385
- const galleryContent = fs4.readFileSync(galleryJsonPath, "utf8");
491
+ const galleryContent = fs6.readFileSync(galleryJsonPath, "utf8");
386
492
  const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
387
- const socialMediaCardImagePath = path4.join(galleryDir, "gallery", "thumbnails", "social-media-card.jpg");
388
- await createGallerySocialMediaCardImage(
389
- path4.resolve(path4.join(galleryDir, "gallery"), galleryData.headerImage),
390
- galleryData.title,
391
- socialMediaCardImagePath,
392
- ui
393
- );
493
+ const socialMediaCardImagePath = path4.join(galleryDir, "gallery", "images", "social-media-card.jpg");
494
+ const headerImagePath = path4.resolve(path4.join(galleryDir, "gallery"), galleryData.headerImage);
495
+ await createGallerySocialMediaCardImage(headerImagePath, galleryData.title, socialMediaCardImagePath, ui);
394
496
  galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${path4.relative(galleryDir, socialMediaCardImagePath)}`;
395
- fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
497
+ fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
498
+ await createOptimizedHeaderImage(headerImagePath, path4.join(galleryDir, "gallery", "images"), ui);
396
499
  if (!baseUrl) {
397
500
  const shouldCopyPhotos = galleryData.sections.some(
398
501
  (section) => section.images.some((image) => !checkFileIsOneFolderUp(image.path))
@@ -405,7 +508,7 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
405
508
  if (baseUrl) {
406
509
  ui.debug("Updating gallery.json with baseUrl");
407
510
  galleryData.mediaBaseUrl = baseUrl;
408
- fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
511
+ fs6.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
409
512
  }
410
513
  ui.debug("Building gallery form template");
411
514
  try {
@@ -419,12 +522,12 @@ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
419
522
  const outputDir = path4.join(galleryDir, "gallery");
420
523
  const buildDir = path4.join(outputDir, "_build");
421
524
  ui.debug(`Copying build output to ${outputDir}`);
422
- fs4.cpSync(buildDir, outputDir, { recursive: true });
525
+ fs6.cpSync(buildDir, outputDir, { recursive: true });
423
526
  ui.debug("Moving index.html to gallery directory");
424
- fs4.copyFileSync(path4.join(outputDir, "index.html"), path4.join(galleryDir, "index.html"));
425
- fs4.rmSync(path4.join(outputDir, "index.html"));
527
+ fs6.copyFileSync(path4.join(outputDir, "index.html"), path4.join(galleryDir, "index.html"));
528
+ fs6.rmSync(path4.join(outputDir, "index.html"));
426
529
  ui.debug("Cleaning up build directory");
427
- fs4.rmSync(buildDir, { recursive: true, force: true });
530
+ fs6.rmSync(buildDir, { recursive: true, force: true });
428
531
  ui.success(`Gallery built successfully`);
429
532
  }
430
533
  async function build(options, ui) {
@@ -455,9 +558,9 @@ async function build(options, ui) {
455
558
  async function cleanGallery(galleryDir, ui) {
456
559
  let filesRemoved = 0;
457
560
  const indexHtmlPath = path4.join(galleryDir, "index.html");
458
- if (fs4.existsSync(indexHtmlPath)) {
561
+ if (fs6.existsSync(indexHtmlPath)) {
459
562
  try {
460
- fs4.rmSync(indexHtmlPath);
563
+ fs6.rmSync(indexHtmlPath);
461
564
  ui.debug(`Removed: ${indexHtmlPath}`);
462
565
  filesRemoved++;
463
566
  } catch (error) {
@@ -465,9 +568,9 @@ async function cleanGallery(galleryDir, ui) {
465
568
  }
466
569
  }
467
570
  const galleryPath = path4.join(galleryDir, "gallery");
468
- if (fs4.existsSync(galleryPath)) {
571
+ if (fs6.existsSync(galleryPath)) {
469
572
  try {
470
- fs4.rmSync(galleryPath, { recursive: true, force: true });
573
+ fs6.rmSync(galleryPath, { recursive: true, force: true });
471
574
  ui.debug(`Removed directory: ${galleryPath}`);
472
575
  filesRemoved++;
473
576
  } catch (error) {
@@ -483,7 +586,7 @@ async function cleanGallery(galleryDir, ui) {
483
586
  async function clean(options, ui) {
484
587
  try {
485
588
  const basePath = path4.resolve(options.gallery);
486
- if (!fs4.existsSync(basePath)) {
589
+ if (!fs6.existsSync(basePath)) {
487
590
  ui.error(`Directory does not exist: ${basePath}`);
488
591
  return;
489
592
  }
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":["../../common/src/gallery.ts","../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","process","LogLevels","result"],"mappings":"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","file":"index.js","sourcesContent":["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 blurHash: z.string().optional(),\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 GalleryMetadataSchema = 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\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: GalleryMetadataSchema,\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 gallery metadata */\nexport type GalleryMetadata = z.infer<typeof GalleryMetadataSchema>;\n\n/** TypeScript type for complete gallery data structure */\nexport type GalleryData = z.infer<typeof GalleryDataSchema>;\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\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.2",
3
+ "version": "2.0.4",
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": "./dist/index.js",
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,8 @@
27
30
  "prepublish": "npm run build"
28
31
  },
29
32
  "dependencies": {
30
- "@simple-photo-gallery/theme-modern": "2.0.0",
33
+ "@simple-photo-gallery/theme-modern": "2.0.1",
34
+ "blurhash": "^2.0.5",
31
35
  "commander": "^12.0.0",
32
36
  "consola": "^3.4.2",
33
37
  "exifreader": "^4.32.0",
@@ -38,6 +42,7 @@
38
42
  "devDependencies": {
39
43
  "@eslint/eslintrc": "^3.3.1",
40
44
  "@eslint/js": "^9.30.1",
45
+ "@simple-photo-gallery/common": "1.0.0",
41
46
  "@types/fs-extra": "^11.0.4",
42
47
  "@types/jest": "^30.0.0",
43
48
  "@types/node": "^24.0.10",