topcoderkz
commited on
Commit
·
b620472
0
Parent(s):
Initial commit: Content automation system framework
Browse files- .env.example +10 -0
- .gitattributes +11 -0
- .gitignore +29 -0
- README.md +25 -0
- config/api_keys.yaml +17 -0
- config/content_strategies.yaml +32 -0
- requirements.txt +9 -0
- setup.sh +14 -0
- src/api_clients.py +70 -0
- src/automation.py +92 -0
- src/main.py +54 -0
- src/utils.py +34 -0
- src/video_renderer.py +62 -0
.env.example
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Keys - Fill these with your actual keys
|
| 2 |
+
GEMINI_API_KEY=your_gemini_api_key_here
|
| 3 |
+
RUNWAYML_API_KEY=your_runwayml_api_key_here
|
| 4 |
+
TTS_API_KEY=your_tts_api_key_here
|
| 5 |
+
GCS_BUCKET_NAME=your_bucket_name_here
|
| 6 |
+
|
| 7 |
+
# Configuration
|
| 8 |
+
AUDIO_LIBRARY_SIZE=27
|
| 9 |
+
VIDEO_LIBRARY_SIZE=47
|
| 10 |
+
DEFAULT_VOICE=en-US-AriaNeural
|
.gitattributes
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.mp3 filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.ttf filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.db filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
sliding_puzzle filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
stockfish/stockfish-ubuntu-x86-64-avx2 filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment variables
|
| 2 |
+
.env
|
| 3 |
+
*.env
|
| 4 |
+
.venv
|
| 5 |
+
|
| 6 |
+
# API keys
|
| 7 |
+
*_key.txt
|
| 8 |
+
*_secret.yaml
|
| 9 |
+
|
| 10 |
+
# Output files
|
| 11 |
+
outputs/videos/*
|
| 12 |
+
!outputs/videos/.gitkeep
|
| 13 |
+
outputs/logs/*.log
|
| 14 |
+
|
| 15 |
+
# Temporary files
|
| 16 |
+
*.tmp
|
| 17 |
+
*.temp
|
| 18 |
+
__pycache__/
|
| 19 |
+
*.pyc
|
| 20 |
+
*.pyo
|
| 21 |
+
*.pyd
|
| 22 |
+
.DS_Store
|
| 23 |
+
.thumbs.db
|
| 24 |
+
|
| 25 |
+
# Large files
|
| 26 |
+
*.mp4
|
| 27 |
+
*.mp3
|
| 28 |
+
*.wav
|
| 29 |
+
*.avi
|
README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Content Automation System
|
| 2 |
+
A Python-based automated video content creation system that generates videos using AI APIs, selects relevant footage from a library, adds text-to-speech audio, and produces finished videos with subtitles.
|
| 3 |
+
|
| 4 |
+
## Quick Start
|
| 5 |
+
|
| 6 |
+
### Prerequisites
|
| 7 |
+
- Python 3.8+
|
| 8 |
+
- API keys for:
|
| 9 |
+
- Google Gemini
|
| 10 |
+
- RunwayML
|
| 11 |
+
- Text-to-Speech service (Azure/Google/Amazon)
|
| 12 |
+
- Google Cloud Storage
|
| 13 |
+
|
| 14 |
+
### Installation
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
git clone <your-repo>
|
| 18 |
+
cd content-automation
|
| 19 |
+
python -m venv venv
|
| 20 |
+
source venv/bin/activate
|
| 21 |
+
pip install -r requirements.txt
|
| 22 |
+
cp .env.example .env
|
| 23 |
+
# Edit .env with your actual API keys
|
| 24 |
+
python src/main.py
|
| 25 |
+
```
|
config/api_keys.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Configuration
|
| 2 |
+
gemini:
|
| 3 |
+
base_url: "https://generativelanguage.googleapis.com/v1beta"
|
| 4 |
+
model: "gemini-pro"
|
| 5 |
+
|
| 6 |
+
runwayml:
|
| 7 |
+
base_url: "https://api.runwayml.com/v1"
|
| 8 |
+
timeout: 300
|
| 9 |
+
|
| 10 |
+
tts:
|
| 11 |
+
provider: "azure" # or "google", "amazon"
|
| 12 |
+
voice: "en-US-AriaNeural"
|
| 13 |
+
rate: "medium"
|
| 14 |
+
|
| 15 |
+
gcs:
|
| 16 |
+
bucket: "somira-videos"
|
| 17 |
+
video_prefix: "automated-content/"
|
config/content_strategies.yaml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Content Strategies Template
|
| 2 |
+
commercial:
|
| 3 |
+
gemini_prompt_template: |
|
| 4 |
+
A photorealistic, comical yet painfully real depiction of {subject}
|
| 5 |
+
in a {setting}. {style_notes}. Format: {aspect_ratio}.
|
| 6 |
+
|
| 7 |
+
runway_prompt_template: |
|
| 8 |
+
{camera_movement}: {action}. {scene_description}.
|
| 9 |
+
{style_notes}. Vertical {aspect_ratio}.
|
| 10 |
+
|
| 11 |
+
styles:
|
| 12 |
+
- name: "commercial"
|
| 13 |
+
camera_movement: "Slow push-in camera"
|
| 14 |
+
style_notes: "Photorealistic, cinematic, bright high-key lighting"
|
| 15 |
+
|
| 16 |
+
- name: "educational"
|
| 17 |
+
camera_movement: "Static shot"
|
| 18 |
+
style_notes: "Clean, professional, even lighting"
|
| 19 |
+
|
| 20 |
+
# Video library metadata
|
| 21 |
+
video_categories:
|
| 22 |
+
product_demo:
|
| 23 |
+
tags: ["somira massager", "product", "demo"]
|
| 24 |
+
usage: "When script mentions product features or demonstration"
|
| 25 |
+
|
| 26 |
+
solution_highlight:
|
| 27 |
+
tags: ["solution", "relief", "comfort"]
|
| 28 |
+
usage: "When script discusses problem-solving or benefits"
|
| 29 |
+
|
| 30 |
+
customer_experience:
|
| 31 |
+
tags: ["satisfaction", "experience", "testimonial"]
|
| 32 |
+
usage: "When script shares customer stories or results"
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
aiohttp>=3.8.0
|
| 2 |
+
google-cloud-storage>=2.0.0
|
| 3 |
+
moviepy>=1.0.3
|
| 4 |
+
openai>=1.0.0
|
| 5 |
+
python-dotenv>=1.0.0
|
| 6 |
+
pyyaml>=6.0
|
| 7 |
+
asyncio>=3.4.3
|
| 8 |
+
pillow>=9.0.0
|
| 9 |
+
numpy>=1.21.0
|
setup.sh
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
echo "Setting up Content Automation System..."
|
| 3 |
+
|
| 4 |
+
# Create directories
|
| 5 |
+
mkdir -p config src assets/video_library assets/audio_library outputs/videos outputs/logs
|
| 6 |
+
|
| 7 |
+
# Run all the creation commands from above (you'd paste all the cat commands here)
|
| 8 |
+
# [Paste all the file creation commands from above here]
|
| 9 |
+
|
| 10 |
+
echo "✅ Setup complete!"
|
| 11 |
+
echo "📝 Next steps:"
|
| 12 |
+
echo "1. Edit .env with your API keys"
|
| 13 |
+
echo "2. Run: pip install -r requirements.txt"
|
| 14 |
+
echo "3. Run: python src/main.py"
|
src/api_clients.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
API clients for external services
|
| 3 |
+
"""
|
| 4 |
+
import aiohttp
|
| 5 |
+
import json
|
| 6 |
+
from utils import logger
|
| 7 |
+
|
| 8 |
+
class APIClients:
|
| 9 |
+
def __init__(self, config):
|
| 10 |
+
self.config = config
|
| 11 |
+
|
| 12 |
+
async def enhance_prompt(self, prompt):
|
| 13 |
+
"""Enhance prompt using Gemini API"""
|
| 14 |
+
# Simplified implementation - replace with actual API call
|
| 15 |
+
logger.info(f"Enhancing prompt: {prompt[:100]}...")
|
| 16 |
+
return prompt # Placeholder
|
| 17 |
+
|
| 18 |
+
async def generate_video(self, prompt):
|
| 19 |
+
"""Generate video using RunwayML API"""
|
| 20 |
+
# Simplified implementation - replace with actual API call
|
| 21 |
+
logger.info(f"Generating video with prompt: {prompt[:100]}...")
|
| 22 |
+
return "generated_video_url" # Placeholder
|
| 23 |
+
|
| 24 |
+
async def generate_tts(self, text):
|
| 25 |
+
"""Generate TTS audio"""
|
| 26 |
+
# Simplified implementation - replace with actual API call
|
| 27 |
+
logger.info(f"Generating TTS for text: {text[:100]}...")
|
| 28 |
+
return {
|
| 29 |
+
'audio_url': 'generated_audio_url',
|
| 30 |
+
'lip_sync_data': {'timestamps': []} # Placeholder
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
async def select_videos(self, tts_script, count=3):
|
| 34 |
+
"""AI agent selects videos based on script"""
|
| 35 |
+
keywords = self._extract_keywords(tts_script)
|
| 36 |
+
logger.info(f"Selecting {count} videos for keywords: {keywords}")
|
| 37 |
+
|
| 38 |
+
# Simplified video selection logic
|
| 39 |
+
selected_videos = []
|
| 40 |
+
for i in range(min(count, 3)): # Max 3 videos
|
| 41 |
+
video_id = (hash(tts_script) + i) % self.config['video_library_size'] + 1
|
| 42 |
+
selected_videos.append({
|
| 43 |
+
'id': video_id,
|
| 44 |
+
'url': f'gs://somira-videos/library/video{video_id}.mp4',
|
| 45 |
+
'reason': f'Matches keyword: {keywords[i % len(keywords)] if keywords else "general"}'
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
return selected_videos
|
| 49 |
+
|
| 50 |
+
async def store_in_gcs(self, file_path):
|
| 51 |
+
"""Store file in Google Cloud Storage"""
|
| 52 |
+
logger.info(f"Storing file in GCS: {file_path}")
|
| 53 |
+
# Simplified implementation
|
| 54 |
+
return f"gs://{self.config['gcs_bucket']}/videos/{hash(file_path)}.mp4"
|
| 55 |
+
|
| 56 |
+
def _extract_keywords(self, text):
|
| 57 |
+
"""Extract keywords from TTS script"""
|
| 58 |
+
text_lower = text.lower()
|
| 59 |
+
keywords = []
|
| 60 |
+
|
| 61 |
+
key_phrases = [
|
| 62 |
+
'somira massager', 'neck pain', 'product', 'massager',
|
| 63 |
+
'solution', 'comfort', 'using the product', 'relaxation'
|
| 64 |
+
]
|
| 65 |
+
|
| 66 |
+
for phrase in key_phrases:
|
| 67 |
+
if phrase in text_lower:
|
| 68 |
+
keywords.append(phrase)
|
| 69 |
+
|
| 70 |
+
return keywords if keywords else ['general']
|
src/automation.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main automation orchestrator
|
| 3 |
+
"""
|
| 4 |
+
import asyncio
|
| 5 |
+
from api_clients import APIClients
|
| 6 |
+
from video_renderer import VideoRenderer
|
| 7 |
+
from utils import logger
|
| 8 |
+
|
| 9 |
+
class ContentAutomation:
|
| 10 |
+
def __init__(self, config):
|
| 11 |
+
self.config = config
|
| 12 |
+
self.api_clients = APIClients(config)
|
| 13 |
+
self.video_renderer = VideoRenderer(config)
|
| 14 |
+
self.current_audio_index = 0
|
| 15 |
+
|
| 16 |
+
async def execute_pipeline(self, content_strategy, tts_script):
|
| 17 |
+
"""Execute the complete automation pipeline"""
|
| 18 |
+
logger.info("Starting automation pipeline...")
|
| 19 |
+
|
| 20 |
+
# Step 1: Simultaneous execution
|
| 21 |
+
assets = await self.execute_step_1(content_strategy, tts_script)
|
| 22 |
+
|
| 23 |
+
# Step 2: Merge and render
|
| 24 |
+
rendered_video = await self.video_renderer.render_video(assets)
|
| 25 |
+
|
| 26 |
+
# Step 3: Add subtitles
|
| 27 |
+
subtitled_video = await self.video_renderer.add_subtitles(rendered_video, tts_script)
|
| 28 |
+
|
| 29 |
+
# Step 4: Store in GCS
|
| 30 |
+
final_url = await self.api_clients.store_in_gcs(subtitled_video)
|
| 31 |
+
|
| 32 |
+
logger.info(f"Pipeline completed. Video stored at: {final_url}")
|
| 33 |
+
return final_url
|
| 34 |
+
|
| 35 |
+
async def execute_step_1(self, content_strategy, tts_script):
|
| 36 |
+
"""Execute all step 1 processes simultaneously"""
|
| 37 |
+
tasks = [
|
| 38 |
+
self.generate_hook_video(content_strategy),
|
| 39 |
+
self.select_background_music(),
|
| 40 |
+
self.select_videos_from_library(tts_script),
|
| 41 |
+
self.generate_tts_audio(tts_script)
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 45 |
+
|
| 46 |
+
return {
|
| 47 |
+
'hook_video': results[0],
|
| 48 |
+
'background_music': results[1],
|
| 49 |
+
'selected_videos': results[2],
|
| 50 |
+
'tts_audio': results[3]
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
async def generate_hook_video(self, strategy):
|
| 54 |
+
"""Generate hook video using AI APIs"""
|
| 55 |
+
try:
|
| 56 |
+
# Enhance prompt with Gemini
|
| 57 |
+
enhanced_prompt = await self.api_clients.enhance_prompt(strategy['gemini_prompt'])
|
| 58 |
+
|
| 59 |
+
# Generate video with RunwayML
|
| 60 |
+
video_url = await self.api_clients.generate_video(enhanced_prompt)
|
| 61 |
+
return video_url
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
logger.error(f"Hook video generation failed: {e}")
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
async def select_background_music(self):
|
| 68 |
+
"""Select background music linearly"""
|
| 69 |
+
audio_index = self.current_audio_index
|
| 70 |
+
self.current_audio_index = (self.current_audio_index + 1) % self.config['audio_library_size']
|
| 71 |
+
|
| 72 |
+
audio_url = f"https://storage.googleapis.com/somira/{audio_index + 1}.mp3"
|
| 73 |
+
logger.info(f"Selected background music: {audio_url}")
|
| 74 |
+
return audio_url
|
| 75 |
+
|
| 76 |
+
async def select_videos_from_library(self, tts_script):
|
| 77 |
+
"""AI agent selects 3 videos based on TTS script"""
|
| 78 |
+
try:
|
| 79 |
+
selected_videos = await self.api_clients.select_videos(tts_script, count=3)
|
| 80 |
+
return selected_videos
|
| 81 |
+
except Exception as e:
|
| 82 |
+
logger.error(f"Video selection failed: {e}")
|
| 83 |
+
return []
|
| 84 |
+
|
| 85 |
+
async def generate_tts_audio(self, tts_script):
|
| 86 |
+
"""Generate TTS audio with lip-sync data"""
|
| 87 |
+
try:
|
| 88 |
+
tts_result = await self.api_clients.generate_tts(tts_script)
|
| 89 |
+
return tts_result
|
| 90 |
+
except Exception as e:
|
| 91 |
+
logger.error(f"TTS generation failed: {e}")
|
| 92 |
+
return None
|
src/main.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Main entry point for Content Automation System
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
import os
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
from automation import ContentAutomation
|
| 9 |
+
|
| 10 |
+
# Load environment variables
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
async def main():
|
| 14 |
+
"""Main execution function"""
|
| 15 |
+
print("🚀 Starting Content Automation System...")
|
| 16 |
+
|
| 17 |
+
# Configuration
|
| 18 |
+
config = {
|
| 19 |
+
'gemini_api_key': os.getenv('GEMINI_API_KEY'),
|
| 20 |
+
'runwayml_api_key': os.getenv('RUNWAYML_API_KEY'),
|
| 21 |
+
'tts_api_key': os.getenv('TTS_API_KEY'),
|
| 22 |
+
'gcs_bucket': os.getenv('GCS_BUCKET_NAME'),
|
| 23 |
+
'audio_library_size': int(os.getenv('AUDIO_LIBRARY_SIZE', 27)),
|
| 24 |
+
'video_library_size': int(os.getenv('VIDEO_LIBRARY_SIZE', 47))
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
# Initialize automation system
|
| 28 |
+
automation = ContentAutomation(config)
|
| 29 |
+
|
| 30 |
+
# Example content strategy
|
| 31 |
+
content_strategy = {
|
| 32 |
+
'gemini_prompt': 'A photorealistic, comical yet painfully real depiction of an attractive blond, blue-eyed female stuck in a neck spasm nightmare in a luxurious home setting.',
|
| 33 |
+
'runway_prompt': 'Slow push-in camera: a blond woman suddenly tilts her head stiffly to the side and blinks in surprise, face frozen like mid-sneeze.',
|
| 34 |
+
'style': 'commercial',
|
| 35 |
+
'aspect_ratio': '9:16'
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Example TTS script
|
| 39 |
+
tts_script = """
|
| 40 |
+
I heard a pop, and suddenly my neck was stuck. I looked like I was mid-sneeze all day.
|
| 41 |
+
After one minute with the Somira massager it was gone. If you ever feel neck pain,
|
| 42 |
+
you'll wish you bought one, because the moment I turned my head.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
# Execute automation pipeline
|
| 47 |
+
final_video_url = await automation.execute_pipeline(content_strategy, tts_script)
|
| 48 |
+
print(f"✅ Automation completed! Final video: {final_video_url}")
|
| 49 |
+
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"❌ Automation failed: {e}")
|
| 52 |
+
|
| 53 |
+
if __name__ == "__main__":
|
| 54 |
+
asyncio.run(main())
|
src/utils.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Utility functions and logging
|
| 3 |
+
"""
|
| 4 |
+
import logging
|
| 5 |
+
import sys
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
# Setup logging
|
| 9 |
+
def setup_logging():
|
| 10 |
+
"""Configure logging"""
|
| 11 |
+
log_dir = Path("outputs/logs")
|
| 12 |
+
log_dir.mkdir(parents=True, exist_ok=True)
|
| 13 |
+
|
| 14 |
+
logging.basicConfig(
|
| 15 |
+
level=logging.INFO,
|
| 16 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 17 |
+
handlers=[
|
| 18 |
+
logging.FileHandler(log_dir / 'automation.log'),
|
| 19 |
+
logging.StreamHandler(sys.stdout)
|
| 20 |
+
]
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
setup_logging()
|
| 24 |
+
logger = logging.getLogger(__name__)
|
| 25 |
+
|
| 26 |
+
def validate_environment():
|
| 27 |
+
"""Validate that required environment variables are set"""
|
| 28 |
+
required_vars = ['GEMINI_API_KEY', 'RUNWAYML_API_KEY', 'TTS_API_KEY']
|
| 29 |
+
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
| 30 |
+
|
| 31 |
+
if missing_vars:
|
| 32 |
+
raise EnvironmentError(f"Missing required environment variables: {', '.join(missing_vars)}")
|
| 33 |
+
|
| 34 |
+
logger.info("Environment validation passed")
|
src/video_renderer.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Video rendering and subtitle engine
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
from utils import logger
|
| 6 |
+
|
| 7 |
+
class VideoRenderer:
|
| 8 |
+
def __init__(self, config):
|
| 9 |
+
self.config = config
|
| 10 |
+
|
| 11 |
+
async def render_video(self, assets):
|
| 12 |
+
"""Render final video by merging all assets"""
|
| 13 |
+
logger.info("Rendering video with assets...")
|
| 14 |
+
|
| 15 |
+
# Simplified implementation - replace with actual video rendering
|
| 16 |
+
# This would use moviepy or similar library
|
| 17 |
+
|
| 18 |
+
hook_video = assets.get('hook_video')
|
| 19 |
+
background_music = assets.get('background_music')
|
| 20 |
+
selected_videos = assets.get('selected_videos', [])
|
| 21 |
+
tts_audio = assets.get('tts_audio')
|
| 22 |
+
|
| 23 |
+
logger.info(f"Merging {len(selected_videos)} selected videos")
|
| 24 |
+
logger.info(f"Using hook video: {hook_video}")
|
| 25 |
+
logger.info(f"Using background music: {background_music}")
|
| 26 |
+
|
| 27 |
+
# Placeholder for actual video rendering logic
|
| 28 |
+
output_path = "outputs/videos/rendered_video.mp4"
|
| 29 |
+
logger.info(f"Video rendered to: {output_path}")
|
| 30 |
+
|
| 31 |
+
return output_path
|
| 32 |
+
|
| 33 |
+
async def add_subtitles(self, video_path, tts_script):
|
| 34 |
+
"""Add subtitles to video"""
|
| 35 |
+
logger.info("Adding subtitles to video...")
|
| 36 |
+
|
| 37 |
+
# Simplified implementation - replace with actual subtitle engine
|
| 38 |
+
# This would add subtitles in the middle of the screen
|
| 39 |
+
|
| 40 |
+
subtitles = self._generate_subtitle_segments(tts_script)
|
| 41 |
+
logger.info(f"Generated {len(subtitles)} subtitle segments")
|
| 42 |
+
|
| 43 |
+
# Placeholder for actual subtitle rendering
|
| 44 |
+
output_path = video_path.replace('.mp4', '_subtitled.mp4')
|
| 45 |
+
logger.info(f"Subtitled video saved to: {output_path}")
|
| 46 |
+
|
| 47 |
+
return output_path
|
| 48 |
+
|
| 49 |
+
def _generate_subtitle_segments(self, text):
|
| 50 |
+
"""Generate subtitle segments from text"""
|
| 51 |
+
sentences = [s.strip() + '.' for s in text.split('.') if s.strip()]
|
| 52 |
+
segments = []
|
| 53 |
+
|
| 54 |
+
for i, sentence in enumerate(sentences):
|
| 55 |
+
segments.append({
|
| 56 |
+
'text': sentence,
|
| 57 |
+
'start_time': i * 3, # 3 seconds per segment
|
| 58 |
+
'end_time': (i + 1) * 3,
|
| 59 |
+
'position': 'middle' # Your nuance: middle of screen
|
| 60 |
+
})
|
| 61 |
+
|
| 62 |
+
return segments
|