Skip to content

Instantly share code, notes, and snippets.

@EIIisD
Created April 29, 2025 19:42
Show Gist options
  • Save EIIisD/d649a8be5cd4fe4ee89273e68fd3c85d to your computer and use it in GitHub Desktop.
Save EIIisD/d649a8be5cd4fe4ee89273e68fd3c85d to your computer and use it in GitHub Desktop.
Walks the Superwhisper recordings folder and reads each new `meta.json` file with zero copy JSON loading. It then watches the folder live and prints the finish time processing time and text as soon as a transcript saves.
import { resolve, join, sep as pathSep } from "node:path";
import { watch } from "node:fs";
import { readdir } from "node:fs/promises";
interface MetaData {
datetime: string;
processingTime: number;
result: string;
segments?: unknown[];
}
/**
* Directory where SuperWhisper stores its recordings.
* `~/Documents/superwhisper/records`
*/
const recordsDir = resolve(
process.env.HOME ?? "", // HOME must be defined, but be defensive
"Library",
"Mobile Documents",
["com", "apple", "CloudDocs"].join("~"),
"Documents",
"superwhisper",
"recordings"
);
/**
* Guards against delivering the same meta.json twice.
*/
const seen = new Set<string>();
/**
* Recursively walk the records directory on start‑up so we catch
* transcriptions that finished while the watcher was offline.
*/
async function scanExisting(dir: string): Promise<void> {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
await scanExisting(full);
} else if (entry.name === "meta.json") {
handle(full);
}
}
}
/**
* Load and process a single meta.json, using Bun.file().json()
* for zero‑copy parsing.
*/
async function handle(file: string): Promise<void> {
if (seen.has(file)) return;
seen.add(file);
try {
const meta = (await Bun.file(file).json()) as MetaData;
processTranscription(meta);
} catch (err) {
console.error("Bad meta file", file, err);
}
}
/**
* Whatever your downstream pipeline does with the parsed metadata.
* Keep this function fast; off‑load heavy work to a Bun Worker if needed.
*/
function processTranscription(meta: MetaData): void {
console.log(
`Transcript finished ${meta.datetime} in ${meta.processingTime} ms:`
);
console.log(meta.result.trim());
// meta.segments holds token‑level timing data if you need it
}
/**
* Entry point.
* • Scans the tree once.
* • Starts a recursive fs.watch() so we react the moment a new
* meta.json is closed on disk.
* • fs.watch() on Bun uses FSEvents on macOS / inotify on Linux,
* so this is push‑driven and CPU‑cheap.
*/
async function main(): Promise<void> {
await scanExisting(recordsDir);
// On macOS the "rename" event fires once the file is closed,
// so we read only complete JSON.
watch(recordsDir, { recursive: true }, (_event, filename) => {
if (!filename) return;
// node:fs may report paths with native separators.
if (
!filename.endsWith(`${pathSep}meta.json`) &&
!filename.endsWith("meta.json")
) {
return;
}
handle(join(recordsDir, filename));
}).unref(); // let the process exit when nothing else is pending
}
// If executed directly (`bun run watch-meta.ts`) run main().
if (import.meta.main) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment