mirror of
https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
synced 2026-06-15 18:01:52 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84ab93ce77 | |||
| 173ed90642 | |||
| 925eb33eab | |||
| 911cba14fd | |||
| 6813659443 | |||
| a0ed2ab7bd | |||
| 4b72568ad3 | |||
| 76c18fa713 | |||
| 2edc7f8351 | |||
| 4a91f03289 | |||
| 1a681eb2b8 |
Binary file not shown.
@@ -10,9 +10,10 @@
|
||||
|
||||
<div class="mitte" id="mitte">
|
||||
<div class="flagsBtns" id="flagsBtns">
|
||||
<button class="de_Btn" id="de_Btn" onclick="changeLanguage('de')"><img src="flags/germany-flag-png-large.jpg" width="25px" height="20px"/></button>
|
||||
<button class="eng_Btn" id="eng_Btn" onclick="changeLanguage('en')"><img src="flags/united-kingdom-flag-png-large.jpg" width="25px" height="20px"/></button>
|
||||
<button class="in_Btn" id="in_Btn" onclick="changeLanguage('in')"><img src="flags/india-flag-png-large.png" width="25px" height="20px"/></button>
|
||||
<select name="ai_type" id="ai_type">
|
||||
</select>
|
||||
<select name="language_option" id="language_option">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h1 id="h1">Video to document</h1>
|
||||
@@ -46,7 +47,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script src="./renderer.js"></script>
|
||||
<script src="languages.js"></script>
|
||||
<script src="script.js"></script>
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
var languageOptions = {
|
||||
"eng":{
|
||||
"title": "Video to document",
|
||||
"h1": "Video to document",
|
||||
"p1": "Drag and drop video file",
|
||||
"fileName": "No video chosen",
|
||||
"manualUploadBtn": "Search video",
|
||||
"checkbox_group": "Choose prefered document style:",
|
||||
"label_format": "Meeting report",
|
||||
"label_summary": "Summary with timestamps",
|
||||
"submitButton": "Submit"
|
||||
},
|
||||
"de":{
|
||||
"title": "Video zu Dokument",
|
||||
"h1": "Video zu Dokument",
|
||||
"p1": "Video per Drag & Drop ablegen",
|
||||
"fileName": "Kein Video ausgewaehlt",
|
||||
"manualUploadBtn": "Video suchen",
|
||||
"checkbox_group": "Bevorzugte Dokumentvarianten:",
|
||||
"label_format": "Meeting Bericht",
|
||||
"label_summary": "Zusammenfassung mit Zeitstempeln",
|
||||
"submitButton": "Absenden"
|
||||
},
|
||||
"in":{
|
||||
"title": "दस्तावेज़ के लिए वीडियो",
|
||||
"h1": "दस्तावेज़ के लिए वीडियो",
|
||||
"p1": "वीडियो फ़ाइल खींचें और छोड़ें",
|
||||
"fileName": "कोई वीडियो नहीं चुना गया",
|
||||
"manualUploadBtn": "वीडियो खोजें",
|
||||
"checkbox_group": "पसंदीदा दस्तावेज़ शैली चुनें:",
|
||||
"label_format": "बैठक रिपोर्ट",
|
||||
"label_summary": "टाइमस्टैम्प के साथ सारांश",
|
||||
"submitButton": "जमा करना"
|
||||
}
|
||||
|
||||
};
|
||||
+58
-1
@@ -2,6 +2,9 @@ import { app, BrowserWindow, ipcMain, dialog } from 'electron';
|
||||
import { exec } from 'child_process';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
// Import audio events and transcription module
|
||||
import { audioEvents } from '../../services/modules/extraction/ffmpegExtractor.js';
|
||||
import { transcribe } from '../../services/modules/transcription-remote/assembly.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -22,7 +25,61 @@ function createWindow() {
|
||||
mainWindow.loadFile('main/index.html');
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow);
|
||||
// Setup pipeline orchestrator
|
||||
function setupOrchestrator() {
|
||||
console.log('🎯 [Pipeline] Orchestrator ready. Listening for audio_ready events...');
|
||||
|
||||
audioEvents.on('audio_ready', async (data) => {
|
||||
const { audioPath, sessionId } = data;
|
||||
|
||||
console.log(`✅ [Pipeline] Audio ready: ${sessionId}`);
|
||||
|
||||
// AC6: Send status to UI - Audio bereit
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'audio_ready',
|
||||
message: 'Audio bereit'
|
||||
});
|
||||
|
||||
try {
|
||||
// AC4: Status transcription_started
|
||||
console.log(`🚀 [Pipeline] Starting transcription: ${sessionId}`);
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'transcription_started',
|
||||
message: 'Transkription gestartet'
|
||||
});
|
||||
|
||||
// AC2: Auto-start transcription (S2-02b)
|
||||
await transcribe(audioPath, sessionId);
|
||||
|
||||
// AC6: Status transcription_completed
|
||||
console.log(`✅ [Pipeline] Transcription completed: ${sessionId}`);
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'transcription_completed',
|
||||
message: 'Transkription abgeschlossen'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
// AC5: Error logging + failed_transcription_start
|
||||
console.error(`❌ [Pipeline] Transcription failed: ${sessionId}`);
|
||||
console.error(` Error:`, error.message);
|
||||
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'failed_transcription_start',
|
||||
message: 'Fehler beim Transkriptionsstart',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
setupOrchestrator();
|
||||
});
|
||||
|
||||
// Kommunikation vom Renderer (Frontend)
|
||||
ipcMain.handle('convert-video', async (event, filePath) => {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
||||
|
||||
try {
|
||||
@@ -15,4 +13,3 @@ try {
|
||||
} catch (error) {
|
||||
console.log("Error in preload.js");
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,16 @@ uploadContainer.addEventListener("drop", (e) => {
|
||||
} catch (error) {
|
||||
console.log("Error in renderer.js with the listerner for the drop function");
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
window.addEventListener('load', (e) => {
|
||||
console.log("test");
|
||||
loadLanguageOptions();
|
||||
});
|
||||
|
||||
language_option.addEventListener('change', (e)=>{
|
||||
const select = document.getElementById('language_option');
|
||||
console.log(select.value);
|
||||
changeLanguage(select.value);
|
||||
});
|
||||
|
||||
+51
-34
@@ -1,4 +1,3 @@
|
||||
|
||||
//listener for the file explorer search
|
||||
manualUploadBtn.addEventListener('click', () => {
|
||||
try {
|
||||
@@ -40,39 +39,21 @@ function checkBoxes() {
|
||||
|
||||
//language changing feature
|
||||
function changeLanguage(language) {
|
||||
if (language === 'en') {
|
||||
document.getElementById('title').textContent = 'Video to document';
|
||||
document.getElementById('h1').textContent = 'Video to document';
|
||||
document.getElementById('p1').textContent = 'Drag and drop video file';
|
||||
document.getElementById('fileName').textContent = 'No video chosen';
|
||||
document.getElementById('manualUploadBtn').textContent = 'Search video';
|
||||
document.getElementById('checkbox_group').textContent = 'Choose prefered document style:';
|
||||
document.getElementById('label_format').textContent = 'Meeting report';
|
||||
document.getElementById('label_summary').textContent = 'Summary with timestamps';
|
||||
document.getElementById('submitButton').textContent = 'Submit';
|
||||
} else if (language === 'de') {
|
||||
document.getElementById('title').textContent = 'Video zu Dokument';
|
||||
document.getElementById('h1').textContent = 'Video zu Dokument';
|
||||
document.getElementById('p1').textContent = 'Video per Drag & Drop ablegen';
|
||||
document.getElementById('fileName').textContent = 'Kein Video ausgewaehlt';
|
||||
document.getElementById('manualUploadBtn').textContent = 'Video suchen';
|
||||
document.getElementById('checkbox_group').textContent = 'Bevorzugte Dokumentvarianten:';
|
||||
document.getElementById('label_format').textContent = 'Meeting Bericht';
|
||||
document.getElementById('label_summary').textContent = 'Zusammenfassung mit Zeitstempeln';
|
||||
document.getElementById('submitButton').textContent = 'Absenden';
|
||||
} else if(language == "in") {
|
||||
document.getElementById('title').textContent = 'दस्तावेज़ के लिए वीडियो';
|
||||
document.getElementById('h1').textContent = 'दस्तावेज़ के लिए वीडियो';
|
||||
document.getElementById('p1').textContent = 'वीडियो फ़ाइल खींचें और छोड़ें';
|
||||
document.getElementById('fileName').textContent = 'कोई वीडियो नहीं चुना गया';
|
||||
document.getElementById('manualUploadBtn').textContent = 'वीडियो खोजें';
|
||||
document.getElementById('checkbox_group').textContent = 'पसंदीदा दस्तावेज़ शैली चुनें:';
|
||||
document.getElementById('label_format').textContent = 'बैठक रिपोर्ट';
|
||||
document.getElementById('label_summary').textContent = 'टाइमस्टैम्प के साथ सारांश';
|
||||
document.getElementById('submitButton').textContent = 'जमा करना';
|
||||
try {
|
||||
document.getElementById('title').textContent = languageOptions[language].title;
|
||||
document.getElementById('h1').textContent = languageOptions[language].h1;
|
||||
document.getElementById('p1').textContent = languageOptions[language].p1;
|
||||
document.getElementById('fileName').textContent = languageOptions[language].fileName;
|
||||
document.getElementById('manualUploadBtn').textContent = languageOptions[language].manualUploadBtn;
|
||||
document.getElementById('checkbox_group').textContent = languageOptions[language].checkbox_group;
|
||||
document.getElementById('label_format').textContent = languageOptions[language].label_format;
|
||||
document.getElementById('label_summary').textContent = languageOptions[language].label_summary;
|
||||
document.getElementById('submitButton').textContent = languageOptions[language].submitButton;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//listener for the file explorer search when something got selected
|
||||
videoUpload.addEventListener('change', () => {
|
||||
@@ -84,8 +65,6 @@ videoUpload.addEventListener('change', () => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
//function to display the file path in the drop down box
|
||||
function handleFiles(files) {
|
||||
try {
|
||||
@@ -113,3 +92,41 @@ function updateProgressBar(bar, value){
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//function to load ai options to the drop down list
|
||||
function loadAiOptions(options){
|
||||
try {
|
||||
var menu = document.getElementById('ai_type');
|
||||
for(i = 0; i < options.length; i++){
|
||||
var opty = options[i];
|
||||
var namey = "option" + i;
|
||||
var choice = document.createElement(namey);
|
||||
choice.textContent = "t";
|
||||
choice.value = i;
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js loadAiOptions function");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
//function to load language options to the drop down list
|
||||
function loadLanguageOptions(){
|
||||
try {
|
||||
var menu = document.getElementById('language_option');
|
||||
var object_holdy;
|
||||
var choice ;
|
||||
object_holdy = Object.keys(languageOptions);
|
||||
for(i = 0; i < object_holdy.length; i++){
|
||||
choice = document.createElement('option');
|
||||
choice.textContent = object_holdy[i];
|
||||
choice.value = object_holdy[i];
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error in script.js loadLanguageOptions function");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,73 @@ console.log(`${mapFunctions.size} Function modules loaded`);
|
||||
console.log("--------------------------------------------------------------------------------");
|
||||
|
||||
|
||||
// ======================== S3-06 : PIPELINE ORCHESTRATOR ========================
|
||||
// Get audioEvents from ffmpegExtractor module
|
||||
const ffmpegExtractor = mapFunctions.get("extraction-video-to-audio");
|
||||
const audioEvents = ffmpegExtractor.audioEvents;
|
||||
|
||||
console.log('🎯 [S3-06] Pipeline Orchestrator ready. Listening for audio_ready events...');
|
||||
|
||||
audioEvents.on('audio_ready', async (data) => {
|
||||
const { audioPath, sessionId } = data;
|
||||
|
||||
console.log(`✅ [Pipeline] Audio ready: ${sessionId}`);
|
||||
console.log(`📁 Audio path: ${audioPath}`);
|
||||
|
||||
// Send status to UI
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'audio_ready',
|
||||
message: 'Audio bereit'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`🚀 [Pipeline] Starting transcription: ${sessionId}`);
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'transcription_started',
|
||||
message: 'Transkription gestartet'
|
||||
});
|
||||
}
|
||||
|
||||
// Get transcription module
|
||||
const assemblyModule = mapFunctions.get("assembly");
|
||||
|
||||
if (assemblyModule && assemblyModule.run) {
|
||||
await assemblyModule.run(audioPath);
|
||||
} else {
|
||||
console.warn('⚠️ Assembly module not found or missing run function');
|
||||
}
|
||||
|
||||
console.log(`✅ [Pipeline] Transcription completed: ${sessionId}`);
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'transcription_completed',
|
||||
message: 'Transkription abgeschlossen'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ [Pipeline] Transcription failed: ${sessionId}`);
|
||||
console.error(` Error:`, error.message);
|
||||
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('pipeline-status', {
|
||||
sessionId,
|
||||
status: 'failed_transcription_start',
|
||||
message: 'Fehler beim Transkriptionsstart',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// ===============================================================================
|
||||
|
||||
|
||||
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
||||
|
||||
Generated
+132
-2
@@ -10,6 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/axios": "^0.9.36",
|
||||
"axios": "^1.13.2",
|
||||
"cli-progress": "^3.12.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron": "^39.1.1",
|
||||
@@ -206,7 +207,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
|
||||
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
@@ -299,6 +299,23 @@
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
|
||||
"integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
|
||||
},
|
||||
"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/body-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
@@ -428,6 +445,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
@@ -569,6 +598,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"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/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -711,6 +749,21 @@
|
||||
"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/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
@@ -855,6 +908,63 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"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.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"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/form-data/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/form-data/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/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -1049,6 +1159,21 @@
|
||||
"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",
|
||||
@@ -1401,6 +1526,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"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/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
@@ -1845,7 +1976,6 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/axios": "^0.9.36",
|
||||
"axios": "^1.13.2",
|
||||
"cli-progress": "^3.12.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron": "^39.1.1",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const EventEmitter = require('events');
|
||||
const audioEvents = new EventEmitter();
|
||||
|
||||
// Ensure ffmpeg binary is available
|
||||
if (!ffmpegPath) {
|
||||
@@ -75,6 +77,12 @@ module.exports = {
|
||||
progressBar.update(100, { timemark: 'done' });
|
||||
progressBar.stop();
|
||||
console.log(`Extraction completed: ${outputAudioPath}`);
|
||||
|
||||
audioEvents.emit('audio_ready', {
|
||||
sessionId: inputVideoName,
|
||||
audioPath: outputAudioPath
|
||||
});
|
||||
|
||||
resolve();
|
||||
})
|
||||
.on('error', (err) => {
|
||||
@@ -88,6 +96,6 @@ module.exports = {
|
||||
console.log();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
audioEvents: audioEvents
|
||||
}
|
||||
@@ -1,8 +1,128 @@
|
||||
module.exports = {
|
||||
name:"assembly", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
type:"transcription", // value used to differentiate each module to order them in the UI
|
||||
displayname:"Assembly", // The displayname used within the UI
|
||||
async function(parameter){
|
||||
// TODO add code to actually process the audio file
|
||||
require('dotenv/config');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
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) {
|
||||
const audioData = fs.readFileSync(audioPath);
|
||||
|
||||
const response = await axios.post(`${BASE_URL}/upload`, audioData, {
|
||||
headers: {
|
||||
'authorization': API_KEY,
|
||||
'content-type': 'application/octet-stream'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data.upload_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract session id from path or URL
|
||||
*/
|
||||
function getSessionId(inputPath) {
|
||||
try {
|
||||
const parsed = new URL(inputPath);
|
||||
const base = path.basename(parsed.pathname);
|
||||
return base.replace(/\.[^.]+$/, '');
|
||||
} catch (err) {
|
||||
return path.basename(inputPath, path.extname(inputPath));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates transcription job with speaker diarization
|
||||
*/
|
||||
async function createTranscript(audioUrl) {
|
||||
const response = await axios.post(`${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) {
|
||||
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, sessionId) {
|
||||
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}`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: "assembly",
|
||||
type: "transcription",
|
||||
displayname: "AssemblyAI",
|
||||
run: async function(audioPath) {
|
||||
try {
|
||||
// Determine if audioPath is an external URL or a local file
|
||||
let audioUrl;
|
||||
if (/^https?:\/\//i.test(audioPath)) {
|
||||
console.log('🔗 Using external audio URL...');
|
||||
audioUrl = audioPath;
|
||||
} else {
|
||||
if (!fs.existsSync(audioPath)) {
|
||||
throw new Error(`Audio file not found: ${audioPath}`);
|
||||
}
|
||||
console.log('📤 Uploading audio file...');
|
||||
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) {
|
||||
console.error('❌ Transcription error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
// services/pipeline/jobs/transcribeLatest.ts
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
// @ts-ignore: module has no type declarations or cannot be resolved in current TS config
|
||||
import assembly from '../../modules/transcription/assembly';
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user