- Create new @link-stack/logger package wrapping Pino for structured logging - Replace all console.log/error/warn statements across the monorepo - Configure environment-aware logging (pretty-print in dev, JSON in prod) - Add automatic redaction of sensitive fields (passwords, tokens, etc.) - Remove dead commented-out logger file from bridge-worker - Follow Pino's standard argument order (context object first, message second) - Support log levels via LOG_LEVEL environment variable - Export TypeScript types for better IDE support This provides consistent, structured logging across all applications and packages, making debugging easier and production logs more parseable.
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import { Readable } from "stream";
|
|
import ffmpeg from "fluent-ffmpeg";
|
|
import * as R from "remeda";
|
|
import { createLogger } from "@link-stack/logger";
|
|
|
|
const logger = createLogger('bridge-worker-media-convert');
|
|
|
|
const requiredCodecs = ["mp3", "webm", "wav"];
|
|
|
|
export interface AudioConvertOpts {
|
|
bitrate?: string;
|
|
audioCodec?: string;
|
|
format?: string;
|
|
}
|
|
|
|
const defaultAudioConvertOpts = {
|
|
bitrate: "32k",
|
|
audioCodec: "libmp3lame",
|
|
format: "mp3",
|
|
};
|
|
|
|
/**
|
|
* Converts an audio file to a different format. defaults to converting to mp3 with a 32k bitrate using the libmp3lame codec
|
|
*
|
|
* @param input the buffer containing the binary data of the input file
|
|
* @param opts options to control how the audio file is converted
|
|
* @return resolves to a buffer containing the binary data of the converted file
|
|
**/
|
|
export const convert = (
|
|
input: Buffer,
|
|
opts?: AudioConvertOpts,
|
|
): Promise<Buffer> => {
|
|
const settings = { ...defaultAudioConvertOpts, ...opts };
|
|
return new Promise((resolve, reject) => {
|
|
const stream = Readable.from(input);
|
|
let out = Buffer.alloc(0);
|
|
const cmd = ffmpeg(stream)
|
|
.audioCodec(settings.audioCodec)
|
|
.audioBitrate(settings.bitrate)
|
|
.toFormat(settings.format)
|
|
.on("error", (err, _stdout, _stderr) => {
|
|
logger.error({ error: err }, 'FFmpeg conversion error');
|
|
reject(err);
|
|
})
|
|
.on("end", () => {
|
|
resolve(out);
|
|
});
|
|
const outstream = cmd.pipe();
|
|
outstream.on("data", (chunk: Buffer) => {
|
|
out = Buffer.concat([out, chunk]);
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Check if ffmpeg is installed and usable. Checks for required codecs and a working ffmpeg installation.
|
|
*
|
|
* @return resolves to true if ffmpeg is installed and usable
|
|
* */
|
|
export const selfCheck = (): Promise<boolean> => {
|
|
return new Promise((resolve) => {
|
|
ffmpeg.getAvailableFormats((err, codecs) => {
|
|
if (err) {
|
|
logger.error({ error: err }, 'FFMPEG error');
|
|
resolve(false);
|
|
}
|
|
|
|
const preds = R.map(
|
|
requiredCodecs,
|
|
(codec) => (available: any) =>
|
|
available[codec] &&
|
|
available[codec].canDemux &&
|
|
available[codec].canMux,
|
|
);
|
|
|
|
resolve(R.allPass(codecs, preds));
|
|
});
|
|
});
|
|
};
|
|
|
|
export const assertFfmpegAvailable = async (): Promise<void> => {
|
|
const r = await selfCheck();
|
|
if (!r)
|
|
throw new Error(
|
|
`ffmpeg is not installed, could not be located, or does not support the required codecs: ${requiredCodecs}`,
|
|
);
|
|
};
|