| |
| """ |
| Voiceover Generator with MiniCPM-o-2.6 (CPU Optimized - No Whisper) |
| """ |
|
|
| import gradio as gr |
| import torch |
| import tempfile |
| import random |
| import numpy as np |
| from pathlib import Path |
| import warnings |
| warnings.filterwarnings('ignore') |
|
|
| |
| |
| |
| OUTPUT_DIR = Path("./generated_voiceovers") |
| OUTPUT_DIR.mkdir(exist_ok=True) |
|
|
| VOICE_STYLES = { |
| "1": {"name": "Professional Narrator", "emotion": "neutral", "speed": 0.9}, |
| "2": {"name": "Enthusiastic YouTuber", "emotion": "happy", "speed": 1.1}, |
| "3": {"name": "Calm Teacher", "emotion": "neutral", "speed": 0.8}, |
| "4": {"name": "Energetic Announcer", "emotion": "happy", "speed": 1.2}, |
| "5": {"name": "Deep Documentary Voice", "emotion": "neutral", "speed": 0.75}, |
| "6": {"name": "Friendly Explainer", "emotion": "happy", "speed": 1.0}, |
| } |
|
|
| |
| INTROS = ["Watch how", "See how", "Discover how", "Learn how", "Ever wondered how"] |
| OUTROS = ["That's engineering in action!", "Simple mechanics, powerful results!", "That's how industry gets things done!"] |
|
|
| ACTION_DESCS = { |
| "increasing torque": ["multiplies rotational force", "boosts pulling power"], |
| "reducing speed": ["controls motion safely", "slows down rotation"], |
| "transferring power": ["sends energy efficiently", "transmits mechanical force"], |
| "lifting heavy loads": ["raises massive weights", "hoists heavy objects"], |
| "driving conveyor belts": ["moves products along", "keeps production flowing"], |
| } |
|
|
| |
| |
| |
| _model = None |
| _tokenizer = None |
|
|
| def load_model(): |
| """Load MiniCPM-o-2.6 without Whisper dependencies""" |
| global _model, _tokenizer |
| |
| if _model is not None: |
| return _model, _tokenizer |
| |
| print("π Loading MiniCPM-o-2.6 model...") |
| |
| try: |
| from transformers import AutoModel, AutoTokenizer |
| |
| |
| _model = AutoModel.from_pretrained( |
| 'openbmb/MiniCPM-o-2_6', |
| trust_remote_code=True, |
| torch_dtype=torch.float32, |
| low_cpu_mem_usage=True, |
| use_safetensors=True |
| ) |
| _model = _model.eval().to('cpu') |
| |
| _tokenizer = AutoTokenizer.from_pretrained( |
| 'openbmb/MiniCPM-o-2_6', |
| trust_remote_code=True |
| ) |
| |
| |
| if hasattr(_model, 'init_tts'): |
| _model.init_tts() |
| |
| print("β
Model loaded successfully!") |
| return _model, _tokenizer |
| |
| except ImportError as e: |
| print(f"β Import Error: {e}") |
| print("π‘ Run: pip install transformers==4.35.0") |
| return None, None |
| except Exception as e: |
| print(f"β Model Error: {e}") |
| return None, None |
|
|
| |
| |
| |
| def generate_audio_fallback(text, voice_style, output_path): |
| """Use Edge TTS as fallback (always works)""" |
| import asyncio |
| import edge_tts |
| |
| voice_map = { |
| "1": "en-US-JennyNeural", |
| "2": "en-US-GuyNeural", |
| "3": "en-GB-SoniaNeural", |
| "4": "en-US-DavisNeural", |
| "5": "en-US-ChristopherNeural", |
| "6": "en-US-AnaNeural", |
| } |
| |
| voice = voice_map.get(voice_style, "en-US-JennyNeural") |
| |
| async def generate(): |
| communicate = edge_tts.Communicate(text, voice) |
| await communicate.save(output_path) |
| |
| asyncio.run(generate()) |
| return output_path |
|
|
| |
| |
| |
| def generate_natural_voiceover(raw_prompt): |
| """Convert technical prompt to natural voiceover""" |
| if not raw_prompt: |
| return "" |
| |
| parts = raw_prompt.split(" in ", 1) |
| mechanism_action = parts[0] |
| industry = parts[1].split(" -")[0] if len(parts) > 1 else "industrial setting" |
| |
| words = mechanism_action.split() |
| mechanism = words[0] if words else "mechanism" |
| action = " ".join(words[1:]) if len(words) > 1 else "operating" |
| |
| |
| action_desc = None |
| for key, descs in ACTION_DESCS.items(): |
| if key in action.lower(): |
| action_desc = random.choice(descs) |
| break |
| |
| if not action_desc: |
| action_desc = f"{action} with precision and reliability" |
| |
| intro = random.choice(INTROS) |
| outro = random.choice(OUTROS) |
| article = "an" if mechanism[0].lower() in 'aeiou' else "a" |
| |
| return f"{intro} {article} {mechanism} {action} inside {article} {industry}. This clever mechanism {action_desc}. {outro}" |
|
|
| def process_prompt(prompt_text, voice_choice, auto_convert): |
| """Main processing function""" |
| |
| if not prompt_text: |
| return None, "β οΈ Please enter a prompt!", "" |
| |
| try: |
| |
| if auto_convert: |
| voiceover_text = generate_natural_voiceover(prompt_text) |
| else: |
| voiceover_text = prompt_text |
| |
| |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp: |
| audio_path = generate_audio_fallback(voiceover_text, voice_choice, tmp.name) |
| |
| info = f""" |
| β
Voiceover Generated! |
| |
| π Prompt: {prompt_text[:80]}... |
| π Voice: {VOICE_STYLES[voice_choice]['name']} |
| """ |
| |
| return audio_path, info, voiceover_text |
| |
| except Exception as e: |
| return None, f"β Error: {str(e)}", "" |
|
|
| |
| |
| |
| def create_interface(): |
| with gr.Blocks(title="Voiceover Generator", theme=gr.themes.Soft()) as demo: |
| gr.Markdown(""" |
| # ποΈ AI Voiceover Generator |
| |
| **Convert technical prompts to natural voiceovers** |
| |
| Example: `"planetary gear increasing torque in automotive plant"` |
| """) |
| |
| with gr.Row(): |
| with gr.Column(): |
| prompt_input = gr.Textbox( |
| label="Technical Prompt", |
| placeholder="planetary gear increasing torque in automotive plant", |
| lines=3 |
| ) |
| |
| voice_dropdown = gr.Dropdown( |
| choices=list(VOICE_STYLES.keys()), |
| label="Voice Style", |
| value="1" |
| ) |
| |
| auto_convert = gr.Checkbox(label="Auto-convert to natural voiceover", value=True) |
| |
| generate_btn = gr.Button("Generate Voiceover", variant="primary") |
| |
| with gr.Column(): |
| audio_output = gr.Audio(label="Generated Voiceover") |
| info_output = gr.Textbox(label="Status", lines=5) |
| text_output = gr.Textbox(label="Voiceover Script", lines=4) |
| |
| generate_btn.click( |
| fn=process_prompt, |
| inputs=[prompt_input, voice_dropdown, auto_convert], |
| outputs=[audio_output, info_output, text_output] |
| ) |
| |
| return demo |
|
|
| |
| |
| |
| if __name__ == "__main__": |
| print("=" * 60) |
| print("ποΈ Voiceover Generator") |
| print("=" * 60) |
| |
| |
| try: |
| import edge_tts |
| except ImportError: |
| print("π¦ Installing edge-tts...") |
| import subprocess |
| subprocess.check_call(['pip', 'install', 'edge-tts']) |
| |
| demo = create_interface() |
| demo.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=True |
| ) |