simple-photo-gallery 0.0.8 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,23 +1,97 @@
1
1
  #!/usr/bin/env node
2
- import process from 'process';
2
+ import process2 from 'process';
3
3
  import { Command } from 'commander';
4
+ import { LogLevels, createConsola } from 'consola';
4
5
  import { execSync, spawn } from 'child_process';
5
- import fs2, { promises } from 'fs';
6
+ import fs4, { promises } from 'fs';
6
7
  import path4 from 'path';
8
+ import { Buffer } from 'buffer';
9
+ import sharp from 'sharp';
10
+ import { z } from 'zod';
7
11
  import exifReader from 'exif-reader';
8
12
  import ffprobe from 'node-ffprobe';
9
- import sharp2 from 'sharp';
10
- import { z } from 'zod';
11
13
 
12
- var findGalleries = (basePath, recursive) => {
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
+ var ThumbnailSchema = z.object({
36
+ path: z.string(),
37
+ pathRetina: z.string(),
38
+ width: z.number(),
39
+ height: z.number()
40
+ });
41
+ var MediaFileSchema = z.object({
42
+ type: z.enum(["image", "video"]),
43
+ path: z.string(),
44
+ alt: z.string().optional(),
45
+ width: z.number(),
46
+ height: z.number(),
47
+ thumbnail: ThumbnailSchema.optional(),
48
+ lastMediaTimestamp: z.string().optional()
49
+ });
50
+ var GallerySectionSchema = z.object({
51
+ title: z.string().optional(),
52
+ description: z.string().optional(),
53
+ images: z.array(MediaFileSchema)
54
+ });
55
+ var SubGallerySchema = z.object({
56
+ title: z.string(),
57
+ headerImage: z.string(),
58
+ path: z.string()
59
+ });
60
+ var GalleryDataSchema = z.object({
61
+ title: z.string(),
62
+ description: z.string(),
63
+ url: z.string().optional(),
64
+ headerImage: z.string(),
65
+ 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
+ }),
81
+ galleryOutputPath: z.string().optional(),
82
+ mediaBaseUrl: z.string().optional(),
83
+ sections: z.array(GallerySectionSchema),
84
+ subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
85
+ });
86
+ function findGalleries(basePath, recursive) {
13
87
  const galleryDirs = [];
14
88
  const galleryJsonPath = path4.join(basePath, "gallery", "gallery.json");
15
- if (fs2.existsSync(galleryJsonPath)) {
89
+ if (fs4.existsSync(galleryJsonPath)) {
16
90
  galleryDirs.push(basePath);
17
91
  }
18
92
  if (recursive) {
19
93
  try {
20
- const entries = fs2.readdirSync(basePath, { withFileTypes: true });
94
+ const entries = fs4.readdirSync(basePath, { withFileTypes: true });
21
95
  for (const entry of entries) {
22
96
  if (entry.isDirectory() && entry.name !== "gallery") {
23
97
  const subPath = path4.join(basePath, entry.name);
@@ -29,95 +103,408 @@ var findGalleries = (basePath, recursive) => {
29
103
  }
30
104
  }
31
105
  return galleryDirs;
32
- };
106
+ }
107
+ function handleFileProcessingError(error, filename, ui) {
108
+ if (error instanceof Error && (error.message.includes("ffprobe") || error.message.includes("ffmpeg"))) {
109
+ ui.warn(
110
+ `Error processing ${filename}: ffprobe (part of ffmpeg) is required to process videos. Please install ffmpeg and ensure it is available in your PATH`
111
+ );
112
+ } else if (error instanceof Error && error.message.includes("unsupported image format")) {
113
+ ui.warn(`Error processing ${filename}: unsupported image format`);
114
+ } else {
115
+ ui.warn(`Error processing ${filename}`);
116
+ }
117
+ ui.debug(error);
118
+ }
119
+ async function getFileMtime(filePath) {
120
+ const stats = await promises.stat(filePath);
121
+ return stats.mtime;
122
+ }
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(metadata) {
127
+ let description;
128
+ if (metadata.exif) {
129
+ try {
130
+ const exifData = exifReader(metadata.exif);
131
+ if (exifData.Image?.ImageDescription) {
132
+ description = exifData.Image.ImageDescription.toString();
133
+ } else if (exifData.Image?.Description) {
134
+ description = exifData.Image.Description.toString();
135
+ }
136
+ } catch {
137
+ }
138
+ }
139
+ return description;
140
+ }
141
+ async function createImageThumbnails(image, metadata, outputPath, outputPathRetina, height) {
142
+ const originalWidth = metadata.width || 0;
143
+ const originalHeight = metadata.height || 0;
144
+ if (originalWidth === 0 || originalHeight === 0) {
145
+ throw new Error("Invalid image dimensions");
146
+ }
147
+ const aspectRatio = originalWidth / originalHeight;
148
+ const width = Math.round(height * aspectRatio);
149
+ await resizeAndSaveThumbnail(image, outputPath, width, height);
150
+ await resizeAndSaveThumbnail(image, outputPathRetina, width * 2, height * 2);
151
+ return { width, height };
152
+ }
153
+ async function getVideoDimensions(filePath) {
154
+ const data = await ffprobe(filePath);
155
+ const videoStream = data.streams.find((stream) => stream.codec_type === "video");
156
+ if (!videoStream) {
157
+ throw new Error("No video stream found");
158
+ }
159
+ const dimensions = {
160
+ width: videoStream.width || 0,
161
+ height: videoStream.height || 0
162
+ };
163
+ if (dimensions.width === 0 || dimensions.height === 0) {
164
+ throw new Error("Invalid video dimensions");
165
+ }
166
+ return dimensions;
167
+ }
168
+ async function createVideoThumbnails(inputPath, videoDimensions, outputPath, outputPathRetina, height, verbose = false) {
169
+ const aspectRatio = videoDimensions.width / videoDimensions.height;
170
+ const width = Math.round(height * aspectRatio);
171
+ const tempFramePath = `${outputPath}.temp.png`;
172
+ return new Promise((resolve, reject) => {
173
+ const ffmpeg = spawn("ffmpeg", [
174
+ "-i",
175
+ inputPath,
176
+ "-vframes",
177
+ "1",
178
+ "-y",
179
+ "-loglevel",
180
+ verbose ? "error" : "quiet",
181
+ tempFramePath
182
+ ]);
183
+ ffmpeg.stderr.on("data", (data) => {
184
+ console.log(`ffmpeg: ${data.toString()}`);
185
+ });
186
+ ffmpeg.on("close", async (code) => {
187
+ if (code === 0) {
188
+ try {
189
+ const frameImage = sharp(tempFramePath);
190
+ await resizeAndSaveThumbnail(frameImage, outputPath, width, height);
191
+ await resizeAndSaveThumbnail(frameImage, outputPathRetina, width * 2, height * 2);
192
+ try {
193
+ await promises.unlink(tempFramePath);
194
+ } catch {
195
+ }
196
+ resolve({ width, height });
197
+ } catch (sharpError) {
198
+ reject(new Error(`Failed to process extracted frame: ${sharpError}`));
199
+ }
200
+ } else {
201
+ reject(new Error(`ffmpeg exited with code ${code}`));
202
+ }
203
+ });
204
+ ffmpeg.on("error", (error) => {
205
+ reject(new Error(`Failed to start ffmpeg: ${error.message}`));
206
+ });
207
+ });
208
+ }
33
209
 
34
- // src/modules/build/index.ts
35
- function buildGallery(galleryDir, templateDir) {
36
- const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
37
- if (!fs2.existsSync(galleryJsonPath)) {
38
- console.log(`No gallery/gallery.json found in ${galleryDir}`);
39
- return;
210
+ // src/config/index.ts
211
+ var DEFAULT_THUMBNAIL_SIZE = 300;
212
+
213
+ // src/modules/thumbnails/index.ts
214
+ async function processImage(imagePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) {
215
+ const fileMtime = await getFileMtime(imagePath);
216
+ if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs4.existsSync(thumbnailPath)) {
217
+ return void 0;
218
+ }
219
+ const image = sharp(imagePath);
220
+ const metadata = await image.metadata();
221
+ const imageDimensions = {
222
+ width: metadata.width || 0,
223
+ height: metadata.height || 0
224
+ };
225
+ if (imageDimensions.width === 0 || imageDimensions.height === 0) {
226
+ throw new Error("Invalid image dimensions");
40
227
  }
41
- const originalEnv = { ...process.env };
228
+ const description = await getImageDescription(metadata);
229
+ const thumbnailDimensions = await createImageThumbnails(
230
+ image,
231
+ metadata,
232
+ thumbnailPath,
233
+ thumbnailPathRetina,
234
+ thumbnailSize
235
+ );
236
+ return {
237
+ type: "image",
238
+ path: imagePath,
239
+ alt: description,
240
+ width: imageDimensions.width,
241
+ height: imageDimensions.height,
242
+ thumbnail: {
243
+ path: thumbnailPath,
244
+ pathRetina: thumbnailPathRetina,
245
+ width: thumbnailDimensions.width,
246
+ height: thumbnailDimensions.height
247
+ },
248
+ lastMediaTimestamp: fileMtime.toISOString()
249
+ };
250
+ }
251
+ async function processVideo(videoPath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp) {
252
+ const fileMtime = await getFileMtime(videoPath);
253
+ if (lastMediaTimestamp && fileMtime <= lastMediaTimestamp && fs4.existsSync(thumbnailPath)) {
254
+ return void 0;
255
+ }
256
+ const videoDimensions = await getVideoDimensions(videoPath);
257
+ const thumbnailDimensions = await createVideoThumbnails(
258
+ videoPath,
259
+ videoDimensions,
260
+ thumbnailPath,
261
+ thumbnailPathRetina,
262
+ thumbnailSize,
263
+ verbose
264
+ );
265
+ return {
266
+ type: "video",
267
+ path: videoPath,
268
+ alt: void 0,
269
+ width: videoDimensions.width,
270
+ height: videoDimensions.height,
271
+ thumbnail: {
272
+ path: thumbnailPath,
273
+ pathRetina: thumbnailPathRetina,
274
+ width: thumbnailDimensions.width,
275
+ height: thumbnailDimensions.height
276
+ },
277
+ lastMediaTimestamp: fileMtime.toISOString()
278
+ };
279
+ }
280
+ async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnailSize, ui) {
42
281
  try {
43
- process.env.GALLERY_JSON_PATH = galleryJsonPath;
44
- process.env.GALLERY_OUTPUT_DIR = path4.join(galleryDir, "gallery");
45
- execSync("npx astro build", { cwd: templateDir, stdio: "inherit" });
282
+ const galleryJsonDir = path4.join(galleryDir, "gallery");
283
+ const filePath = path4.resolve(path4.join(galleryJsonDir, mediaFile.path));
284
+ const fileName = path4.basename(filePath);
285
+ const fileNameWithoutExt = path4.parse(fileName).name;
286
+ const thumbnailFileName = `${fileNameWithoutExt}.jpg`;
287
+ const thumbnailPath = path4.join(thumbnailsPath, thumbnailFileName);
288
+ const thumbnailPathRetina = thumbnailPath.replace(".jpg", "@2x.jpg");
289
+ const relativeThumbnailPath = path4.relative(galleryJsonDir, thumbnailPath);
290
+ const relativeThumbnailRetinaPath = path4.relative(galleryJsonDir, thumbnailPathRetina);
291
+ const lastMediaTimestamp = mediaFile.lastMediaTimestamp ? new Date(mediaFile.lastMediaTimestamp) : void 0;
292
+ const verbose = ui.level === LogLevels.debug;
293
+ ui.debug(` Processing ${mediaFile.type}: ${fileName}`);
294
+ const updatedMediaFile = await (mediaFile.type === "image" ? processImage(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, lastMediaTimestamp) : processVideo(filePath, thumbnailPath, thumbnailPathRetina, thumbnailSize, verbose, lastMediaTimestamp));
295
+ if (!updatedMediaFile) {
296
+ ui.debug(` Skipping ${fileName} because it has already been processed`);
297
+ return mediaFile;
298
+ }
299
+ updatedMediaFile.path = mediaFile.path;
300
+ if (updatedMediaFile.thumbnail) {
301
+ updatedMediaFile.thumbnail.path = relativeThumbnailPath;
302
+ updatedMediaFile.thumbnail.pathRetina = relativeThumbnailRetinaPath;
303
+ }
304
+ return updatedMediaFile;
46
305
  } catch (error) {
47
- console.error(error);
48
- console.error(`Build failed for ${galleryDir}`);
49
- return;
50
- } finally {
51
- process.env = originalEnv;
306
+ handleFileProcessingError(error, path4.basename(mediaFile.path), ui);
307
+ return mediaFile;
52
308
  }
53
- const outputDir = path4.join(galleryDir, "gallery");
54
- const buildDir = path4.join(outputDir, "_build");
55
- fs2.cpSync(buildDir, outputDir, { recursive: true });
56
- fs2.copyFileSync(path4.join(outputDir, "index.html"), path4.join(galleryDir, "index.html"));
57
- fs2.rmSync(path4.join(outputDir, "index.html"));
58
- console.log("Cleaning up build directory...");
59
- fs2.rmSync(buildDir, { recursive: true, force: true });
60
309
  }
61
- async function build(options) {
62
- const themePath = await import.meta.resolve("@simple-photo-gallery/theme-modern/package.json");
63
- const themeDir = path4.dirname(new URL(themePath).pathname);
64
- const galleryDirs = findGalleries(options.path, options.recursive);
65
- if (galleryDirs.length === 0) {
66
- console.log("No gallery/gallery.json files found.");
67
- return;
310
+ async function processGalleryThumbnails(galleryDir, ui) {
311
+ const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
312
+ const thumbnailsPath = path4.join(galleryDir, "gallery", "thumbnails");
313
+ ui.start(`Creating thumbnails: ${galleryDir}`);
314
+ try {
315
+ fs4.mkdirSync(thumbnailsPath, { recursive: true });
316
+ const galleryContent = fs4.readFileSync(galleryJsonPath, "utf8");
317
+ const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
318
+ const thumbnailSize = galleryData.thumbnailSize || DEFAULT_THUMBNAIL_SIZE;
319
+ let processedCount = 0;
320
+ for (const section of galleryData.sections) {
321
+ for (const [index, mediaFile] of section.images.entries()) {
322
+ section.images[index] = await processMediaFile(mediaFile, galleryDir, thumbnailsPath, thumbnailSize, ui);
323
+ }
324
+ processedCount += section.images.length;
325
+ }
326
+ fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
327
+ ui.success(`Created thumbnails for ${processedCount} media files`);
328
+ return processedCount;
329
+ } catch (error) {
330
+ ui.error(`Error creating thumbnails for ${galleryDir}`);
331
+ throw error;
68
332
  }
69
- for (const dir of galleryDirs) {
70
- buildGallery(path4.resolve(dir), themeDir);
333
+ }
334
+ async function thumbnails(options, ui) {
335
+ try {
336
+ const galleryDirs = findGalleries(options.gallery, options.recursive);
337
+ if (galleryDirs.length === 0) {
338
+ ui.error("No galleries found.");
339
+ return;
340
+ }
341
+ let totalGalleries = 0;
342
+ let totalProcessed = 0;
343
+ for (const galleryDir of galleryDirs) {
344
+ const processed = await processGalleryThumbnails(galleryDir, ui);
345
+ if (processed > 0) {
346
+ ++totalGalleries;
347
+ totalProcessed += processed;
348
+ }
349
+ }
350
+ ui.box(
351
+ `Created thumbnails for ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} with ${totalProcessed} media ${totalProcessed === 1 ? "file" : "files"}`
352
+ );
353
+ } catch (error) {
354
+ ui.error("Error creating thumbnails");
355
+ throw error;
71
356
  }
72
357
  }
73
358
 
74
- // src/modules/init/const/index.ts
75
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif", ".svg"]);
76
- var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
77
-
78
- // src/modules/init/utils/index.ts
79
- async function getImageMetadata(filePath) {
80
- try {
81
- const metadata = await sharp2(filePath).metadata();
82
- let description;
83
- if (metadata.exif) {
84
- try {
85
- const exifData = exifReader(metadata.exif);
86
- if (exifData.Image?.ImageDescription) {
87
- description = exifData.Image.ImageDescription;
88
- } else if (exifData.Image?.Description) {
89
- description = exifData.Image.Description.toString();
90
- }
91
- } catch {
359
+ // src/modules/build/index.ts
360
+ function checkFileIsOneFolderUp(filePath) {
361
+ const normalizedPath = path4.normalize(filePath);
362
+ const pathParts = normalizedPath.split(path4.sep);
363
+ return pathParts.length === 2 && pathParts[0] === "..";
364
+ }
365
+ function copyPhotos(galleryData, galleryDir, ui) {
366
+ for (const section of galleryData.sections) {
367
+ for (const image of section.images) {
368
+ if (!checkFileIsOneFolderUp(image.path)) {
369
+ const sourcePath = path4.join(galleryDir, "gallery", image.path);
370
+ const fileName = path4.basename(image.path);
371
+ const destPath = path4.join(galleryDir, fileName);
372
+ ui.debug(`Copying photo to ${destPath}`);
373
+ fs4.copyFileSync(sourcePath, destPath);
92
374
  }
93
375
  }
94
- return {
95
- width: metadata.width || 0,
96
- height: metadata.height || 0,
97
- description
98
- };
376
+ }
377
+ }
378
+ async function buildGallery(galleryDir, templateDir, ui, baseUrl) {
379
+ ui.start(`Building gallery ${galleryDir}`);
380
+ await processGalleryThumbnails(galleryDir, ui);
381
+ const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
382
+ const galleryContent = fs4.readFileSync(galleryJsonPath, "utf8");
383
+ const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
384
+ const socialMediaCardImagePath = path4.join(galleryDir, "gallery", "thumbnails", "social-media-card.jpg");
385
+ await createGallerySocialMediaCardImage(
386
+ path4.resolve(path4.join(galleryDir, "gallery"), galleryData.headerImage),
387
+ galleryData.title,
388
+ socialMediaCardImagePath,
389
+ ui
390
+ );
391
+ galleryData.metadata.image = galleryData.metadata.image || `${galleryData.url || ""}/${path4.relative(galleryDir, socialMediaCardImagePath)}`;
392
+ fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
393
+ if (!baseUrl) {
394
+ const shouldCopyPhotos = galleryData.sections.some(
395
+ (section) => section.images.some((image) => !checkFileIsOneFolderUp(image.path))
396
+ );
397
+ if (shouldCopyPhotos && await ui.prompt("All photos need to be copied. Are you sure you want to continue?", { type: "confirm" })) {
398
+ ui.debug("Copying photos");
399
+ copyPhotos(galleryData, galleryDir, ui);
400
+ }
401
+ }
402
+ if (baseUrl) {
403
+ ui.debug("Updating gallery.json with baseUrl");
404
+ galleryData.mediaBaseUrl = baseUrl;
405
+ fs4.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
406
+ }
407
+ ui.debug("Building gallery form template");
408
+ try {
409
+ process2.env.GALLERY_JSON_PATH = galleryJsonPath;
410
+ process2.env.GALLERY_OUTPUT_DIR = path4.join(galleryDir, "gallery");
411
+ execSync("npx astro build", { cwd: templateDir, stdio: ui.level === LogLevels.debug ? "inherit" : "ignore" });
99
412
  } catch (error) {
100
- console.warn(`Warning: Could not get metadata for image ${filePath}:`, error);
101
- return { width: 0, height: 0 };
413
+ ui.error(`Build failed for ${galleryDir}`);
414
+ throw error;
102
415
  }
416
+ const outputDir = path4.join(galleryDir, "gallery");
417
+ const buildDir = path4.join(outputDir, "_build");
418
+ ui.debug(`Copying build output to ${outputDir}`);
419
+ fs4.cpSync(buildDir, outputDir, { recursive: true });
420
+ ui.debug("Moving index.html to gallery directory");
421
+ fs4.copyFileSync(path4.join(outputDir, "index.html"), path4.join(galleryDir, "index.html"));
422
+ fs4.rmSync(path4.join(outputDir, "index.html"));
423
+ ui.debug("Cleaning up build directory");
424
+ fs4.rmSync(buildDir, { recursive: true, force: true });
425
+ ui.success(`Gallery built successfully`);
103
426
  }
104
- async function getVideoDimensions(filePath) {
427
+ async function build(options, ui) {
428
+ try {
429
+ const galleryDirs = findGalleries(options.gallery, options.recursive);
430
+ if (galleryDirs.length === 0) {
431
+ ui.error("No galleries found.");
432
+ return;
433
+ }
434
+ const themePath = await import.meta.resolve("@simple-photo-gallery/theme-modern/package.json");
435
+ const themeDir = path4.dirname(new URL(themePath).pathname);
436
+ let totalGalleries = 0;
437
+ for (const dir of galleryDirs) {
438
+ const baseUrl = options.baseUrl ? `${options.baseUrl}${path4.relative(options.gallery, dir)}` : void 0;
439
+ await buildGallery(path4.resolve(dir), themeDir, ui, baseUrl);
440
+ ++totalGalleries;
441
+ }
442
+ ui.box(`Built ${totalGalleries} ${totalGalleries === 1 ? "gallery" : "galleries"} successfully`);
443
+ } catch (error) {
444
+ if (error instanceof Error && error.message.includes("Cannot find package")) {
445
+ ui.error("Theme package not found: @simple-photo-gallery/theme-modern/package.json");
446
+ } else {
447
+ ui.error("Error building gallery");
448
+ }
449
+ throw error;
450
+ }
451
+ }
452
+ async function cleanGallery(galleryDir, ui) {
453
+ let filesRemoved = 0;
454
+ const indexHtmlPath = path4.join(galleryDir, "index.html");
455
+ if (fs4.existsSync(indexHtmlPath)) {
456
+ try {
457
+ fs4.rmSync(indexHtmlPath);
458
+ ui.debug(`Removed: ${indexHtmlPath}`);
459
+ filesRemoved++;
460
+ } catch (error) {
461
+ ui.warn(`Failed to remove index.html: ${error}`);
462
+ }
463
+ }
464
+ const galleryPath = path4.join(galleryDir, "gallery");
465
+ if (fs4.existsSync(galleryPath)) {
466
+ try {
467
+ fs4.rmSync(galleryPath, { recursive: true, force: true });
468
+ ui.debug(`Removed directory: ${galleryPath}`);
469
+ filesRemoved++;
470
+ } catch (error) {
471
+ ui.warn(`Failed to remove gallery directory: ${error}`);
472
+ }
473
+ }
474
+ if (filesRemoved > 0) {
475
+ ui.success(`Cleaned gallery at: ${galleryDir}`);
476
+ } else {
477
+ ui.info(`No gallery files found at: ${galleryDir}`);
478
+ }
479
+ }
480
+ async function clean(options, ui) {
105
481
  try {
106
- const data = await ffprobe(filePath);
107
- const videoStream = data.streams.find((stream) => stream.codec_type === "video");
108
- if (videoStream) {
109
- return {
110
- width: videoStream.width || 0,
111
- height: videoStream.height || 0
112
- };
482
+ const basePath = path4.resolve(options.gallery);
483
+ if (!fs4.existsSync(basePath)) {
484
+ ui.error(`Directory does not exist: ${basePath}`);
485
+ return;
486
+ }
487
+ const galleryDirs = findGalleries(basePath, options.recursive);
488
+ if (galleryDirs.length === 0) {
489
+ ui.info("No galleries found to clean.");
490
+ return;
113
491
  }
114
- return { width: 0, height: 0 };
492
+ for (const dir of galleryDirs) {
493
+ await cleanGallery(dir, ui);
494
+ }
495
+ ui.box(`Successfully cleaned ${galleryDirs.length} ${galleryDirs.length === 1 ? "gallery" : "galleries"}`);
115
496
  } catch (error) {
116
- console.warn(`Warning: Could not get dimensions for video ${filePath}:`, error);
117
- return { width: 0, height: 0 };
497
+ ui.error("Error cleaning galleries");
498
+ throw error;
118
499
  }
119
500
  }
120
- function isMediaFile(fileName) {
501
+
502
+ // src/modules/init/const/index.ts
503
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".tiff", ".tif", ".svg"]);
504
+ var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mkv", ".m4v", ".3gp"]);
505
+
506
+ // src/modules/init/utils/index.ts
507
+ function getMediaFileType(fileName) {
121
508
  const ext = path4.extname(fileName).toLowerCase();
122
509
  if (IMAGE_EXTENSIONS.has(ext)) return "image";
123
510
  if (VIDEO_EXTENSIONS.has(ext)) return "video";
@@ -128,58 +515,62 @@ function capitalizeTitle(folderName) {
128
515
  }
129
516
 
130
517
  // src/modules/init/index.ts
131
- async function scanDirectory(dirPath) {
518
+ async function scanDirectory(dirPath, ui) {
132
519
  const mediaFiles = [];
520
+ const subGalleryDirectories = [];
133
521
  try {
134
522
  const entries = await promises.readdir(dirPath, { withFileTypes: true });
135
523
  for (const entry of entries) {
136
524
  if (entry.isFile()) {
137
525
  const fullPath = path4.join(dirPath, entry.name);
138
- const mediaType = isMediaFile(entry.name);
526
+ const mediaType = getMediaFileType(entry.name);
139
527
  if (mediaType) {
140
- console.log(`Processing ${mediaType}: ${entry.name}`);
141
- let metadata = { width: 0, height: 0 };
142
- try {
143
- if (mediaType === "image") {
144
- metadata = await getImageMetadata(fullPath);
145
- } else if (mediaType === "video") {
146
- try {
147
- const videoDimensions = await getVideoDimensions(fullPath);
148
- metadata = { ...videoDimensions };
149
- } catch (videoError) {
150
- if (typeof videoError === "object" && videoError !== null && "message" in videoError && typeof videoError.message === "string" && (videoError.message.includes("ffprobe") || videoError.message.includes("ffmpeg"))) {
151
- console.error(
152
- `Error: ffprobe (part of ffmpeg) is required to process videos. Please install ffmpeg and ensure it is available in your PATH. Skipping video: ${entry.name}`
153
- );
154
- } else {
155
- console.error(`Error processing video ${entry.name}:`, videoError);
156
- }
157
- continue;
158
- }
159
- }
160
- } catch (mediaError) {
161
- console.error(`Error processing file ${entry.name}:`, mediaError);
162
- continue;
163
- }
164
528
  const mediaFile = {
165
529
  type: mediaType,
166
530
  path: fullPath,
167
- width: metadata.width,
168
- height: metadata.height
531
+ width: 0,
532
+ height: 0
169
533
  };
170
- if (metadata.description) {
171
- mediaFile.alt = metadata.description;
172
- }
173
534
  mediaFiles.push(mediaFile);
174
535
  }
536
+ } else if (entry.isDirectory() && entry.name !== "gallery") {
537
+ subGalleryDirectories.push(path4.join(dirPath, entry.name));
175
538
  }
176
539
  }
177
540
  } catch (error) {
178
- console.error(`Error scanning directory ${dirPath}:`, error);
541
+ if (error instanceof Error && error.message.includes("ENOENT")) {
542
+ ui.error(`Directory does not exist: ${dirPath}`);
543
+ } else if (error instanceof Error && error.message.includes("ENOTDIR")) {
544
+ ui.error(`Path is not a directory: ${dirPath}`);
545
+ } else {
546
+ ui.error(`Error scanning directory ${dirPath}:`, error);
547
+ }
548
+ throw error;
179
549
  }
180
- return mediaFiles;
550
+ return { mediaFiles, subGalleryDirectories };
551
+ }
552
+ async function getGallerySettingsFromUser(galleryName, defaultImage, ui) {
553
+ ui.info(`Enter gallery settings for the gallery in folder "${galleryName}"`);
554
+ const title = await ui.prompt("Enter gallery title", { type: "text", default: "My Gallery", placeholder: "My Gallery" });
555
+ const description = await ui.prompt("Enter gallery description", {
556
+ type: "text",
557
+ default: "My gallery with fantastic photos.",
558
+ placeholder: "My gallery with fantastic photos."
559
+ });
560
+ const url = await ui.prompt("Enter the URL where the gallery will be hosted (important for social media image)", {
561
+ type: "text",
562
+ default: "",
563
+ placeholder: ""
564
+ });
565
+ const headerImageName = await ui.prompt("Enter the name of the header image", {
566
+ type: "text",
567
+ default: defaultImage,
568
+ placeholder: defaultImage
569
+ });
570
+ const headerImage = path4.join("..", headerImageName);
571
+ return { title, description, url, headerImage };
181
572
  }
182
- async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = []) {
573
+ async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = [], useDefaultSettings, ui) {
183
574
  const galleryDir = path4.dirname(galleryJsonPath);
184
575
  const relativeMediaFiles = mediaFiles.map((file) => ({
185
576
  ...file,
@@ -189,11 +580,11 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = [])
189
580
  ...subGallery,
190
581
  headerImage: subGallery.headerImage ? path4.relative(galleryDir, subGallery.headerImage) : ""
191
582
  }));
192
- const galleryData = {
583
+ let galleryData = {
193
584
  title: "My Gallery",
194
585
  description: "My gallery with fantastic photos.",
195
586
  headerImage: relativeMediaFiles[0]?.path || "",
196
- metadata: { ogUrl: "" },
587
+ metadata: {},
197
588
  sections: [
198
589
  {
199
590
  images: relativeMediaFiles
@@ -204,42 +595,58 @@ async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = [])
204
595
  galleries: relativeSubGalleries
205
596
  }
206
597
  };
598
+ if (!useDefaultSettings) {
599
+ galleryData = {
600
+ ...galleryData,
601
+ ...await getGallerySettingsFromUser(
602
+ path4.basename(path4.join(galleryDir, "..")),
603
+ path4.basename(mediaFiles[0]?.path || ""),
604
+ ui
605
+ )
606
+ };
607
+ }
207
608
  await promises.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
208
609
  }
209
- async function processDirectory(dirPath, options) {
610
+ async function processDirectory(scanPath, outputPath, recursive, useDefaultSettings, ui) {
611
+ ui.start(`Scanning ${scanPath}`);
210
612
  let totalFiles = 0;
613
+ let totalGalleries = 1;
211
614
  const subGalleries = [];
212
- const mediaFiles = await scanDirectory(dirPath);
615
+ const { mediaFiles, subGalleryDirectories } = await scanDirectory(scanPath, ui);
213
616
  totalFiles += mediaFiles.length;
214
- if (options.recursive) {
215
- try {
216
- const entries = await promises.readdir(dirPath, { withFileTypes: true });
217
- for (const entry of entries) {
218
- if (entry.isDirectory() && entry.name !== "gallery") {
219
- const subDirPath = path4.join(dirPath, entry.name);
220
- const result2 = await processDirectory(subDirPath, options);
221
- totalFiles += result2.totalFiles;
222
- if (result2.subGallery) {
223
- subGalleries.push(result2.subGallery);
224
- }
225
- }
617
+ if (recursive) {
618
+ for (const subGalleryDir of subGalleryDirectories) {
619
+ const result2 = await processDirectory(
620
+ subGalleryDir,
621
+ path4.join(outputPath, path4.basename(subGalleryDir)),
622
+ recursive,
623
+ useDefaultSettings,
624
+ ui
625
+ );
626
+ totalFiles += result2.totalFiles;
627
+ totalGalleries += result2.totalGalleries;
628
+ if (result2.subGallery) {
629
+ subGalleries.push(result2.subGallery);
226
630
  }
227
- } catch (error) {
228
- console.error(`Error reading directory ${dirPath}:`, error);
229
631
  }
230
632
  }
231
633
  if (mediaFiles.length > 0 || subGalleries.length > 0) {
232
- const outputPath = options.output ? path4.resolve(options.output, path4.relative(path4.resolve(options.path), dirPath), "gallery") : path4.join(dirPath, "gallery");
233
- const galleryJsonPath = path4.join(outputPath, "gallery.json");
234
- await promises.mkdir(outputPath, { recursive: true });
235
- await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries);
236
- console.log(
237
- `Gallery JSON created at: ${galleryJsonPath} (${mediaFiles.length} files, ${subGalleries.length} subgalleries)`
238
- );
634
+ const galleryPath = path4.join(outputPath, "gallery");
635
+ const galleryJsonPath = path4.join(galleryPath, "gallery.json");
636
+ try {
637
+ await promises.mkdir(galleryPath, { recursive: true });
638
+ await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries, useDefaultSettings, ui);
639
+ ui.success(
640
+ `Create gallery with ${mediaFiles.length} files and ${subGalleries.length} subgalleries at: ${galleryJsonPath}`
641
+ );
642
+ } catch (error) {
643
+ ui.error(`Error creating gallery.json at ${galleryJsonPath}`);
644
+ throw error;
645
+ }
239
646
  }
240
- const result = { totalFiles };
647
+ const result = { totalFiles, totalGalleries };
241
648
  if (mediaFiles.length > 0 || subGalleries.length > 0) {
242
- const dirName = path4.basename(dirPath);
649
+ const dirName = path4.basename(scanPath);
243
650
  result.subGallery = {
244
651
  title: capitalizeTitle(dirName),
245
652
  headerImage: mediaFiles[0]?.path || "",
@@ -248,250 +655,56 @@ async function processDirectory(dirPath, options) {
248
655
  }
249
656
  return result;
250
657
  }
251
- async function init(options) {
252
- const scanPath = path4.resolve(options.path);
253
- console.log(`Scanning directory: ${scanPath}`);
254
- console.log(`Recursive: ${options.recursive}`);
255
- try {
256
- await promises.access(scanPath);
257
- const result = await processDirectory(scanPath, options);
258
- console.log(`Total files processed: ${result.totalFiles}`);
259
- } catch (error) {
260
- throw new Error(`Error during scan: ${error}`);
261
- }
262
- }
263
- async function checkFfmpegAvailability() {
264
- return new Promise((resolve) => {
265
- const ffmpeg = spawn("ffmpeg", ["-version"]);
266
- ffmpeg.on("close", (code) => {
267
- resolve(code === 0);
268
- });
269
- ffmpeg.on("error", () => {
270
- resolve(false);
271
- });
272
- });
273
- }
274
- async function resizeAndSaveThumbnail(image, outputPath, width, height) {
275
- await image.resize(width, height, { withoutEnlargement: true }).jpeg({ quality: 90 }).toFile(outputPath);
276
- }
277
- async function createImageThumbnail(inputPath, outputPath, height) {
278
- try {
279
- await promises.access(inputPath);
280
- const image = sharp2(inputPath);
281
- const metadata = await image.metadata();
282
- const originalWidth = metadata.width || 0;
283
- const originalHeight = metadata.height || 0;
284
- if (originalWidth === 0 || originalHeight === 0) {
285
- throw new Error("Invalid image dimensions");
286
- }
287
- const aspectRatio = originalWidth / originalHeight;
288
- const width = Math.round(height * aspectRatio);
289
- await resizeAndSaveThumbnail(image, outputPath, width, height);
290
- return { width, height };
291
- } catch (error) {
292
- throw new Error(`Failed to create image thumbnail for ${inputPath}: ${error}`);
293
- }
294
- }
295
- async function createVideoThumbnail(inputPath, outputPath, height) {
658
+ async function init(options, ui) {
296
659
  try {
297
- await promises.access(inputPath);
298
- const ffmpegAvailable = await checkFfmpegAvailability();
299
- if (!ffmpegAvailable) {
300
- console.warn(`Warning: ffmpeg is not available. Skipping thumbnail creation for video: ${path4.basename(inputPath)}`);
301
- throw new Error("FFMPEG_NOT_AVAILABLE");
302
- }
303
- const videoData = await ffprobe(inputPath);
304
- const videoStream = videoData.streams.find((stream) => stream.codec_type === "video");
305
- if (!videoStream) {
306
- throw new Error("No video stream found");
307
- }
308
- const originalWidth = videoStream.width || 0;
309
- const originalHeight = videoStream.height || 0;
310
- if (originalWidth === 0 || originalHeight === 0) {
311
- throw new Error("Invalid video dimensions");
312
- }
313
- const aspectRatio = originalWidth / originalHeight;
314
- const width = Math.round(height * aspectRatio);
315
- const tempFramePath = `${outputPath}.temp.png`;
316
- return new Promise((resolve, reject) => {
317
- const ffmpeg = spawn("ffmpeg", [
318
- "-i",
319
- inputPath,
320
- "-vframes",
321
- "1",
322
- "-y",
323
- // Overwrite output file
324
- tempFramePath
325
- ]);
326
- ffmpeg.stderr.on("data", (data) => {
327
- console.debug(`ffmpeg: ${data.toString()}`);
328
- });
329
- ffmpeg.on("close", async (code) => {
330
- if (code === 0) {
331
- try {
332
- const frameImage = sharp2(tempFramePath);
333
- await resizeAndSaveThumbnail(frameImage, outputPath, width, height);
334
- try {
335
- await promises.unlink(tempFramePath);
336
- } catch {
337
- }
338
- resolve({ width, height });
339
- } catch (sharpError) {
340
- reject(new Error(`Failed to process extracted frame: ${sharpError}`));
341
- }
342
- } else {
343
- reject(new Error(`ffmpeg exited with code ${code}`));
344
- }
345
- });
346
- ffmpeg.on("error", (error) => {
347
- reject(new Error(`Failed to start ffmpeg: ${error.message}`));
348
- });
349
- });
660
+ const scanPath = path4.resolve(options.photos);
661
+ const outputPath = options.gallery ? path4.resolve(options.gallery) : scanPath;
662
+ const result = await processDirectory(scanPath, outputPath, options.recursive, options.default, ui);
663
+ ui.box(
664
+ `Created ${result.totalGalleries} ${result.totalGalleries === 1 ? "gallery" : "galleries"} with ${result.totalFiles} media ${result.totalFiles === 1 ? "file" : "files"}`
665
+ );
350
666
  } catch (error) {
351
- if (error instanceof Error && error.message === "FFMPEG_NOT_AVAILABLE") {
352
- throw error;
353
- }
354
- if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" && (error.message.includes("ffmpeg") || error.message.includes("ffprobe"))) {
355
- throw new Error(
356
- `Error: ffmpeg is required to process videos. Please install ffmpeg and ensure it is available in your PATH. Failed to process: ${path4.basename(inputPath)}`
357
- );
358
- }
359
- throw new Error(`Failed to create video thumbnail for ${inputPath}: ${error}`);
667
+ ui.error("Error initializing gallery");
668
+ throw error;
360
669
  }
361
670
  }
362
- var ThumbnailSchema = z.object({
363
- path: z.string(),
364
- width: z.number(),
365
- height: z.number()
366
- });
367
- var MediaFileSchema = z.object({
368
- type: z.enum(["image", "video"]),
369
- path: z.string(),
370
- alt: z.string().optional(),
371
- width: z.number(),
372
- height: z.number(),
373
- thumbnail: ThumbnailSchema.optional()
374
- });
375
- var GallerySectionSchema = z.object({
376
- title: z.string().optional(),
377
- description: z.string().optional(),
378
- images: z.array(MediaFileSchema)
379
- });
380
- var SubGallerySchema = z.object({
381
- title: z.string(),
382
- headerImage: z.string(),
383
- path: z.string()
384
- });
385
- var GalleryDataSchema = z.object({
386
- title: z.string(),
387
- description: z.string(),
388
- headerImage: z.string(),
389
- metadata: z.object({
390
- ogUrl: z.string()
391
- }),
392
- galleryOutputPath: z.string().optional(),
393
- sections: z.array(GallerySectionSchema),
394
- subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) })
395
- });
396
671
 
397
- // src/modules/thumbnails/index.ts
398
- var processGallery = async (galleryDir, size) => {
399
- const galleryJsonPath = path4.join(galleryDir, "gallery", "gallery.json");
400
- const thumbnailsPath = path4.join(galleryDir, "gallery", "thumbnails");
401
- console.log(`
402
- Processing gallery in: ${galleryDir}`);
403
- try {
404
- fs2.mkdirSync(thumbnailsPath, { recursive: true });
405
- const galleryContent = fs2.readFileSync(galleryJsonPath, "utf8");
406
- const galleryData = GalleryDataSchema.parse(JSON.parse(galleryContent));
407
- let processedCount = 0;
408
- for (const section of galleryData.sections) {
409
- for (const [index, mediaFile] of section.images.entries()) {
410
- section.images[index] = await processMediaFile(mediaFile, galleryDir, thumbnailsPath, size);
411
- }
412
- processedCount += section.images.length;
413
- }
414
- fs2.writeFileSync(galleryJsonPath, JSON.stringify(galleryData, null, 2));
415
- return processedCount;
416
- } catch (error) {
417
- console.error(`Error creating thumbnails for ${galleryDir}:`, error);
418
- return 0;
419
- }
420
- };
421
- async function processMediaFile(mediaFile, galleryDir, thumbnailsPath, size) {
422
- try {
423
- const galleryJsonDir = path4.join(galleryDir, "gallery");
424
- const filePath = path4.resolve(path4.join(galleryJsonDir, mediaFile.path));
425
- const fileName = path4.basename(filePath);
426
- const fileNameWithoutExt = path4.parse(fileName).name;
427
- const thumbnailFileName = `${fileNameWithoutExt}.jpg`;
428
- const thumbnailPath = path4.join(thumbnailsPath, thumbnailFileName);
429
- const relativeThumbnailPath = path4.relative(galleryJsonDir, thumbnailPath);
430
- console.log(`Processing ${mediaFile.type}: ${fileName}`);
431
- let thumbnailDimensions;
432
- if (mediaFile.type === "image") {
433
- thumbnailDimensions = await createImageThumbnail(filePath, thumbnailPath, size);
434
- } else if (mediaFile.type === "video") {
435
- thumbnailDimensions = await createVideoThumbnail(filePath, thumbnailPath, size);
436
- } else {
437
- console.warn(`Unknown media type: ${mediaFile.type}, skipping...`);
438
- return mediaFile;
439
- }
440
- const updatedMediaFile = {
441
- ...mediaFile,
442
- thumbnail: {
443
- path: relativeThumbnailPath,
444
- width: thumbnailDimensions.width,
445
- height: thumbnailDimensions.height
446
- }
447
- };
448
- return updatedMediaFile;
449
- } catch (error) {
450
- if (error instanceof Error && error.message === "FFMPEG_NOT_AVAILABLE") {
451
- console.warn(`\u26A0 Skipping video thumbnail (ffmpeg not available): ${path4.basename(mediaFile.path)}`);
452
- } else {
453
- console.error(`Error processing ${mediaFile.path}:`, error);
454
- }
455
- return mediaFile;
672
+ // src/index.ts
673
+ var program = new Command();
674
+ program.name("gallery").description("Simple Photo Gallery CLI").version("0.0.1").option("-v, --verbose", "Verbose output (debug level)", false).option("-q, --quiet", "Minimal output (only warnings/errors)", false).showHelpAfterError(true);
675
+ function createConsolaUI(globalOpts) {
676
+ let level = LogLevels.info;
677
+ if (globalOpts.quiet) {
678
+ level = LogLevels.warn;
679
+ } else if (globalOpts.verbose) {
680
+ level = LogLevels.debug;
456
681
  }
682
+ return createConsola({
683
+ level
684
+ }).withTag("simple-photo-gallery");
457
685
  }
458
- async function thumbnails(options) {
459
- const size = Number.parseInt(options.size);
460
- const galleryDirs = findGalleries(options.path, options.recursive);
461
- if (galleryDirs.length === 0) {
462
- console.log("No gallery/gallery.json files found.");
463
- return;
464
- }
465
- let totalProcessed = 0;
466
- for (const galleryDir of galleryDirs) {
467
- const processed = await processGallery(galleryDir, size);
468
- totalProcessed += processed;
469
- }
470
- if (options.recursive) {
471
- console.log(`Completed processing ${totalProcessed} total media files across ${galleryDirs.length} galleries.`);
472
- } else {
473
- console.log(`Completed processing ${totalProcessed} media files.`);
474
- }
686
+ function withConsolaUI(handler) {
687
+ return async (opts) => {
688
+ const ui = createConsolaUI(program.opts());
689
+ try {
690
+ await handler(opts, ui);
691
+ } catch (error) {
692
+ ui.debug(error);
693
+ process2.exitCode = 1;
694
+ }
695
+ };
475
696
  }
476
-
477
- // src/index.ts
478
- var program = new Command();
479
- program.name("gallery").description("Simple Photo Gallery CLI").version("0.0.1");
480
697
  program.command("init").description("Initialize a gallery by scaning a folder for images and videos").option(
481
- "-p, --path <path>",
482
- "Path where the gallery should be initialized. Default: current working directory",
483
- process.cwd()
484
- ).option("-o, --output <path>", "Output directory for the gallery.json file", "").option("-r, --recursive", "Scan subdirectories recursively", false).action(init);
485
- program.command("thumbnails").description("Create thumbnails for all media files in the gallery").option(
486
- "-p, --path <path>",
487
- "Path to the folder containing the gallery.json file. Default: current working directory",
488
- process.cwd()
489
- ).option("-s, --size <size>", "Thumbnail height in pixels", "200").option("-r, --recursive", "Scan subdirectories recursively", false).action(thumbnails);
490
- program.command("build").description("Build the HTML gallery in the specified directory").option(
491
- "-p, --path <path>",
492
- "Path to the folder containing the gallery.json file. Default: current working directory",
493
- process.cwd()
494
- ).option("-r, --recursive", "Scan subdirectories recursively", false).action(build);
698
+ "-p, --photos <path>",
699
+ "Path to the folder where the photos are stored. Default: current working directory",
700
+ process2.cwd()
701
+ ).option(
702
+ "-g, --gallery <path>",
703
+ "Path to the directory where the gallery will be initialized. Default: same directory as the photos folder"
704
+ ).option("-r, --recursive", "Recursively create galleries from all photos subdirectories", false).option("-d, --default", "Use default gallery settings instead of asking the user", false).action(withConsolaUI(init));
705
+ program.command("thumbnails").description("Create thumbnails for all media files in the gallery").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process2.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).action(withConsolaUI(thumbnails));
706
+ program.command("build").description("Build the HTML gallery in the specified directory").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process2.cwd()).option("-r, --recursive", "Scan subdirectories recursively", false).option("-b, --base-url <url>", "Base URL where the photos are hosted").action(withConsolaUI(build));
707
+ program.command("clean").description("Remove all gallery files and folders (index.html, gallery/)").option("-g, --gallery <path>", "Path to the directory of the gallery. Default: current working directory", process2.cwd()).option("-r, --recursive", "Clean subdirectories recursively", false).action(withConsolaUI(clean));
495
708
  program.parse();
496
709
  //# sourceMappingURL=index.js.map
497
710
  //# sourceMappingURL=index.js.map