Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 95 additions & 28 deletions audio/server/imageUtils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,98 @@
import { DeskThing } from '@deskthing/server';
import { existsSync, mkdirSync, writeFile, readdirSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import { fileExtension, imagesDir } from "./settings"


const pipelineAsync = promisify(pipeline);

export const saveImage = async (imageData: string, fileName: string): Promise<string | undefined> => {
// Handle both base64 and binary image data
if (imageData.startsWith('data:image')) {
// Check if it's a URL
if (imageData.startsWith('http://') || imageData.startsWith('https://') || imageData.startsWith('file://')) {
DeskThing.sendLog('Processing image URL');
return await downloadImage(imageData, fileName);
}
// Handle base64 data
else if (imageData.startsWith('data:image')) {
DeskThing.sendLog('Processing base64 image data');
return await saveBase64Image(imageData, fileName);
} else {
// Assume it's raw binary data
}
// Handle binary data
else {
DeskThing.sendLog('Processing binary image data');
return await saveBinaryImage(imageData, fileName);
}
}


const downloadImage = async (url: string, fileName: string): Promise<string | undefined> => {
try {
// Handle local files
if (url.startsWith('file://')) {
return await handleLocalFile(url, fileName, imagesDir);
}

// Download from url
const response = await fetch(url);
if (!response.ok) {
DeskThing.sendError(`Failed to fetch image: ${response.status} ${response.statusText}`);
return;
}


const filePath = join(imagesDir, `${fileName}.${fileExtension}`);

// Get the response as a stream and save it
if (response.body) {
const fileStream = createWriteStream(filePath);
await pipelineAsync(response.body as any, fileStream);

DeskThing.sendLog(`Successfully downloaded image: ${fileName}.${fileExtension}`);
return `/resource/image/audio/${fileName}.${fileExtension}`;
} else {
DeskThing.sendError('No response body received');
return;
}

} catch (error) {
DeskThing.sendError(`Failed to download image: ${error instanceof Error ? error.message : 'Unknown error'}`);
return;
}
}


const handleLocalFile = async (fileUrl: string, fileName: string, imagesDir: string): Promise<string | undefined> => {
try {
// Convert file:// URL to local path
const localPath = fileUrl.replace('file://', '');

// Check if local file exists
if (!existsSync(localPath)) {
DeskThing.sendError(`Local file does not exist: ${localPath}`);
return;
}

// Always save as PNG for compatibility
const targetPath = join(imagesDir, `${fileName}.${fileExtension}`);

// Read the local file and copy it
const fs = await import('node:fs/promises');
await fs.copyFile(localPath, targetPath);

DeskThing.sendLog(`Successfully copied local file: ${fileName}.${fileExtension}`);
return `/resource/image/audio/${fileName}.${fileExtension}`;

} catch (error) {
DeskThing.sendError(`Failed to handle local file: ${error instanceof Error ? error.message : 'Unknown error'}`);
return;
}
}



const saveBase64Image = async (base64Image: string, fileName: string): Promise<string | undefined> => {
// Extract the image format from the data URL
const matches = base64Image.match(/^data:image\/([a-zA-Z+]+);base64,/);
Expand All @@ -22,19 +101,12 @@ const saveBase64Image = async (base64Image: string, fileName: string): Promise<s
return;
}

const imageFormat = matches[1];
// Always save as PNG for compatibility
const base64Data = base64Image.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');

// Create the images directory if it doesn't exist
const imagesDir = join(__dirname, '../images');
if (!existsSync(imagesDir)) {
DeskThing.sendLog('Creating images directory');
mkdirSync(imagesDir, { recursive: true });
}


// Generate file path with correct extension
const filePath = join(imagesDir, `${fileName}.${imageFormat}`);

const filePath = join(imagesDir, `${fileName}.${fileExtension}`);
// Write the file and return a promise
return new Promise((resolve, reject) => {
writeFile(filePath, base64Data, 'base64', (err) => {
Expand All @@ -43,23 +115,18 @@ const saveBase64Image = async (base64Image: string, fileName: string): Promise<s
reject(err);
return;
}
DeskThing.sendLog(`Successfully saved base64 image: ${fileName}.${imageFormat}`);
resolve(`/resource/image/audio/${fileName}.${imageFormat}`);
DeskThing.sendLog(`Successfully saved base64 image: ${fileName}.${fileExtension}`);
resolve(`/resource/image/audio/${fileName}.${fileExtension}`);
});
});
}

const saveBinaryImage = async (binaryData: string, fileName: string): Promise<string | undefined> => {
// Create the images directory if it doesn't exist
const imagesDir = join(__dirname, '../images');
if (!existsSync(imagesDir)) {
DeskThing.sendLog('Creating images directory');
mkdirSync(imagesDir, { recursive: true });
}

// Generate file path (assuming png as default)
const filePath = join(imagesDir, `${fileName}.png`);


// Generate file path
const filePath = join(imagesDir, `${fileName}.${fileExtension}`);

// Write the file and return a promise
return new Promise((resolve, reject) => {
writeFile(filePath, binaryData, 'binary', (err) => {
Expand All @@ -68,8 +135,8 @@ const saveBinaryImage = async (binaryData: string, fileName: string): Promise<st
reject(err);
return;
}
DeskThing.sendLog(`Successfully saved binary image: ${fileName}.png`);
resolve(`/resource/image/audio/${fileName}.png`);
DeskThing.sendLog(`Successfully saved binary image: ${fileName}.${fileExtension}`);
resolve(`/resource/image/audio/${fileName}.${fileExtension}`);
});
});
}
Expand Down
6 changes: 6 additions & 0 deletions audio/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { DESKTHING_EVENTS } from "@deskthing/types";
import { initializeListeners } from "./initializer"
import { MediaStore } from "./mediaStore";
import { deleteImages } from "./imageUtils";
import { existsSync, mkdirSync, writeFile, readdirSync, unlinkSync } from 'node:fs';
import { fileExtension, imagesDir } from "./settings";

const start = async () => {
await initializeListeners()
if (!existsSync(imagesDir)) {
DeskThing.sendLog('Creating images directory'); // Move this to start, that way we save unnecessary checks down the line
mkdirSync(imagesDir, { recursive: true });
}
DeskThing.sendLog('Server Started!');
};

Expand Down
8 changes: 8 additions & 0 deletions audio/server/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

export { imagesDir, fileExtension }
import { join } from 'node:path';



const imagesDir = join(__dirname, '../images');
const fileExtension = 'png'