Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -24,12 +24,39 @@ from io import BytesIO
|
|
| 24 |
|
| 25 |
class ImageScraper:
|
| 26 |
def __init__(self):
|
| 27 |
-
self.
|
|
|
|
|
|
|
| 28 |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
| 29 |
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
| 30 |
'Accept-Language': 'en-US,en;q=0.5',
|
| 31 |
'Connection': 'keep-alive',
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
def get_stock_images(self) -> List[str]:
|
| 35 |
"""Return preset stock images relevant to digital security and technology"""
|
|
@@ -103,6 +130,9 @@ class EnhancedVideoGenerator:
|
|
| 103 |
logging.error(f"Initialization failed: {str(e)}")
|
| 104 |
raise RuntimeError("Failed to initialize video generator")
|
| 105 |
|
|
|
|
|
|
|
|
|
|
| 106 |
def setup_logging(self):
|
| 107 |
"""Configure logging for the application"""
|
| 108 |
logging.basicConfig(
|
|
@@ -339,20 +369,83 @@ class EnhancedVideoGenerator:
|
|
| 339 |
self.logger.error(f"Progress bar drawing failed: {str(e)}")
|
| 340 |
|
| 341 |
def generate_voice_over(self, script: str) -> AudioFileClip:
|
| 342 |
-
"""Generate voice-over audio using gTTS"""
|
| 343 |
try:
|
|
|
|
| 344 |
audio_path = self.temp_dir / "voice.mp3"
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
)
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
return AudioFileClip(str(audio_path))
|
| 352 |
-
|
| 353 |
except Exception as e:
|
| 354 |
-
|
| 355 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 358 |
"""Create video with selected images"""
|
|
|
|
| 24 |
|
| 25 |
class ImageScraper:
|
| 26 |
def __init__(self):
|
| 27 |
+
self.PIXABAY_API_KEY = "48069976-37e20099248207cee12385560" # Replace with your key
|
| 28 |
+
self.stock_images = [
|
| 29 |
+
"https://images.pexels.com/photos/60504/security-protection-anti-virus-software-60504.jpeg",
|
| 30 |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
| 31 |
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
| 32 |
'Accept-Language': 'en-US,en;q=0.5',
|
| 33 |
'Connection': 'keep-alive',
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
def get_images(self, query: str, num_images: int = 15) -> List[str]:
|
| 37 |
+
try:
|
| 38 |
+
search_terms = [
|
| 39 |
+
query,
|
| 40 |
+
"digital security technology",
|
| 41 |
+
"cyber security",
|
| 42 |
+
"data protection",
|
| 43 |
+
"digital technology"
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
all_urls = []
|
| 47 |
+
for term in search_terms:
|
| 48 |
+
url = f"https://pixabay.com/api/?key={self.PIXABAY_API_KEY}&q={term.replace(' ', '+')}&image_type=photo&per_page=5"
|
| 49 |
+
response = requests.get(url)
|
| 50 |
+
data = response.json()
|
| 51 |
+
|
| 52 |
+
if 'hits' in data:
|
| 53 |
+
all_urls.extend([img['largeImageURL'] for img in data['hits']])
|
| 54 |
+
|
| 55 |
+
return list(set(all_urls))[:num_images] if all_urls else self.stock_images
|
| 56 |
+
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"Pixabay API error: {e}")
|
| 59 |
+
return self.stock_images
|
| 60 |
|
| 61 |
def get_stock_images(self) -> List[str]:
|
| 62 |
"""Return preset stock images relevant to digital security and technology"""
|
|
|
|
| 130 |
logging.error(f"Initialization failed: {str(e)}")
|
| 131 |
raise RuntimeError("Failed to initialize video generator")
|
| 132 |
|
| 133 |
+
self.ELEVEN_LABS_API_KEY = "sk_acdad9d2d82d504bddbe5ed4aa290ca772c106aed5b128ba" # Replace with your key
|
| 134 |
+
|
| 135 |
+
|
| 136 |
def setup_logging(self):
|
| 137 |
"""Configure logging for the application"""
|
| 138 |
logging.basicConfig(
|
|
|
|
| 369 |
self.logger.error(f"Progress bar drawing failed: {str(e)}")
|
| 370 |
|
| 371 |
def generate_voice_over(self, script: str) -> AudioFileClip:
|
|
|
|
| 372 |
try:
|
| 373 |
+
# Try ElevenLabs first
|
| 374 |
audio_path = self.temp_dir / "voice.mp3"
|
| 375 |
+
|
| 376 |
+
headers = {
|
| 377 |
+
"xi-api-key": self.ELEVEN_LABS_API_KEY,
|
| 378 |
+
"Content-Type": "application/json"
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
data = {
|
| 382 |
+
"text": script,
|
| 383 |
+
"model_id": "eleven_monolingual_v1",
|
| 384 |
+
"voice_settings": {
|
| 385 |
+
"stability": 0.75,
|
| 386 |
+
"similarity_boost": 0.75
|
| 387 |
+
}
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
response = requests.post(
|
| 391 |
+
"https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM",
|
| 392 |
+
headers=headers,
|
| 393 |
+
json=data
|
| 394 |
)
|
| 395 |
+
|
| 396 |
+
if response.status_code == 200:
|
| 397 |
+
with open(audio_path, "wb") as f:
|
| 398 |
+
f.write(response.content)
|
| 399 |
+
else:
|
| 400 |
+
# Fallback to Azure TTS
|
| 401 |
+
speech_config = speechsdk.SpeechConfig(
|
| 402 |
+
subscription=self.AZURE_SPEECH_KEY,
|
| 403 |
+
region=self.AZURE_REGION
|
| 404 |
+
)
|
| 405 |
+
speech_config.speech_synthesis_voice_name = "en-US-JennyNeural"
|
| 406 |
+
|
| 407 |
+
synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config)
|
| 408 |
+
result = synthesizer.speak_text_async(script).get()
|
| 409 |
+
|
| 410 |
+
if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
|
| 411 |
+
with open(audio_path, "wb") as f:
|
| 412 |
+
f.write(result.audio_data)
|
| 413 |
+
|
| 414 |
return AudioFileClip(str(audio_path))
|
| 415 |
+
|
| 416 |
except Exception as e:
|
| 417 |
+
print(f"Voice generation error: {e}")
|
| 418 |
+
return self.generate_fallback_audio(script)
|
| 419 |
+
|
| 420 |
+
def generate_subtitles(self, script: str, duration: int) -> str:
|
| 421 |
+
words = script.split()
|
| 422 |
+
words_per_second = len(words) / duration
|
| 423 |
+
subtitle_path = self.temp_dir / "subtitles.srt"
|
| 424 |
+
|
| 425 |
+
with open(subtitle_path, 'w') as f:
|
| 426 |
+
current_time = 0
|
| 427 |
+
words_per_subtitle = int(words_per_second * 3) # 3 seconds per subtitle
|
| 428 |
+
|
| 429 |
+
for i in range(0, len(words), words_per_subtitle):
|
| 430 |
+
subtitle_words = words[i:i + words_per_subtitle]
|
| 431 |
+
if subtitle_words:
|
| 432 |
+
start_time = self.format_time(current_time)
|
| 433 |
+
current_time += len(subtitle_words) / words_per_second
|
| 434 |
+
end_time = self.format_time(current_time)
|
| 435 |
+
|
| 436 |
+
f.write(f"{i//words_per_subtitle + 1}\n")
|
| 437 |
+
f.write(f"{start_time} --> {end_time}\n")
|
| 438 |
+
f.write(f"{' '.join(subtitle_words)}\n\n")
|
| 439 |
+
|
| 440 |
+
return str(subtitle_path)
|
| 441 |
+
|
| 442 |
+
@staticmethod
|
| 443 |
+
def format_time(seconds: float) -> str:
|
| 444 |
+
hours = int(seconds // 3600)
|
| 445 |
+
minutes = int((seconds % 3600) // 60)
|
| 446 |
+
secs = int(seconds % 60)
|
| 447 |
+
msecs = int((seconds - int(seconds)) * 1000)
|
| 448 |
+
return f"{hours:02d}:{minutes:02d}:{secs:02d},{msecs:03d}"
|
| 449 |
|
| 450 |
def create_video(self, script: str, style: str, duration: int, output_path: str, selected_images: List[str]) -> str:
|
| 451 |
"""Create video with selected images"""
|