Below is one common explanation and solution for this kind of issue. In your case the error message
illegal operation on a directory, open 'C:\local\Temp\test.webm'
often appears when the code (or one of its asynchronous steps) ends up trying to open a directory instead of a file. Here are two points to check:
1. Ensure You’re Writing to and Reading from the File (Not a Directory)
When using the OS temporary directory (via os.tmpdir), you need to make sure you’re appending a filename to it. For example:
import { join } from "path";
import { tmpdir } from "os";
const tempDir = tmpdir();
const saveTo = join(tempDir, "audio.webm");
Double–check that:
- You are not accidentally creating a directory with the same name as your file.
- You’re using the same variable when writing (using createWriteStream) and later reading the file (using createReadStream or fs.readFile).
2. Await the Completion of the File Upload (Stream Piping)
In Azure Functions V4 the asynchronous behavior might differ from your local environment. In your trigger you call:
without awaiting its completion. If you start processing (reading the file, checking its length, etc.) before the stream writing is complete, the file may appear empty (or not exist yet) or you might even cause an error if the file isn’t fully flushed.
To fix that, modify your code to return a promise that resolves only when the file has been written. Here’s one approach:
Update writeAudioFile to Return a Promise
async function writeAudioFile(request: HttpRequest): Promise<void> {
return new Promise((resolve, reject) => {
try {
console.log("testing function app");
console.log({ path: saveTo });
const bodyPipe = request.body;
// Check if the temp directory exists
stat(tmpdir())
.catch(async (e: any) => {
if (e.code === "ENOENT") {
console.log({ error_while_checking: e });
await mkdir(tmpdir(), { recursive: true });
} else {
console.error("Error while trying to create directory when uploading a file", e);
throw new Error("Something went wrong.");
}
})
.finally(() => {
const busboyHeader: IncomingHttpHeaders = readHeader(request, 'content-type');
const bb: busboy.Busboy = busboy.default({ headers: busboyHeader });
console.log({ temp_directory: saveTo });
const writeStream = createWriteStream(saveTo);
// When writeStream finishes, resolve the promise
writeStream.on("finish", () => {
console.log("File writing complete");
resolve();
});
writeStream.on("error", (err) => {
reject(err);
});
bb.on('file', (
fieldname: string,
file: NodeJS.ReadableStream,
filename: string,
encoding: string,
mimetype: string
) => {
file.on("data", (chunk) => {
writeStream.write(chunk);
});
});
bb.on('close', () => {
console.log("end streamin audio file");
writeStream.end();
});
if (bodyPipe && typeof bodyPipe === "object") {
bodyPipe.pipeTo(Writable.toWeb(bb))
.catch(reject);
} else {
reject(new Error("bodyPipe is null"));
}
});
} catch (e) {
console.error("Error while piping a file\n", e);
reject(e);
}
});
}
Update Your Azure Function Trigger to Await the File Upload
export async function voiceTranscriptTrigger(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
try {
// Await until the file is fully written
await writeAudioFile(request);
const data = await readFile(saveTo);
const length = data.byteLength;
const fileExist = existsSync(saveTo);
if (typeof data === 'object' && fileExist && length) {
const client = getClient();
const result = await client.audio.transcriptions.create({
model: "", // e.g. gpt-3.5-turbo-1106
file: createReadStream(saveTo),
});
unlinkSync(saveTo);
const response: HttpResponseInit = {
status: 200,
body: JSON.stringify({ transcript: result.text })
};
return response;
} else {
throw { body: "Failed reading audio data" };
}
} catch (e) {
console.error("Error while trying to upload a file\n", e);
const errorResponse: HttpResponseInit = {
status: 500,
body: JSON.stringify({ "error": e })
};
return errorResponse;
}
};
Summary
- File vs. Directory: Verify that you’re not confusing a file with a directory. The temp path must point to a file (for example, "audio.webm") within the temporary folder.
- Asynchronous Pipelines: Ensure you wait until the file is completely written before attempting to read/process it. In your trigger function, use “await writeAudioFile(request)” so that all streaming operations have completed.
With these adjustments, your Azure Function V4 should correctly write to a temporary file and then later read that file for further processing.
Let me know if you have any more questions on this topic.