audio.mjs
77 lines 2.6 KB
Raw
sha256:41d741fb345c4abdb640838aa3d847de02ccffd7a39fce04894e743e683b50d0 fix(security): pin patched transitive deps to clear Dependa… Human minor ⚠ breaking 7 days ago
1 /**
2 * Audio/video importer. Transcribes via OpenAI Whisper, writes vault note. Phase 7.
3 */
4
5 import fs from 'fs';
6 import path from 'path';
7 import { transcribe } from '../transcribe.mjs';
8 import { writeNote } from '../write.mjs';
9 import { normalizeSlug } from '../vault.mjs';
10
11 /** Extensions Whisper supports */
12 const SUPPORTED = new Set(['.mp3', '.mp4', '.mpeg', '.mpga', '.m4a', '.wav', '.webm']);
13
14 /**
15 * @param {string} input - Path to audio file
16 * @param {{ vaultPath: string, outputBase: string, project?: string, tags: string[], dryRun: boolean }} ctx
17 * @returns {Promise<{ imported: { path: string, source_id?: string }[], count: number }>}
18 */
19 export async function importAudio(input, ctx) {
20 return importMedia(input, ctx, 'audio');
21 }
22
23 /**
24 * @param {string} input - Path to video file
25 * @param {{ vaultPath: string, outputBase: string, project?: string, tags: string[], dryRun: boolean }} ctx
26 */
27 export async function importVideo(input, ctx) {
28 return importMedia(input, ctx, 'video');
29 }
30
31 async function importMedia(input, ctx, sourceType) {
32 const { vaultPath, outputBase, project, tags, dryRun } = ctx;
33 const absInput = path.isAbsolute(input) ? input : path.resolve(process.cwd(), input);
34 if (!fs.existsSync(absInput) || !fs.statSync(absInput).isFile()) {
35 throw new Error(`${sourceType} file not found: ${input}`);
36 }
37
38 const ext = path.extname(absInput).toLowerCase();
39 if (!SUPPORTED.has(ext)) {
40 throw new Error(
41 `Unsupported format: ${ext}. Use mp3, mp4, mpeg, mpga, m4a, wav, or webm. Transcription requires OpenAI Whisper (OPENAI_API_KEY).`
42 );
43 }
44
45 const baseName = path.basename(absInput, ext);
46 const sourceId = baseName.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
47 const now = new Date().toISOString().slice(0, 10);
48 const outputRel = path.join(outputBase, `${sourceType}_${sourceId}.md`).replace(/\\/g, '/');
49
50 let transcript = '';
51 if (dryRun) {
52 transcript = `(dry-run: would transcribe ${path.basename(absInput)} via OpenAI Whisper)`;
53 } else {
54 const { text, transcoded } = await transcribe(absInput);
55 transcript = text;
56 if (transcoded) {
57 transcript =
58 '> *Transcoded for Whisper (ffmpeg) before upload.*\n\n' +
59 (transcript || '');
60 }
61 }
62 const body = transcript || `(No speech detected in ${path.basename(absInput)})`;
63
64 const frontmatter = {
65 source: sourceType,
66 source_id: sourceId,
67 date: now,
68 title: baseName,
69 ...(project && { project: normalizeSlug(project) }),
70 ...(tags.length && { tags }),
71 };
72
73 if (!dryRun) {
74 writeNote(vaultPath, outputRel, { body, frontmatter });
75 }
76 return { imported: [{ path: outputRel, source_id: sourceId }], count: 1 };
77 }
File History 1 commit
sha256:41d741fb345c4abdb640838aa3d847de02ccffd7a39fce04894e743e683b50d0 fix(security): pin patched transitive deps to clear Dependa… Human minor 7 days ago