From e7e97a7f6012abef3d9f491efde0f6ab0d5d4f99 Mon Sep 17 00:00:00 2001 From: Azeufack Noupeu Willy Date: Thu, 13 Nov 2025 13:07:18 +0100 Subject: [PATCH 1/3] feat(S2-02b): Implement AssemblyAI external transcription with speaker diarization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add assembly.ts module for REST API transcription via AssemblyAI - Implement 5-step pipeline: upload → create job → poll status → download → save - Enable speaker_labels for diarization (Speaker A, B, C...) - Add millisecond-precision timestamps for each utterance - Store JSON transcripts in storage/transcripts/{session_id}.json - Add axios, dotenv dependencies - Add transcribeLatest.ts helper for quick testing User Story: S2-02b - Externe Transkription per REST API --- package-lock.json | 44 +++ package.json | 8 + .../modules/transcription/assembly.test.js | 14 + services/modules/transcription/assembly.ts | 133 ++++++++ .../modules/transcription/package-lock.json | 319 ++++++++++++++++++ services/modules/transcription/package.json | 9 + services/pipeline/jobs/transcribeLatest.ts | 52 +++ 7 files changed, 579 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 services/modules/transcription/assembly.test.js create mode 100644 services/modules/transcription/assembly.ts create mode 100644 services/modules/transcription/package-lock.json create mode 100644 services/modules/transcription/package.json create mode 100644 services/pipeline/jobs/transcribeLatest.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4b57d9f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "video2document", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "dotenv": "^17.2.3" + }, + "devDependencies": { + "@types/node": "^24.10.0" + } + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..48d43a3 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "dotenv": "^17.2.3" + }, + "devDependencies": { + "@types/node": "^24.10.0" + } +} diff --git a/services/modules/transcription/assembly.test.js b/services/modules/transcription/assembly.test.js new file mode 100644 index 0000000..66ce753 --- /dev/null +++ b/services/modules/transcription/assembly.test.js @@ -0,0 +1,14 @@ +import 'dotenv/config'; +import assemblyModule from './assembly.ts'; + +// Test: URL passed as argument OR local file ./storage/audio/test.wav +const audioPath = process.argv[2] || './storage/audio/test.wav'; + +assemblyModule.run(audioPath) + .then(result => { + console.log('✅ Success!'); + console.log('Transcript ID:', result.id); + }) + .catch(error => { + console.error('❌ Error:', error?.message || error); + }); diff --git a/services/modules/transcription/assembly.ts b/services/modules/transcription/assembly.ts new file mode 100644 index 0000000..46fe236 --- /dev/null +++ b/services/modules/transcription/assembly.ts @@ -0,0 +1,133 @@ +import 'dotenv/config'; +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const API_KEY = process.env.ASSEMBLYAI_API_KEY; +const BASE_URL = 'https://api.assemblyai.com/v2'; + +/** + * Uploads audio file to AssemblyAI + */ +async function uploadAudio(audioPath: string): Promise { + const audioData = fs.readFileSync(audioPath); + + const response = await axios.post<{ upload_url: string }>(`${BASE_URL}/upload`, audioData, { + headers: { + 'authorization': API_KEY, + 'content-type': 'application/octet-stream' + } + }); + + return response.data.upload_url; +} + +/** + * Extract a session id (basename without extension) from a local path or a URL + */ +function getSessionId(inputPath: string): string { + try { + const parsed = new URL(inputPath); + const base = path.basename(parsed.pathname); + return base.replace(/\.[^.]+$/, ''); + } catch (err) { + // not a URL, treat as local path + return path.basename(inputPath, path.extname(inputPath)); + } +} + +/** + * Creates transcription job with speaker diarization + */ +async function createTranscript(audioUrl: string): Promise { + const response = await axios.post<{ id: string }>(`${BASE_URL}/transcript`, { + audio_url: audioUrl, + speaker_labels: true, + language_detection: true + }, { + headers: { + 'authorization': API_KEY, + 'content-type': 'application/json' + } + }); + + return response.data.id; +} + +/** + * Polls transcript status until completed + */ +async function pollTranscript(transcriptId: string): Promise { + while (true) { + const response = await axios.get(`${BASE_URL}/transcript/${transcriptId}`, { + headers: { 'authorization': API_KEY } + }); + + const status = response.data.status; + + if (status === 'completed') { + return response.data; + } else if (status === 'error') { + throw new Error(`Transcription failed: ${response.data.error}`); + } + + // Wait 3 seconds before next poll + await new Promise(resolve => setTimeout(resolve, 3000)); + } +} + +/** + * Saves transcript to storage + */ +function saveTranscript(transcript: any, sessionId: string): void { + const outputDir = path.join(__dirname, '..', '..', '..', 'storage', 'transcripts'); + + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const outputPath = path.join(outputDir, `${sessionId}.json`); + fs.writeFileSync(outputPath, JSON.stringify(transcript, null, 2)); + + console.log(`✅ Transcript saved: ${outputPath}`); +} + +export default { + name: "assembly", + type: "transcription", + displayname: "AssemblyAI", + run: async (audioPath: string) => { + try { + // Determine if audioPath is an external URL or a local file + let audioUrl: string; + if (/^https?:\/\//i.test(audioPath)) { + console.log('🔗 Using external audio URL...'); + audioUrl = audioPath; + } else { + console.log('🔄 Uploading local audio...'); + if (!fs.existsSync(audioPath)) { + throw new Error(`Audio file not found: ${audioPath}`); + } + audioUrl = await uploadAudio(audioPath); + } + + console.log('🔄 Creating transcript job...'); + const transcriptId = await createTranscript(audioUrl); + + console.log('⏳ Waiting for transcription...'); + const transcript = await pollTranscript(transcriptId); + + const sessionId = getSessionId(audioPath); + saveTranscript(transcript, sessionId); + + return transcript; + } catch (error: any) { + console.error('❌ Transcription error:', error.message); + throw error; + } + } +}; \ No newline at end of file diff --git a/services/modules/transcription/package-lock.json b/services/modules/transcription/package-lock.json new file mode 100644 index 0000000..58c2913 --- /dev/null +++ b/services/modules/transcription/package-lock.json @@ -0,0 +1,319 @@ +{ + "name": "transcription", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.13.2" + }, + "devDependencies": { + "@types/axios": "^0.9.36", + "@types/node": "^24.10.0" + } + }, + "node_modules/@types/axios": { + "version": "0.9.36", + "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.9.36.tgz", + "integrity": "sha512-NLOpedx9o+rxo/X5ChbdiX6mS1atE4WHmEEIcR9NLenRVa5HoVjAvjafwU3FPTqnZEstpoqCaW7fagqSoTDNeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/services/modules/transcription/package.json b/services/modules/transcription/package.json new file mode 100644 index 0000000..6d24fe5 --- /dev/null +++ b/services/modules/transcription/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "axios": "^1.13.2" + }, + "devDependencies": { + "@types/axios": "^0.9.36", + "@types/node": "^24.10.0" + } +} diff --git a/services/pipeline/jobs/transcribeLatest.ts b/services/pipeline/jobs/transcribeLatest.ts new file mode 100644 index 0000000..fc75ad5 --- /dev/null +++ b/services/pipeline/jobs/transcribeLatest.ts @@ -0,0 +1,52 @@ +// services/pipeline/jobs/transcribeLatest.ts +import path from 'path'; +import fs from 'fs'; +import assembly from '../../modules/transcription/assembly'; + +/** + * Finds the most recently modified .wav file in storage/audio/ + */ +function getLatestWav(): string { + const audioDir = path.join(process.cwd(), 'storage', 'audio'); + const files = fs.readdirSync(audioDir).filter(f => f.toLowerCase().endsWith('.wav')); + if (files.length === 0) throw new Error('⚠️ No .wav file found in storage/audio'); + + const newest = files + .map(f => ({ f, t: fs.statSync(path.join(audioDir, f)).mtimeMs })) + .sort((a, b) => b.t - a.t)[0].f; + + return path.join(audioDir, newest); +} + +/** + * Full transcription pipeline according to the defined workflow: + * 1. Audio Upload → AssemblyAI + * 2. Job Creation (transcript_id) + * 3. Polling Status (queued → processing → completed) + * 4. Download Transcript JSON + * 5. Storage: /transcripts/{session_id}.json + */ +async function main() { + const audioPath = getLatestWav(); + + console.log('1️⃣ Audio Upload → AssemblyAI'); + console.log(' Source:', audioPath); + + console.log('2️⃣ Job Creation (transcript_id)'); + console.log('3️⃣ Polling Status (queued → processing → completed)'); + console.log('4️⃣ Download Transcript JSON'); + console.log('5️⃣ Storage: /transcripts/{session_id}.json'); + + // Execute the transcription process via the AssemblyAI module + const result = await assembly.run(audioPath); + + console.log('✅ Transcription completed successfully'); + console.log('🆔 Transcript ID:', result.id); + console.log('📁 Transcript file saved under: storage/transcripts/'); +} + +// Entry point +main().catch((err) => { + console.error('❌ Transcription pipeline failed:', err.message || err); + process.exit(1); +}); From 9254ddc57f019b37202e04abae7ebf60bfd6bbb9 Mon Sep 17 00:00:00 2001 From: MikeHughes-BIN Date: Thu, 13 Nov 2025 17:34:22 +0100 Subject: [PATCH 2/3] Changed the Folder Structure for better maintainability --- .../modules/transcription/{ => remote/assembly_AI}/assembly.js | 0 .../transcription/{ => remote/assembly_AI}/assembly.test.js | 0 .../modules/transcription/{ => remote/assembly_AI}/assembly.ts | 0 .../modules/transcription/{ => remote/assembly_AI}/example.js | 0 .../transcription/{ => remote/assembly_AI}/package-lock.json | 0 .../modules/transcription/{ => remote/assembly_AI}/package.json | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename services/modules/transcription/{ => remote/assembly_AI}/assembly.js (100%) rename services/modules/transcription/{ => remote/assembly_AI}/assembly.test.js (100%) rename services/modules/transcription/{ => remote/assembly_AI}/assembly.ts (100%) rename services/modules/transcription/{ => remote/assembly_AI}/example.js (100%) rename services/modules/transcription/{ => remote/assembly_AI}/package-lock.json (100%) rename services/modules/transcription/{ => remote/assembly_AI}/package.json (100%) diff --git a/services/modules/transcription/assembly.js b/services/modules/transcription/remote/assembly_AI/assembly.js similarity index 100% rename from services/modules/transcription/assembly.js rename to services/modules/transcription/remote/assembly_AI/assembly.js diff --git a/services/modules/transcription/assembly.test.js b/services/modules/transcription/remote/assembly_AI/assembly.test.js similarity index 100% rename from services/modules/transcription/assembly.test.js rename to services/modules/transcription/remote/assembly_AI/assembly.test.js diff --git a/services/modules/transcription/assembly.ts b/services/modules/transcription/remote/assembly_AI/assembly.ts similarity index 100% rename from services/modules/transcription/assembly.ts rename to services/modules/transcription/remote/assembly_AI/assembly.ts diff --git a/services/modules/transcription/example.js b/services/modules/transcription/remote/assembly_AI/example.js similarity index 100% rename from services/modules/transcription/example.js rename to services/modules/transcription/remote/assembly_AI/example.js diff --git a/services/modules/transcription/package-lock.json b/services/modules/transcription/remote/assembly_AI/package-lock.json similarity index 100% rename from services/modules/transcription/package-lock.json rename to services/modules/transcription/remote/assembly_AI/package-lock.json diff --git a/services/modules/transcription/package.json b/services/modules/transcription/remote/assembly_AI/package.json similarity index 100% rename from services/modules/transcription/package.json rename to services/modules/transcription/remote/assembly_AI/package.json From 79e0c487558bd2084e523212dc4a7728dc526aaa Mon Sep 17 00:00:00 2001 From: MikeHughes-BIN Date: Thu, 13 Nov 2025 17:35:40 +0100 Subject: [PATCH 3/3] Reduced Number of test paths to avoid redundancy --- {tests => test}/integration/.gitkeep | 0 {tests => test}/unit/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {tests => test}/integration/.gitkeep (100%) rename {tests => test}/unit/.gitkeep (100%) diff --git a/tests/integration/.gitkeep b/test/integration/.gitkeep similarity index 100% rename from tests/integration/.gitkeep rename to test/integration/.gitkeep diff --git a/tests/unit/.gitkeep b/test/unit/.gitkeep similarity index 100% rename from tests/unit/.gitkeep rename to test/unit/.gitkeep