simple-photo-gallery 0.0.3 → 0.0.5
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.d.ts +1 -0
- package/dist/index.js +497 -0
- package/dist/index.js.map +1 -0
- package/package.json +7 -7
- package/dist/src/index.js +0 -29
- package/dist/src/modules/build/index.js +0 -57
- package/dist/src/modules/build/types/index.js +0 -1
- package/dist/src/modules/build/utils/index.js +0 -7
- package/dist/src/modules/init/const/index.js +0 -4
- package/dist/src/modules/init/index.js +0 -156
- package/dist/src/modules/init/types/index.js +0 -1
- package/dist/src/modules/init/utils/index.js +0 -93
- package/dist/src/modules/thumbnails/index.js +0 -98
- package/dist/src/modules/thumbnails/types/index.js +0 -1
- package/dist/src/modules/thumbnails/utils/index.js +0 -127
- package/dist/src/types/index.js +0 -35
- package/dist/src/utils/index.js +0 -34
- package/dist/tests/gallery.test.js +0 -170
- package/src/index.ts +0 -50
- package/src/modules/build/index.ts +0 -68
- package/src/modules/build/types/index.ts +0 -4
- package/src/modules/build/utils/index.ts +0 -9
- package/src/modules/init/const/index.ts +0 -5
- package/src/modules/init/index.ts +0 -193
- package/src/modules/init/types/index.ts +0 -16
- package/src/modules/init/types/node-ffprobe.d.ts +0 -17
- package/src/modules/init/utils/index.ts +0 -98
- package/src/modules/thumbnails/index.ts +0 -121
- package/src/modules/thumbnails/types/index.ts +0 -5
- package/src/modules/thumbnails/utils/index.ts +0 -162
- package/src/types/index.ts +0 -46
- package/src/utils/index.ts +0 -37
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'process';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { execSync, spawn } from 'child_process';
|
|
5
|
+
import fs2, { promises } from 'fs';
|
|
6
|
+
import path4 from 'path';
|
|
7
|
+
import exifReader from 'exif-reader';
|
|
8
|
+
import ffprobe from 'node-ffprobe';
|
|
9
|
+
import sharp2 from 'sharp';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
var findGalleries = (basePath, recursive) => {
|
|
13
|
+
const galleryDirs = [];
|
|
14
|
+
const galleryJsonPath = path4.join(basePath, "gallery", "gallery.json");
|
|
15
|
+
if (fs2.existsSync(galleryJsonPath)) {
|
|
16
|
+
galleryDirs.push(basePath);
|
|
17
|
+
}
|
|
18
|
+
if (recursive) {
|
|
19
|
+
try {
|
|
20
|
+
const entries = fs2.readdirSync(basePath, { withFileTypes: true });
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.isDirectory() && entry.name !== "gallery") {
|
|
23
|
+
const subPath = path4.join(basePath, entry.name);
|
|
24
|
+
const subResults = findGalleries(subPath, recursive);
|
|
25
|
+
galleryDirs.push(...subResults);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return galleryDirs;
|
|
32
|
+
};
|
|
33
|
+
|
|
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;
|
|
40
|
+
}
|
|
41
|
+
const originalEnv = { ...process.env };
|
|
42
|
+
try {
|
|
43
|
+
process.env.GALLERY_JSON_PATH = galleryJsonPath;
|
|
44
|
+
process.env.GALLERY_OUTPUT_DIR = path4.join(galleryDir, "gallery");
|
|
45
|
+
execSync("yarn build", { cwd: templateDir, stdio: "inherit" });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error(error);
|
|
48
|
+
console.error(`Build failed for ${galleryDir}`);
|
|
49
|
+
return;
|
|
50
|
+
} finally {
|
|
51
|
+
process.env = originalEnv;
|
|
52
|
+
}
|
|
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
|
+
}
|
|
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;
|
|
68
|
+
}
|
|
69
|
+
for (const dir of galleryDirs) {
|
|
70
|
+
buildGallery(path4.resolve(dir), themeDir);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
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 {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
width: metadata.width || 0,
|
|
96
|
+
height: metadata.height || 0,
|
|
97
|
+
description
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.warn(`Warning: Could not get metadata for image ${filePath}:`, error);
|
|
101
|
+
return { width: 0, height: 0 };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function getVideoDimensions(filePath) {
|
|
105
|
+
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
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return { width: 0, height: 0 };
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn(`Warning: Could not get dimensions for video ${filePath}:`, error);
|
|
117
|
+
return { width: 0, height: 0 };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isMediaFile(fileName) {
|
|
121
|
+
const ext = path4.extname(fileName).toLowerCase();
|
|
122
|
+
if (IMAGE_EXTENSIONS.has(ext)) return "image";
|
|
123
|
+
if (VIDEO_EXTENSIONS.has(ext)) return "video";
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function capitalizeTitle(folderName) {
|
|
127
|
+
return folderName.replace("-", " ").replace("_", " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/modules/init/index.ts
|
|
131
|
+
async function scanDirectory(dirPath) {
|
|
132
|
+
const mediaFiles = [];
|
|
133
|
+
try {
|
|
134
|
+
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (entry.isFile()) {
|
|
137
|
+
const fullPath = path4.join(dirPath, entry.name);
|
|
138
|
+
const mediaType = isMediaFile(entry.name);
|
|
139
|
+
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
|
+
const mediaFile = {
|
|
165
|
+
type: mediaType,
|
|
166
|
+
path: fullPath,
|
|
167
|
+
width: metadata.width,
|
|
168
|
+
height: metadata.height
|
|
169
|
+
};
|
|
170
|
+
if (metadata.description) {
|
|
171
|
+
mediaFile.alt = metadata.description;
|
|
172
|
+
}
|
|
173
|
+
mediaFiles.push(mediaFile);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error(`Error scanning directory ${dirPath}:`, error);
|
|
179
|
+
}
|
|
180
|
+
return mediaFiles;
|
|
181
|
+
}
|
|
182
|
+
async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = []) {
|
|
183
|
+
const galleryDir = path4.dirname(galleryJsonPath);
|
|
184
|
+
const relativeMediaFiles = mediaFiles.map((file) => ({
|
|
185
|
+
...file,
|
|
186
|
+
path: path4.relative(galleryDir, file.path)
|
|
187
|
+
}));
|
|
188
|
+
const relativeSubGalleries = subGalleries.map((subGallery) => ({
|
|
189
|
+
...subGallery,
|
|
190
|
+
headerImage: subGallery.headerImage ? path4.relative(galleryDir, subGallery.headerImage) : ""
|
|
191
|
+
}));
|
|
192
|
+
const galleryData = {
|
|
193
|
+
title: "My Gallery",
|
|
194
|
+
description: "My gallery with fantastic photos.",
|
|
195
|
+
headerImage: relativeMediaFiles[0]?.path || "",
|
|
196
|
+
metadata: { ogUrl: "" },
|
|
197
|
+
sections: [
|
|
198
|
+
{
|
|
199
|
+
images: relativeMediaFiles
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
subGalleries: {
|
|
203
|
+
title: "Sub Galleries",
|
|
204
|
+
galleries: relativeSubGalleries
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
await promises.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
|
|
208
|
+
}
|
|
209
|
+
async function processDirectory(dirPath, options) {
|
|
210
|
+
let totalFiles = 0;
|
|
211
|
+
const subGalleries = [];
|
|
212
|
+
const mediaFiles = await scanDirectory(dirPath);
|
|
213
|
+
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
|
+
}
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(`Error reading directory ${dirPath}:`, error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
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
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const result = { totalFiles };
|
|
241
|
+
if (mediaFiles.length > 0 || subGalleries.length > 0) {
|
|
242
|
+
const dirName = path4.basename(dirPath);
|
|
243
|
+
result.subGallery = {
|
|
244
|
+
title: capitalizeTitle(dirName),
|
|
245
|
+
headerImage: mediaFiles[0]?.path || "",
|
|
246
|
+
path: path4.join("..", dirName)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
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) {
|
|
296
|
+
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
|
+
});
|
|
350
|
+
} 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}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
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
|
+
|
|
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;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
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
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/index.ts
|
|
478
|
+
var program = new Command();
|
|
479
|
+
program.name("gallery").description("Simple Photo Gallery CLI").version("0.0.1");
|
|
480
|
+
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);
|
|
495
|
+
program.parse();
|
|
496
|
+
//# sourceMappingURL=index.js.map
|
|
497
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/index.ts","../src/modules/build/index.ts","../src/modules/init/const/index.ts","../src/modules/init/utils/index.ts","../src/modules/init/index.ts","../src/modules/thumbnails/utils/index.ts","../src/types/index.ts","../src/modules/thumbnails/index.ts","../src/index.ts"],"names":["path","fs","sharp","result","ffprobe","process"],"mappings":"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","file":"index.js","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\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 const 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","import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { findGalleries } from '../../utils';\n\nimport type { BuildOptions } from './types';\n\nfunction buildGallery(galleryDir: string, templateDir: string) {\n // Make sure the gallery.json file exists\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n if (!fs.existsSync(galleryJsonPath)) {\n console.log(`No gallery/gallery.json found in ${galleryDir}`);\n return;\n }\n\n // Build the template\n const originalEnv = { ...process.env };\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('yarn build', { cwd: templateDir, stdio: 'inherit' });\n } catch (error) {\n console.error(error);\n console.error(`Build failed for ${galleryDir}`);\n return;\n } finally {\n // Restore original environment and gallery.json\n process.env = originalEnv;\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 fs.cpSync(buildDir, outputDir, { recursive: true });\n\n // Move the index.html to the 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 console.log('Cleaning up build directory...');\n fs.rmSync(buildDir, { recursive: true, force: true });\n}\n\nexport async function build(options: BuildOptions): Promise<void> {\n // Get the template directory\n // Resolve the theme-modern package directory\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 // Find all gallery directories\n const galleryDirs = findGalleries(options.path, options.recursive);\n\n // If no galleries are found, exit\n if (galleryDirs.length === 0) {\n console.log('No gallery/gallery.json files found.');\n return;\n }\n\n // Process each gallery\n for (const dir of galleryDirs) {\n buildGallery(path.resolve(dir), themeDir);\n }\n}\n","// Image extensions\nexport const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff', '.tif', '.svg']);\n\n// Video extensions\nexport const VIDEO_EXTENSIONS = new Set(['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', '.3gp']);\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport exifReader from 'exif-reader';\nimport ffprobe from 'node-ffprobe';\nimport sharp from 'sharp';\n\nimport { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '../const';\n\nexport async function getImageMetadata(filePath: string): Promise<{ width: number; height: number; description?: string }> {\n try {\n const metadata = await sharp(filePath).metadata();\n let description: string | undefined;\n\n // Extract description from EXIF data\n if (metadata.exif) {\n try {\n const exifData = exifReader(metadata.exif);\n if (exifData.Image?.ImageDescription) {\n description = exifData.Image.ImageDescription;\n } else if (exifData.Image?.Description) {\n description = exifData.Image.Description.toString();\n }\n } catch {\n // EXIF parsing failed, but that's OK\n }\n }\n\n return {\n width: metadata.width || 0,\n height: metadata.height || 0,\n description,\n };\n } catch (error) {\n console.warn(`Warning: Could not get metadata for image ${filePath}:`, error);\n return { width: 0, height: 0 };\n }\n}\n\nexport async function getVideoDimensions(filePath: string): Promise<{ width: number; height: number }> {\n try {\n const data = await ffprobe(filePath);\n const videoStream = data.streams.find((stream) => stream.codec_type === 'video');\n if (videoStream) {\n return {\n width: videoStream.width || 0,\n height: videoStream.height || 0,\n };\n }\n return { width: 0, height: 0 };\n } catch (error) {\n console.warn(`Warning: Could not get dimensions for video ${filePath}:`, error);\n return { width: 0, height: 0 };\n }\n}\n\nexport function isMediaFile(fileName: string): 'image' | 'video' | 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\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\nexport async function hasMediaFiles(dirPath: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n // Check for media files in current directory\n for (const entry of entries) {\n if (entry.isFile() && isMediaFile(entry.name)) {\n return true;\n }\n }\n\n // Check subdirectories recursively\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'gallery') {\n const subDirPath = path.join(dirPath, entry.name);\n if (await hasMediaFiles(subDirPath)) {\n return true;\n }\n }\n }\n } catch (error) {\n console.error(`Error checking for media files in ${dirPath}:`, error);\n }\n\n return false;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { capitalizeTitle, getImageMetadata, getVideoDimensions, isMediaFile } from './utils';\n\nimport type { ProcessDirectoryResult, ScanOptions, SubGallery } from './types';\nimport type { MediaFile } from '../../types';\n\nasync function scanDirectory(dirPath: string): Promise<MediaFile[]> {\n const mediaFiles: MediaFile[] = [];\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 = isMediaFile(entry.name);\n if (mediaType) {\n console.log(`Processing ${mediaType}: ${entry.name}`);\n\n let metadata: { width: number; height: number; description?: string } = { width: 0, height: 0 };\n\n try {\n if (mediaType === 'image') {\n metadata = await getImageMetadata(fullPath);\n } else if (mediaType === 'video') {\n try {\n const videoDimensions = await getVideoDimensions(fullPath);\n metadata = { ...videoDimensions };\n } catch (videoError: unknown) {\n if (\n typeof videoError === 'object' &&\n videoError !== null &&\n 'message' in videoError &&\n typeof (videoError as { message: string }).message === 'string' &&\n ((videoError as { message: string }).message.includes('ffprobe') ||\n (videoError as { message: string }).message.includes('ffmpeg'))\n ) {\n console.error(\n `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}`,\n );\n } else {\n console.error(`Error processing video ${entry.name}:`, videoError);\n }\n continue; // Skip this file\n }\n }\n } catch (mediaError) {\n console.error(`Error processing file ${entry.name}:`, mediaError);\n continue; // Skip this file\n }\n\n const mediaFile: MediaFile = {\n type: mediaType,\n path: fullPath,\n width: metadata.width,\n height: metadata.height,\n };\n\n if (metadata.description) {\n mediaFile.alt = metadata.description;\n }\n\n mediaFiles.push(mediaFile);\n }\n }\n }\n } catch (error) {\n console.error(`Error scanning directory ${dirPath}:`, error);\n }\n\n return mediaFiles;\n}\n\nasync function createGalleryJson(\n mediaFiles: MediaFile[],\n galleryJsonPath: string,\n subGalleries: SubGallery[] = [],\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 const galleryData = {\n title: 'My Gallery',\n description: 'My gallery with fantastic photos.',\n headerImage: relativeMediaFiles[0]?.path || '',\n metadata: { ogUrl: '' },\n sections: [\n {\n images: relativeMediaFiles,\n },\n ],\n subGalleries: {\n title: 'Sub Galleries',\n galleries: relativeSubGalleries,\n },\n };\n\n await fs.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));\n}\n\nasync function processDirectory(dirPath: string, options: ScanOptions): Promise<ProcessDirectoryResult> {\n let totalFiles = 0;\n const subGalleries: SubGallery[] = [];\n\n // Scan current directory for media files\n const mediaFiles = await scanDirectory(dirPath);\n totalFiles += mediaFiles.length;\n\n // Process subdirectories only if recursive mode is enabled\n if (options.recursive) {\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isDirectory() && entry.name !== 'gallery') {\n const subDirPath = path.join(dirPath, entry.name);\n const result = await processDirectory(subDirPath, options);\n totalFiles += result.totalFiles;\n\n // If the subdirectory had media files, add it as a subGallery\n if (result.subGallery) {\n subGalleries.push(result.subGallery);\n }\n }\n }\n } catch (error) {\n console.error(`Error reading directory ${dirPath}:`, error);\n }\n }\n\n // Create gallery.json if there are media files or subGalleries\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const outputPath = options.output\n ? path.resolve(options.output, path.relative(path.resolve(options.path), dirPath), 'gallery')\n : path.join(dirPath, 'gallery');\n const galleryJsonPath = path.join(outputPath, 'gallery.json');\n\n // Create output directory\n await fs.mkdir(outputPath, { recursive: true });\n\n // Create gallery.json for this directory\n await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries);\n\n console.log(\n `Gallery JSON created at: ${galleryJsonPath} (${mediaFiles.length} files, ${subGalleries.length} subgalleries)`,\n );\n }\n\n // Return result with suGgallery info if this directory has media files\n const result: ProcessDirectoryResult = { totalFiles };\n\n if (mediaFiles.length > 0 || subGalleries.length > 0) {\n const dirName = path.basename(dirPath);\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\nexport async function init(options: ScanOptions): Promise<void> {\n const scanPath = path.resolve(options.path);\n\n console.log(`Scanning directory: ${scanPath}`);\n console.log(`Recursive: ${options.recursive}`);\n\n try {\n // Ensure scan path exists\n await fs.access(scanPath);\n\n // Process the directory tree with the specified recursion setting\n const result = await processDirectory(scanPath, options);\n console.log(`Total files processed: ${result.totalFiles}`);\n } catch (error) {\n throw new Error(`Error during scan: ${error}`);\n }\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport ffprobe from 'node-ffprobe';\nimport sharp from 'sharp';\n\nimport type { Buffer } from 'node:buffer';\n\n// Check if ffmpeg is available\nasync function checkFfmpegAvailability(): Promise<boolean> {\n return new Promise((resolve) => {\n const ffmpeg = spawn('ffmpeg', ['-version']);\n\n ffmpeg.on('close', (code) => {\n resolve(code === 0);\n });\n\n ffmpeg.on('error', () => {\n resolve(false);\n });\n });\n}\n\n// Utility function to resize and save thumbnail\nasync function resizeAndSaveThumbnail(image: sharp.Sharp, outputPath: string, width: number, height: number): Promise<void> {\n await image.resize(width, height, { withoutEnlargement: true }).jpeg({ quality: 90 }).toFile(outputPath);\n}\n\nexport async function createImageThumbnail(\n inputPath: string,\n outputPath: string,\n height: number,\n): Promise<{ width: number; height: number }> {\n try {\n // Check if input file exists\n await fs.access(inputPath);\n\n // Create thumbnail using sharp\n const image = sharp(inputPath);\n const metadata = await image.metadata();\n\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 await resizeAndSaveThumbnail(image, outputPath, width, height);\n\n return { width, height };\n } catch (error) {\n throw new Error(`Failed to create image thumbnail for ${inputPath}: ${error}`);\n }\n}\n\nexport async function createVideoThumbnail(\n inputPath: string,\n outputPath: string,\n height: number,\n): Promise<{ width: number; height: number }> {\n try {\n // Check if input file exists\n await fs.access(inputPath);\n\n // Check if ffmpeg is available\n const ffmpegAvailable = await checkFfmpegAvailability();\n if (!ffmpegAvailable) {\n console.warn(`Warning: ffmpeg is not available. Skipping thumbnail creation for video: ${path.basename(inputPath)}`);\n throw new Error('FFMPEG_NOT_AVAILABLE');\n }\n\n // Get video metadata using ffprobe\n const videoData = await ffprobe(inputPath);\n const videoStream = videoData.streams.find((stream) => stream.codec_type === 'video');\n\n if (!videoStream) {\n throw new Error('No video stream found');\n }\n\n const originalWidth = videoStream.width || 0;\n const originalHeight = videoStream.height || 0;\n\n if (originalWidth === 0 || originalHeight === 0) {\n throw new Error('Invalid video dimensions');\n }\n\n // Calculate width maintaining aspect ratio\n const aspectRatio = originalWidth / originalHeight;\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', // Overwrite output file\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.debug(`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\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 } catch (error) {\n if (error instanceof Error && error.message === 'FFMPEG_NOT_AVAILABLE') {\n // Re-throw the specific error for graceful handling upstream\n throw error;\n }\n if (\n typeof error === 'object' &&\n error !== null &&\n 'message' in error &&\n typeof (error as { message: string }).message === 'string' &&\n ((error as { message: string }).message.includes('ffmpeg') ||\n (error as { message: string }).message.includes('ffprobe'))\n ) {\n throw new Error(\n `Error: ffmpeg is required to process videos. Please install ffmpeg and ensure it is available in your PATH. Failed to process: ${path.basename(inputPath)}`,\n );\n }\n throw new Error(`Failed to create video thumbnail for ${inputPath}: ${error}`);\n }\n}\n","import { z } from 'zod';\n\nexport const ThumbnailSchema = z.object({\n path: z.string(),\n width: z.number(),\n height: z.number(),\n});\n\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});\n\nexport const GallerySectionSchema = z.object({\n title: z.string().optional(),\n description: z.string().optional(),\n images: z.array(MediaFileSchema),\n});\n\nexport const SubGallerySchema = z.object({\n title: z.string(),\n headerImage: z.string(),\n path: z.string(),\n});\n\nexport const GalleryDataSchema = z.object({\n title: z.string(),\n description: z.string(),\n headerImage: z.string(),\n metadata: z.object({\n ogUrl: z.string(),\n }),\n galleryOutputPath: z.string().optional(),\n sections: z.array(GallerySectionSchema),\n subGalleries: z.object({ title: z.string(), galleries: z.array(SubGallerySchema) }),\n});\n\nexport type Thumbnail = z.infer<typeof ThumbnailSchema>;\nexport type MediaFile = z.infer<typeof MediaFileSchema>;\nexport type GallerySection = z.infer<typeof GallerySectionSchema>;\nexport type SubGallery = z.infer<typeof SubGallerySchema>;\nexport type GalleryData = z.infer<typeof GalleryDataSchema>;\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { createImageThumbnail, createVideoThumbnail } from './utils';\n\nimport { GalleryDataSchema, type MediaFile } from '../../types';\nimport { findGalleries } from '../../utils';\n\nimport type { ThumbnailOptions } from './types';\nconst processGallery = async (galleryDir: string, size: number): Promise<number> => {\n const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');\n const thumbnailsPath = path.join(galleryDir, 'gallery', 'thumbnails');\n\n console.log(`\\nProcessing gallery in: ${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 // 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, size);\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 return processedCount;\n } catch (error) {\n console.error(`Error creating thumbnails for ${galleryDir}:`, error);\n return 0;\n }\n};\n\nasync function processMediaFile(\n mediaFile: MediaFile,\n galleryDir: string,\n thumbnailsPath: string,\n size: number,\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 relativeThumbnailPath = path.relative(galleryJsonDir, thumbnailPath);\n\n console.log(`Processing ${mediaFile.type}: ${fileName}`);\n\n let thumbnailDimensions: { width: number; height: number };\n\n if (mediaFile.type === 'image') {\n thumbnailDimensions = await createImageThumbnail(filePath, thumbnailPath, size);\n } else if (mediaFile.type === 'video') {\n thumbnailDimensions = await createVideoThumbnail(filePath, thumbnailPath, size);\n } else {\n console.warn(`Unknown media type: ${mediaFile.type}, skipping...`);\n return mediaFile;\n }\n\n // Update media file with thumbnail information\n const updatedMediaFile: MediaFile = {\n ...mediaFile,\n thumbnail: {\n path: relativeThumbnailPath,\n width: thumbnailDimensions.width,\n height: thumbnailDimensions.height,\n },\n };\n\n return updatedMediaFile;\n } catch (error) {\n if (error instanceof Error && error.message === 'FFMPEG_NOT_AVAILABLE') {\n console.warn(`⚠ Skipping video thumbnail (ffmpeg not available): ${path.basename(mediaFile.path)}`);\n } else {\n console.error(`Error processing ${mediaFile.path}:`, error);\n }\n\n return mediaFile;\n }\n}\n\nexport async function thumbnails(options: ThumbnailOptions): Promise<void> {\n const size = Number.parseInt(options.size);\n\n // Find all gallery directories\n const galleryDirs = findGalleries(options.path, options.recursive);\n\n // If no galleries are found, exit\n if (galleryDirs.length === 0) {\n console.log('No gallery/gallery.json files found.');\n return;\n }\n\n // Process each gallery directory\n let totalProcessed = 0;\n for (const galleryDir of galleryDirs) {\n const processed = await processGallery(galleryDir, size);\n totalProcessed += processed;\n }\n\n // Log processing stats\n if (options.recursive) {\n console.log(`Completed processing ${totalProcessed} total media files across ${galleryDirs.length} galleries.`);\n } else {\n console.log(`Completed processing ${totalProcessed} media files.`);\n }\n}\n","#!/usr/bin/env node\n\nimport process from 'node:process';\n\nimport { Command } from 'commander';\n\nimport { build } from './modules/build';\nimport { init } from './modules/init';\nimport { thumbnails } from './modules/thumbnails';\n\nconst program = new Command();\n\nprogram.name('gallery').description('Simple Photo Gallery CLI').version('0.0.1');\n\nprogram\n .command('init')\n .description('Initialize a gallery by scaning a folder for images and videos')\n .option(\n '-p, --path <path>',\n 'Path where the gallery should be initialized. Default: current working directory',\n process.cwd(),\n )\n .option('-o, --output <path>', 'Output directory for the gallery.json file', '')\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .action(init);\n\nprogram\n .command('thumbnails')\n .description('Create thumbnails for all media files in the gallery')\n .option(\n '-p, --path <path>',\n 'Path to the folder containing the gallery.json file. Default: current working directory',\n process.cwd(),\n )\n .option('-s, --size <size>', 'Thumbnail height in pixels', '200')\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .action(thumbnails);\n\nprogram\n .command('build')\n .description('Build the HTML gallery in the specified directory')\n .option(\n '-p, --path <path>',\n 'Path to the folder containing the gallery.json file. Default: current working directory',\n process.cwd(),\n )\n .option('-r, --recursive', 'Scan subdirectories recursively', false)\n .action(build);\n\nprogram.parse();\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-photo-gallery",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Simple Photo Gallery CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Vladimir Haltakov, Tomasz Rusin",
|
|
@@ -10,15 +10,14 @@
|
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://simple.photo",
|
|
12
12
|
"files": [
|
|
13
|
-
"dist"
|
|
14
|
-
"src"
|
|
13
|
+
"dist"
|
|
15
14
|
],
|
|
16
|
-
"bin":
|
|
17
|
-
"simple-photo-gallery": "./dist/index.js"
|
|
18
|
-
},
|
|
15
|
+
"bin": "./dist/index.js",
|
|
19
16
|
"scripts": {
|
|
20
17
|
"gallery": "tsx src/index.ts",
|
|
21
|
-
"
|
|
18
|
+
"clean": "rm -rf dist",
|
|
19
|
+
"compile": "tsc --noEmit",
|
|
20
|
+
"build": "yarn clean && tsup",
|
|
22
21
|
"check": "tsc --noEmit && eslint . && prettier --check .",
|
|
23
22
|
"lint": "eslint .",
|
|
24
23
|
"lint:fix": "eslint . --fix",
|
|
@@ -54,6 +53,7 @@
|
|
|
54
53
|
"jest": "^30.0.5",
|
|
55
54
|
"prettier": "^3.4.2",
|
|
56
55
|
"ts-jest": "^29.4.1",
|
|
56
|
+
"tsup": "^8.5.0",
|
|
57
57
|
"tsx": "^4.20.3",
|
|
58
58
|
"typescript": "^5.9.0",
|
|
59
59
|
"typescript-eslint": "^8.38.0"
|