media-exporter-processor
Version:
Media processing API with thumbnail generation and cloud storage
329 lines • 15.3 kB
JavaScript
;
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