Created
April 29, 2025 19:42
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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