diff options
| author | Maze <167211895+mazeincoding@users.noreply.github.com> | 2026-01-31 00:20:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 00:20:04 +0100 |
| commit | 7bf0984698e43b9aa2dd879f65951318ed410390 (patch) | |
| tree | ccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/transcription | |
| parent | 0173db99448d1c4c6415dcf15272e17551b48b04 (diff) | |
| download | opencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz | |
codebase overhaul (#697)
Diffstat (limited to 'apps/transcription')
| -rw-r--r-- | apps/transcription/README.md | 86 | ||||
| -rw-r--r-- | apps/transcription/requirements.txt | 5 | ||||
| -rw-r--r-- | apps/transcription/transcription.py | 143 |
3 files changed, 0 insertions, 234 deletions
diff --git a/apps/transcription/README.md b/apps/transcription/README.md deleted file mode 100644 index 07be6790..00000000 --- a/apps/transcription/README.md +++ /dev/null @@ -1,86 +0,0 @@ -Before you follow anything in this guide, please make sure you've followed the steps in the [README](../../README.md) (under "Optional: Auto-captions (Transcription) Setup").
-
-Open your terminal and make sure you're in the `apps/transcription` directory.
-
-1. Create virtual environment
-
-```bash
-python -m venv env
-```
-
-2. Activate it
-
-**Windows:**
-
-```bash
-env\Scripts\activate
-```
-
-**macOS/Linux:**
-
-```bash
-source env/bin/activate
-```
-
-> Note: if you're using VS Code/Cursor and you're seeing errors with the imports about the modules not being found,
-> You might have to press CTRL + Shift + P -> Python: Select Interpreter -> Enter interpreter path -> Find -> env -> scripts -> python.exe
-
-3. Install libraries/packages/whatever you wanna call them
-
-```bash
-pip install -r requirements.txt
-```
-
-4. Make sure you have a Modal account. If you don't: [create one](https://modal.com/)
-
-> If you don't know what Modal is: it allows us to process the actual audio and transcribe with Whisper by providing the infra to run Python code with a lot of RAM, generally affordable.
-
-5. Once you've got a Modal accoumt, run this:
-
-```bash
-python -m modal setup
-```
-
-It's gonna open a browser so you can authenticate.
-
-6. Test it if you want to make sure it actually works:
-
-```bash
-modal run transcription.py
-```
-
-6. Deploy the function!
-
-```bash
-modal deploy transcription.py
-```
-
-7. Set the required secrets in Modal
-
-So the script we just deployed interacts with Cloudflare to do two things:
-
-- Download the audio (so it can be transcribed with Whisper)
-- Delete the file after processing (privacy)
-
-To do those things, the script needs access to these environment variables:
-```bash
-CLOUDFLARE_ACCOUNT_ID=your-account-id
-R2_ACCESS_KEY_ID=your-access-key-id
-R2_SECRET_ACCESS_KEY=your-secret-access-key
-R2_BUCKET_NAME=opencut-transcription
-```
-
-Remember, we set these earlier in `.env.local`.
-
-So let's do it:
-
- - Go to [Modal Secrets](https://modal.com/secrets/mazewinther/main)
- - Click "Custom" and enter "opencut-r2-secrets" for the name.
- - Now you can just click "Import .env" and copy/paste the 4 variables from your `.env.local` file. Copy and paste these only:
- ```bash
- CLOUDFLARE_ACCOUNT_ID=your-account-id
- R2_ACCESS_KEY_ID=your-access-key-id
- R2_SECRET_ACCESS_KEY=your-secret-access-key
- R2_BUCKET_NAME=opencut-transcription
- ```
- - Click "Done" and you should see some cool particles!
\ No newline at end of file diff --git a/apps/transcription/requirements.txt b/apps/transcription/requirements.txt deleted file mode 100644 index c002e683..00000000 --- a/apps/transcription/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -modal
-openai-whisper
-boto3
-pydantic
-cryptography
\ No newline at end of file diff --git a/apps/transcription/transcription.py b/apps/transcription/transcription.py deleted file mode 100644 index ad55f6d4..00000000 --- a/apps/transcription/transcription.py +++ /dev/null @@ -1,143 +0,0 @@ -import modal
-from pydantic import BaseModel
-
-app = modal.App("opencut-transcription")
-
-class TranscribeRequest(BaseModel):
- filename: str
- language: str = "auto"
- decryptionKey: str = None
- iv: str = None
-
-@app.function(
- image=modal.Image.debian_slim()
- .apt_install(["ffmpeg"])
- .pip_install(["openai-whisper", "boto3", "fastapi[standard]", "pydantic", "cryptography"]),
- gpu="A10G",
- timeout=300, # 5m
- secrets=[modal.Secret.from_name("opencut-r2-secrets")]
-)
-@modal.fastapi_endpoint(method="POST")
-def transcribe_audio(request: TranscribeRequest):
- import whisper
- import boto3
- import tempfile
- import os
- import json
-
- try:
- filename = request.filename
- language = request.language
- decryption_key = request.decryptionKey
- iv = request.iv
-
- if not filename:
- return {
- "error": "Missing filename parameter"
- }
-
- # Initialize R2 client
- s3_client = boto3.client(
- 's3',
- endpoint_url=f'https://{os.environ["CLOUDFLARE_ACCOUNT_ID"]}.r2.cloudflarestorage.com',
- aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
- aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
- region_name='auto'
- )
-
- # Create temporary file for audio
- with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as temp_file:
- temp_path = temp_file.name
-
- try:
- # Download audio from R2
- s3_client.download_file(
- os.environ["R2_BUCKET_NAME"],
- filename,
- temp_path
- )
-
- # If decryption key provided, decrypt the file directly (zero-knowledge)
- if decryption_key and iv:
- import base64
- from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
- from cryptography.hazmat.backends import default_backend
-
- # Read the encrypted file
- with open(temp_path, 'rb') as f:
- encrypted_data = f.read()
-
- # Decode the key and IV from base64
- key_bytes = base64.b64decode(decryption_key)
- iv_bytes = base64.b64decode(iv)
-
- # Decrypt the data using AES-GCM
- # Extract the tag (last 16 bytes) and ciphertext
- tag = encrypted_data[-16:]
- ciphertext = encrypted_data[:-16]
-
- cipher = Cipher(
- algorithms.AES(key_bytes),
- modes.GCM(iv_bytes, tag),
- backend=default_backend()
- )
- decryptor = cipher.decryptor()
- decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
-
- # Write decrypted audio back to temp file
- with open(temp_path, 'wb') as f:
- f.write(decrypted_data)
-
- # Load Whisper model
- model = whisper.load_model("base")
-
- # Transcribe audio
- if language == "auto":
- result = model.transcribe(temp_path)
- else:
- result = model.transcribe(temp_path, language=language.lower())
-
- # Delete audio file from R2 (cleanup)
- s3_client.delete_object(
- Bucket=os.environ["R2_BUCKET_NAME"],
- Key=filename
- )
-
- # Adjust segment timing - Whisper is consistently late by ~500ms
- adjusted_segments = []
- for segment in result["segments"]:
- adjusted_segment = segment.copy()
- # Shift start/end times earlier by 500ms, don't go below 0
- adjusted_segment["start"] = max(0, segment["start"] - 0.5)
- adjusted_segment["end"] = max(0.5, segment["end"] - 0.5) # Ensure duration is at least 0.5s
- adjusted_segments.append(adjusted_segment)
-
- return {
- "text": result["text"],
- "segments": adjusted_segments,
- "language": result["language"]
- }
-
- finally:
- # Clean up temporary file
- if os.path.exists(temp_path):
- os.unlink(temp_path)
-
- except Exception as e:
- import traceback
- print(f"Transcription error: {str(e)}")
- print(f"Traceback: {traceback.format_exc()}")
-
- # Return error response that matches expected format
- return {
- "error": str(e),
- "text": "",
- "segments": [],
- "language": "unknown"
- }
-
-@app.local_entrypoint()
-def main():
- # Test function - you can call this with modal run transcription.py
- print("Transcription service is ready to deploy!")
- print("Deploy with: modal deploy transcription.py")
\ No newline at end of file |
