mirror of
https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
synced 2026-06-15 18:01:52 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3053674888 | |||
| 6c7bd4661a | |||
| e7271f216a |
Binary file not shown.
+2
-20
@@ -963,23 +963,5 @@ app.*.symbols
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
|
||||
# Storage files
|
||||
storage/documents/*
|
||||
storage/transcriptionSummaries/*
|
||||
storage/transcripts/*
|
||||
storage/video/*
|
||||
# The inverse for the .gitkeep files, to make sure the folders are there but not the local files
|
||||
!storage/documents/.gitkeep
|
||||
!storage/transcriptionSummaries/.gitkeep
|
||||
!storage/transcripts/.gitkeep
|
||||
!storage/video/.gitkeep
|
||||
|
||||
*.mp4
|
||||
*.webm
|
||||
*.mp3
|
||||
*.mov
|
||||
*.wav
|
||||
*.flac
|
||||
|
||||
|
||||
!testvideo.mp4
|
||||
#Storage files
|
||||
storage/
|
||||
+25
-20
@@ -1,28 +1,33 @@
|
||||
image: node:latest
|
||||
workflow:
|
||||
rules:
|
||||
# Run the pipeline for merge requests or when committing to a branch
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
image: python:3.14.0
|
||||
|
||||
stages:
|
||||
# - install
|
||||
- setup
|
||||
- test
|
||||
|
||||
# job-install:
|
||||
# stage: install
|
||||
# script:
|
||||
# - npm install
|
||||
# artifacts:
|
||||
# untracked: false
|
||||
# when: on_success
|
||||
# access: all
|
||||
# expire_in: "30 days"
|
||||
# paths:
|
||||
# - node_modules
|
||||
setup_environment:
|
||||
stage: setup
|
||||
script:
|
||||
- pip install --upgrade pip
|
||||
- pip install -r requirements.txt
|
||||
- echo "Dependencies installed successfully."
|
||||
|
||||
only:
|
||||
- main
|
||||
- feature/ci-pipeline-s1-09a-1 # You can add more branches if needed
|
||||
|
||||
|
||||
job-test:
|
||||
test_app:
|
||||
stage: test
|
||||
script:
|
||||
- npm install
|
||||
- echo "ASSEMBLYAI_API_KEY=$apikey_assembly" > .env
|
||||
- echo "GOOGLE_API_KEY=$apikey_gemini" >> .env
|
||||
- echo "SAIA_API_KEY=$apikey_saia" >> .env
|
||||
- npm test
|
||||
- echo "Running V2D Framework basic test..."
|
||||
- python -m unittest discover || echo "No tests found."
|
||||
|
||||
only:
|
||||
- main
|
||||
- feature/ci-pipeline-s1-09a-1
|
||||
|
||||
|
||||
@@ -1,378 +1,93 @@
|
||||
# Video2Document
|
||||
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/topics/git/add_files/#add-files-to-a-git-repository) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.rlp.net/proj-wise2526-video2document/video2document/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
Follow the steps below to install Video2Document (v2d):
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
### 1. Install Node.js
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
Download and install Node.js from [https://nodejs.org/en](https://nodejs.org/en)
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
### 2. Clone the Repository
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
Clone the repository using Git:
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.rlp.net/proj-wise2526-video2document/video2document.git
|
||||
```
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
Alternatively, you can download the ZIP file and extract it manually.
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
### 3. Start the Program
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
The start scripts automatically install dependencies and launch the program. Choose the appropriate method for your operating system:
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
start.bat
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
./start.sh
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
./start.sh
|
||||
```
|
||||
|
||||
#### Alternative: Manual Installation and Start
|
||||
|
||||
If the start scripts don't work, you can manually install dependencies and start the program:
|
||||
|
||||
```bash
|
||||
cd video2document
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
### 5. Install FFmpeg
|
||||
|
||||
Make sure that **ffmpeg** is installed on your system, as it may be required for video processing.
|
||||
|
||||
---
|
||||
|
||||
**Installation complete!** You're now ready to use Video2Document.
|
||||
|
||||
###General Despription
|
||||
## Project Overview
|
||||
|
||||
V2D (Video to Document Framework) is a student project developed to convert video and audio content into structured, readable documents using AI-based text processing. The tool is designed to help users automatically generate documents such as meeting reports, summaries or structured texts from recorded videos or audio files.
|
||||
|
||||
The main problem V2D addresses is the time-consuming manual effort required to listen to recordings and write documents afterwards. By automating transcription, structuring and document generation, the tool improves efficiency and usability for students, teams and small organizations.
|
||||
|
||||
The project was created as part of a university software engineering course and is developed using agile methods (Scrum). It focuses on modular design, usability and extensibility, allowing new document types and features to be added easily.
|
||||
|
||||
### Used Packages and Dependencies
|
||||
|
||||
This project uses several Node.js packages to handle video processing, AI-based document generation, backend communication, and the desktop user interface. Below is an overview of the most important dependencies and their purpose.
|
||||
|
||||
##Core Dependencies:
|
||||
|
||||
Electron:
|
||||
Used to build the desktop application. It allows the project to run as a cross-platform GUI application using web technologies.
|
||||
|
||||
Express:
|
||||
Acts as a lightweight backend server that handles internal API requests and communication between different parts of the application.
|
||||
|
||||
Axios:
|
||||
Used for making HTTP requests, for example when communicating with external APIs such as AI services.
|
||||
|
||||
dotenv:
|
||||
Used to securely load API keys and other sensitive configuration values from environment variables instead of hardcoding them in the source code.
|
||||
|
||||
@google/genai:
|
||||
Provides access to Google’s Generative AI models. This package is used to generate structured documents from transcribed text based on configurable prompts.
|
||||
|
||||
ffmpeg-static:
|
||||
Provides a static FFmpeg binary so that video and audio processing works without requiring FFmpeg to be installed separately on the system.
|
||||
|
||||
fluent-ffmpeg:
|
||||
Used together with FFmpeg to process video and audio files, for example extracting audio tracks from uploaded videos.
|
||||
|
||||
puppeteer:
|
||||
Used to render and process HTML content programmatically. This is helpful for document previews and automated content generation.
|
||||
|
||||
html-to-docx:
|
||||
Converts generated HTML documents into downloadable DOCX files, which are provided to the user as the final output.
|
||||
|
||||
mocha:
|
||||
A testing framework used to run unit tests and ensure that core functionalities work as expected.
|
||||
|
||||
##Development Dependencies:
|
||||
|
||||
TypeScript:
|
||||
Used to add static typing to the project, improving code quality, readability, and maintainability.
|
||||
|
||||
ts-node:
|
||||
Allows running TypeScript files directly without manually compiling them first.
|
||||
|
||||
@types/node:
|
||||
Provides TypeScript type definitions for Node.js APIs.
|
||||
|
||||
@types/fluent-ffmpeg:
|
||||
Type definitions for fluent-ffmpeg to improve development experience and error checking.
|
||||
|
||||
@types/cli-progress:
|
||||
Provides type support for progress bar functionality used during processing tasks.
|
||||
|
||||
###Why These Packages Are Needed
|
||||
|
||||
Together, these packages enable:
|
||||
|
||||
*Processing video and audio files
|
||||
|
||||
*Communicating with AI models for document generation
|
||||
|
||||
*Secure handling of API keys
|
||||
|
||||
*Generating structured documents (DOCX)
|
||||
|
||||
*Providing a user-friendly desktop interface
|
||||
|
||||
*Ensuring code quality through testing
|
||||
|
||||
###API Keys and Configuration
|
||||
|
||||
The V2D – Video to Document tool uses external AI and media processing services to convert video and audio content into structured documents.
|
||||
To access these services, several API keys are required. For security reasons, API keys are not stored in the repository and must be provided via environment variables.
|
||||
|
||||
##Supported API Keys
|
||||
|
||||
The project currently supports the following API keys. Depending on the configuration and selected provider, one or more of these keys may be used.
|
||||
|
||||
Google Gemini API
|
||||
|
||||
Environment variable: GOOGLE_API_KEY
|
||||
|
||||
Usage:
|
||||
Used for AI-based document generation. The Large Language Model processes transcripts and creates structured documents such as meeting reports.
|
||||
|
||||
OpenAI (ChatGPT)
|
||||
|
||||
Environment variable: OPENAI_API_KEY
|
||||
|
||||
Usage:
|
||||
Alternative AI provider for text processing and document generation.
|
||||
|
||||
AssemblyAI
|
||||
|
||||
Environment variable: ASSEMBLYAI_API_KEY
|
||||
|
||||
Usage:
|
||||
Speech-to-text processing for audio and video files.
|
||||
|
||||
Saya
|
||||
|
||||
Environment variable: SAYA_API_KEY
|
||||
|
||||
Usage:
|
||||
Additional or experimental AI provider that can be integrated into the document generation pipeline.
|
||||
|
||||
##How to Set API Keys
|
||||
|
||||
API keys must be configured as environment variables before starting the application.
|
||||
|
||||
Linux / macOS
|
||||
export GOOGLE_API_KEY="your_api_key_here"
|
||||
export OPENAI_API_KEY="your_api_key_here"
|
||||
export ASSEMBLYAI_API_KEY="your_api_key_here"
|
||||
export SAYA_API_KEY="your_api_key_here"
|
||||
|
||||
Windows (PowerShell)
|
||||
setx GOOGLE_API_KEY "your_api_key_here"
|
||||
setx OPENAI_API_KEY "your_api_key_here"
|
||||
setx ASSEMBLYAI_API_KEY "your_api_key_here"
|
||||
setx SAYA_API_KEY "your_api_key_here"
|
||||
|
||||
|
||||
Alternatively, for local development, a .env file can be used:
|
||||
|
||||
GOOGLE_API_KEY=your_api_key_here
|
||||
OPENAI_API_KEY=your_api_key_here
|
||||
ASSEMBLYAI_API_KEY=your_api_key_here
|
||||
SAYA_API_KEY=your_api_key_here
|
||||
|
||||
|
||||
⚠️ Important:
|
||||
The .env file must not be committed to the repository and should be listed in .gitignore.
|
||||
|
||||
Security Notes:
|
||||
|
||||
*API keys are injected at runtime
|
||||
|
||||
*No secrets are stored in the source code
|
||||
|
||||
*Prevents accidental exposure of sensitive data
|
||||
|
||||
*Supports secure collaboration in GitLab and CI/CD environments
|
||||
|
||||
*Follows best practices for secret management
|
||||
|
||||
|
||||
|
||||
###End-to-End User Guide (Video → Final Document)
|
||||
|
||||
This section describes how a user can create a structured document from a video using the V2D – Video to Document tool.
|
||||
|
||||
Start the Application:
|
||||
|
||||
Ensure all required API keys are configured as environment variables.
|
||||
|
||||
|
||||
Install dependencies:
|
||||
|
||||
npm install
|
||||
|
||||
|
||||
Start the application:
|
||||
|
||||
npm start
|
||||
|
||||
|
||||
The Electron-based GUI will open.
|
||||
|
||||
#Upload a Video File:
|
||||
|
||||
In the application interface, select Upload Video.
|
||||
|
||||
Choose a supported video file (e.g. .mp4, .mov).
|
||||
|
||||
The video is loaded into the system for processing.
|
||||
|
||||
|
||||
|
||||
#Audio Extraction:
|
||||
|
||||
The application automatically extracts audio from the uploaded video.
|
||||
|
||||
This is handled internally using FFmpeg.
|
||||
|
||||
No user interaction is required for this step.
|
||||
|
||||
|
||||
#Speech-to-Text Transcription:
|
||||
|
||||
The extracted audio is sent to the speech-to-text service.
|
||||
|
||||
The transcription process converts spoken content into text.
|
||||
|
||||
The generated transcript is stored internally and used for further processing.
|
||||
|
||||
|
||||
#Select Document Type:
|
||||
|
||||
The user selects a document type (e.g. Meeting Report).
|
||||
|
||||
Each document type is based on a predefined prompt template.
|
||||
|
||||
The selected template defines the structure and style of the final document.
|
||||
|
||||
|
||||
#Document Generation:
|
||||
|
||||
The transcript and selected prompt are sent to the AI service.
|
||||
|
||||
The AI model processes the input and generates a structured document.
|
||||
|
||||
The output is formatted in Markdown.
|
||||
|
||||
|
||||
#Document Preview:
|
||||
|
||||
The generated document is displayed in the application preview.
|
||||
|
||||
Users can review the content before exporting.
|
||||
|
||||
No manual editing is required, but validation is possible.
|
||||
|
||||
|
||||
#Export the Final Document:
|
||||
|
||||
The user exports the document in the desired format.
|
||||
|
||||
|
||||
#Supported formats include:
|
||||
|
||||
Markdown (.md)
|
||||
|
||||
Word (.docx)
|
||||
|
||||
The document is saved locally.
|
||||
|
||||
|
||||
#Completion:
|
||||
|
||||
The final document is now ready for use.
|
||||
|
||||
The user can repeat the process with another video if needed.
|
||||
|
||||
|
||||
###Resources
|
||||
|
||||
This section lists the main technologies, libraries, and external resources used in the V2D (Video to Document) project. These resources are required to understand, run, and further develop the application.
|
||||
|
||||
##Project Dependencies
|
||||
|
||||
The following packages and tools are used in this project (as defined in package.json):
|
||||
|
||||
Application & Backend
|
||||
|
||||
Node.js – JavaScript runtime environment used for backend processing
|
||||
|
||||
Electron – Framework for building the desktop application
|
||||
|
||||
Express – Backend web framework for handling requests and internal APIs
|
||||
|
||||
AI & API Communication
|
||||
|
||||
@google/genai – Used for AI-based document generation
|
||||
|
||||
Axios – HTTP client for communicating with external APIs (LLMs)
|
||||
|
||||
Video & Audio Processing
|
||||
|
||||
ffmpeg-static – Extracts audio from uploaded video files
|
||||
|
||||
fluent-ffmpeg – Controls FFmpeg operations programmatically
|
||||
|
||||
Document Generation & Preview
|
||||
|
||||
html-to-docx – Converts structured content into Word documents
|
||||
|
||||
Puppeteer – Renders HTML content for document preview and processing
|
||||
|
||||
Configuration & Security
|
||||
|
||||
dotenv – Loads API keys securely from environment variables
|
||||
|
||||
Testing & Development
|
||||
|
||||
Mocha – Unit testing framework
|
||||
|
||||
TypeScript – Improves code quality and type safety
|
||||
|
||||
These dependencies enable the complete end-to-end workflow from video input to structured document output.
|
||||
|
||||
|
||||
##Relevant Repositories
|
||||
|
||||
V2D Main Repository:
|
||||
https://gitlab.rlp.net/proj-wise2526-video2document/video2document
|
||||
|
||||
This repository contains the full source code, configuration files, and documentation for the V2D project.
|
||||
|
||||
Downloads & External Resources
|
||||
|
||||
The following tools and documentation may be required to run or understand the project:
|
||||
|
||||
Node.js: https://nodejs.org
|
||||
|
||||
Electron Documentation: https://www.electronjs.org/docs
|
||||
|
||||
FFmpeg: https://ffmpeg.org
|
||||
|
||||
Google Generative AI: https://ai.google.dev
|
||||
|
||||
Puppeteer: https://pptr.dev
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Custom Document</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f0f2f5;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
margin-top: 50px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
display: block;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 5px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#result {
|
||||
margin-top: 20px;
|
||||
color: #333;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Manage document types</h1>
|
||||
|
||||
<label for="existingDocs">Vorhandene Dokumente auswählen (optional):</label>
|
||||
<!--Drop Down-->
|
||||
<select name="existingDocs" id="existingDocs">
|
||||
<option value="newDoc">-- Neues Dokument erstellen --</option>
|
||||
</select>
|
||||
|
||||
<div id="docNameWrapper">
|
||||
<label for="docName">Dokumentname:</label>
|
||||
<input type="text" id="docName" placeholder="Gib hier den Dokumentnamen ein">
|
||||
</div>
|
||||
|
||||
<label for="prompt">Dein Prompt:</label>
|
||||
<textarea id="prompt" placeholder="Schreibe hier den Prompt für dein Dokument..."></textarea>
|
||||
|
||||
<div class="buttons">
|
||||
<a href="index.html">
|
||||
<button id="goBackBtn">Abbrechen</button>
|
||||
</a>
|
||||
<button id="deleteBtn">Dokument löschen</button>
|
||||
<button id="generateBtn">Dokument speichern</button>
|
||||
</div>
|
||||
|
||||
<div id="result"></div>
|
||||
</div>
|
||||
|
||||
<script src="languages.js"></script>
|
||||
<script>
|
||||
const goBackBtn = document.getElementById("goBackBtn");
|
||||
const generateBtn = document.getElementById("generateBtn");
|
||||
const deleteBtn = document.getElementById("deleteBtn");
|
||||
const existingDocs = document.getElementById("existingDocs");
|
||||
const docNameInput = document.getElementById("docName");
|
||||
const promptInput = document.getElementById("prompt");
|
||||
const resultDiv = document.getElementById("result");
|
||||
const exampleText = "";
|
||||
|
||||
// dokumente speichern
|
||||
generateBtn.addEventListener("click", () => {
|
||||
const name = docNameInput.value.trim();
|
||||
const content = promptInput.value.trim();
|
||||
if (!name || !content) {
|
||||
resultDiv.textContent = "Bitte Dokumentname und Prompt ausfüllen.";
|
||||
setTimeout(() => {
|
||||
resultDiv.textContent = "";
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
window.api.saveTxtFile(name, content).then();
|
||||
resultDiv.textContent = "Dokument erfolgreich gespeichert!";
|
||||
setTimeout(() => {
|
||||
resultDiv.textContent = "";
|
||||
}, 3000);
|
||||
reloadDocuments();
|
||||
|
||||
});
|
||||
|
||||
// dokumente löschen
|
||||
deleteBtn.addEventListener("click", () => {
|
||||
const name = docNameInput.value.trim();
|
||||
|
||||
if (!name) {
|
||||
resultDiv.textContent = "Bitte Dokumentname angeben.";
|
||||
setTimeout(() => {
|
||||
resultDiv.textContent = "";
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmDelete = confirm(
|
||||
`Möchtest du das Dokument "${name}" wirklich löschen?`
|
||||
);
|
||||
|
||||
if (!confirmDelete) return;
|
||||
|
||||
window.api.deleteTxtFile(name).then((success) => {
|
||||
if (success) {
|
||||
resultDiv.textContent = "Dokument erfolgreich gelöscht!";
|
||||
reloadDocuments();
|
||||
existingDocs.value = "newDoc";
|
||||
existingDocs.dispatchEvent(new Event("change"));
|
||||
} else {
|
||||
resultDiv.textContent = "Dokument konnte nicht gelöscht werden.";
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
resultDiv.textContent = "";
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//function to load existingDoc options to the drop down list
|
||||
const select = document.getElementById('existingDocs');
|
||||
|
||||
window.api.getTxtFiles().then(files => {
|
||||
reloadDocuments();
|
||||
});
|
||||
|
||||
//content anzeigen
|
||||
const docNameWrapper = document.getElementById("docNameWrapper");
|
||||
|
||||
existingDocs.addEventListener("change", async () => {
|
||||
const selected = existingDocs.value;
|
||||
|
||||
if (selected === "newDoc") {
|
||||
docNameWrapper.classList.remove("hidden");
|
||||
docNameInput.value = "";
|
||||
promptInput.value = exampleText;
|
||||
return;
|
||||
}
|
||||
docNameWrapper.classList.add("hidden");
|
||||
|
||||
const content = await window.api.readTxtFile(selected);
|
||||
promptInput.value = content;
|
||||
docNameInput.value = selected.replace(".txt", "");
|
||||
});
|
||||
|
||||
//reload drop down
|
||||
function reloadDocuments() {
|
||||
[...existingDocs.querySelectorAll('option:not([value="newDoc"])')]
|
||||
.forEach(o => o.remove());
|
||||
|
||||
window.api.getTxtFiles().then(files => {
|
||||
files.forEach(file => {
|
||||
const option = document.createElement('option');
|
||||
option.value = file;
|
||||
option.textContent = file
|
||||
.replace('.txt', '') // Endung entfernen
|
||||
.replace(/_/g, ' ') // Leerzeichen ersetzen
|
||||
.replace(/\b\w/g, c => c.toUpperCase()) // ersten Buchstaben groß
|
||||
existingDocs.appendChild(option);
|
||||
//customDocumentTypes.appendChild(option);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<script src="./renderer.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
+22
-144
@@ -3,165 +3,43 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title id="title">Video to document</title>
|
||||
<title>Video Upload Drag and Drop + Button</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="h1-wrapper">
|
||||
<section class="p-menu1">
|
||||
<nav id="navbar" class="navigation" role="navigation">
|
||||
<input id="toggle1" type="checkbox" />
|
||||
<label class="hamburger1" for="toggle1">
|
||||
<div class="top"></div>
|
||||
<div class="meat"></div>
|
||||
<div class="bottom"></div>
|
||||
</label>
|
||||
|
||||
<nav class="menu1">
|
||||
<a href="custom_document.html" class="li1">Manage document types</a>
|
||||
<a href="help_page.html" class="li1">Help</a>
|
||||
</nav>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<h1 id="h1">Video to document</h1>
|
||||
</div>
|
||||
|
||||
<div class="step-nav">
|
||||
<div class="step-item active" data-step="1">1. Step</div>
|
||||
<div class="step-item" data-step="2">2. Step</div>
|
||||
<div class="step-item" data-step="3">3. Step</div>
|
||||
<div class="step-item" data-step="4">4. Step</div>
|
||||
<div class="step-item" data-step="5">5. Step</div>
|
||||
<div class="step-item" data-step="6">6. Step</div>
|
||||
</div>
|
||||
|
||||
<div id="middleContainerWrapper" class="middle-container-wrapper">
|
||||
<button id="prevBtn" class="navBtn" disabled>←</button>
|
||||
|
||||
<div class="mitte" id="mitte">
|
||||
|
||||
<div class="step" id="step1">
|
||||
<div class="mitte" id="mitte">
|
||||
<h1>Video to document</h1>
|
||||
<div class="upload-container" id="uploadContainer">
|
||||
<p id="p1">Drag and drop video file</p>
|
||||
<video id="previewThumbnail" autoplay="false">
|
||||
</video>
|
||||
<p>Drag and drop video file</p>
|
||||
<div class="file-name" id="fileName">No video chosen</div>
|
||||
<div id="thumbnailContainer">
|
||||
<img id="thumbnailImage" style="display:none;">
|
||||
</div>
|
||||
|
||||
<button class="custom-btn" id="manualUploadBtn">Search video</button>
|
||||
<input type="file" id="videoUpload" accept="video/*">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step" id="step2" style="display:none;">
|
||||
<div class="KI-wrapper">
|
||||
<label id="labelKI">Select ki:</label>
|
||||
<select name="ai_type" id="ai_type"></select>
|
||||
</div>
|
||||
|
||||
<div class="transcript-wrap">
|
||||
<label id="labelTranscription">Select transcription:</label>
|
||||
<select name="transkript_type" id="transkript_type"></select>
|
||||
</div>
|
||||
|
||||
<div class="type-wrapper">
|
||||
<label id="labelType">Select type:</label>
|
||||
<select name="output_type" id="output_type">
|
||||
<option value="pdf">.pdf</option>
|
||||
<option value="word">.word</option>
|
||||
<option value="txt">.txt</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="language-wrapper">
|
||||
<label id="labelLanguage">Select language:</label>
|
||||
<img id="labelLanguageFlag" src="flags/united-kingdom-flag-png-large.jpg" width="20" height="10" >
|
||||
<select name="language_option" id="language_option">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step" id="step3" style="display:none;">
|
||||
<div class="checkbox-group">
|
||||
<label id="checkbox-label" for="checkbox-group">Choose prefered document style:</label>
|
||||
<div class="checkbox-group">
|
||||
<label for="checkbox-group">Choose prefered document style:</label>
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" name ="docFormat" id="docFormat" value="followup-report">
|
||||
<label id="label_format" for="docFormat">Follow-up Report</label>
|
||||
<input type="checkbox" id="docFormat">
|
||||
<label for "docFormat">Meeting report</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" name="docFormat" id="docFormatSummary1" value="agenda">
|
||||
<label id="label_summary" for="docFormatSummary">Agenda</label>
|
||||
<input type="checkbox" id="docFormatCustom">
|
||||
<label for "docFormatCustom">Summary with timestamps</label>
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" name="docFormat" id="docFormatSummary2" value="result-protocol">
|
||||
<label id="label_summary" for="docFormatSummary">Resultprotocol</label>
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" name="docFormat" id="docFormatSummary3" value="sprint-planning">
|
||||
<label id="label_summary" for="docFormatSummary">Sprint Planning Note</label>
|
||||
</div>
|
||||
<div class="checkbox-container">
|
||||
<input type="checkbox" name="docFormat" id="docFormatCustom" value="custom">
|
||||
<select name="customDocumentTypes" id="customDocumentTypes">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step" id="step4" style="display:none;">
|
||||
<button class="submit-btn" id="submitButton" onclick="checkBoxes()" disabled>Submit</button>
|
||||
|
||||
<div class="testy" id="testy">
|
||||
<div class="box2" id="box1">
|
||||
</div>
|
||||
<p>---Starting---</p>
|
||||
<div class="box2" id="box2">
|
||||
</div>
|
||||
<p>---Transkribing---</p>
|
||||
<div class="box2" id="box3">
|
||||
</div>
|
||||
<p>---Document creation---</p>
|
||||
<div class="box2" id="box4">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step" id="step5" style="display:none;">
|
||||
<div class="speakerView" id="speakerView">
|
||||
<label id="labelSpeaker">Select Speaker:</label>
|
||||
<select name="cur_speaker" id="cur_speaker">
|
||||
</select>
|
||||
</div>
|
||||
<div class="speakerAudio" id="speakerAutio">
|
||||
<label id="labelSpeakerAudio">Selected Speaker</label>
|
||||
<audio controls id="speakerAudioViewer">
|
||||
Currently there is no audio file here.
|
||||
</audio>
|
||||
</div>
|
||||
<div class="speakerWrite" id="speakerWrite">
|
||||
<label id="labelSpeakerWriter">Write name:</label>
|
||||
<input type="text" id="newSpeaker">
|
||||
</div>
|
||||
<div class="speakerButton-group">
|
||||
<button id="speakerLocker" onclick="rewriteSpeakerName()">Rename Speaker</button>
|
||||
<button id="speakerResender" onclick="sendSpeakerPackages()">Rewrite document</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step" id="step6" style="display:none;">
|
||||
<button class="download-btn" id="downloadButton" onclick="fileDownload()">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id ="nextBtn" class="navBtn">→</button>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="languages.js"></script>
|
||||
<script src="script.js"></script>
|
||||
<script src="./renderer.js"></script>
|
||||
<button class="submit-btn" id="submitButton">Submit Video</button>
|
||||
|
||||
<div class="progressbar">
|
||||
<div class="progress_fill"></div>
|
||||
<span class="progress_text">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,48 +0,0 @@
|
||||
var languageOptions = {
|
||||
"eng":{
|
||||
"flagPath": "flags/united-kingdom-flag-png-large.jpg",
|
||||
"labelKI": "Select ki:",
|
||||
"labelTranscription": "Select transcription:",
|
||||
"labelLanguage": "Select language:",
|
||||
"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":{
|
||||
"flagPath": "flags/germany-flag-png-large.jpg",
|
||||
"labelKI": "Waehle KI:",
|
||||
"labelTranscription": "Waehle Transkription:",
|
||||
"labelLanguage": "Waehle Sprache:",
|
||||
"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":{
|
||||
"flagPath": "flags/india-flag-png-large.png",
|
||||
"labelKI": "की का चयन करें:",
|
||||
"labelTranscription": "प्रतिलेखन चुनें:",
|
||||
"labelLanguage": "भाषा चुने:",
|
||||
"title": "दस्तावेज़ के लिए वीडियो",
|
||||
"h1": "दस्तावेज़ के लिए वीडियो",
|
||||
"p1": "वीडियो फ़ाइल खींचें और छोड़ें",
|
||||
"fileName": "कोई वीडियो नहीं चुना गया",
|
||||
"manualUploadBtn": "वीडियो खोजें",
|
||||
"checkbox_group": "पसंदीदा दस्तावेज़ शैली चुनें:",
|
||||
"label_format": "बैठक रिपोर्ट",
|
||||
"label_summary": "टाइमस्टैम्प के साथ सारांश",
|
||||
"submitButton": "जमा करना"
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,48 +1,7 @@
|
||||
|
||||
|
||||
const { contextBridge, ipcRenderer, webUtils } = require('electron')
|
||||
|
||||
try {
|
||||
contextBridge.exposeInMainWorld("explorer", {
|
||||
contextBridge.exposeInMainWorld("explorer", {
|
||||
onFileDrop: (file) => webUtils.getPathForFile(file)
|
||||
})
|
||||
contextBridge.exposeInMainWorld("submit", {
|
||||
submit: (meeting_specifications) => { ipcRenderer.send("file_submit", meeting_specifications) }
|
||||
})
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
getFilePath: (file) => { return webUtils.getPathForFile(file) }
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld("onStartup", {
|
||||
getModuleNames: () => ipcRenderer.invoke('get-module-names')
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
progress: (callback) => ipcRenderer.on('progress', callback)
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld('audios', {
|
||||
speakerAudios: (callback) => ipcRenderer.on('speakerAudios', callback)
|
||||
})
|
||||
contextBridge.exposeInMainWorld("submitSpeaker", {
|
||||
speaker_submit: (speaker_names) => { ipcRenderer.send("speaker_submit", speaker_names) }
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld("download", {
|
||||
file_download: () => { ipcRenderer.send("file_download") }
|
||||
})
|
||||
|
||||
//documenttypes
|
||||
contextBridge.exposeInMainWorld('api', {
|
||||
getTxtFiles: () => ipcRenderer.invoke('get-txt-files'),
|
||||
saveTxtFile: (name, content) =>
|
||||
ipcRenderer.invoke('save-txt-file', name, content),
|
||||
readTxtFile: (fileName) =>
|
||||
ipcRenderer.invoke('read-txt-file', fileName),
|
||||
deleteTxtFile: (fileName) =>
|
||||
ipcRenderer.invoke('delete-txt-file', fileName)
|
||||
});
|
||||
|
||||
|
||||
ipcRenderer.on("error", (event, err) => { alert(err) })
|
||||
} catch (error) {
|
||||
console.log("Error in preload.js");
|
||||
}
|
||||
})
|
||||
|
||||
+8
-229
@@ -1,243 +1,22 @@
|
||||
|
||||
uploadContainer.addEventListener("dragover", (e) =>{
|
||||
try {
|
||||
const dropzone = document.getElementById("uploadContainer");
|
||||
|
||||
dropzone.addEventListener("dragover", (e) =>{
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
} catch (error) {
|
||||
console.log("Error in renderer.js dragover listener function")
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//listener for when a file get dropped on the drag&drop field
|
||||
uploadContainer.addEventListener("drop", (e) => {
|
||||
try {
|
||||
dropzone.addEventListener("drop", (e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const files = e.dataTransfer.files
|
||||
const filePath = window.explorer.onFileDrop(files[0])
|
||||
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
||||
var pathToLower = filePath.toLowerCase();
|
||||
if(testEndings.some(e => pathToLower.endsWith(e))){
|
||||
var holdy = filePath + "";
|
||||
if(holdy.endsWith(".mp4")){
|
||||
console.log(filePath)
|
||||
|
||||
const files1 = e.dataTransfer.files;
|
||||
handleFiles(files1);
|
||||
}else{
|
||||
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log("Error in renderer.js with the listerner for the drop function");
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('load', async (e) => {
|
||||
try {
|
||||
loadLanguageOptions();
|
||||
const value = await window.onStartup.getModuleNames();
|
||||
loadAiOptions(value.ai_modules);
|
||||
loadTranscriptionOptions(value.transcription_modules);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
language_option.addEventListener('change', (e)=>{
|
||||
try {
|
||||
const select = document.getElementById('language_option');
|
||||
changeLanguage(select.value);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//listener for the file explorer search when something got selected
|
||||
videoUpload.addEventListener("change", () => {
|
||||
try {
|
||||
if (videoUpload.files.length > 0) {
|
||||
const file = videoUpload.files;
|
||||
handleFiles(file);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
//listener for the file explorer search
|
||||
manualUploadBtn.addEventListener('click', () => {
|
||||
try {
|
||||
videoUpload.click();
|
||||
} catch (error) {
|
||||
console.log("Error in manualBtn EventListener click");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
stepButtons.forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
try {
|
||||
const step = parseInt(btn.dataset.step);
|
||||
showStep(step);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
prevBtn.addEventListener("click", () => {
|
||||
try {
|
||||
if (currentStep > 1) showStep(currentStep - 1);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
nextBtn.addEventListener("click", () => {
|
||||
try {
|
||||
if(currentStep < totalSteps) showStep(currentStep + 1);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//Checkboxlistener so that only one can be selected at a time
|
||||
docFormat.addEventListener("change", (e) =>{
|
||||
try {
|
||||
if(docFormat.checked){
|
||||
docFormatSummary1.checked = false;
|
||||
docFormatSummary2.checked = false;
|
||||
docFormatSummary3.checked = false;
|
||||
docFormatCustom.checked = false;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
docFormatSummary1.addEventListener("change", (e) =>{
|
||||
try {
|
||||
if(docFormatSummary1.checked){
|
||||
docFormat.checked = false;
|
||||
docFormatSummary2.checked = false;
|
||||
docFormatSummary3.checked = false;
|
||||
docFormatCustom.checked = false;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
docFormatSummary2.addEventListener("change", (e) =>{
|
||||
try {
|
||||
if(docFormatSummary2.checked){
|
||||
docFormatSummary1.checked = false;
|
||||
docFormat.checked = false;
|
||||
docFormatSummary3.checked = false;
|
||||
docFormatCustom.checked = false;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
docFormatSummary3.addEventListener("change", (e) =>{
|
||||
try {
|
||||
if(docFormatSummary3.checked){
|
||||
docFormatSummary1.checked = false;
|
||||
docFormatSummary2.checked = false;
|
||||
docFormat.checked = false;
|
||||
docFormatCustom.checked = false;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
docFormatCustom.addEventListener("change", (e) =>{
|
||||
try {
|
||||
if(docFormatCustom.checked){
|
||||
docFormatSummary1.checked = false;
|
||||
docFormatSummary2.checked = false;
|
||||
docFormatSummary3.checked = false;
|
||||
docFormat.checked = false;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
//Speaker change listener
|
||||
cur_speaker.addEventListener("change", (e) =>{
|
||||
try {
|
||||
document.getElementById("speakerAudioViewer").src = speakerAudios[document.getElementById("cur_speaker").value].src;
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
window.audios.speakerAudios((event, arg) => {
|
||||
loadSpeakerOptions(arg);
|
||||
setSpeakerAudiosValue(arg);
|
||||
});
|
||||
|
||||
window.electron.progress((event, arg) => {
|
||||
if(arg.curstep == 1){
|
||||
setCircleOne();
|
||||
}else if(arg.curstep == 2){
|
||||
setCircleZwo();
|
||||
} else if(arg.curstep == 3){
|
||||
setCircleThree();
|
||||
}else if(arg.curstep == 4){
|
||||
setCircleFour();
|
||||
}
|
||||
});
|
||||
|
||||
function setCircleOne(){
|
||||
try {
|
||||
if(document.getElementById("box1").style.backgroundColor == "green"){
|
||||
document.getElementById("box1").style.backgroundColor = "red";
|
||||
}else{
|
||||
document.getElementById("box1").style.backgroundColor = "green";
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
};
|
||||
function setCircleZwo(){
|
||||
try {
|
||||
if(document.getElementById("box2").style.backgroundColor == "green"){
|
||||
document.getElementById("box2").style.backgroundColor = "red";
|
||||
}else{
|
||||
document.getElementById("box2").style.backgroundColor = "green";
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
function setCircleThree(){
|
||||
try {
|
||||
if(document.getElementById("box3").style.backgroundColor == "green"){
|
||||
document.getElementById("box3").style.backgroundColor = "red";
|
||||
}else{
|
||||
document.getElementById("box3").style.backgroundColor = "green";
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
function setCircleFour(){
|
||||
try {
|
||||
if(document.getElementById("box4").style.backgroundColor == "green"){
|
||||
document.getElementById("box4").style.backgroundColor = "red";
|
||||
}else{
|
||||
document.getElementById("box4").style.backgroundColor = "green";
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
+18
-321
@@ -1,340 +1,37 @@
|
||||
let currentVideoPath = null;
|
||||
//function to check if one checkbox is at least klicked
|
||||
function checkBoxes() {
|
||||
try {
|
||||
const checkboxes = document.querySelectorAll('input[name="docFormat"]');
|
||||
let isChecked = false;
|
||||
var checkedCounter = 0;
|
||||
checkboxes.forEach(function(checkbox){
|
||||
if(checkbox.checked){
|
||||
isChecked = true;
|
||||
checkedCounter++;
|
||||
}
|
||||
});
|
||||
|
||||
if (isChecked) {
|
||||
//Code to submit the video
|
||||
var selectedCheckboxes = {};
|
||||
checkboxes.forEach(function(checkbox){
|
||||
if(checkbox.checked){
|
||||
selectedCheckboxes[checkbox.nextElementSibling.textContent] = "";
|
||||
}
|
||||
});
|
||||
|
||||
const testEndings = [".mp4", ".mov", ".avi", ".mkv"];
|
||||
var pathTest = window.electronAPI.getFilePath(videoUpload.files[0]);
|
||||
var pathToLower = pathTest.toLowerCase();
|
||||
if(testEndings.some(e => pathToLower.endsWith(e))){
|
||||
//assembly of the json for the main
|
||||
|
||||
var typeCheckbox;
|
||||
if(document.getElementById("docFormat").checked) typeCheckbox = document.getElementById("docFormat").value;
|
||||
if(document.getElementById("docFormatSummary1").checked) typeCheckbox = document.getElementById("docFormatSummary1").value;
|
||||
if(document.getElementById("docFormatSummary2").checked) typeCheckbox = document.getElementById("docFormatSummary2").value;
|
||||
if(document.getElementById("docFormatSummary3").checked) typeCheckbox = document.getElementById("docFormatSummary3").value;
|
||||
if(document.getElementById("docFormatCustom").checked) typeCheckbox = document.getElementById("docFormatCustom").value;
|
||||
|
||||
document.getElementById("testy").style.visibility = "visible"
|
||||
document.getElementById("box1").style.backgroundColor = "red";
|
||||
document.getElementById("box2").style.backgroundColor = "red";
|
||||
document.getElementById("box3").style.backgroundColor = "red";
|
||||
document.getElementById("box4").style.backgroundColor = "red";
|
||||
console.log(selectedCheckboxes);
|
||||
const outputType = document.getElementById("output_type");
|
||||
const transcriptionType = document.getElementById("transkript_type");
|
||||
const aiType = document.getElementById("ai_type");
|
||||
const sendingPackage = {
|
||||
"video": {
|
||||
"module":"extraction-video-to-audio",
|
||||
"inputVideoPath": pathTest
|
||||
},
|
||||
"transcription": {
|
||||
"module": transcriptionType.value
|
||||
},
|
||||
"document": {
|
||||
"module":aiType.value,
|
||||
"type": typeCheckbox,
|
||||
"outputType": outputType.value
|
||||
}
|
||||
};
|
||||
window.submit.submit(sendingPackage)
|
||||
}else{
|
||||
alert('The given file is not compatible. These are the available types: [".mp4", ".mov", ".avi", ".mkv"].');
|
||||
}
|
||||
const uploadContainer = document.getElementById('uploadContainer');
|
||||
const fileInput = document.getElementById('videoUpload');
|
||||
const fileName = document.getElementById('fileName');
|
||||
const manualBtn = document.getElementById('manualUploadBtn');
|
||||
const videoPreview = document.getElementById('videoPreview');
|
||||
|
||||
|
||||
} else {
|
||||
//language only english at the moment
|
||||
alert('Please select at least one document type.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js checkBoxes function");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//language changing feature
|
||||
function changeLanguage(language) {
|
||||
try {
|
||||
document.getElementById('labelLanguageFlag').src = languageOptions[language].flagPath;
|
||||
document.getElementById('labelKI').textContent = languageOptions[language].labelKI;
|
||||
document.getElementById('labelTranscription').textContent = languageOptions[language].labelTranscription;
|
||||
document.getElementById('labelLanguage').textContent = languageOptions[language].labelLanguage;
|
||||
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 in script.js changeLanguage function");
|
||||
console.log(error);
|
||||
}
|
||||
manualBtn.addEventListener('click', () => {
|
||||
fileInput.click();
|
||||
});
|
||||
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
handleFiles(fileInput.files);
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
//function to display the file path in the drop down box
|
||||
function handleFiles(files) {
|
||||
try {
|
||||
if (files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type.startsWith('video/')) {
|
||||
const filePath = window.explorer.onFileDrop(files[0])
|
||||
videoUpload.files = files;
|
||||
fileInput.files = files;
|
||||
fileName.textContent = `Chosen video: ${file.name}`;
|
||||
currentVideoPath = filePath;
|
||||
generateThumbnail(filePath);
|
||||
activateSubmitBtn(true);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js handleFiles function");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//function to regulate the progress on the progressbar
|
||||
function updateProgressBar(bar, value) {
|
||||
try {
|
||||
function updateProgressBar(bar, value){
|
||||
value = Math.round(value);
|
||||
bar.querySelector(".progress_fill").style.width = `${value}%`;
|
||||
bar.querySelector(".progress_text").textContent = `${value}%`;
|
||||
} catch (error) {
|
||||
console.log("Error in scripts.js updateProgressBar function");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//function to load ai options to the drop down list
|
||||
function loadAiOptions(options){
|
||||
try {
|
||||
var menu = document.getElementById('ai_type');
|
||||
var object_holdy;
|
||||
var choice ;
|
||||
object_holdy = options
|
||||
for(i = 0; i < options.length; i++){
|
||||
choice = document.createElement('option');
|
||||
choice.textContent = object_holdy[i].displayname;
|
||||
choice.value = object_holdy[i].name;
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js loadAiOptions function");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
//function to load transcription options to the drop down list
|
||||
function loadTranscriptionOptions(options){
|
||||
try {
|
||||
var menu = document.getElementById('transkript_type');
|
||||
var object_holdy;
|
||||
var choice ;
|
||||
object_holdy = options
|
||||
for(i = 0; i < options.length; i++){
|
||||
choice = document.createElement('option');
|
||||
choice.textContent = object_holdy[i].displayname;
|
||||
choice.value = object_holdy[i].name;
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js function loadTranscriptionOptions");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
//function to load data type options to the drop down list
|
||||
function loadDataTypeOptions(options){
|
||||
try {
|
||||
var menu = document.getElementById('output_type');
|
||||
var object_holdy;
|
||||
var choice ;
|
||||
object_holdy = options
|
||||
for(i = 0; i < options.length; i++){
|
||||
choice = document.createElement('option');
|
||||
choice.textContent = object_holdy[i].displayname;
|
||||
choice.value = object_holdy[i].name;
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in script.js function loadDataTypeOptions");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//function to load speaker options to the drop down list
|
||||
function loadSpeakerOptions(options){
|
||||
try {
|
||||
var menu = document.getElementById('cur_speaker');
|
||||
var l = document.getElementById('cur_speaker').options.length -1;
|
||||
for(i = l; i >= 0; i--){
|
||||
menu.remove(i);
|
||||
}
|
||||
var object_holdy;
|
||||
var choice;
|
||||
object_holdy = Object.keys(options);
|
||||
for(i = 0; i < object_holdy.length; i++){
|
||||
choice = document.createElement('option');
|
||||
choice.textContent = options[object_holdy[i]].name;
|
||||
choice.value = object_holdy[i];
|
||||
menu.appendChild(choice);
|
||||
}
|
||||
document.getElementById("speakerAudioViewer").src = options.speakerA.src;
|
||||
} catch (error) {
|
||||
console.log("Error in script.js loadSpeakerOptions function");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
//function to load speaker audio file options to the drop down list
|
||||
function loadSpeakerAudio(option){
|
||||
try {
|
||||
var menu = document.getElementById('speakerAudioViewer');
|
||||
var aud = document.createElement("source");
|
||||
aud.src = options;
|
||||
menu.appendChild(aud);
|
||||
} catch (error) {
|
||||
console.log("Error in script.js loadSpeakerAudio function");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function activateSubmitBtn(hasFile){
|
||||
try {
|
||||
document.getElementById("submitButton").disabled = !hasFile;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function deaktivateProgressbar(){
|
||||
try {
|
||||
document.getElementById("progressbar").style.visibility = "hidden";
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
//Video thumbnail generation
|
||||
function generateThumbnail(path){
|
||||
const videoElement = document.getElementById("previewThumbnail");
|
||||
while (videoElement.firstChild) videoElement.removeChild(videoElement.firstChild);
|
||||
videoElement.src = path;
|
||||
videoElement.type = "video/mov";
|
||||
videoElement.load();
|
||||
videoElement.style.maxWidth = 40;
|
||||
videoElement.style.maxHeight = 40;
|
||||
videoElement.autoplay = false;
|
||||
}
|
||||
|
||||
//Step-navigation
|
||||
const steps = document.querySelectorAll(".step");
|
||||
const stepButtons = document.querySelectorAll(".step-item");
|
||||
let currentStep = 1;
|
||||
const totalSteps = steps.length;
|
||||
|
||||
function showStep(stepNumber) {
|
||||
if (stepNumber < 1 || stepNumber > totalSteps){
|
||||
console.error("StepNumber out of Bounds", stepNumber);
|
||||
return;
|
||||
}
|
||||
steps.forEach(step => step.style.display = "none");
|
||||
document.getElementById("step" + stepNumber).style.display = "flex";
|
||||
|
||||
stepButtons.forEach(btn => btn.classList.remove("active"));
|
||||
document.querySelector(`.step-item[data-step="${stepNumber}"]`).classList.add("active");
|
||||
const activeBtn = document.querySelector(`.step-item[data-step="${stepNumber}"]`);
|
||||
|
||||
if(activeBtn) activeBtn.classList.add("active");
|
||||
|
||||
prevBtn.disabled = stepNumber == 1;
|
||||
nextBtn.disabled = stepNumber === totalSteps;
|
||||
currentStep = stepNumber;
|
||||
}
|
||||
|
||||
|
||||
//Audio value setter
|
||||
var speakerAudios = {};
|
||||
var speakerEndValues = {};
|
||||
function setSpeakerAudiosValue(valy){
|
||||
try {
|
||||
speakerAudios = valy;
|
||||
speakerRewriten = valy;
|
||||
document.getElementById("speakerAudioViewer").src = valy.speakerA.src;
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function rewriteSpeakerName(){
|
||||
try {
|
||||
var tempy = document.getElementById("cur_speaker").value;
|
||||
speakerAudios[tempy].name = document.getElementById("newSpeaker").value;
|
||||
loadSpeakerOptions(speakerAudios);
|
||||
} catch (error) {
|
||||
console.log("\n\n\n" + error + "\n\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
function sendSpeakerPackages(){
|
||||
try {
|
||||
window.submitSpeaker.speaker_submit(speakerAudios);
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function fileDownload() {
|
||||
try {
|
||||
window.download.file_download();
|
||||
} catch (error) {
|
||||
console.error("Download failed:", error);
|
||||
}
|
||||
}
|
||||
+24
-405
@@ -5,32 +5,11 @@ body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #f2f3f4;
|
||||
gap: 10px;
|
||||
background-color: #444;
|
||||
gap: 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#h1 {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin: 0;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#h1-wrapper {
|
||||
position: relative;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
width: 770px;
|
||||
height: 60px;
|
||||
background-color: #FFF;
|
||||
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.upload-container {
|
||||
background: white;
|
||||
@@ -38,10 +17,9 @@ body {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
width: 350px;
|
||||
height: 200px;
|
||||
width: 400px;
|
||||
transition: border 0.3s, background-color 0.3s;
|
||||
border: 2px dashed #7378c9;
|
||||
border: 2px dashed #ccc;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,35 +40,11 @@ body {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#thumbnailContainer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#thumbnailImage {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#previewThumbnail {
|
||||
width: 150px;
|
||||
height: 100px;
|
||||
/*border: 1px dashed black;*/
|
||||
}
|
||||
|
||||
.custom-btn {
|
||||
padding: 10px 20px;
|
||||
margin-top: 10px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
@@ -108,85 +62,27 @@ body {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
#step2 {
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.KI-wrapper {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
--borderColor: #007bfff5;
|
||||
--borderWidth: .125em;
|
||||
}
|
||||
|
||||
.checkbox-group input[type=checkbox] {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
vertical-align: middle;
|
||||
background: #fff;
|
||||
font-size: 1.8em;
|
||||
border-radius: 0.125em;
|
||||
display: inline-block;
|
||||
border: var(--borderWidth) solid var(--borderColor);
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:before,
|
||||
.checkbox-group input[type=checkbox]:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: var(--borderColor);
|
||||
width: calc(var(--borderWidth) * 3);
|
||||
height: var(--borderWidth);
|
||||
top: 50%;
|
||||
left: 10%;
|
||||
transform-origin: left center;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:before {
|
||||
transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(0);
|
||||
transition: transform 200ms ease-in 200ms;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:after {
|
||||
width: calc(var(--borderWidth) * 5);
|
||||
transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(0);
|
||||
transform-origin: left center;
|
||||
transition: transform 200ms ease-in;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:checked:before {
|
||||
transform: rotate(45deg) translate(calc(var(--borderWidth) / -2), calc(var(--borderWidth) / -2)) scaleX(1);
|
||||
transition: transform 200ms ease-in;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:checked:after {
|
||||
width: calc(var(--borderWidth) * 5);
|
||||
transform: rotate(-45deg) translateY(calc(var(--borderWidth) * 2)) scaleX(1);
|
||||
transition: transform 200ms ease-out 200ms;
|
||||
}
|
||||
.checkbox-group input[type=checkbox]:focus {
|
||||
outline: calc(var(--borderWidth) / 2) dotted rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.checkbox-container{
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-items: left;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
margin: 130px auto 10px auto;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
@@ -195,36 +91,30 @@ input[type="file"] {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mitte {
|
||||
background-color: #FFF;
|
||||
background-color: #eaf0ff;
|
||||
display: flex;
|
||||
width: 700px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40px;
|
||||
padding: 5% 50px;
|
||||
margin-top: 20px;
|
||||
gap: 10px;
|
||||
border: 0px;
|
||||
border: 2px;
|
||||
border-color: black;
|
||||
border-style: solid;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.progressbar{
|
||||
position: relative;
|
||||
width: 210px;
|
||||
height: 30px;
|
||||
margin: 50px 20px 5px 20px;
|
||||
background: rgb(42, 46, 78);
|
||||
background: darkslategray;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.progress_fill{
|
||||
@@ -239,275 +129,4 @@ input[type="file"] {
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
transform: translateY(-50%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.dropdownMenus {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 1px;
|
||||
margin-bottom: 30px;
|
||||
gap: 170px;
|
||||
padding: 2px 10px 2px 10px;
|
||||
}
|
||||
|
||||
#ai_type, #transkript_type, #language_option {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.labelDiv {
|
||||
gap: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow-wrap:inherit;
|
||||
padding-bottom: 20px;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/*Step bar*/
|
||||
.step-nav {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
padding: 10px 30px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.step-item {
|
||||
padding: 10px 25px;
|
||||
border-radius: 8px;
|
||||
background: #eee;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.step-item.active {
|
||||
background: #007BFF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.step-item:hover {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
|
||||
/*panels*/
|
||||
.step {
|
||||
margin-top: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
/*Navigation arrows*/
|
||||
.step-nav-arrows {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.middle-container-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
width: max-content;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.navBtn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 25px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.navBtn:disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.testy{
|
||||
background-color: #FFF;
|
||||
display: flex;
|
||||
width: auto;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
gap: 10px;
|
||||
border: 0px;
|
||||
border-color: black;
|
||||
border-style: solid;
|
||||
border-radius: 6px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.box2 {
|
||||
background-color: red;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 5px;
|
||||
border: 1px solid black;
|
||||
margin: 5px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
li {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.p-menu1 {
|
||||
margin-left: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.hamburger1 {
|
||||
height: 45px;
|
||||
margin: 0;
|
||||
padding-top: 8px;
|
||||
display: grid;
|
||||
grid-template-rows: repeat(3, 1fr);
|
||||
justify-items: center;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
.hamburger1 div {
|
||||
background-color: rgb(61, 61, 61);
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 5px;
|
||||
margin-top: 0;
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
#toggle1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#toggle1:checked + .hamburger1 .top {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
margin-top: 22.5px;
|
||||
}
|
||||
|
||||
#toggle1:checked + .hamburger1 .meat {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
#toggle1:checked + .hamburger1 .bottom {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
#toggle1:checked ~ .menu1 {
|
||||
height: 150px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/* Menu */
|
||||
.menu1 {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 20px;
|
||||
width: 240px;
|
||||
border-radius: 5px;
|
||||
background-color: #1C3B69;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr repeat(4, 0.5fr);
|
||||
grid-row-gap: 25px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
clear: both;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
height: 0px;
|
||||
overflow: hidden;
|
||||
transition: height 0.3s ease, width 0.3s ease;
|
||||
z-index: 9999;
|
||||
-webkit-transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.menu1 a:first-child {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.menu1 a:last-child {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.li1 {
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 10px 0px;
|
||||
font: 700 20px 'Oswald', sans-serif;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.li1:hover {
|
||||
background-color: #FFF;
|
||||
color: rgb(61, 61, 61);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
-webkit-transition: all 0.3s ease;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#step2, #step3, #step5 {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
#step4 {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#step5 {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
margin-left: auto;
|
||||
margin-right: 0px;
|
||||
margin-top: 130px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#speakerLocker, #speakerResender{
|
||||
padding: 10px 20px;
|
||||
margin: 20px auto;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -35,8 +35,6 @@ console.log("-------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// --------------------------------------------------------- CLI COMMANDS --------------------------------------------------------- //
|
||||
|
||||
const rl = readline.createInterface({
|
||||
@@ -57,245 +55,4 @@ rl.on("line", data =>{
|
||||
|
||||
// ----------------------------------------------------------- ELECTRON ----------------------------------------------------------- //
|
||||
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new electron.BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
preload: `${mainDir}/electron/main/preload.js`
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.loadFile('./electron/main/index.html');
|
||||
}
|
||||
|
||||
electron.app.whenReady().then(createWindow);
|
||||
|
||||
|
||||
// electron.ipcMain.on("extract", (event, args) => {
|
||||
// mapFunctions.get("extraction-video-to-audio").function(args)
|
||||
// })
|
||||
|
||||
// setTimeout(() => {
|
||||
// mainWindow.webContents.send("fuck", "worked uwu")
|
||||
// }, 5000);
|
||||
|
||||
electron.ipcMain.handle('get-module-names', async () => {
|
||||
let module_array = {
|
||||
"ai_modules":[],
|
||||
"transcription_modules":[]
|
||||
}
|
||||
mapFunctions.forEach(e => {
|
||||
switch(e.type){
|
||||
case "llm":
|
||||
module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
|
||||
break;
|
||||
case "transcription":
|
||||
module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
|
||||
break;
|
||||
}
|
||||
})
|
||||
// console.log(module_array);
|
||||
return module_array
|
||||
});
|
||||
|
||||
|
||||
// electron.ipcMain.on("get_modules", async (event, args) => {
|
||||
// let module_array = {
|
||||
// "ai_modules":[],
|
||||
// "transcription_modules":[]
|
||||
// }
|
||||
// mapFunctions.forEach(e => {
|
||||
// switch(e.type){
|
||||
// case "llm":
|
||||
// module_array.ai_modules.push({"name": e.name, "displayname": e.displayname})
|
||||
// break;
|
||||
// case "transcription":
|
||||
// module_array.transcription_modules.push({"name": e.name, "displayname": e.displayname})
|
||||
// break;
|
||||
// }
|
||||
// })
|
||||
// console.log(module_array);
|
||||
|
||||
// mainWindow.webContents.send("modules", module_array)
|
||||
// })
|
||||
|
||||
var globalArgs = {}
|
||||
var globalFinalHtmlPath = ""
|
||||
|
||||
electron.ipcMain.on("file_submit", async (event, args) => {
|
||||
try {
|
||||
globalArgs = args
|
||||
let curstep = 0
|
||||
let totalsteps = 4
|
||||
|
||||
const TEMPLATE_MAP = {
|
||||
"followup-report": "followup_report.txt",
|
||||
"agenda": "agenda.txt",
|
||||
"result-protocol": "result_protocol.txt",
|
||||
"sprint-planning": "sprint_planning_note.txt",
|
||||
"custom": "custom_document.txt"
|
||||
};
|
||||
|
||||
const templateFile = TEMPLATE_MAP[args.document.type];
|
||||
|
||||
if (!templateFile) {
|
||||
throw new Error("Unknown document type: " + args.document.type);
|
||||
}
|
||||
|
||||
console.log(args);
|
||||
let audiopath = ""
|
||||
let transcriptpath = ""
|
||||
|
||||
console.log("\n\n Running the Video to Audio Extractor");
|
||||
// This code handles the Video to Audio extraction module call
|
||||
await mapFunctions.get("module-handler").function(args.video.module, {inputVideoPath: args.video.inputVideoPath, outputType: mapFunctions.get(args.transcription.module).audioformat}).then(resp => {
|
||||
console.log(resp);
|
||||
audiopath = resp
|
||||
curstep++
|
||||
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||
}).catch(err => {
|
||||
mainWindow.webContents.send("error", err)
|
||||
console.log(err);
|
||||
return
|
||||
})
|
||||
|
||||
|
||||
console.log("\n\n Running the Audio to Transcription module");
|
||||
// TODO implement transcription module
|
||||
// This code handles the Audio to Text transcription module call
|
||||
await mapFunctions.get("module-handler").function(args.transcription.module, audiopath).then(resp => {
|
||||
console.log(resp);
|
||||
transcriptpath = resp
|
||||
curstep++
|
||||
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||
}).catch(err => {
|
||||
mainWindow.webContents.send("error", err)
|
||||
console.log(err);
|
||||
return
|
||||
})
|
||||
|
||||
|
||||
console.log("\n\n Running the Transcription Summarizer module");
|
||||
// This code summarises the transcript, so that it can be used by an llm
|
||||
// await mapFunctions.get("summarize-transcription").function('A:\\programing\\@projects\\video2document\\storage\\transcripts\\IMG_2978.json').then(resp => {
|
||||
await mapFunctions.get("summarize-transcription2").function(transcriptpath).then(resp => {
|
||||
console.log(resp);
|
||||
transcriptpath = resp
|
||||
curstep++
|
||||
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||
}).catch(err => {
|
||||
mainWindow.webContents.send("error", err)
|
||||
console.log(err);
|
||||
return
|
||||
})
|
||||
|
||||
console.log("\n\n Running the LLM module");
|
||||
// TODO implement documentation module
|
||||
// This code handles the Text to Document processing module call
|
||||
|
||||
console.log(`\n\n Running the LLM for Document Style ${args.document.type}`);
|
||||
|
||||
await mapFunctions.get("module-handler").function(args.document.module, { inputTranscriptPath: transcriptpath, documentTypePath: "./storage/documentType/" + templateFile, language: "en" }).then(resp => {
|
||||
console.log(resp);
|
||||
globalFinalHtmlPath = resp
|
||||
curstep++
|
||||
mainWindow.webContents.send("progress", {curstep:curstep, totalsteps:totalsteps})
|
||||
}).catch(err => {
|
||||
mainWindow.webContents.send("error", err)
|
||||
console.log(err);
|
||||
return
|
||||
})
|
||||
|
||||
|
||||
await mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: transcriptpath }).then(resp => {
|
||||
mainWindow.webContents.send("speakerAudios", resp)
|
||||
}).catch(err => {
|
||||
mainWindow.webContents.send("error", err)
|
||||
console.log(err);
|
||||
return
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
|
||||
electron.ipcMain.on("file_download", async() => {
|
||||
await mapFunctions.get("htmlDocumentConverter").convert({inputPath:globalFinalHtmlPath, format: globalArgs.document.outputType, showDialog: true});
|
||||
})
|
||||
|
||||
electron.ipcMain.on("speaker_submit", async() => {
|
||||
console.log("\n\n\nJa also hier kam was an \n\n\n");
|
||||
})
|
||||
|
||||
let q =
|
||||
{
|
||||
video: {
|
||||
module: "String", // The name of the module, idk if we ever implement other extraction modules, the default one is extraction-video-to-audio
|
||||
inputVideoPath: "String", // See script.js on line 27 for an example of what this should look like
|
||||
outputType: "String" // The file format to be used for the audio output file, such as wav, mp3, flac and so on
|
||||
},
|
||||
transcription:{
|
||||
module: "String" // The module name of the transcription model you want to use
|
||||
},
|
||||
document:{
|
||||
module: "String", // The module name of the AI model you want to use to create the document
|
||||
styles: [ // An array of all the document styles/prompts you want to have the document be processed with
|
||||
{
|
||||
prompt: "String",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let q1 = {
|
||||
"ai_modules": [
|
||||
{name:"abc", displayname:"ABC"},
|
||||
{name:"qeg", displayname:"aqghegahu"}
|
||||
],
|
||||
"transcription_modules": [
|
||||
{name:"abc", displayname:"ABC"},
|
||||
{name:"qeg", displayname:"aqghegahu"}
|
||||
]
|
||||
}
|
||||
|
||||
//gibt Documentfiles an preload zurück
|
||||
|
||||
electron.ipcMain.handle('get-txt-files', () => {
|
||||
const storagePath = `${mainDir}/storage/documentType`
|
||||
|
||||
return fs.readdirSync(storagePath)
|
||||
.filter(f => f.endsWith('.txt'))
|
||||
});
|
||||
|
||||
//speichern neuer document types
|
||||
|
||||
electron.ipcMain.handle('save-txt-file', (event, fileName, content) => {
|
||||
const filePath = `${mainDir}/storage/documentType/${fileName}.txt`;
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
return true;
|
||||
});
|
||||
|
||||
//read file content
|
||||
electron.ipcMain.handle('read-txt-file', (event, fileName) => {
|
||||
const filePath = `${mainDir}/storage/documentType/${fileName}`;
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
});
|
||||
|
||||
//delete documentfiles
|
||||
|
||||
electron.ipcMain.handle('delete-txt-file', (event, fileName) => {
|
||||
const filePath = `${mainDir}/storage/documentType/${fileName}.txt`;
|
||||
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// TODO - Add Electron support to the project
|
||||
|
||||
Generated
+190
-2684
File diff suppressed because it is too large
Load Diff
+6
-12
@@ -1,16 +1,11 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.30.0",
|
||||
"@types/axios": "^0.9.36",
|
||||
"axios": "^1.13.2",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron": "^39.1.1",
|
||||
"@google/genai": "^1.29.0",
|
||||
"cli-progress": "^3.12.0",
|
||||
"express": "^5.1.0",
|
||||
"ffmpeg-static": "^5.3.0",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"html-to-docx": "^1.8.0",
|
||||
"mocha": "^11.7.5",
|
||||
"puppeteer": "^24.33.0"
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"fluent-ffmpeg": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cli-progress": "^3.11.6",
|
||||
@@ -28,8 +23,7 @@
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron main.js",
|
||||
"test": "mocha ./test/unit/test.js"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
pytest
|
||||
-15
@@ -6,20 +6,5 @@ platform = process.platform
|
||||
mainDir = __dirname
|
||||
fs = require("fs")
|
||||
readline = require("readline")
|
||||
|
||||
puppeteer = require("puppeteer")
|
||||
htmltodocx = require("html-to-docx")
|
||||
config = require("./config/config")
|
||||
|
||||
ffmpegPath = require('ffmpeg-static');
|
||||
ffmpeg = require('fluent-ffmpeg');
|
||||
path = require('path');
|
||||
// cliProgress = require('cli-progress');
|
||||
|
||||
// { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||
|
||||
electron = require('electron');
|
||||
|
||||
axios = require("axios")
|
||||
|
||||
console.log(require('dotenv').config({path: __dirname + '/.env'}));
|
||||
@@ -0,0 +1,5 @@
|
||||
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/Kurzgesagt.mov
|
||||
npx ts-node ./transcribe.ts ../storage/audio/Kurzgesagt.wav
|
||||
|
||||
npx ts-node ./extract.ts /Users/mikehughes/Downloads/Testvideo/GitLabMeeting.mov
|
||||
npx ts-node ./transcribe.ts ../storage/audio/GitLabMeeting.wav
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import { extractAudioFromVideo } from "../services/modules/extraction/ffmpegExtractor.ts";
|
||||
|
||||
const videoPath = process.argv[2];
|
||||
|
||||
if (!videoPath) {
|
||||
console.error("Usage: ts-node extractAudio.ts <videoPath>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Extracting audio from: ${videoPath}`);
|
||||
|
||||
await extractAudioFromVideo(videoPath); // Call the extraction function (ffmpegExtractor.ts in services/modules/extraction)
|
||||
|
||||
console.log("Audio extraction completed successfully.");
|
||||
} catch (err) {
|
||||
console.error("Audio extraction failed:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -1,47 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
//node show-models.js, remember to set SAIA_API_KEY in your environment before running the script
|
||||
|
||||
const SAIA_API_KEY = process.env.SAIA_API_KEY;
|
||||
const SAIA_MODELS_URL = "https://chat-ai.academiccloud.de/v1/models";
|
||||
|
||||
// Script to list available models
|
||||
(async () => {
|
||||
if (!SAIA_API_KEY) {
|
||||
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Fetching available models from SAIA...\n");
|
||||
|
||||
try {
|
||||
const response = await fetch(SAIA_MODELS_URL, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||
"Accept": "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log("Available models:");
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
|
||||
if (data.data && Array.isArray(data.data)) {
|
||||
console.log("\n\nModel IDs:");
|
||||
data.data.forEach(model => {
|
||||
console.log(`- ${model.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching models:", error);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,18 @@
|
||||
import { whisperLocal } from "../services/modules/transcription/local/whisperLocal.ts";
|
||||
|
||||
const audioPath = process.argv[2];
|
||||
if (!audioPath) {
|
||||
console.error("Please provide an audio file path as argument.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const whisper = new whisperLocal();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const text = await whisper.transcribe(audioPath);
|
||||
console.log(text);
|
||||
} catch (err) {
|
||||
console.error("Transcription failed:", err);
|
||||
}
|
||||
})();
|
||||
@@ -1,102 +0,0 @@
|
||||
|
||||
const ffmpeg = require("fluent-ffmpeg");
|
||||
const ffmpegPath = require("ffmpeg-static");
|
||||
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
module.exports = {
|
||||
name: "extract-speaker-snippets",
|
||||
type: "audio",
|
||||
displayname: "Extract Speaker Snippets",
|
||||
|
||||
async function(parameter) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
let output = {}
|
||||
|
||||
// console.log("Extract Speaker Snippets\n");
|
||||
|
||||
// Pfade
|
||||
const AUDIO_PATH = parameter.audioPath; // Gesamt-Audio
|
||||
const JSON_PATH = parameter.jsonPath; // json summary
|
||||
const OUTPUT_DIR = path.join(__dirname, "/../../../storage/audio/speakerSnippets");
|
||||
|
||||
|
||||
if (!AUDIO_PATH || !JSON_PATH) {
|
||||
// console.error("no audioPath or jsonPath available");
|
||||
reject(new Error("no audioPath or jsonPath available"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Output-Ordner
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
// JSON laden
|
||||
let entries;
|
||||
try {
|
||||
entries = JSON.parse(fs.readFileSync(JSON_PATH, "utf8"));
|
||||
} catch (err) {
|
||||
// console.error("JSON reading failed", err);
|
||||
reject(new Error(err));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(entries)) {
|
||||
// console.error("JSON is not an Array");
|
||||
reject(new Error("JSON is not an Array"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Pro Speaker genau EINEN Satz merken
|
||||
const speakerMap = {};
|
||||
|
||||
for (const item of entries) {
|
||||
if (!speakerMap[item.speaker]) {
|
||||
speakerMap[item.speaker] = item;
|
||||
}
|
||||
}
|
||||
|
||||
// FFmpeg pro Speaker ausführen (sequenziell)
|
||||
for (const speaker of Object.keys(speakerMap)) {
|
||||
const data = speakerMap[speaker];
|
||||
|
||||
// ms → Sekunden
|
||||
const startSec = data.start / 1000;
|
||||
const durationSec = (data.end - data.start) / 1000;
|
||||
|
||||
if (durationSec <= 0) {
|
||||
// console.log(`invalid times for Speaker ${speaker}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const outFile = path.join(OUTPUT_DIR, `speaker_${speaker}.wav`);
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
ffmpeg(AUDIO_PATH)
|
||||
.setStartTime(startSec)
|
||||
.setDuration(durationSec)
|
||||
.output(outFile)
|
||||
.on("end", () => {
|
||||
output[`speaker${speaker}`] = {src: outFile, name: `speaker${speaker}`}
|
||||
// console.log(`Snippet erstellt: speaker_${speaker}.wav`);
|
||||
res();
|
||||
})
|
||||
.on("error", (err) => {
|
||||
// console.error(`FFmpeg Fehler (${speaker})`, err.message);
|
||||
rej(err);
|
||||
return
|
||||
})
|
||||
.run();
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
resolve(output)
|
||||
// console.log("\nAlle Speaker-Snippets erstellt\n");
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -1,197 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer');
|
||||
const htmlToDocx = require('html-to-docx');
|
||||
const { execSync } = require('child_process');
|
||||
const os = require('os');
|
||||
|
||||
const outputDir = path.join(__dirname, "../../../storage/documents");
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
async function showSaveDialog(defaultName, format) {
|
||||
const platform = os.platform();
|
||||
|
||||
if (platform === 'darwin') {
|
||||
// macOS
|
||||
const applescript = `
|
||||
set defaultName to "${defaultName}.${format}"
|
||||
set theFile to choose file name with prompt "Dokument speichern als:" default name defaultName
|
||||
POSIX path of theFile
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = execSync(`osascript -e '${applescript}'`, { encoding: 'utf8' });
|
||||
return result.trim();
|
||||
} catch (err) {
|
||||
if (err.status === 1) return null; // User canceled
|
||||
throw err;
|
||||
}
|
||||
} else if (platform === 'win32') {
|
||||
const safeName = decodeURIComponent(defaultName);
|
||||
|
||||
const powershell = `
|
||||
Add-Type -AssemblyName System.Windows.Forms;
|
||||
$dialog = New-Object System.Windows.Forms.SaveFileDialog;
|
||||
$dialog.FileName = '${safeName}.${format}';
|
||||
$dialog.Filter = '${format.toUpperCase()} Dateien (*.${format})|*.${format}|Alle Dateien (*.*)|*.*';
|
||||
$dialog.Title = 'Dokument speichern als';
|
||||
$result = $dialog.ShowDialog();
|
||||
if ($result -eq 'OK') { $dialog.FileName }
|
||||
`;
|
||||
|
||||
try {
|
||||
const result = execSync(
|
||||
`powershell -NoProfile -Command "${powershell.replace(/\r?\n/g, ' ')}"`,
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
return result.trim() || null;
|
||||
} catch (err) {
|
||||
if (err.status === 1) return null; // User cancelled
|
||||
throw new Error("Save dialog failed: " + err.message);
|
||||
}
|
||||
} else {
|
||||
// Linux - zenity oder kdialog
|
||||
try {
|
||||
const result = execSync(
|
||||
`zenity --file-selection --save --confirm-overwrite --filename="${defaultName}.${format}"`,
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
return result.trim();
|
||||
} catch (err) {
|
||||
try {
|
||||
const result = execSync(
|
||||
`kdialog --getsavefilename . "${defaultName}.${format}"`,
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
return result.trim();
|
||||
} catch (err2) {
|
||||
// Fallback
|
||||
return path.join(os.homedir(), 'Downloads', `${defaultName}.${format}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const module_exports = {
|
||||
name: "htmlDocumentConverter",
|
||||
type: "converter",
|
||||
displayname: "HTML Document Converter",
|
||||
description: "Converts LLM-generated HTML to PDF, DOCX, TXT, or HTML",
|
||||
|
||||
/**
|
||||
* Main conversion function
|
||||
* @param {Object} options
|
||||
* @param {string} options.inputPath - Path to the HTML input
|
||||
* @param {string} options.format - 'pdf' | 'docx' | 'html' | 'txt'
|
||||
* @param {string} [options.outputName] - Optional output filename (without extension)
|
||||
* @param {boolean} [options.showDialog] - Show save dialog (default: false in module mode, true in CLI mode)
|
||||
*/
|
||||
async convert({ inputPath, format = 'pdf', outputName, showDialog = false }) {
|
||||
if (!fs.existsSync(inputPath)) {
|
||||
throw new Error(`Input file not found: ${inputPath}`);
|
||||
}
|
||||
|
||||
const ext = path.extname(inputPath).toLowerCase();
|
||||
const baseName = outputName || path.basename(inputPath, ext);
|
||||
|
||||
let outputFile;
|
||||
|
||||
if (showDialog) {
|
||||
// Zeige nativen Dialog
|
||||
outputFile = await showSaveDialog(baseName, format);
|
||||
if (!outputFile) {
|
||||
console.log('Speichervorgang abgebrochen.');
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Nutze Standard-Ausgabeverzeichnis
|
||||
outputFile = path.join(outputDir, `${baseName}.${format.toLowerCase()}`);
|
||||
}
|
||||
|
||||
let htmlContent = fs.readFileSync(inputPath, 'utf8');
|
||||
|
||||
// Remove <think> tags if present
|
||||
htmlContent = htmlContent.replace(/<think>[\s\S]*?<\/think>/gi, '');
|
||||
|
||||
switch (format.toLowerCase()) {
|
||||
case 'html':
|
||||
fs.writeFileSync(outputFile, htmlContent, 'utf8');
|
||||
break;
|
||||
case 'pdf':
|
||||
await this.htmlToPDF(htmlContent, outputFile);
|
||||
break;
|
||||
case 'docx':
|
||||
await this.htmlToDOCX(htmlContent, outputFile);
|
||||
break;
|
||||
case 'txt':
|
||||
fs.writeFileSync(outputFile, this.htmlToTXT(htmlContent), 'utf8');
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported format: ${format}`);
|
||||
}
|
||||
|
||||
console.log(`Erfolgreich gespeichert: ${outputFile}`);
|
||||
return outputFile;
|
||||
},
|
||||
|
||||
// HTML → PDF
|
||||
async htmlToPDF(html, outputPath) {
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setContent(html, { waitUntil: 'networkidle0' });
|
||||
await page.pdf({
|
||||
path: outputPath,
|
||||
format: 'A4',
|
||||
printBackground: true,
|
||||
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' }
|
||||
});
|
||||
await browser.close();
|
||||
},
|
||||
|
||||
// HTML → DOCX
|
||||
async htmlToDOCX(html, outputPath) {
|
||||
const buffer = await htmlToDocx(html);
|
||||
fs.writeFileSync(outputPath, buffer);
|
||||
},
|
||||
|
||||
// HTML → TXT (rudimentär)
|
||||
htmlToTXT(html) {
|
||||
return html.replace(/<[^>]*>/g, '').replace(/\s+\n/g, '\n').trim();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = module_exports;
|
||||
|
||||
// CLI usage mit Dialog
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
console.log('Usage: node htmlDocumentConverter.js <input.html> [format]');
|
||||
console.log('Formats: pdf (default), docx, html, txt');
|
||||
console.log('');
|
||||
console.log('Ein nativer "Speichern unter" Dialog wird automatisch geöffnet.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const inputPath = args[0];
|
||||
const format = args[1] || 'pdf';
|
||||
|
||||
try {
|
||||
await module_exports.convert({
|
||||
inputPath,
|
||||
format,
|
||||
showDialog: true
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Konvertierung fehlgeschlagen:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
name:"chatgpt", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
type:"document", // value used to differentiate each module to order them in the UI
|
||||
displayname:"ChatGPT", // The displayname used within the UI
|
||||
async function(parameter){
|
||||
// TODO add code to actually send the transcript to ChatGPT and get a response back
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import fs from "fs";
|
||||
|
||||
// Mike Hughes 10/11/25 - Code inspired from
|
||||
// https://www.geeksforgeeks.org/javascript/javascript-program-to-read-text-file/ for reading text files in JS
|
||||
// and from https://ai.google.dev/gemini-api/docs/quickstart?hl=de for using Gemini API
|
||||
// Before using, make sure to export the Google Gemini API key as an environment variable:
|
||||
// export GOOGLE_API_KEY="your_api_key_here"
|
||||
// Keys can be obtained from https://aistudio.google.com/app/api-keys
|
||||
// Also make sure to install the package via sudo npm install @google/genai
|
||||
|
||||
const ai = new GoogleGenAI({});
|
||||
let transcript = "";
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const transcript = await fs.promises.readFile('../../../../storage/transcripts/Kurzgesagt.txt', 'utf-8');
|
||||
|
||||
const response = await ai.models.generateContent({
|
||||
model: "gemini-2.5-flash",
|
||||
contents: `Create a short documentation about the content of the following transcript:\n\n${transcript}`,
|
||||
});
|
||||
|
||||
console.log(response.text);
|
||||
fs.writeFileSync("../../../../storage/documents/Kurzgesagt_documentation.md", response.text, "utf8");
|
||||
} catch (error) {
|
||||
console.error('Error generating content:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,74 +0,0 @@
|
||||
// Ensure ffmpeg binary is available
|
||||
if (!ffmpegPath) {
|
||||
throw new Error('FFmpeg binary not found!');
|
||||
}
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
// Prepare output directory (always storage/audio under project root)
|
||||
const outputDir = `${__dirname}/../../../storage/audio`;
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
name:"extraction-video-to-audio", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
type:"extractor", // value used to differentiate each module to order them in the UI
|
||||
displayname:"Default extractor", // The displayname used within the UI
|
||||
async function(parameter){
|
||||
/*
|
||||
parameter structure:
|
||||
{
|
||||
inputVideoPath: String, // Path to the file
|
||||
outputType: String // Audio file output format
|
||||
}
|
||||
*/
|
||||
try {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.extractAudioFromVideo(parameter.inputVideoPath, parameter.outputType)
|
||||
.then((resp) => resolve(resp))
|
||||
.catch((err) => {reject(err)});
|
||||
})
|
||||
} catch (error) {
|
||||
console.log(parameter.outputType);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
// Derive input and output paths
|
||||
// const inputVideoPath = process.argv[2];
|
||||
// console.log(process.argv);
|
||||
|
||||
/**
|
||||
* Extracts audio from a video using ffmpeg.
|
||||
* - Converts video to WAV (16 kHz, Mono, PCM optional if needed)
|
||||
* - Shows CLI progress bar
|
||||
* - Handles errors gracefully (without errors)
|
||||
*/
|
||||
extractAudioFromVideo: async function (videoFilePath, outputType){
|
||||
let inputVideoName = path.basename(videoFilePath, path.extname(videoFilePath));
|
||||
let outputAudioPath = path.join(outputDir, `${inputVideoName}.${outputType}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
ffmpeg(videoFilePath)
|
||||
.outputFormat(outputType)
|
||||
// .audioCodec('pcm_s16le')
|
||||
.audioChannels(1)
|
||||
.audioFrequency(16000)
|
||||
.on('end', () => {
|
||||
resolve(outputAudioPath);
|
||||
})
|
||||
.on('error', (err) => {
|
||||
// console.error(`failed_audio_extraction on type ${outputType}: ${err.message}`);
|
||||
reject(err);
|
||||
})
|
||||
.save(outputAudioPath);
|
||||
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import ffmpegPath from 'ffmpeg-static';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import cliProgress from 'cli-progress';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Base code reference: https://docs.yemreak.com/arsiv/programming/extract-audio-from-video-with-typescript-and-ffmpeg
|
||||
// Test command: npx ts-node ./extract.ts /path/to/video.mp4
|
||||
|
||||
/**
|
||||
* Extracts audio from a video file and saves it as WAV.
|
||||
* @param videoFilePath Path to the input video file.
|
||||
*/
|
||||
|
||||
// Ensure ffmpeg binary is available
|
||||
if (!ffmpegPath) {
|
||||
throw new Error('FFmpeg binary not found!');
|
||||
}
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
|
||||
// Ensure an input video path is provided via CLI
|
||||
if (process.argv.length < 3) {
|
||||
console.error('Usage: ts-node ./extract.ts <input-video-path>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Resolve __dirname equivalent in ESM
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
// Prepare output directory (always storage/audio under project root)
|
||||
const outputDir = path.join(__dirname, '..', '..', '..', 'storage', 'audio');
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Derive input and output paths
|
||||
const inputVideoPath = process.argv[2];
|
||||
const inputVideoName = path.basename(inputVideoPath, path.extname(inputVideoPath));
|
||||
const outputAudioPath = path.join(outputDir, `${inputVideoName}.wav`);
|
||||
|
||||
// Initialize CLI progress bar
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
format: 'Processing |{bar}| {percentage}% | {timemark}',
|
||||
barCompleteChar: '\u2588',
|
||||
barIncompleteChar: '\u2591',
|
||||
hideCursor: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Extracts audio from a video using ffmpeg.
|
||||
* - Converts video to WAV (16 kHz, Mono, PCM optional if needed)
|
||||
* - Shows CLI progress bar
|
||||
* - Handles errors gracefully (without errors)
|
||||
*/
|
||||
export function extractAudioFromVideo(videoFilePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
ffmpeg(videoFilePath)
|
||||
.outputFormat('wav')
|
||||
.audioCodec('pcm_s16le')
|
||||
.audioChannels(1)
|
||||
.audioFrequency(16000)
|
||||
.on('progress', (progress) => {
|
||||
if (!progressBar.isActive) progressBar.start(100, 0, { timemark: '00:00:00' });
|
||||
if (progress.percent) {
|
||||
progressBar.update(progress.percent, { timemark: progress.timemark });
|
||||
}
|
||||
})
|
||||
.on('end', () => {
|
||||
progressBar.update(100, { timemark: 'done' });
|
||||
progressBar.stop();
|
||||
console.log(`Extraction completed: ${outputAudioPath}`);
|
||||
resolve();
|
||||
})
|
||||
.on('error', (err) => {
|
||||
progressBar.stop();
|
||||
console.error(`failed_audio_extraction: ${err.message}`);
|
||||
reject(err);
|
||||
})
|
||||
.save(outputAudioPath);
|
||||
});
|
||||
}
|
||||
|
||||
// Run extraction if executed directly from CLI
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
extractAudioFromVideo(inputVideoPath)
|
||||
.then(() => console.log('Audio extraction successful.'))
|
||||
.catch((err) => console.error(err));
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
|
||||
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
//Speaker, ALL-Sentences, Start, End
|
||||
|
||||
module.exports = {
|
||||
name: "summarize-transcription", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||
displayname: "Summarizer", // The displayname used within the UI
|
||||
async function(args) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let inputJson = args.json;
|
||||
|
||||
//JSON Path
|
||||
if (args.jsonPath) {
|
||||
try {
|
||||
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||
inputJson = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
// console.error("Failed to load JSON from file:", e);
|
||||
reject(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
// JSON parsen
|
||||
if (typeof args === "string") {
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
fs.readFile(args, 'utf8', function (err, data) {
|
||||
if (err){
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
inputJson = JSON.parse(data);
|
||||
res()
|
||||
});
|
||||
})
|
||||
} catch (e) {
|
||||
// console.log("Invalid JSON in summarize-transcription");
|
||||
// console.log(e)
|
||||
reject(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const words = inputJson.words;
|
||||
if (!Array.isArray(words)) {
|
||||
reject("No words Array found")
|
||||
return
|
||||
}
|
||||
|
||||
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||
|
||||
const result = [];
|
||||
let currentSentence = "";
|
||||
let currentSpeaker = null;
|
||||
let startTime = null;
|
||||
let endTime = null;
|
||||
|
||||
for (const w of words) {
|
||||
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||
if (startTime === null) startTime = w.start;
|
||||
endTime = w.end;
|
||||
|
||||
//speaker changing
|
||||
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||
const lastEntry = result[result.length - 1];
|
||||
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||
lastEntry.sentence += " " + currentSentence;
|
||||
lastEntry.end = endTime;
|
||||
} else {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
}
|
||||
currentSentence = "";
|
||||
startTime = w.start;
|
||||
}
|
||||
currentSpeaker = w.speaker;
|
||||
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||
const lastWord = w.text.trim();
|
||||
const lastChar = lastWord.slice(-1);
|
||||
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||
|
||||
//sentence ending
|
||||
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||
const lastEntry = result[result.length - 1];
|
||||
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||
lastEntry.sentence += " " + currentSentence;
|
||||
lastEntry.end = endTime;
|
||||
} else {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
}
|
||||
currentSentence = "";
|
||||
startTime = null;
|
||||
endTime = null;
|
||||
currentSpeaker = null;
|
||||
}
|
||||
}
|
||||
|
||||
// safe last sentence
|
||||
if (currentSentence) {
|
||||
const lastEntry = result[result.length - 1];
|
||||
if (lastEntry && lastEntry.speaker === currentSpeaker) {
|
||||
lastEntry.sentence += " " + currentSentence;
|
||||
lastEntry.end = endTime;
|
||||
} else {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Output as Text
|
||||
const output = result.map(r =>
|
||||
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||
);
|
||||
|
||||
// Output on cosole
|
||||
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||
|
||||
try {
|
||||
const jsonPath = path.join(outputDir, "transcription_result.json");
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||
|
||||
const txtPath = path.join(outputDir, "transcription_result.txt");
|
||||
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||
|
||||
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||
|
||||
resolve(jsonPath);
|
||||
} catch (err) {
|
||||
// console.error("Error saving Summary:", err);
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
|
||||
// Prepare output directory (always storage/transcriptionSummaries under project root)
|
||||
const outputDir = `${__dirname}/../../../storage/transcriptionSummaries`;
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
function getSessionId(inputPath) {
|
||||
try {
|
||||
const parsed = new URL(inputPath);
|
||||
const base = path.basename(parsed.pathname);
|
||||
return base.replace(/\.[^.]+$/, '');
|
||||
} catch {
|
||||
return path.basename(inputPath, path.extname(inputPath));
|
||||
}
|
||||
}
|
||||
|
||||
//Speaker, Sentence, Start, End
|
||||
|
||||
module.exports = {
|
||||
name: "summarize-transcription2", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
type: "summarizer", // value used to differentiate each module to order them in the UI
|
||||
displayname: "Summarizer", // The displayname used within the UI
|
||||
async function(args) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let inputJson = args.json;
|
||||
|
||||
//JSON Path
|
||||
if (args.jsonPath) {
|
||||
try {
|
||||
const raw = fs.readFileSync(args.jsonPath, "utf-8");
|
||||
inputJson = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
console.error("Failed to load JSON from file:", e);
|
||||
reject("Could not read JSON from file path.")
|
||||
return
|
||||
}
|
||||
}
|
||||
// JSON parsen
|
||||
if (typeof args === "string") {
|
||||
try {
|
||||
await new Promise((res, rej) => {
|
||||
fs.readFile(args, 'utf8', function (err, data) {
|
||||
if (err){
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
inputJson = JSON.parse(data);
|
||||
res()
|
||||
});
|
||||
})
|
||||
} catch (e) {
|
||||
// console.log("Invalid JSON in summarize-transcription");
|
||||
// console.log(e)
|
||||
reject(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const words = inputJson.words;
|
||||
if (!Array.isArray(words)) {
|
||||
reject("No words Array found")
|
||||
return;
|
||||
}
|
||||
|
||||
const ENDINGS = [".", "!", "?"]; // '...' auch als Satzende ?
|
||||
const ABBREVIATIONS = new Set(["z.B.", "bzw.", "u.a.", "Dr.", "Mr.", "Mrs.", "Prof.", "etc."]); //TODO weitere Ergaenzen
|
||||
|
||||
const result = [];
|
||||
let currentSentence = "";
|
||||
let currentSpeaker = null;
|
||||
let startTime = null;
|
||||
let endTime = null;
|
||||
|
||||
for (const w of words) {
|
||||
if (!currentSpeaker) currentSpeaker = w.speaker;
|
||||
if (startTime === null) startTime = w.start;
|
||||
endTime = w.end;
|
||||
|
||||
//speaker changing
|
||||
if (currentSpeaker !== w.speaker && currentSentence) {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
currentSentence = "";
|
||||
startTime = w.start;
|
||||
}
|
||||
currentSpeaker = w.speaker;
|
||||
currentSentence += (currentSentence ? " " : "") + w.text; //sentence beginning or not
|
||||
const lastWord = w.text.trim();
|
||||
const lastChar = lastWord.slice(-1);
|
||||
const isAbbreviation = ABBREVIATIONS.has(lastWord);
|
||||
|
||||
//sentence ending
|
||||
if (ENDINGS.includes(lastChar) && !isAbbreviation) {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
currentSentence = "";
|
||||
startTime = null;
|
||||
endTime = null;
|
||||
currentSpeaker = null;
|
||||
}
|
||||
}
|
||||
|
||||
// safe last sentence
|
||||
if (currentSentence) {
|
||||
result.push({
|
||||
speaker: currentSpeaker,
|
||||
sentence: currentSentence,
|
||||
start: startTime,
|
||||
end: endTime
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Output as Text
|
||||
const output = result.map(r =>
|
||||
`Sprecher ${r.speaker} [${r.start.toFixed(2)} - ${r.end.toFixed(2)}]: ${r.sentence}`
|
||||
);
|
||||
|
||||
// Output on cosole
|
||||
//console.log("\n------------\nMerged Transcription Result:\n", output, "\n------------\n");
|
||||
|
||||
try {
|
||||
|
||||
let filename = getSessionId(args);
|
||||
|
||||
const jsonPath = path.join(outputDir, `${filename}-${new Date().getTime()}.json`);
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2), "utf-8");
|
||||
|
||||
const txtPath = path.join(outputDir, `${filename}-${new Date().getTime()}.txt`);
|
||||
fs.writeFileSync(txtPath, output.join("\n"), "utf-8");
|
||||
|
||||
// console.log(`Summary successfully saved:\n- ${jsonPath}\n- ${txtPath}`);
|
||||
resolve(jsonPath);
|
||||
} catch (err) {
|
||||
// console.error("Error saving Summary:", err);
|
||||
reject(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// const fs = require('fs');
|
||||
// const path = require('path');
|
||||
|
||||
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
|
||||
}
|
||||
|
||||
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
|
||||
const SAIA_API_KEY = process.env.SAIA_API_KEY; // Ensure SAIA API key is set in environment variables
|
||||
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
|
||||
|
||||
const module_exports = {
|
||||
name: "llm-saia_openai_gpt",
|
||||
type: "llm",
|
||||
displayname: "GPT 120B",
|
||||
description: "Generates documents using OpenAI GPT OSS 120B via SAIA platform",
|
||||
|
||||
async function(parameter) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// console.log("SAIA OpenAI GPT module invoked with parameters:", parameter);
|
||||
|
||||
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
||||
parameter.inputTranscriptPath, // Path to input transcript file
|
||||
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
||||
parameter.language // Language for the document which is chosen in the front end by the user
|
||||
));
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error in SAIA OpenAI GPT module:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
|
||||
const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
|
||||
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
|
||||
|
||||
// return
|
||||
// --- REST CALL ---
|
||||
const response = await fetch(SAIA_URL, { //safe model response in variable
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "openai-gpt-oss-120b",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." },
|
||||
{ role: "user", content: promptText }
|
||||
],
|
||||
temperature: 0
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) { //ok is true when a responce was successfull
|
||||
const text = await response.text();
|
||||
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Get generated text from response or default to empty string (if null)
|
||||
// SAIA uses OpenAI-compatible structure: data.choices[x].message.content
|
||||
const output = data.choices?.[0]?.message?.content || "";
|
||||
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
|
||||
// console.log(inputTranscriptName);
|
||||
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
|
||||
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||
|
||||
// console.log("Generated document written to:", outPath);
|
||||
resolve(outPath)
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error generating SAIA content:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = module_exports;
|
||||
|
||||
// CLI Mode: Allow direct execution
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.error("Usage: node llm-openai-gpt.js <transcriptPath> <documentTypePath> [language]");
|
||||
console.error("Example: node llm-openai-gpt.js ./transcript.json ./docType.txt de");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [transcriptPath, documentTypePath, language] = args;
|
||||
|
||||
// Check if API key is set
|
||||
if (!SAIA_API_KEY) {
|
||||
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if files exist
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(documentTypePath)) {
|
||||
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Starting document generation...");
|
||||
console.log(`Transcript: ${transcriptPath}`);
|
||||
console.log(`Document Type: ${documentTypePath}`);
|
||||
console.log(`Language: ${language || 'en (default)'}`);
|
||||
|
||||
await module_exports.createDocumentFromTranscript(
|
||||
transcriptPath,
|
||||
documentTypePath,
|
||||
language || 'en'
|
||||
);
|
||||
|
||||
console.log("Done!");
|
||||
})();
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
|
||||
}
|
||||
|
||||
// Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||
const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; // Ensure Google API key is set in environment variables: export GOOGLE_API_KEY="your_api_key_here"
|
||||
const GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent"; // URL for the REST call, used model and action
|
||||
|
||||
const module_exports = {
|
||||
name: "llm-gemini",
|
||||
type: "llm",
|
||||
displayname: "Gemini LLM",
|
||||
description: "Generates documents using Google Gemini LLM",
|
||||
|
||||
async function(parameter) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// console.log("Gemini LLM module invoked with parameters:", parameter);
|
||||
|
||||
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
||||
parameter.inputTranscriptPath, // Path to input transcript file
|
||||
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
||||
parameter.language // Language for the document which is chosen in the front end by the user
|
||||
));
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error in Gemini LLM module:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
|
||||
const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
|
||||
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
|
||||
|
||||
// --- REST CALL ---
|
||||
const response = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, { //safe model response in variable
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
contents: [
|
||||
{
|
||||
parts: [
|
||||
{ text: promptText } // Input prompt for content generation
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) { //ok is true when a responce was successfull
|
||||
const text = await response.text();
|
||||
throw new Error(`Gemini API error (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Get generated text from response or default to empty string (if null)
|
||||
const output = data?.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
||||
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
|
||||
// console.log(inputTranscriptName);
|
||||
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
|
||||
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||
|
||||
// console.log("Generated document written to:", outPath);
|
||||
resolve(outPath)
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error generating Gemini content:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = module_exports;
|
||||
|
||||
// CLI Mode: Allow direct execution
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.error("Usage: node llm-gemini.js <transcriptPath> <documentTypePath> [language]");
|
||||
console.error("Example: node llm-gemini.js ./transcript.json ./docType.txt de");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [transcriptPath, documentTypePath, language] = args;
|
||||
|
||||
// Check if API key is set
|
||||
if (!GEMINI_API_KEY) {
|
||||
console.error("ERROR: GOOGLE_API_KEY environment variable is not set!");
|
||||
console.error("Please set it with: export GOOGLE_API_KEY='your_api_key_here'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if files exist
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(documentTypePath)) {
|
||||
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Starting document generation...");
|
||||
console.log(`Transcript: ${transcriptPath}`);
|
||||
console.log(`Document Type: ${documentTypePath}`);
|
||||
console.log(`Language: ${language || 'en (default)'}`);
|
||||
|
||||
await module_exports.createDocumentFromTranscript(
|
||||
transcriptPath,
|
||||
documentTypePath,
|
||||
language || 'en'
|
||||
);
|
||||
|
||||
console.log("Done!");
|
||||
})();
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const outputDir = path.join(__dirname, "../../../storage/documents"); // path for output directory
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true }); // Create output directory if it doesn't exist
|
||||
}
|
||||
|
||||
// Ensure SAIA API key is set in environment variables: export SAIA_API_KEY="your_api_key_here"
|
||||
const SAIA_API_KEY = process.env.SAIA_API_KEY;
|
||||
const SAIA_URL = "https://chat-ai.academiccloud.de/v1/chat/completions"; // URL for the REST call, used model and action
|
||||
|
||||
const module_exports = {
|
||||
name: "qwen3-235b-a22b",
|
||||
type: "llm",
|
||||
displayname: "QWEN 3 235B",
|
||||
description: "Generates documents using QWEN 3 235B via SAIA platform",
|
||||
|
||||
async function(parameter) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// console.log("SAIA QWEN 3 235B module invoked with parameters:", parameter);
|
||||
|
||||
resolve(await this.createDocumentFromTranscript( //Call the function to create document with transcript, document type and language
|
||||
parameter.inputTranscriptPath, // Path to input transcript file
|
||||
parameter.documentTypePath, // Path to document type file which is chosen in the front end by the user
|
||||
parameter.language // Language for the document which is chosen in the front end by the user
|
||||
));
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error in SAIA QWEN 3 235B module:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
createDocumentFromTranscript: async function(transcriptPath, documentTypePath, language = "en") { // default language is English
|
||||
return new Promise(async(resolve, reject) => {
|
||||
try {
|
||||
const transcript = await fs.promises.readFile(transcriptPath, "utf-8"); //read transcript file from Path
|
||||
const documentType = await fs.promises.readFile(documentTypePath, "utf-8"); //read document type from Path
|
||||
const promptText = `${documentType}, in language ${language}, transcript:\n\n${transcript}`; //combine doc type, language and transcript - Change prompt here if needed
|
||||
|
||||
// --- REST CALL ---
|
||||
const response = await fetch(SAIA_URL, { //safe model response in variable
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${SAIA_API_KEY}`,
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "qwen3-235b-a22b",
|
||||
messages: [
|
||||
{ role: "system", content: "You are a helpful assistant that generates HTML documents from transcripts. Output only valid HTML content without any preamble, explanations, or markdown formatting." },
|
||||
{ role: "user", content: promptText }
|
||||
],
|
||||
temperature: 0
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) { //ok is true when a responce was successfull
|
||||
const text = await response.text();
|
||||
throw new Error(`SAIA API error (${response.status}): ${text}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Get generated text from response or default to empty string (if null)
|
||||
// SAIA uses OpenAI-compatible structure: data.choices[x].message.content
|
||||
const output = data.choices?.[0]?.message?.content || "";
|
||||
let inputTranscriptName = path.basename(transcriptPath, path.extname(transcriptPath)); // Name for the output file
|
||||
// console.log(inputTranscriptName);
|
||||
const outPath = path.join(outputDir, `${inputTranscriptName}.html`); // Output file path & name to make naming dynamic. Pulled from input transcript name
|
||||
fs.writeFileSync(outPath, output, "utf8"); // Write output to file
|
||||
|
||||
// console.log("Generated document written to:", outPath);
|
||||
resolve(outPath)
|
||||
|
||||
} catch (error) {
|
||||
// console.error("Error generating SAIA content:", error);
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = module_exports;
|
||||
|
||||
// CLI Mode: Allow direct execution
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.error("Usage: node qwen3.js <transcriptPath> <documentTypePath> [language]");
|
||||
console.error("Example: node qwen3.js ./transcript.json ./docType.txt de");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [transcriptPath, documentTypePath, language] = args;
|
||||
|
||||
// Check if API key is set
|
||||
if (!SAIA_API_KEY) {
|
||||
console.error("ERROR: SAIA_API_KEY environment variable is not set!");
|
||||
console.error("Please set it with: export SAIA_API_KEY='your_api_key_here'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if files exist
|
||||
if (!fs.existsSync(transcriptPath)) {
|
||||
console.error(`ERROR: Transcript file not found: ${transcriptPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(documentTypePath)) {
|
||||
console.error(`ERROR: Document type file not found: ${documentTypePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Starting document generation...");
|
||||
console.log(`Transcript: ${transcriptPath}`);
|
||||
console.log(`Document Type: ${documentTypePath}`);
|
||||
console.log(`Language: ${language || 'en (default)'}`);
|
||||
|
||||
await module_exports.createDocumentFromTranscript(
|
||||
transcriptPath,
|
||||
documentTypePath,
|
||||
language || 'en'
|
||||
);
|
||||
|
||||
console.log("Done!");
|
||||
})();
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
const API_KEY = process.env.ASSEMBLYAI_API_KEY;
|
||||
const BASE_URL = 'https://api.assemblyai.com/v2';
|
||||
|
||||
//---------------------------------------------------Upload audio---------------------------------------------------
|
||||
|
||||
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---------------------------------------------------
|
||||
|
||||
function getSessionId(inputPath) {
|
||||
try {
|
||||
const parsed = new URL(inputPath);
|
||||
const base = path.basename(parsed.pathname);
|
||||
return base.replace(/\.[^.]+$/, '');
|
||||
} catch {
|
||||
return path.basename(inputPath, path.extname(inputPath));
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------Create transcript---------------------------------------------------
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//---------------------------------------------------Poll transcript---------------------------------------------------
|
||||
|
||||
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;
|
||||
if (status === 'error') throw new Error(`Transcription failed: ${response.data.error}`);
|
||||
|
||||
await new Promise(res => setTimeout(res, 3000));
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------Save transcript---------------------------------------------------
|
||||
|
||||
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}`);
|
||||
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
//---------------------------------------------------Modul---------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
name: 'assembly',
|
||||
type: 'transcription',
|
||||
displayname: 'AssemblyAI',
|
||||
audioformat: "mp3",
|
||||
|
||||
async function(audioFileName) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
// audioFileName ist nur "datei.mp3"
|
||||
const audioPath = audioFileName;
|
||||
|
||||
let audioUrl;
|
||||
|
||||
if (/^https?:\/\//i.test(audioFileName)) {
|
||||
audioUrl = audioFileName;
|
||||
} else {
|
||||
if (!fs.existsSync(audioPath)) {
|
||||
throw new Error(`Audio file not found: ${audioPath}`);
|
||||
}
|
||||
audioUrl = await uploadAudio(audioPath);
|
||||
}
|
||||
|
||||
const transcriptId = await createTranscript(audioUrl);
|
||||
const transcript = await pollTranscript(transcriptId);
|
||||
|
||||
const sessionId = getSessionId(audioFileName);
|
||||
|
||||
resolve(saveTranscript(transcript, sessionId));
|
||||
|
||||
} catch (error) {
|
||||
// console.error('Transcription error:', error.message);
|
||||
reject(error);
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -1,133 +0,0 @@
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<any> {
|
||||
while (true) {
|
||||
const response = await axios.get<any>(`${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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url"; // To handle __dirname in ES modules
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url); // Get current file path
|
||||
const __dirname = path.dirname(__filename); // Get current directory path
|
||||
const transcriptsDir = path.resolve(__dirname, "../../../storage/transcriptions");
|
||||
const transcriptsDir = path.resolve(__dirname, "../../storage/transcriptions");
|
||||
|
||||
|
||||
export class whisperLocal { // is called by transcribe.ts
|
||||
@@ -26,6 +26,7 @@ export class whisperLocal { // is called by transcribe.ts
|
||||
|
||||
async transcribe(audioPath: string): Promise<string> { //asyncronous function to transcribe audio
|
||||
return new Promise((resolve, reject) => {
|
||||
const transcriptsDir = path.resolve(__dirname, "../../../../storage/transcripts"); //storage directory for transcripts
|
||||
|
||||
if (!fs.existsSync(transcriptsDir)) { //if transcripts directory does not exist, create it
|
||||
fs.mkdirSync(transcriptsDir, { recursive: true });
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
module.exports = {
|
||||
name:"Startup_function",
|
||||
async function(){
|
||||
@@ -6,20 +5,5 @@ module.exports = {
|
||||
|
||||
// We are now calling the example function from the example folder
|
||||
mapFunctions.get("example").function("Startup")
|
||||
|
||||
// let transcript = await mapFunctions.get("assembly").function('../../storage/audio/IMG_2978.wav');
|
||||
|
||||
// let summary = await mapFunctions.get("summarize-transcription").function({jsonPath:'/Users/santa/Proj25/video2document/storage/transcripts/IMG_2978.json'});
|
||||
// let snippets = await mapFunctions.get("extract-speaker-snippets").function({audioPath:'/Users/santa/Proj25/video2document/storage/audio/KittyKat.wav', jsonPath1: '/Users/santa/Proj25/video2document/storage/transcriptionSummaries/KittyKat-1765806474958.json' });
|
||||
|
||||
|
||||
|
||||
|
||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./a.mp4", outputType:"wav"})
|
||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"wav"})
|
||||
// mapFunctions.get("extraction-video-to-audio").function({inputVideoPath:"./b.mp4", outputType:"flac"})
|
||||
|
||||
// mapFunctions.get("ipc-handler").function("extraction-video-to-audio", {inputVideoPath:"./a.mp4", outputType:"flac"})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
module.exports = {
|
||||
name:"module-handler", // Unique name for our function that will later be used to get the function from the map via "mapFunctions.get("example").function()"
|
||||
async function(module,parameter){
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if(mapFunctions.get(module) == undefined){
|
||||
reject("requested modules not found")
|
||||
return
|
||||
}
|
||||
mapFunctions.get(module).function(parameter).then((result) => {
|
||||
resolve(result)
|
||||
}).catch((err) => {
|
||||
reject(err)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// 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';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
@echo off
|
||||
cd /d %~dp0
|
||||
|
||||
:: Prüfen ob Node.js installiert ist
|
||||
where node >nul 2>nul
|
||||
if %errorlevel% neq 0 (
|
||||
echo Node.js ist nicht installiert.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Node.js gefunden:
|
||||
node -v
|
||||
|
||||
npm install
|
||||
if %errorlevel% neq 0 (
|
||||
echo npm install fehlgeschlagen.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
npm start
|
||||
pause
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Prüfen ob Node.js installiert ist
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
echo "Node.js ist nicht installiert."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Node.js gefunden: $(node -v)"
|
||||
|
||||
# Abhängigkeiten installieren
|
||||
npm install || exit 1
|
||||
|
||||
# Projekt starten
|
||||
npm start
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -1,26 +0,0 @@
|
||||
Du bist ein erfahrener Moderator und Projektmanager.
|
||||
|
||||
AUFGABE:
|
||||
Erstelle eine sinnvolle Meeting-Agenda basierend auf dem folgenden Transkript.
|
||||
|
||||
ANFORDERUNGEN:
|
||||
- Rekonstruiere die tatsächlichen Themenblöcke
|
||||
- Ordne sie logisch und chronologisch
|
||||
- Fasse ähnliche Diskussionen zusammen
|
||||
- Keine irrelevanten Details aufnehmen
|
||||
|
||||
STRUKTUR:
|
||||
- Titel der Agenda
|
||||
- Ziel des Meetings (1–2 Sätze)
|
||||
- Agenda-Punkte (nummeriert)
|
||||
- Thema
|
||||
- Kurzbeschreibung
|
||||
- Ziel des Punktes (Information, Entscheidung, Diskussion)
|
||||
|
||||
STIL:
|
||||
- Klar, kompakt
|
||||
- Business-orientiert
|
||||
- Keine Sprecher- oder Zeitangaben
|
||||
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||
|
||||
TRANSKRIPT:
|
||||
@@ -1,22 +0,0 @@
|
||||
Du bist ein intelligenter Dokumenten-Generator.
|
||||
|
||||
AUFGABE:
|
||||
Erstelle ein individuelles Dokument basierend auf:
|
||||
1) dem Meeting-Transkript
|
||||
2) der zusätzlichen Nutzeranweisung
|
||||
|
||||
WICHTIG:
|
||||
- Priorisiere die Nutzeranweisung
|
||||
- Nutze das Transkript als Wissensquelle
|
||||
- Struktur, Tonalität und Detailgrad anpassen
|
||||
- Inhalte logisch zusammenführen
|
||||
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||
|
||||
FORMAT:
|
||||
- Passe Struktur und Stil an den Nutzerwunsch an
|
||||
- Klare Überschriften
|
||||
- Keine Sprecher- oder Zeitangaben
|
||||
|
||||
TRANSKRIPT & NUTZERANWEISUNG:
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
Du bist ein professioneller Meeting-Analyst und Business Writer.
|
||||
|
||||
AUFGABE:
|
||||
Erstelle einen strukturierten Follow-up Report basierend auf dem folgenden Meeting-Transkript.
|
||||
|
||||
ANFORDERUNGEN:
|
||||
- Fasse Inhalte sinngemäß zusammen
|
||||
- Entferne Redundanzen und Smalltalk
|
||||
- Formuliere klar, präzise und professionell
|
||||
- Verwende neutrale Business-Sprache
|
||||
- Keine Zeitstempel oder Sprecher-Namen zitieren
|
||||
- Leite Entscheidungen und Aufgaben logisch ab, wenn sie implizit sind
|
||||
- Markiere offene Punkte klar
|
||||
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||
|
||||
STRUKTUR DES DOKUMENTS:
|
||||
1. Titel & Metadaten
|
||||
- Meetingtitel (ableiten)
|
||||
- Datum (falls im Transkript erwähnt, sonst „nicht angegeben“)
|
||||
- Teilnehmer (zusammengefasst)
|
||||
|
||||
2. Executive Summary (max. 5 Bullet Points)
|
||||
|
||||
3. Besprochene Themen
|
||||
- Thema
|
||||
- Kernaussagen
|
||||
- Relevante Erkenntnisse
|
||||
|
||||
4. Entscheidungen
|
||||
- Entscheidung
|
||||
- Kontext / Begründung
|
||||
|
||||
5. Action Items
|
||||
- Aufgabe
|
||||
- Verantwortlich (falls ableitbar)
|
||||
- Ziel / Zweck
|
||||
|
||||
6. Offene Fragen & Risiken
|
||||
|
||||
STIL:
|
||||
- Überschriften klar strukturiert
|
||||
- Bullet Points bevorzugen
|
||||
- Präzise, keine Umgangssprache
|
||||
|
||||
TRANSKRIPT:
|
||||
@@ -1,27 +0,0 @@
|
||||
Du bist ein professioneller Protokollführer.
|
||||
|
||||
AUFGABE:
|
||||
Erstelle ein Ergebnisprotokoll basierend auf dem Meeting-Transkript.
|
||||
|
||||
FOKUS:
|
||||
- Ergebnisse statt Diskussionen
|
||||
- Entscheidungen, Beschlüsse, Vereinbarungen
|
||||
- Klare, überprüfbare Aussagen
|
||||
|
||||
STRUKTUR:
|
||||
1. Meeting-Informationen
|
||||
2. Ergebnisse je Thema
|
||||
- Thema
|
||||
- Ergebnis / Beschluss
|
||||
3. Entscheidungen
|
||||
4. Aufgaben & Verantwortlichkeiten
|
||||
5. Offene Punkte
|
||||
|
||||
REGELN:
|
||||
- Keine Meinungen oder Spekulationen
|
||||
- Keine Zeit- oder Sprecherangaben
|
||||
- Sachlich, formal
|
||||
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||
|
||||
|
||||
TRANSKRIPT:
|
||||
@@ -1,35 +0,0 @@
|
||||
Du bist ein erfahrener Scrum Master.
|
||||
|
||||
AUFGABE:
|
||||
Erstelle Sprint Planning Notes aus dem folgenden Meeting-Transkript.
|
||||
|
||||
FOKUS:
|
||||
- Sprint-Ziele
|
||||
- User Stories / Tasks
|
||||
- Abhängigkeiten
|
||||
- Risiken
|
||||
- Commitments
|
||||
|
||||
STRUKTUR:
|
||||
1. Sprint Overview
|
||||
- Sprint-Ziel
|
||||
- Zeitraum (falls erwähnt)
|
||||
|
||||
2. Geplante Arbeit
|
||||
- User Story / Task
|
||||
- Beschreibung
|
||||
- Akzeptanzkriterien (falls ableitbar)
|
||||
|
||||
3. Abhängigkeiten & Blocker
|
||||
|
||||
4. Risiken & Annahmen
|
||||
|
||||
5. Vereinbarungen / Team-Commitments
|
||||
|
||||
STIL:
|
||||
- Agile-konform
|
||||
- Klar & umsetzungsorientiert
|
||||
- Bullet Points bevorzugen
|
||||
- Namen aus dem Transkript speakerA, speakerB etc. sollen weiterhin bestehen bleiben wie sie sind und nicht im Dokument ersetzt werden
|
||||
|
||||
TRANSKRIPT:
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'dotenv/config';
|
||||
import assemblyModule from '../../services/modules/transcription-remote/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);
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const path = require('path');
|
||||
const assemblyModule = require('../../services/modules/transcription-remote/assembly.js');
|
||||
|
||||
// Audio-Datei oder URL aus Kommandozeile, Standard: test.wav
|
||||
const audioPath = process.argv[2] || './storage/audio/IMG_2978.wav';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const transcript = await assemblyModule.run(audioPath);
|
||||
|
||||
console.log('Transcription succesful');
|
||||
console.log('Transcript ID:', transcript?.id);
|
||||
console.log('Speaker labels:', transcript?.utterances?.length || 0);
|
||||
} catch (error) {
|
||||
console.error('Error in Transcription:', error?.message || error);
|
||||
}
|
||||
})();
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
**Goal:** Generate a structured meeting report (Markdown). **Output ONLY:** final .md. No meta.
|
||||
|
||||
```json
|
||||
{
|
||||
"FORMAT": "markdown",
|
||||
|
||||
"STRUCTURE": {
|
||||
"titlepage": ["title","date","start","end","duration","location","host","participants"],
|
||||
"toc": "[section](#anchor) — HH:MM:SS",
|
||||
"section": {
|
||||
"h2": "<topic> — HH:MM:SS",
|
||||
"summary": "1 sentence",
|
||||
"key_points": "<=5 bullets, quotes optional",
|
||||
"decisions": "list: text | owner | due",
|
||||
"actions": "table: id | task | owner | due | status"
|
||||
},
|
||||
"exec_summary": "3 short sentences",
|
||||
"consolidated": ["decisions", "actions"],
|
||||
"appendix": "optional"
|
||||
},
|
||||
|
||||
"STYLE": {
|
||||
"tone": "neutral, concise",
|
||||
"ts_format": "HH:MM:SS",
|
||||
"no_meta": true
|
||||
},
|
||||
|
||||
"PROCESS": {
|
||||
"timestamps": "use if present; else estimate minimal",
|
||||
"speakers": "use labels; else Speaker X",
|
||||
"long_transcripts": "chunk → summarize → merge",
|
||||
"unclear": "UNKLAR:<reason>"
|
||||
},
|
||||
|
||||
"JSON_OUTPUT_OPTIONAL": true,
|
||||
|
||||
"PROMPT_SNIPPET": "Generate meeting report in markdown using STRUCTURE and STYLE. Output only the report."
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
const gemini = require("../../../services/modules/llm-gemini/gemini.js");
|
||||
|
||||
gemini.function({
|
||||
inputTranscriptPath: "./transcript.json",
|
||||
documentTypePath: "./documentType.json",
|
||||
language: "de"
|
||||
});
|
||||
@@ -1,122 +0,0 @@
|
||||
[
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "Smoke from hundreds of wildfires in Canada is triggering air quality alerts throughout the US Skylines from Maine to Maryland to Minnesota are gray and smoggy. And in some places, the air quality warnings include the warning to stay inside. We wanted to better understand what's happening here and why, so we called Peter DeCarlo, an associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Good morning, Professor.",
|
||||
"start": 240,
|
||||
"end": 26560
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "Good morning.",
|
||||
"start": 28060,
|
||||
"end": 28620
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "So what is it about the conditions right now that have caused this round of wildfires to affect so many people so far away?",
|
||||
"start": 29100,
|
||||
"end": 37100
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "Well, there's a couple of things. The season has been pretty dry already, and then the fact that we're getting hit in the US is because there's a couple weather systems that are essentially channeling the smoke from those Canadian wildfires through Pennsylvania into the mid Atlantic and the Northeast and kind of just dropping the smoke there.",
|
||||
"start": 39100,
|
||||
"end": 55820
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "So what is it in this haze that makes it harmful? And I'm assuming it is harmful.",
|
||||
"start": 56590,
|
||||
"end": 60670
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "It is, it is. The levels outside right now in Baltimore are considered unhealthy. And most of that is due to what's called particulate matter, which are tiny particles, microscopic, smaller than the width of your hair, that can get into your lungs and impact your respiratory system, your cardiovascular system, and even your neurological, your brain.",
|
||||
"start": 62350,
|
||||
"end": 82590
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "What makes this particularly harmful? Is it the volume of particulate? Is it something in particular? What is it exactly? Can you just drill down on that a little bit more?",
|
||||
"start": 83630,
|
||||
"end": 92190
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "Yeah. So the concentration of particulate matter, I was looking at some of the monitors that we have was reaching levels of what are, in science speak, 150 micrograms per meter cubed, which is more than 10 times what the annual average should be in about four times higher than what you're supposed to have on a 24 hour average. And so the concentrations of these particles in the air are just much, much, much higher than we typically see. And exposure to those high levels can lead to a host of health problems.",
|
||||
"start": 93550,
|
||||
"end": 123350
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "And who is most vulnerable? I noticed that in New York City, for example, they're canceling outdoor activities. And so here it is in the early days of summer and they have to keep all the kids inside. So who tends to be vulnerable in a situation like this?",
|
||||
"start": 123430,
|
||||
"end": 135990
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "It's the youngest. So children, obviously, whose bodies are still developing, the elderly who are, you know, their bodies are more in decline and they're more susceptible to the health impacts of breathing, the poor air quality. And then people who have pre existing health conditions, people with respiratory conditions or heart conditions, can be triggered by high levels of air pollution.",
|
||||
"start": 137610,
|
||||
"end": 156650
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "Could this get worse?",
|
||||
"start": 157450,
|
||||
"end": 158650
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "That's a good question. I mean, I think if in some areas it's much worse than others and it just depends on kind of where the smoke is concentrated. I think New York has some of the higher concentrations right now, but that's going to change as that air moves away from the New York area. But over the course of the next few days, we will see different areas being hit at different times with the highest concentrations.",
|
||||
"start": 162170,
|
||||
"end": 183420
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "I was going to ask you about.",
|
||||
"start": 183740,
|
||||
"end": 184660
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "More fires start burning. I don't expect the concentrations to go up too much higher.",
|
||||
"start": 184660,
|
||||
"end": 189020
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "I was going to ask you how and you started to answer this, but how much longer could this last? Forgive me if I'm asking you to speculate, but what do you think?",
|
||||
"start": 189100,
|
||||
"end": 196220
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "Well, I think the fires are going to burn for a little bit longer. But the key for us in the US Is the weather system changing. Right now it's the weather systems that are pulling that air into our Mid Atlantic and Northeast region. As those weather systems change and shift, we'll see that smoke going elsewhere and not impact us in this region as much. I think that's going to be the defining factor. I think the next couple days we're going to see a shift in that weather pattern and start to push the smoke away from where we are.",
|
||||
"start": 198280,
|
||||
"end": 227480
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "And finally, with the impacts of climate change, we are seeing more wildfires. Will we be seeing more of these kinds of wide ranging air quality consequences or circumstances?",
|
||||
"start": 227930,
|
||||
"end": 239850
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "I mean, that is one of the predictions for climate change. Looking into the future, the fire season is starting earlier and lasting longer and we're seeing more frequent fires. So yeah, this is probably something that we'll be seeing more, more frequently. This tends to be much more of an issue in the western U.S. so the eastern U.S. getting hit right now is a little bit new. But yeah, I think with climate change moving forward, this is something that is going to happen more frequently.",
|
||||
"start": 241370,
|
||||
"end": 267570
|
||||
},
|
||||
{
|
||||
"speaker": "A",
|
||||
"sentence": "That's Peter DeCarlo, associate professor in the Department of Environmental Health and Engineering at Johns Hopkins University. Professor DeCarlo, thanks so much for joining us and sharing this expertise with us.",
|
||||
"start": 267970,
|
||||
"end": 278210
|
||||
},
|
||||
{
|
||||
"speaker": "B",
|
||||
"sentence": "Thank you for having me.",
|
||||
"start": 279410,
|
||||
"end": 280530
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const transSummarizer = require("../../services/modules/jsonTools/transcriptionSummarizer.js");
|
||||
|
||||
// JSON-Datei laden
|
||||
const inputJson = JSON.parse(fs.readFileSync("./testFile.json", "utf8"));
|
||||
|
||||
// Übergabe an den Summarizer
|
||||
transSummarizer.function({
|
||||
json: inputJson
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def test_health():
|
||||
response = client.get("/health")
|
||||
assert response.status_code == 200
|
||||
@@ -1,243 +0,0 @@
|
||||
// DO NOT TOUCH THIS
|
||||
require("../../requires.js")
|
||||
|
||||
mapFunctions = new Map()
|
||||
// Loading the Function Map
|
||||
var path = `${mainDir}/services/modules`
|
||||
var folders = fs.readdirSync(path).filter(function (file) {
|
||||
return fs.statSync(path+'/'+file).isDirectory();
|
||||
});
|
||||
folders.forEach(element => {
|
||||
var commandFiles = fs.readdirSync(`${path}/${element}`).filter(file => file.endsWith('.js') && !file.startsWith("index"));
|
||||
for (const file of commandFiles) {
|
||||
delete require.cache[require.resolve(`${path}/${element}/${file}`)];
|
||||
const command = require(`${path}/${element}/${file}`);
|
||||
mapFunctions.set(command.name, command);
|
||||
}
|
||||
});
|
||||
// You can touch beyond this point
|
||||
|
||||
let audiopath
|
||||
let transcriptPath
|
||||
let summarizePath
|
||||
let llmpath
|
||||
let speakers
|
||||
|
||||
describe("Unit Tests", function() {
|
||||
|
||||
describe('Audio Extraction', function () {
|
||||
this.slow(1000)
|
||||
this.timeout(3000)
|
||||
it('Extract .mp4 to .mp3', function (done) {
|
||||
|
||||
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "mp3"}).then(resp => {
|
||||
audiopath = resp
|
||||
// console.log(resp);
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err);
|
||||
})
|
||||
})
|
||||
it('Extract .mp4 to .flac', function (done) {
|
||||
|
||||
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "flac"}).then(resp => {
|
||||
// console.log(resp);
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err);
|
||||
})
|
||||
})
|
||||
it('Extracting to a nonexistant format', function (done) {
|
||||
|
||||
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: __dirname.replaceAll("\\","/")+"/testvideo.mp4", outputType: "qqq"}).then(resp => {
|
||||
// console.log(resp);
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('Extracting from nonexistant file', function (done) {
|
||||
|
||||
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: "a", outputType: "mp3"}).then(resp => {
|
||||
// console.log(resp);
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
it('Extracting from nonexistant file to nonexistant format', function (done) {
|
||||
|
||||
mapFunctions.get("extraction-video-to-audio").function({inputVideoPath: "a", outputType: "qqq"}).then(resp => {
|
||||
// console.log(resp);
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
describe("Audio Transcription", function() {
|
||||
this.slow(20000)
|
||||
this.timeout(120000)
|
||||
|
||||
it('Assembly', function (done) {
|
||||
mapFunctions.get("assembly").function(audiopath).then(resp => {
|
||||
// console.log(resp);
|
||||
transcriptPath = resp
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
it('Assembly Wrong file', function (done) {
|
||||
mapFunctions.get("assembly").function("a").then(resp => {
|
||||
// console.log(resp);
|
||||
// transcriptPath = resp
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
// console.log(err);
|
||||
done()
|
||||
})
|
||||
})
|
||||
// TODO add more Transcription Tool tests here
|
||||
})
|
||||
|
||||
describe("Transcript Summarizer", function() {
|
||||
this.slow(100)
|
||||
this.timeout(1000)
|
||||
|
||||
it("Summarizer 1", function (done){
|
||||
mapFunctions.get("summarize-transcription").function(transcriptPath).then(resp => {
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
it("Summarizer 1 Wrong File", function (done){
|
||||
mapFunctions.get("summarize-transcription").function("a").then(resp => {
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("Summarizer 2 (Main)", function (done){
|
||||
mapFunctions.get("summarize-transcription2").function(transcriptPath).then(resp => {
|
||||
summarizePath = resp
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
it("Summarizer 2 (Main) Wrong File", function (done){
|
||||
mapFunctions.get("summarize-transcription2").function("a").then(resp => {
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Large Language Model", function() {
|
||||
this.slow(30000)
|
||||
this.timeout(120000)
|
||||
|
||||
it("ChatGPT", function (done){
|
||||
mapFunctions.get("llm-saia_openai_gpt").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
|
||||
llmpath = resp
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it("Gemini", function (done){
|
||||
mapFunctions.get("llm-gemini").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
|
||||
done()
|
||||
}).catch(err => {
|
||||
if(err.includes("(503)")){done()} // Error 503 is gemini overload, so an Error that they can at any time throw at us which would crash the pipeline, so we just ignore it and we just imagine that the test passed
|
||||
else{
|
||||
console.log(err);
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it("Qwen3", function (done){
|
||||
mapFunctions.get("qwen3-235b-a22b").function({inputTranscriptPath: summarizePath, documentTypePath: "./storage/documentType/followup_report.txt", language: "en"}).then(resp => {
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
it("ChatGPT (Nonexistant Type File)", function (done){
|
||||
mapFunctions.get("llm-saia_openai_gpt").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
it("Gemini (Nonexistant Type File)", function (done){
|
||||
mapFunctions.get("llm-gemini").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
it("Qwen3 (Nonexistant Type File)", function (done){
|
||||
mapFunctions.get("qwen3-235b-a22b").function({inputTranscriptPath: summarizePath, documentTypePath: "a", language: "en"}).then(resp => {
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Audio Snippet", function() {
|
||||
this.slow(1000)
|
||||
this.timeout(5000)
|
||||
|
||||
// transcriptPath = "A:\\programing\\@projects\\video2document\\storage\\transcriptionSummaries\\testvideo-1765900665001.json"
|
||||
// audiopath = "A:\\programing\\@projects\\video2document\\storage\\audio\\testvideo.mp3"
|
||||
|
||||
|
||||
it("Audio Snipper Generator", function (done){
|
||||
mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: summarizePath }).then(resp => {
|
||||
speakers = resp
|
||||
done()
|
||||
}).catch(err => {
|
||||
done(err)
|
||||
})
|
||||
})
|
||||
|
||||
it("Audio Snipper Generator (Nonexistant Transcript File)", function (done){
|
||||
mapFunctions.get("extract-speaker-snippets").function({audioPath: audiopath, jsonPath: "a" }).then(resp => {
|
||||
speakers = resp
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("Audio Snipper Generator (Nonexistant Audio File)", function (done){
|
||||
mapFunctions.get("extract-speaker-snippets").function({audioPath: "a", jsonPath: summarizePath }).then(resp => {
|
||||
speakers = resp
|
||||
done("Didnt crash")
|
||||
}).catch(err => {
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
after(function() {
|
||||
console.log(`\n\n\n${audiopath} \n${transcriptPath} \n${summarizePath} \n${llmpath}`);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user