LPX55's picture
Upload folder using huggingface_hub
b8edc35 verified
Raw
History Blame Contribute Delete
14.2 kB
import sys
import httpx
import os
import argparse
from datetime import datetime
from dotenv import load_dotenv
from deepgram import DeepgramClient, PrerecordedOptions
from deepgram_captions import DeepgramConverter, srt
from moviepy.video.io.VideoFileClip import VideoFileClip
from moviepy.audio.io.AudioFileClip import AudioFileClip
import deepl
import re
load_dotenv()
def cleanup_srt_punctuation(srt_content):
# Split the SRT content into blocks
blocks = re.split(r'\n\s*\n', srt_content.strip())
parsed_blocks = []
for block in blocks:
lines = block.split('\n')
if len(lines) >= 2:
index = lines[0]
timecode = lines[1]
text = "\n".join(lines[2:]) if len(lines) > 2 else ""
parsed_blocks.append({
"index": index,
"timecode": timecode,
"text": text
})
# Rule 1: Clean up spaces before punctuation within each block
for block in parsed_blocks:
if "text" in block:
block["text"] = re.sub(r'\s+([.,!?~:;。、])', r'\1', block["text"])
# Rule 2 & 3: Handle leading punctuation and punctuation-only blocks
for i in range(len(parsed_blocks)):
block = parsed_blocks[i]
if "text" not in block:
continue
text = block["text"].strip()
# Check if the block is only punctuation
if text and all(c in ".,!?~:;。、" or c.isspace() for c in text):
for j in range(i - 1, -1, -1):
prev_block = parsed_blocks[j]
if "text" in prev_block and prev_block["text"].strip():
prev_block["text"] = prev_block["text"].rstrip() + " " + text
prev_block["text"] = re.sub(r'\s+([.,!?~:;。、])', r'\1', prev_block["text"])
break
block["text"] = ""
continue
# Check if the block starts with leading punctuation (e.g. ", text")
match = re.match(r'^([.,!?~:;。、\s]+)(.*)', block["text"])
if match:
lead_punct = match.group(1).strip()
remaining_text = match.group(2)
if lead_punct:
for j in range(i - 1, -1, -1):
prev_block = parsed_blocks[j]
if "text" in prev_block and prev_block["text"].strip():
prev_block["text"] = prev_block["text"].rstrip() + " " + lead_punct
prev_block["text"] = re.sub(r'\s+([.,!?~:;。、])', r'\1', prev_block["text"])
break
block["text"] = remaining_text
# Reconstruct and re-index the SRT string, filtering out empty blocks
reconstructed = []
entry = 1
for block in parsed_blocks:
text = block["text"].strip()
if text:
reconstructed.append(f"{entry}\n{block['timecode']}\n{text}")
entry += 1
return "\n\n".join(reconstructed) + "\n"
def translate_srt_content(srt_content, deepl_api_key, target_lang):
import deepl
# Split the SRT content into blocks
blocks = re.split(r'\n\s*\n', srt_content.strip())
parsed_blocks = []
text_list = []
for block in blocks:
lines = block.split('\n')
if len(lines) >= 2:
index = lines[0]
timecode = lines[1]
text = "\n".join(lines[2:]) if len(lines) > 2 else ""
# Extract speaker tag if any (e.g. "[speaker 0] Hello" or "[Speaker 1]")
tag = ""
clean_text = text
match = re.match(r'^(\[speaker \d+\]\s*)(.*)', text, re.IGNORECASE)
if match:
tag = match.group(1)
clean_text = match.group(2)
parsed_blocks.append({
"index": index,
"timecode": timecode,
"tag": tag,
"clean_text": clean_text
})
if clean_text.strip():
text_list.append(clean_text)
else:
parsed_blocks.append({
"raw": block
})
# Translate clean texts using DeepL text translation
translator = deepl.Translator(deepl_api_key)
translated_texts = []
# Chunk text requests to avoid hitting DeepL payload size limits
chunk_size = 50
for i in range(0, len(text_list), chunk_size):
chunk = text_list[i:i + chunk_size]
try:
results = translator.translate_text(chunk, target_lang=target_lang)
translated_texts.extend([r.text for r in results])
except Exception as e:
print(f"Error translating chunk: {e}")
translated_texts.extend(chunk)
# Reassemble the parsed blocks
text_idx = 0
reconstructed = []
entry = 1
for block in parsed_blocks:
if "raw" in block:
reconstructed.append(block["raw"])
else:
clean_text = block["clean_text"]
tag = block["tag"]
if clean_text.strip():
translated_text = translated_texts[text_idx] if text_idx < len(translated_texts) else clean_text
text_idx += 1
full_text = tag + translated_text
else:
full_text = tag + clean_text
# Filter out empty blocks after translation and re-index sequentially
stripped_text = full_text.strip()
if stripped_text:
reconstructed.append(f"{entry}\n{block['timecode']}\n{stripped_text}")
entry += 1
return "\n\n".join(reconstructed) + "\n"
def main():
parser = argparse.ArgumentParser(description="Transcribe video/audio to SRT subtitles using Deepgram.")
parser.add_argument("filepath", type=str, help="Path to the audio or video file to transcribe.")
parser.add_argument("-m", "--model", type=str, default="nova-3", help="Deepgram model to use (default: %(default)s).")
parser.add_argument("-l", "--language", type=str, default=None, help="BCP-47 language tag (e.g. 'en', 'es', 'fr'), or 'auto'/'detect' to enable automatic language detection.")
parser.add_argument("--no-diarize", dest="diarize", action="store_false", help="Disable speaker diarization.")
parser.add_argument("-t", "--translate-to", type=str, default=None, help="Translate the generated subtitles to this BCP-47 language tag (e.g. 'ko', 'en', 'ja') using DeepL.")
parser.set_defaults(diarize=True)
args = parser.parse_args()
filepath = args.filepath
# Resolve filepath. If it doesn't exist directly but exists in 'media/', use it from there.
if not os.path.exists(filepath):
media_fallback = os.path.join("media", filepath)
if os.path.exists(media_fallback):
filepath = media_fallback
if not os.path.exists(filepath):
print(f"Error: File '{filepath}' not found.")
print("Please check the path or place the file in the 'media' directory.")
return
_, ext = os.path.splitext(filepath.lower())
if ext == '.srt':
if not args.translate_to:
print("Error: When passing an .srt file, you must specify a target language using -t or --translate-to.")
return
deepl_api_key = os.getenv("DEEPL_API_KEY") or os.getenv("DEEPL_AUTH_KEY")
if not deepl_api_key:
print("Error: DEEPL_API_KEY or DEEPL_AUTH_KEY environment variable is not set.")
print("Please set it in your environment or add it to your .env file to use translation.")
return
try:
target_lang = args.translate_to.upper()
if target_lang == "EN":
target_lang = "EN-US"
elif target_lang == "PT":
target_lang = "PT-BR"
base, _ = os.path.splitext(filepath)
translated_srt_path = f"{base}.{args.translate_to.lower()}.srt"
print(f"Translating {filepath} to {args.translate_to} using DeepL...")
with open(filepath, "r", encoding="utf-8") as f:
original_content = f.read()
translated_content = translate_srt_content(original_content, deepl_api_key, target_lang)
cleaned_content = cleanup_srt_punctuation(translated_content)
with open(translated_srt_path, "w", encoding="utf-8") as f:
f.write(cleaned_content)
print(f"Successfully translated subtitles. Saved to: {translated_srt_path}")
except Exception as translate_err:
print(f"An error occurred during translation: {translate_err}")
return
api_key = os.getenv("DEEPGRAM_API_KEY")
if not api_key:
print("Error: DEEPGRAM_API_KEY environment variable is not set.")
print("Please set it in your environment or add it to a .env file in the project directory.")
return
try:
deepgram = DeepgramClient(api_key)
is_audio = ext in {'.mp3', '.wav', '.m4a', '.flac', '.ogg', '.aac', '.wma', '.opus', '.webm', '.m4b', '.mp4a', '.aiff', '.aif', '.mp2'}
audio_filepath = filepath
should_remove_audio = False
if not is_audio:
audio_filepath = f"{filepath}-audio.mp3"
should_remove_audio = False
audio_exists = False
if os.path.exists(audio_filepath) and os.path.getsize(audio_filepath) > 0:
try:
with VideoFileClip(filepath) as video_clip:
video_duration = video_clip.duration
with AudioFileClip(audio_filepath) as audio_clip:
audio_duration = audio_clip.duration
if abs(video_duration - audio_duration) < 1.0:
audio_exists = True
print(f"Found existing audio file '{audio_filepath}' with matching duration. Skipping extraction.")
except Exception as check_err:
print(f"Could not verify existing audio file: {check_err}. Re-extracting...")
if not audio_exists:
try:
with VideoFileClip(filepath) as video_clip:
audio_clip = video_clip.audio
audio_clip.write_audiofile(audio_filepath)
except Exception as e:
print(f"An error occurred extracting audio from video: {e}")
return
with open(audio_filepath, "rb") as file:
buffer_data = file.read()
payload = {"buffer": buffer_data}
options_dict = {
"model": args.model,
"smart_format": True,
"utterances": True,
"punctuate": True,
"diarize": args.diarize,
}
if args.language:
if args.language.lower() in {"auto", "detect"}:
options_dict["detect_language"] = True
else:
options_dict["language"] = args.language
options = PrerecordedOptions(**options_dict)
print("Making request to deepgram")
before = datetime.now()
response = deepgram.listen.rest.v("1").transcribe_file(
payload, options, timeout=httpx.Timeout(30000.0, connect=10.0)
)
after = datetime.now()
print("Got response from deepgram")
print(response.to_json(indent=4))
# Check if the transcription contains words to avoid IndexError on silent audio files
has_words = False
try:
if hasattr(response, 'results') and response.results:
if response.results.channels and response.results.channels[0].alternatives:
if response.results.channels[0].alternatives[0].words:
has_words = True
except Exception:
pass
if not has_words:
print("No speech or words detected in the audio file. Generating empty subtitle file.")
captions = ""
else:
transcription = DeepgramConverter(response)
captions = srt(transcription)
original_srt_path = f"{filepath}-captions.srt"
cleaned_captions = cleanup_srt_punctuation(captions)
with open(original_srt_path, "a", encoding="utf-8") as f:
f.write(cleaned_captions)
if args.translate_to:
print(f"Translating subtitles to {args.translate_to} using DeepL...")
deepl_api_key = os.getenv("DEEPL_API_KEY") or os.getenv("DEEPL_AUTH_KEY")
if not deepl_api_key:
print("Error: DEEPL_API_KEY or DEEPL_AUTH_KEY environment variable is not set.")
print("Please set it in your environment or add it to your .env file to use translation.")
else:
try:
target_lang = args.translate_to.upper()
# DeepL-specific target language code overrides
if target_lang == "EN":
target_lang = "EN-US"
elif target_lang == "PT":
target_lang = "PT-BR"
translated_srt_path = f"{filepath}-captions.{args.translate_to.lower()}.srt"
# Translate and post-process
translated_content = translate_srt_content(cleaned_captions, deepl_api_key, target_lang)
cleaned_content = cleanup_srt_punctuation(translated_content)
with open(translated_srt_path, "w", encoding="utf-8") as f:
f.write(cleaned_content)
print(f"Successfully translated subtitles. Saved to: {translated_srt_path}")
except Exception as translate_err:
print(f"An error occurred during translation: {translate_err}")
if should_remove_audio:
os.remove(audio_filepath)
except Exception as e:
print(f"Exception: {e}")
if __name__ == "__main__":
main()