UNPKG

media-exporter-processor

Version:

Media processing API with thumbnail generation and cloud storage

329 lines 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ImageProcessingService = void 0; const child_process_1 = require("child_process"); const fs_1 = require("fs"); class ImageProcessingService { constructor(thumbnailService, uploadService) { this.thumbnailService = thumbnailService; this.uploadService = uploadService; } async processImage(imageBuffer, queryParams, originalFilename) { // Ensure temp directory exists const tempDir = "/tmp"; try { await fs_1.promises.access(tempDir); } catch { await fs_1.promises.mkdir(tempDir, { recursive: true }); } const tempFile = `/tmp/image-${Date.now()}-${Math.random() .toString(36) .substring(2)}.jpg`; try { // Write image to temporary file console.log(`Writing image buffer (${imageBuffer.length} bytes) to ${tempFile}`); await fs_1.promises.writeFile(tempFile, imageBuffer); // Verify file was written and is accessible const stats = await fs_1.promises.stat(tempFile); console.log(`Temp file created: ${tempFile} (${stats.size} bytes)`); // Double-check file can be read await fs_1.promises.access(tempFile, fs_1.constants.R_OK); console.log(`Temp file is readable: ${tempFile}`); // Add a small delay to ensure file system sync await new Promise((resolve) => setTimeout(resolve, 100)); // Parse and validate parameters const metadata = this.parseImageMetadata(queryParams, originalFilename); // Generate thumbnails FIRST from the original image console.log(`Generating thumbnails from: ${tempFile}`); const thumbnails = await this.thumbnailService.generateImageThumbnails(tempFile); console.log(`Generated ${thumbnails.length} thumbnails`); // Add metadata to image using exiftool const processedImageBuffer = await this.addMetadataToImage(tempFile, metadata); // Upload image and thumbnails to R2 const result = await this.uploadService.uploadImageWithThumbnails(processedImageBuffer, originalFilename || `image-${Date.now()}.jpg`, thumbnails, queryParams.prefix, this.createImageMetadataHeaders(metadata)); return result; } finally { // Clean up temporary file try { await fs_1.promises.unlink(tempFile); } catch (error) { // Ignore cleanup errors } } } parseImageMetadata(queryParams, originalFilename) { const latitude = queryParams.lat || 37.7749; const longitude = queryParams.lon || -122.4194; const altitude = queryParams.alt; let creationDate; if (queryParams.timestamp) { // Try to parse as Unix timestamp first (if it's a number) if (/^\d+$/.test(queryParams.timestamp)) { creationDate = new Date(parseInt(queryParams.timestamp) * 1000); } else { // Try to parse as ISO string creationDate = new Date(queryParams.timestamp); } // Validate the date if (isNaN(creationDate.getTime())) { throw new Error("Invalid timestamp format. Use ISO string or Unix timestamp."); } } else { // Default to current date if no timestamp provided creationDate = new Date(); } return { latitude, longitude, altitude, creationDate, originalFilename, }; } async addMetadataToImage(imagePath, metadata) { const outputPath = `${imagePath}-processed.jpg`; try { await this.runExiftool(imagePath, outputPath, metadata); return await fs_1.promises.readFile(outputPath); } finally { try { await fs_1.promises.unlink(outputPath); } catch (error) { // Ignore cleanup errors } } } async runExiftool(inputPath, outputPath, metadata) { console.log(`Using direct ExifTool binary approach for images`); // Debug: Check what's available in the file system try { const rootContents = await fs_1.promises.readdir("/"); console.log(`Contents of /:`, rootContents); } catch (error) { console.log(`Could not read / directory:`, error); } // Debug: Check what's available in /opt try { const optContents = await fs_1.promises.readdir("/opt"); console.log(`Contents of /opt:`, optContents); if (optContents.includes("bin")) { const optBinContents = await fs_1.promises.readdir("/opt/bin"); console.log(`Contents of /opt/bin:`, optBinContents); } if (optContents.includes("lib")) { const optLibContents = await fs_1.promises.readdir("/opt/lib"); console.log(`Contents of /opt/lib:`, optLibContents); // Check for ExifTool in perl5 directories try { const perl5Contents = await fs_1.promises.readdir("/opt/lib/perl5"); console.log(`Contents of /opt/lib/perl5:`, perl5Contents); if (perl5Contents.includes("site_perl")) { const sitePerlContents = await fs_1.promises.readdir("/opt/lib/perl5/site_perl"); console.log(`Contents of /opt/lib/perl5/site_perl:`, sitePerlContents); // Check the version directory if (sitePerlContents.includes("5.34.1")) { const versionContents = await fs_1.promises.readdir("/opt/lib/perl5/site_perl/5.34.1"); console.log(`Contents of /opt/lib/perl5/site_perl/5.34.1:`, versionContents); // Look for ExifTool or Image directory if (versionContents.includes("Image")) { const imageContents = await fs_1.promises.readdir("/opt/lib/perl5/site_perl/5.34.1/Image"); console.log(`Contents of /opt/lib/perl5/site_perl/5.34.1/Image:`, imageContents); } // Look for exiftool script if (versionContents.includes("exiftool")) { console.log(`Found exiftool script in site_perl!`); } } } } catch (error) { console.log(`Could not read /opt/lib/perl5:`, error); } } } catch (error) { console.log(`Could not read /opt directory:`, error); } const { latitude, longitude, altitude, creationDate } = metadata; const alt = altitude !== undefined ? altitude : 0; console.log(`Copying file from ${inputPath} to ${outputPath}`); // Copy the file first await fs_1.promises.copyFile(inputPath, outputPath); console.log(`Writing image metadata: GPS(${latitude}, ${longitude}, ${alt}), Date: ${creationDate.toISOString()}`); return new Promise((resolve, reject) => { const args = [ "-overwrite_original", "-m", "-P", `-GPSLatitude=${Math.abs(latitude)}`, `-GPSLatitudeRef=${latitude >= 0 ? "N" : "S"}`, `-GPSLongitude=${Math.abs(longitude)}`, `-GPSLongitudeRef=${longitude >= 0 ? "E" : "W"}`, `-GPSAltitude=${alt}`, `-GPSAltitudeRef=0`, `-DateTime=${creationDate .toISOString() .replace("T", " ") .replace(/\..+/, "")}`, `-DateTimeOriginal=${creationDate .toISOString() .replace("T", " ") .replace(/\..+/, "")}`, `-DateTimeDigitized=${creationDate .toISOString() .replace("T", " ") .replace(/\..+/, "")}`, `-Title=Image with Location`, `-Description=Image with GPS coordinates: ${latitude}, ${longitude}${altitude ? `, ${altitude}m` : ""} - Created: ${creationDate.toISOString()}`, `-Comment=GPS: ${latitude.toFixed(6)}, ${longitude.toFixed(6)}, ${alt.toFixed(1)}m`, outputPath, ]; // Debug: Check ExifTool layer structure const debugPaths = ["/opt/exiftool", "/opt/exiftool/bin", "/opt/lib"]; for (const debugPath of debugPaths) { try { const contents = require("fs").readdirSync(debugPath); console.log(`Contents of ${debugPath}:`, contents); } catch (error) { console.log(`Could not read ${debugPath}:`, error); } } // Check if perl is available in ALL possible locations const perlPaths = [ "/usr/bin/perl", "/bin/perl", "/opt/bin/perl", "/opt/exiftool/bin/perl", "/opt/exiftool/perl", "/opt/perl/bin/perl", ]; let foundPerlPath = null; for (const perlPath of perlPaths) { try { require("fs").accessSync(perlPath, require("fs").constants.F_OK); console.log(`Found Perl at: ${perlPath}`); if (!foundPerlPath) foundPerlPath = perlPath; // Use first found } catch (error) { console.log(`Perl not found at: ${perlPath}`); } } if (foundPerlPath) { console.log(`Will use Perl at: ${foundPerlPath}`); } else { console.log(`No Perl found anywhere! Trying system perl...`); foundPerlPath = "perl"; // fallback to PATH } // Try different possible ExifTool paths const possiblePaths = [ "/opt/exiftool/bin/exiftool", // Your layer path (confirmed working) "/opt/bin/exiftool", // Alternative layer path "/bin/exiftool", "/opt/exiftool", "exiftool", // fallback to PATH ]; // Try to find a working ExifTool path synchronously let exiftoolPath = possiblePaths[0]; for (const path of possiblePaths) { try { require("fs").accessSync(path, require("fs").constants.F_OK); exiftoolPath = path; console.log(`Found ExifTool at: ${exiftoolPath}`); break; } catch (error) { console.log(`ExifTool not found at: ${path}`); } } console.log(`Using ExifTool at: ${exiftoolPath}`); // Debug: Check what's actually in /opt/lib64 (we saw this directory exists) try { const lib64Contents = require("fs").readdirSync("/opt/lib64"); console.log(`Contents of /opt/lib64:`, lib64Contents); // Check each file for libnsl const libnslFiles = lib64Contents.filter((file) => file.includes("libnsl")); if (libnslFiles.length > 0) { console.log(`Found libnsl files in /opt/lib64:`, libnslFiles); } else { console.log(`No libnsl files found in /opt/lib64`); } } catch (error) { console.log(`Could not read /opt/lib64:`, error); } // Also check system lib64 try { const systemLib64Contents = require("fs").readdirSync("/lib64"); console.log(`Contents of /lib64:`, systemLib64Contents.slice(0, 10), `... (showing first 10)`); const systemLibnslFiles = systemLib64Contents.filter((file) => file.includes("libnsl")); if (systemLibnslFiles.length > 0) { console.log(`Found libnsl files in /lib64:`, systemLibnslFiles); } } catch (error) { console.log(`Could not read /lib64:`, error); } // Use the correct paths we discovered from debug output const env = { ...process.env, PATH: "/opt/bin:" + (process.env.PATH || ""), PERL5LIB: "/opt/lib/perl5:/opt/lib/perl5/site_perl", LD_LIBRARY_PATH: "/opt/lib64:/opt/lib:/lib64:/usr/lib64", }; console.log(`Using ExifTool: ${exiftoolPath} ${args.join(" ")}`); console.log(`Environment PATH: ${env.PATH}`); console.log(`Environment PERL5LIB: ${env.PERL5LIB}`); console.log(`Environment LD_LIBRARY_PATH: ${env.LD_LIBRARY_PATH}`); const exiftoolProcess = (0, child_process_1.spawn)(exiftoolPath, args, { env }); let stdout = ""; let stderr = ""; exiftoolProcess.stdout.on("data", (data) => { stdout += data.toString(); }); exiftoolProcess.stderr.on("data", (data) => { stderr += data.toString(); }); exiftoolProcess.on("close", (code) => { console.log(`ExifTool process completed with code: ${code}`); console.log(`ExifTool stdout: ${stdout}`); if (stderr) { console.log(`ExifTool stderr: ${stderr}`); } if (code === 0) { console.log(`ExifTool write operation completed successfully`); resolve(); } else { reject(new Error(`ExifTool failed with code ${code}: ${stderr || stdout}`)); } }); exiftoolProcess.on("error", (error) => { console.error(`ExifTool spawn error:`, error); reject(new Error(`ExifTool spawn error: ${error.message}`)); }); }); } createImageMetadataHeaders(metadata) { return { "x-amz-meta-latitude": metadata.latitude.toString(), "x-amz-meta-longitude": metadata.longitude.toString(), "x-amz-meta-altitude": metadata.altitude?.toString() || "", "x-amz-meta-creation-date": metadata.creationDate.toISOString(), "x-amz-meta-original-filename": metadata.originalFilename || "", }; } } exports.ImageProcessingService = ImageProcessingService; //# sourceMappingURL=ImageProcessingService.js.map