simple-photo-gallery 0.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/README.md ADDED
@@ -0,0 +1,349 @@
1
+ # Simple Photo Gallery CLI
2
+
3
+ A command-line tool for creating beautiful photo galleries from your image and video collections. This CLI helps you scan directories, generate thumbnails, and set up Astro-based gallery websites.
4
+
5
+ ## Features
6
+
7
+ - 📸 **Media Scanning**: Automatically scan directories for images and videos
8
+ - 🖼️ **Thumbnail Generation**: Create optimized thumbnails for fast loading
9
+ - 🎥 **Video Support**: Handle video files with ffmpeg integration
10
+ - 📱 **Responsive Design**: Generate galleries that work on all devices
11
+ - ⚡ **Fast Performance**: Optimized thumbnails and lazy loading
12
+ - 🔧 **Astro Integration**: Seamless setup with Astro static site generator
13
+
14
+ ## Installation
15
+
16
+ ### Prerequisites
17
+
18
+ - Node.js 18+
19
+ - npm or yarn
20
+ - ffmpeg (for video processing)
21
+
22
+ ### Install ffmpeg
23
+
24
+ **macOS:**
25
+
26
+ ```bash
27
+ brew install ffmpeg
28
+ ```
29
+
30
+ **Ubuntu/Debian:**
31
+
32
+ ```bash
33
+ sudo apt update
34
+ sudo apt install ffmpeg
35
+ ```
36
+
37
+ **Windows:**
38
+ Download from [ffmpeg.org](https://ffmpeg.org/download.html)
39
+
40
+ ### Install the CLI
41
+
42
+ ```bash
43
+ # Clone the repository
44
+ git clone <repository-url>
45
+ cd simple-photo-gallery-public/cli
46
+
47
+ # Install dependencies
48
+ npm install
49
+
50
+ # Build the CLI
51
+ npm run build
52
+
53
+ # Link globally (optional)
54
+ npm link
55
+ ```
56
+
57
+ ## Commands
58
+
59
+ ### `gallery init`
60
+
61
+ Scan a directory for images and videos to create a `gallery.json` file.
62
+
63
+ ```bash
64
+ gallery init [options]
65
+ ```
66
+
67
+ **Options:**
68
+
69
+ - `-p, --path <path>` - Path to scan for media files (default: current directory)
70
+ - `-o, --output <path>` - Output directory for gallery.json
71
+ - `-r, --recursive` - Scan subdirectories recursively
72
+
73
+ **Examples:**
74
+
75
+ ```bash
76
+ # Scan current directory
77
+ gallery init
78
+
79
+ # Scan specific directory
80
+ gallery init --path /path/to/photos
81
+
82
+ # Scan recursively
83
+ gallery init --path /path/to/photos --recursive
84
+
85
+ # Specify output directory
86
+ gallery init --path /path/to/photos --output /path/to/output
87
+ ```
88
+
89
+ ### `gallery thumbnails`
90
+
91
+ Generate thumbnails for all media files in the gallery.
92
+
93
+ ```bash
94
+ gallery thumbnails [options]
95
+ ```
96
+
97
+ **Options:**
98
+
99
+ - `-p, --path <path>` - Path containing .simple-photo-gallery folder (default: current directory)
100
+ - `-s, --size <size>` - Thumbnail height in pixels (default: 200)
101
+
102
+ **Examples:**
103
+
104
+ ```bash
105
+ # Generate thumbnails with default size
106
+ gallery thumbnails
107
+
108
+ # Generate thumbnails with custom size
109
+ gallery thumbnails --size 300
110
+
111
+ # Specify gallery path
112
+ gallery thumbnails --path /path/to/gallery
113
+ ```
114
+
115
+ ### `gallery build`
116
+
117
+ Configure Astro template to work with external image directories and build galleries.
118
+
119
+ ```bash
120
+ gallery build [options]
121
+ ```
122
+
123
+ **Options:**
124
+
125
+ - `-i, --images-path <path>` - Path to images directory (required)
126
+ - `-r, --recursive` - Scan subdirectories recursively for gallery/gallery.json files
127
+
128
+ **Examples:**
129
+
130
+ ```bash
131
+ # Setup template for a single gallery directory
132
+ gallery build --images-path ../my-photos
133
+
134
+ # Setup template recursively for multiple gallery directories
135
+ gallery build --images-path ../my-photos --recursive
136
+ ```
137
+
138
+ ## Complete Workflow Examples
139
+
140
+ ### Basic Gallery Creation
141
+
142
+ 1. **Prepare your photos directory:**
143
+
144
+ ```bash
145
+ mkdir my-photos
146
+ # Copy your photos and videos to my-photos/
147
+ ```
148
+
149
+ 2. **Scan for media files:**
150
+
151
+ ```bash
152
+ cd my-photos
153
+ gallery init
154
+ ```
155
+
156
+ 3. **Generate thumbnails:**
157
+
158
+ ```bash
159
+ gallery thumbnails --size 250
160
+ ```
161
+
162
+ 4. **Set up Astro template:**
163
+
164
+ ```bash
165
+ cd ../template
166
+ gallery build
167
+ ```
168
+
169
+ 5. **Build and serve:**
170
+
171
+ ```bash
172
+ npm run dev
173
+ ```
174
+
175
+ ### Advanced Workflow with Multiple Sections
176
+
177
+ 1. **Organize photos in subdirectories:**
178
+
179
+ ```
180
+ my-photos/
181
+ ├── vacation/
182
+ │ ├── beach.jpg
183
+ │ ├── mountains.jpg
184
+ │ └── sunset.mp4
185
+ ├── family/
186
+ │ ├── birthday.jpg
187
+ │ └── christmas.jpg
188
+ └── events/
189
+ ├── wedding.jpg
190
+ └── graduation.jpg
191
+ ```
192
+
193
+ 2. **Scan with recursive option:**
194
+
195
+ ```bash
196
+ cd my-photos
197
+ gallery init --recursive
198
+ ```
199
+
200
+ 3. **Edit gallery.json to create sections:**
201
+
202
+ ```json
203
+ {
204
+ "title": "My Photo Collection",
205
+ "description": "A collection of my favorite moments",
206
+ "headerImage": "vacation/beach.jpg",
207
+ "metadata": { "ogUrl": "" },
208
+ "sections": [
209
+ {
210
+ "title": "Vacation Memories",
211
+ "description": "Amazing trips and adventures",
212
+ "images": [
213
+ // vacation images will be here
214
+ ]
215
+ },
216
+ {
217
+ "title": "Family Moments",
218
+ "description": "Precious family memories",
219
+ "images": [
220
+ // family images will be here
221
+ ]
222
+ },
223
+ {
224
+ "title": "Special Events",
225
+ "description": "Important life events",
226
+ "images": [
227
+ // events images will be here
228
+ ]
229
+ }
230
+ ]
231
+ }
232
+ ```
233
+
234
+ 4. **Generate thumbnails:**
235
+
236
+ ```bash
237
+ gallery thumbnails --size 300 --recursive
238
+ ```
239
+
240
+ 5. **Set up and build:**
241
+
242
+ ```bash
243
+ cd ../template
244
+ gallery build --recursive
245
+ ```
246
+
247
+ ## File Structure
248
+
249
+ After running the CLI, your project will have this structure:
250
+
251
+ ```
252
+ my-photos/
253
+ ├── .simple-photo-gallery/
254
+ │ ├── gallery.json # Gallery metadata and file list
255
+ │ └── thumbnails/ # Generated thumbnails
256
+ │ ├── image1.jpg
257
+ │ ├── image2.jpg
258
+ │ └── video1.jpg
259
+ ├── photo1.jpg
260
+ ├── photo2.jpg
261
+ └── video1.mp4
262
+
263
+ template/
264
+ ├── astro.config.ts # Modified Astro config
265
+ ├── gallery.json # Copied gallery data
266
+ └── ... (other Astro files)
267
+ ```
268
+
269
+ ## Supported Media Formats
270
+
271
+ **Images:**
272
+
273
+ - JPEG (.jpg, .jpeg)
274
+ - PNG (.png)
275
+ - WebP (.webp)
276
+ - GIF (.gif)
277
+ - TIFF (.tiff, .tif)
278
+
279
+ **Videos:**
280
+
281
+ - MP4 (.mp4)
282
+ - MOV (.mov)
283
+ - AVI (.avi)
284
+ - WebM (.webm)
285
+ - MKV (.mkv)
286
+
287
+ _Note: Video processing requires ffmpeg to be installed and available in your PATH._
288
+
289
+ ## Troubleshooting
290
+
291
+ ### Common Issues
292
+
293
+ **"ffprobe not found" error:**
294
+
295
+ - Install ffmpeg: `brew install ffmpeg` (macOS) or `sudo apt install ffmpeg` (Ubuntu)
296
+ - Ensure ffmpeg is in your PATH
297
+
298
+ **"Gallery not found" error:**
299
+
300
+ - Run `gallery init` first to create the gallery.json file
301
+ - Check that the path to .simple-photo-gallery folder is correct
302
+
303
+ **Thumbnail generation fails:**
304
+
305
+ - Check file permissions
306
+ - Ensure sufficient disk space
307
+ - Verify media files are not corrupted
308
+
309
+ ### Getting Help
310
+
311
+ - Check that all required dependencies are installed
312
+ - Verify file paths are correct and accessible
313
+ - Ensure ffmpeg is properly installed for video processing
314
+ - Review the generated gallery.json file for any issues
315
+
316
+ ## Development
317
+
318
+ ### Building from Source
319
+
320
+ ```bash
321
+ # Install dependencies
322
+ npm install
323
+
324
+ # Build the CLI
325
+ npm run build
326
+
327
+ # Run in development mode
328
+ npm run gallery
329
+ ```
330
+
331
+ ### Code Quality
332
+
333
+ ```bash
334
+ # Check TypeScript
335
+ npm run check
336
+
337
+ # Lint code
338
+ npm run lint
339
+
340
+ # Fix linting issues
341
+ npm run lint:fix
342
+
343
+ # Format code
344
+ npm run format
345
+ ```
346
+
347
+ ## License
348
+
349
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { Command } from 'commander';
4
+ import { build } from './modules/build';
5
+ import { init } from './modules/init';
6
+ import { thumbnails } from './modules/thumbnails';
7
+ const program = new Command();
8
+ program.name('gallery').description('Simple Photo Gallery CLI').version('0.0.1');
9
+ program
10
+ .command('init')
11
+ .description('Initialize a gallery by scaning a folder for images and videos')
12
+ .option('-p, --path <path>', 'Path where the gallery should be initialized. Default: current working directory', process.cwd())
13
+ .option('-o, --output <path>', 'Output directory for the gallery.json file', '')
14
+ .option('-r, --recursive', 'Scan subdirectories recursively', false)
15
+ .action(init);
16
+ program
17
+ .command('thumbnails')
18
+ .description('Create thumbnails for all media files in the gallery')
19
+ .option('-p, --path <path>', 'Path to the folder containing the gallery.json file. Default: current working directory', process.cwd())
20
+ .option('-s, --size <size>', 'Thumbnail height in pixels', '200')
21
+ .option('-r, --recursive', 'Scan subdirectories recursively', false)
22
+ .action(thumbnails);
23
+ program
24
+ .command('build')
25
+ .description('Build the HTML gallery in the specified directory')
26
+ .option('-p, --path <path>', 'Path to the folder containing the gallery.json file. Default: current working directory', process.cwd())
27
+ .option('-r, --recursive', 'Scan subdirectories recursively', false)
28
+ .action(build);
29
+ program.parse();
@@ -0,0 +1,56 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+ import { resolveFromCurrentDir } from './utils';
6
+ import { findGalleries } from '../../utils';
7
+ function buildGallery(galleryDir, templateDir) {
8
+ // Make sure the gallery.json file exists
9
+ const galleryJsonPath = path.join(galleryDir, 'gallery', 'gallery.json');
10
+ if (!fs.existsSync(galleryJsonPath)) {
11
+ console.log(`No gallery/gallery.json found in ${galleryDir}`);
12
+ return;
13
+ }
14
+ // Build the template
15
+ const originalEnv = { ...process.env };
16
+ try {
17
+ // Set the environment variable for the gallery.json path that will be used by the template
18
+ process.env.GALLERY_JSON_PATH = galleryJsonPath;
19
+ process.env.GALLERY_OUTPUT_DIR = path.join(galleryDir, 'gallery');
20
+ execSync('npm run build', { cwd: templateDir, stdio: 'inherit' });
21
+ }
22
+ catch (error) {
23
+ console.error(error);
24
+ console.error(`Build failed for ${galleryDir}`);
25
+ return;
26
+ }
27
+ finally {
28
+ // Restore original environment and gallery.json
29
+ process.env = originalEnv;
30
+ }
31
+ // Copy the build output to the output directory
32
+ const outputDir = path.join(galleryDir, 'gallery');
33
+ const buildDir = path.join(outputDir, '_build');
34
+ fs.cpSync(buildDir, outputDir, { recursive: true });
35
+ // Move the index.html to the gallery directory
36
+ fs.copyFileSync(path.join(outputDir, 'index.html'), path.join(galleryDir, 'index.html'));
37
+ fs.rmSync(path.join(outputDir, 'index.html'));
38
+ // Clean up the _build directory
39
+ console.log('Cleaning up build directory...');
40
+ fs.rmSync(buildDir, { recursive: true, force: true });
41
+ }
42
+ export async function build(options) {
43
+ // Get the template directory
44
+ const templateDir = path.join(resolveFromCurrentDir('../../../../../'), 'template');
45
+ // Find all gallery directories
46
+ const galleryDirs = findGalleries(options.path, options.recursive);
47
+ // If no galleries are found, exit
48
+ if (galleryDirs.length === 0) {
49
+ console.log('No gallery/gallery.json files found.');
50
+ return;
51
+ }
52
+ // Process each gallery
53
+ for (const dir of galleryDirs) {
54
+ buildGallery(dir, templateDir);
55
+ }
56
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import path from 'node:path';
2
+ // __dirname workaround for ESM modules
3
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
4
+ // Helper function to resolve paths relative to current file
5
+ export const resolveFromCurrentDir = (...segments) => {
6
+ return path.resolve(__dirname, ...segments);
7
+ };
@@ -0,0 +1,4 @@
1
+ // Image extensions
2
+ export const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.tiff', '.tif', '.svg']);
3
+ // Video extensions
4
+ export const VIDEO_EXTENSIONS = new Set(['.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v', '.3gp']);
@@ -0,0 +1,156 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { capitalizeTitle, getImageMetadata, getVideoDimensions, isMediaFile } from './utils';
4
+ async function scanDirectory(dirPath) {
5
+ const mediaFiles = [];
6
+ try {
7
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
8
+ for (const entry of entries) {
9
+ if (entry.isFile()) {
10
+ const fullPath = path.join(dirPath, entry.name);
11
+ const mediaType = isMediaFile(entry.name);
12
+ if (mediaType) {
13
+ console.log(`Processing ${mediaType}: ${entry.name}`);
14
+ let metadata = { width: 0, height: 0 };
15
+ try {
16
+ if (mediaType === 'image') {
17
+ metadata = await getImageMetadata(fullPath);
18
+ }
19
+ else if (mediaType === 'video') {
20
+ try {
21
+ const videoDimensions = await getVideoDimensions(fullPath);
22
+ metadata = { ...videoDimensions };
23
+ }
24
+ catch (videoError) {
25
+ if (typeof videoError === 'object' &&
26
+ videoError !== null &&
27
+ 'message' in videoError &&
28
+ typeof videoError.message === 'string' &&
29
+ (videoError.message.includes('ffprobe') ||
30
+ videoError.message.includes('ffmpeg'))) {
31
+ console.error(`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}`);
32
+ }
33
+ else {
34
+ console.error(`Error processing video ${entry.name}:`, videoError);
35
+ }
36
+ continue; // Skip this file
37
+ }
38
+ }
39
+ }
40
+ catch (mediaError) {
41
+ console.error(`Error processing file ${entry.name}:`, mediaError);
42
+ continue; // Skip this file
43
+ }
44
+ const mediaFile = {
45
+ type: mediaType,
46
+ path: fullPath,
47
+ width: metadata.width,
48
+ height: metadata.height,
49
+ };
50
+ if (metadata.description) {
51
+ mediaFile.alt = metadata.description;
52
+ }
53
+ mediaFiles.push(mediaFile);
54
+ }
55
+ }
56
+ }
57
+ }
58
+ catch (error) {
59
+ console.error(`Error scanning directory ${dirPath}:`, error);
60
+ }
61
+ return mediaFiles;
62
+ }
63
+ async function createGalleryJson(mediaFiles, galleryJsonPath, subGalleries = []) {
64
+ const galleryDir = path.dirname(galleryJsonPath);
65
+ // Convert media file paths to be relative to gallery.json
66
+ const relativeMediaFiles = mediaFiles.map((file) => ({
67
+ ...file,
68
+ path: path.relative(galleryDir, file.path),
69
+ }));
70
+ // Convert subGallery header image paths to be relative to gallery.json
71
+ const relativeSubGalleries = subGalleries.map((subGallery) => ({
72
+ ...subGallery,
73
+ headerImage: subGallery.headerImage ? path.relative(galleryDir, subGallery.headerImage) : '',
74
+ }));
75
+ const galleryData = {
76
+ title: 'My Gallery',
77
+ description: 'My gallery with fantastic photos.',
78
+ headerImage: relativeMediaFiles[0]?.path || '',
79
+ metadata: { ogUrl: '' },
80
+ sections: [
81
+ {
82
+ images: relativeMediaFiles,
83
+ },
84
+ ],
85
+ subGalleries: {
86
+ title: 'Sub Galleries',
87
+ galleries: relativeSubGalleries,
88
+ },
89
+ };
90
+ await fs.writeFile(galleryJsonPath, JSON.stringify(galleryData, null, 2));
91
+ }
92
+ async function processDirectory(dirPath, options) {
93
+ let totalFiles = 0;
94
+ const subGalleries = [];
95
+ // Scan current directory for media files
96
+ const mediaFiles = await scanDirectory(dirPath);
97
+ totalFiles += mediaFiles.length;
98
+ // Process subdirectories only if recursive mode is enabled
99
+ if (options.recursive) {
100
+ try {
101
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
102
+ for (const entry of entries) {
103
+ if (entry.isDirectory() && entry.name !== 'gallery') {
104
+ const subDirPath = path.join(dirPath, entry.name);
105
+ const result = await processDirectory(subDirPath, options);
106
+ totalFiles += result.totalFiles;
107
+ // If the subdirectory had media files, add it as a subGallery
108
+ if (result.subGallery) {
109
+ subGalleries.push(result.subGallery);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.error(`Error reading directory ${dirPath}:`, error);
116
+ }
117
+ }
118
+ // Create gallery.json if there are media files or subGalleries
119
+ if (mediaFiles.length > 0 || subGalleries.length > 0) {
120
+ const outputPath = options.output
121
+ ? path.resolve(options.output, path.relative(path.resolve(options.path), dirPath), 'gallery')
122
+ : path.join(dirPath, 'gallery');
123
+ const galleryJsonPath = path.join(outputPath, 'gallery.json');
124
+ // Create output directory
125
+ await fs.mkdir(outputPath, { recursive: true });
126
+ // Create gallery.json for this directory
127
+ await createGalleryJson(mediaFiles, galleryJsonPath, subGalleries);
128
+ console.log(`Gallery JSON created at: ${galleryJsonPath} (${mediaFiles.length} files, ${subGalleries.length} subgalleries)`);
129
+ }
130
+ // Return result with suGgallery info if this directory has media files
131
+ const result = { totalFiles };
132
+ if (mediaFiles.length > 0 || subGalleries.length > 0) {
133
+ const dirName = path.basename(dirPath);
134
+ result.subGallery = {
135
+ title: capitalizeTitle(dirName),
136
+ headerImage: mediaFiles[0]?.path || '',
137
+ path: path.join('..', dirName),
138
+ };
139
+ }
140
+ return result;
141
+ }
142
+ export async function init(options) {
143
+ const scanPath = path.resolve(options.path);
144
+ console.log(`Scanning directory: ${scanPath}`);
145
+ console.log(`Recursive: ${options.recursive}`);
146
+ try {
147
+ // Ensure scan path exists
148
+ await fs.access(scanPath);
149
+ // Process the directory tree with the specified recursion setting
150
+ const result = await processDirectory(scanPath, options);
151
+ console.log(`Total files processed: ${result.totalFiles}`);
152
+ }
153
+ catch (error) {
154
+ throw new Error(`Error during scan: ${error}`);
155
+ }
156
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import exifReader from 'exif-reader';
4
+ import ffprobe from 'node-ffprobe';
5
+ import sharp from 'sharp';
6
+ import { IMAGE_EXTENSIONS, VIDEO_EXTENSIONS } from '../const';
7
+ export async function getImageMetadata(filePath) {
8
+ try {
9
+ const metadata = await sharp(filePath).metadata();
10
+ let description;
11
+ // Extract description from EXIF data
12
+ if (metadata.exif) {
13
+ try {
14
+ const exifData = exifReader(metadata.exif);
15
+ if (exifData.Image?.ImageDescription) {
16
+ description = exifData.Image.ImageDescription;
17
+ }
18
+ else if (exifData.Image?.Description) {
19
+ description = exifData.Image.Description.toString();
20
+ }
21
+ }
22
+ catch {
23
+ // EXIF parsing failed, but that's OK
24
+ }
25
+ }
26
+ return {
27
+ width: metadata.width || 0,
28
+ height: metadata.height || 0,
29
+ description,
30
+ };
31
+ }
32
+ catch (error) {
33
+ console.warn(`Warning: Could not get metadata for image ${filePath}:`, error);
34
+ return { width: 0, height: 0 };
35
+ }
36
+ }
37
+ export async function getVideoDimensions(filePath) {
38
+ try {
39
+ const data = await ffprobe(filePath);
40
+ const videoStream = data.streams.find((stream) => stream.codec_type === 'video');
41
+ if (videoStream) {
42
+ return {
43
+ width: videoStream.width || 0,
44
+ height: videoStream.height || 0,
45
+ };
46
+ }
47
+ return { width: 0, height: 0 };
48
+ }
49
+ catch (error) {
50
+ console.warn(`Warning: Could not get dimensions for video ${filePath}:`, error);
51
+ return { width: 0, height: 0 };
52
+ }
53
+ }
54
+ export function isMediaFile(fileName) {
55
+ const ext = path.extname(fileName).toLowerCase();
56
+ if (IMAGE_EXTENSIONS.has(ext))
57
+ return 'image';
58
+ if (VIDEO_EXTENSIONS.has(ext))
59
+ return 'video';
60
+ return null;
61
+ }
62
+ export function capitalizeTitle(folderName) {
63
+ return folderName
64
+ .replace('-', ' ')
65
+ .replace('_', ' ')
66
+ .split(' ')
67
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
68
+ .join(' ');
69
+ }
70
+ export async function hasMediaFiles(dirPath) {
71
+ try {
72
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
73
+ // Check for media files in current directory
74
+ for (const entry of entries) {
75
+ if (entry.isFile() && isMediaFile(entry.name)) {
76
+ return true;
77
+ }
78
+ }
79
+ // Check subdirectories recursively
80
+ for (const entry of entries) {
81
+ if (entry.isDirectory() && entry.name !== 'gallery') {
82
+ const subDirPath = path.join(dirPath, entry.name);
83
+ if (await hasMediaFiles(subDirPath)) {
84
+ return true;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ catch (error) {
90
+ console.error(`Error checking for media files in ${dirPath}:`, error);
91
+ }
92
+ return false;
93
+ }