refactor: remove AssetSelector, use asset_manager singletons directly
Browse files- Remove AssetSelector wrapper class entirely
- automation.py now uses AudioLib and AssetProcessor directly
- Remove health_check methods (api_clients, automation, main)
- Remove video_library wrapper properties from asset_processor
- Simplify code by eliminating unnecessary abstraction layers
- src/api_clients.py +0 -49
- src/asset_manager/asset_processor.py +4 -9
- src/asset_selector.py +0 -79
- src/automation.py +11 -60
- src/main.py +0 -27
- src/process_csv.py +1 -1
src/api_clients.py
CHANGED
|
@@ -745,55 +745,6 @@ class APIClients:
|
|
| 745 |
logger.error(f"β Voice selection failed: {e}, using default")
|
| 746 |
return await self._select_sequential_voice("female_young")
|
| 747 |
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
async def health_check(self) -> Dict[str, bool]:
|
| 751 |
-
"""Check health of all API connections"""
|
| 752 |
-
logger.info("π₯ Running health check...")
|
| 753 |
-
|
| 754 |
-
health = {"gemini": False, "runwayml": False, "tts": False, "gcs": False}
|
| 755 |
-
|
| 756 |
-
try:
|
| 757 |
-
test_prompt = "Hello"
|
| 758 |
-
enhanced = await self.enhance_prompt(test_prompt)
|
| 759 |
-
if enhanced and len(enhanced) > 0:
|
| 760 |
-
health["gemini"] = True
|
| 761 |
-
logger.info(" β
Gemini API: Connected")
|
| 762 |
-
else:
|
| 763 |
-
logger.error(" β Gemini API: No response")
|
| 764 |
-
except Exception as e:
|
| 765 |
-
logger.error(f" β Gemini API: {e}")
|
| 766 |
-
|
| 767 |
-
try:
|
| 768 |
-
from google.cloud.exceptions import NotFound
|
| 769 |
-
|
| 770 |
-
try:
|
| 771 |
-
self.gcs_bucket.exists()
|
| 772 |
-
health["gcs"] = True
|
| 773 |
-
logger.info(" β
Google Cloud Storage: Connected")
|
| 774 |
-
except NotFound:
|
| 775 |
-
logger.error(" β Google Cloud Storage: Bucket not found")
|
| 776 |
-
except Exception as e:
|
| 777 |
-
logger.error(f" β Google Cloud Storage: {e}")
|
| 778 |
-
|
| 779 |
-
if self.runway_api_key and len(self.runway_api_key) > 10:
|
| 780 |
-
health["runwayml"] = True
|
| 781 |
-
logger.info(" β
RunwayML API (gen4_turbo): Configured")
|
| 782 |
-
else:
|
| 783 |
-
logger.error(" β RunwayML API: Not configured")
|
| 784 |
-
|
| 785 |
-
if self.tts_client:
|
| 786 |
-
health["tts"] = True
|
| 787 |
-
logger.info(" β
TTS API: Configured")
|
| 788 |
-
else:
|
| 789 |
-
logger.error(" β TTS API: Not configured")
|
| 790 |
-
|
| 791 |
-
all_healthy = all(health.values())
|
| 792 |
-
status = "β
All systems operational!" if all_healthy else "β οΈ Some services have issues"
|
| 793 |
-
logger.info(f"\n{status}")
|
| 794 |
-
|
| 795 |
-
return health
|
| 796 |
-
|
| 797 |
async def store_in_cache(self, file_path: str, method_type: str, file_ext: str = ".mp4") -> str:
|
| 798 |
"""Store file in Google Cloud Storage and return its public URL."""
|
| 799 |
try:
|
|
|
|
| 745 |
logger.error(f"β Voice selection failed: {e}, using default")
|
| 746 |
return await self._select_sequential_voice("female_young")
|
| 747 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
async def store_in_cache(self, file_path: str, method_type: str, file_ext: str = ".mp4") -> str:
|
| 749 |
"""Store file in Google Cloud Storage and return its public URL."""
|
| 750 |
try:
|
src/asset_manager/asset_processor.py
CHANGED
|
@@ -29,11 +29,6 @@ class AssetProcessor:
|
|
| 29 |
self.data_holder = data_holder
|
| 30 |
self._video_lib = get_video_lib()
|
| 31 |
|
| 32 |
-
@property
|
| 33 |
-
def video_library(self) -> pd.DataFrame:
|
| 34 |
-
"""Get video library from singleton"""
|
| 35 |
-
return self._video_lib.video_library
|
| 36 |
-
|
| 37 |
def _parse_duration(self, duration_str: str) -> int:
|
| 38 |
"""Parse duration from various string formats to integer seconds"""
|
| 39 |
try:
|
|
@@ -100,8 +95,8 @@ Video Options: {video_context}
|
|
| 100 |
selected = []
|
| 101 |
for item in selection:
|
| 102 |
video_index = item["video_index"]
|
| 103 |
-
if video_index < len(self.video_library):
|
| 104 |
-
video_row = self.video_library[self.video_library["Video URL (No Audio)"] == item["video_url"]]
|
| 105 |
video = video_row.iloc[0]
|
| 106 |
selected.append(
|
| 107 |
{
|
|
@@ -117,7 +112,7 @@ Video Options: {video_context}
|
|
| 117 |
}
|
| 118 |
)
|
| 119 |
if "alternate_video_index" in item:
|
| 120 |
-
video_row = self.video_library[self.video_library["Video URL (No Audio)"] == item["alternate_video_url"]]
|
| 121 |
video = video_row.iloc[0]
|
| 122 |
selected[-1]["alternate_url"] = video.get("Video URL (No Audio)", video.get("url", ""))
|
| 123 |
|
|
@@ -157,7 +152,7 @@ Video Options: {video_context}
|
|
| 157 |
f"{next((v.get('duration', 0) for v in self.data_holder.visual_assets['all_videos'] if v['url'] == row.get('Video URL (No Audio)')), 0)}s - "
|
| 158 |
f"Alignment: {row.get('Video Alignment with the TTS Script', row.get('alignment', ''))} - "
|
| 159 |
f"Usage Count: {self.data_holder.video_usage_count.get(row.get('Video URL (No Audio)'), 0)}"
|
| 160 |
-
for i, row in self.video_library.iterrows()
|
| 161 |
]
|
| 162 |
)
|
| 163 |
|
|
|
|
| 29 |
self.data_holder = data_holder
|
| 30 |
self._video_lib = get_video_lib()
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
def _parse_duration(self, duration_str: str) -> int:
|
| 33 |
"""Parse duration from various string formats to integer seconds"""
|
| 34 |
try:
|
|
|
|
| 95 |
selected = []
|
| 96 |
for item in selection:
|
| 97 |
video_index = item["video_index"]
|
| 98 |
+
if video_index < len(self._video_lib.video_library):
|
| 99 |
+
video_row = self._video_lib.video_library[self._video_lib.video_library["Video URL (No Audio)"] == item["video_url"]]
|
| 100 |
video = video_row.iloc[0]
|
| 101 |
selected.append(
|
| 102 |
{
|
|
|
|
| 112 |
}
|
| 113 |
)
|
| 114 |
if "alternate_video_index" in item:
|
| 115 |
+
video_row = self._video_lib.video_library[self._video_lib.video_library["Video URL (No Audio)"] == item["alternate_video_url"]]
|
| 116 |
video = video_row.iloc[0]
|
| 117 |
selected[-1]["alternate_url"] = video.get("Video URL (No Audio)", video.get("url", ""))
|
| 118 |
|
|
|
|
| 152 |
f"{next((v.get('duration', 0) for v in self.data_holder.visual_assets['all_videos'] if v['url'] == row.get('Video URL (No Audio)')), 0)}s - "
|
| 153 |
f"Alignment: {row.get('Video Alignment with the TTS Script', row.get('alignment', ''))} - "
|
| 154 |
f"Usage Count: {self.data_holder.video_usage_count.get(row.get('Video URL (No Audio)'), 0)}"
|
| 155 |
+
for i, row in self._video_lib.video_library.iterrows()
|
| 156 |
]
|
| 157 |
)
|
| 158 |
|
src/asset_selector.py
DELETED
|
@@ -1,79 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
AssetSelector - Thin wrapper for backward compatibility
|
| 3 |
-
Use asset_manager classes directly for new code.
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import pandas as pd
|
| 7 |
-
from typing import List, Dict, Optional, Tuple
|
| 8 |
-
from utils import logger
|
| 9 |
-
from data_holder import DataHolder
|
| 10 |
-
|
| 11 |
-
from asset_manager import get_video_lib, get_audio_lib, AssetProcessor
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class AssetSelector:
|
| 15 |
-
"""
|
| 16 |
-
Wrapper class for backward compatibility.
|
| 17 |
-
New code should use asset_manager classes directly:
|
| 18 |
-
- get_video_lib() for video library
|
| 19 |
-
- get_audio_lib() for audio library
|
| 20 |
-
- AssetProcessor(data_holder) for video selection
|
| 21 |
-
"""
|
| 22 |
-
|
| 23 |
-
def __init__(self, config: Dict, data_holder: DataHolder = None, gcloud_wrapper=None):
|
| 24 |
-
self.config = config
|
| 25 |
-
self.data_holder = data_holder
|
| 26 |
-
|
| 27 |
-
# Use singletons from asset_manager
|
| 28 |
-
self._video_lib = get_video_lib()
|
| 29 |
-
initial_audio_index = config.get("current_audio_index", 0)
|
| 30 |
-
self._audio_lib = get_audio_lib(initial_audio_index)
|
| 31 |
-
self._audio_lib.current_audio_index = initial_audio_index
|
| 32 |
-
|
| 33 |
-
# Processor for video selection (only create when data_holder available)
|
| 34 |
-
self._processor = AssetProcessor(data_holder) if data_holder else None
|
| 35 |
-
|
| 36 |
-
@property
|
| 37 |
-
def video_library(self) -> pd.DataFrame:
|
| 38 |
-
return self._video_lib.video_library
|
| 39 |
-
|
| 40 |
-
@property
|
| 41 |
-
def audio_library(self) -> pd.DataFrame:
|
| 42 |
-
return self._audio_lib.audio_library
|
| 43 |
-
|
| 44 |
-
@property
|
| 45 |
-
def current_audio_index(self) -> int:
|
| 46 |
-
return self._audio_lib.current_audio_index
|
| 47 |
-
|
| 48 |
-
@current_audio_index.setter
|
| 49 |
-
def current_audio_index(self, value: int):
|
| 50 |
-
self._audio_lib.current_audio_index = value
|
| 51 |
-
self.config["current_audio_index"] = value
|
| 52 |
-
|
| 53 |
-
def inc_audio_index(self):
|
| 54 |
-
self._audio_lib.inc_audio_index()
|
| 55 |
-
self.config["current_audio_index"] = self._audio_lib.current_audio_index
|
| 56 |
-
|
| 57 |
-
def get_audio_beats(self, audio_link: str) -> Optional[List[float]]:
|
| 58 |
-
return self._audio_lib.get_audio_beats(audio_link)
|
| 59 |
-
|
| 60 |
-
async def select_videos(self, tts_script, timed_transcript, max_duration: int = 12) -> List[Dict]:
|
| 61 |
-
"""Delegate to AssetProcessor"""
|
| 62 |
-
if not self._processor:
|
| 63 |
-
self._processor = AssetProcessor(self.data_holder)
|
| 64 |
-
return await self._processor.select_videos(tts_script, timed_transcript, max_duration)
|
| 65 |
-
|
| 66 |
-
def select_background_music(self) -> str:
|
| 67 |
-
selected = self._audio_lib.select_background_music()
|
| 68 |
-
self.config["current_audio_index"] = self._audio_lib.current_audio_index
|
| 69 |
-
return selected
|
| 70 |
-
|
| 71 |
-
def reset_audio_index(self):
|
| 72 |
-
self._audio_lib.reset_audio_index()
|
| 73 |
-
self.config["current_audio_index"] = 0
|
| 74 |
-
|
| 75 |
-
def select_random_videos(self, count: int) -> List[str]:
|
| 76 |
-
"""Delegate to AssetProcessor"""
|
| 77 |
-
if not self._processor:
|
| 78 |
-
self._processor = AssetProcessor(self.data_holder)
|
| 79 |
-
return self._processor.select_random_videos(count)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/automation.py
CHANGED
|
@@ -9,7 +9,6 @@ from typing import Dict, List, Optional, Any
|
|
| 9 |
from pathlib import Path
|
| 10 |
from api_clients import APIClients
|
| 11 |
from video_renderer import VideoRenderer
|
| 12 |
-
from asset_selector import AssetSelector
|
| 13 |
from utils import logger
|
| 14 |
import utils
|
| 15 |
from moviepy.config import change_settings
|
|
@@ -27,18 +26,19 @@ import numpy as np
|
|
| 27 |
from file_downloader import FileDownloader
|
| 28 |
from data_holder import DataHolder
|
| 29 |
import setup_config
|
| 30 |
-
from asset_manager import get_asset_downloader
|
| 31 |
from file_downloader import FileDownloader
|
| 32 |
|
| 33 |
class ContentAutomation:
|
| 34 |
-
def __init__(self, config: Dict[str, Any], data_holder: DataHolder = None,
|
| 35 |
self.config = config
|
| 36 |
self.data_holder = data_holder or DataHolder()
|
| 37 |
# Reuse provided api_clients or create new one
|
| 38 |
self.api_clients = api_clients or APIClients(config, self.data_holder)
|
| 39 |
self.video_renderer = VideoRenderer(config, self.data_holder)
|
| 40 |
-
#
|
| 41 |
-
self.
|
|
|
|
| 42 |
self.asset_downloader = get_asset_downloader()
|
| 43 |
self.file_downloader = FileDownloader()
|
| 44 |
self.pipeline_start_time = None
|
|
@@ -144,7 +144,7 @@ class ContentAutomation:
|
|
| 144 |
)
|
| 145 |
|
| 146 |
logger.info("\nπ΅ STEP 7: Background Music")
|
| 147 |
-
self.data_holder.visual_assets["background_music_url"] = self.
|
| 148 |
local_path = self.file_downloader.safe_download(self.data_holder.visual_assets["background_music_url"])
|
| 149 |
if local_path:
|
| 150 |
self.data_holder.visual_assets["background_music_local"] = str(local_path)
|
|
@@ -235,7 +235,7 @@ class ContentAutomation:
|
|
| 235 |
with AudioFileClip(self.data_holder.visual_assets["background_music_local"]) as audio_clip:
|
| 236 |
music_duration = audio_clip.duration - 0.5
|
| 237 |
|
| 238 |
-
beat_times = self.
|
| 239 |
if beat_times:
|
| 240 |
beat_times = self.extend_beats_to_audio_end(
|
| 241 |
beat_times,
|
|
@@ -263,11 +263,9 @@ class ContentAutomation:
|
|
| 263 |
logger.info(f"Beat times: {beat_times}")
|
| 264 |
|
| 265 |
num_videos_needed = len(beat_times) + 2
|
| 266 |
-
|
| 267 |
-
logger.info(f"video library size: {len(self.asset_selector.video_library)}")
|
| 268 |
|
| 269 |
# Select enough videos
|
| 270 |
-
self.data_holder.visual_assets["selected_videos"] = self.
|
| 271 |
logger.info(self.data_holder.visual_assets["selected_videos"])
|
| 272 |
|
| 273 |
videos = self.data_holder.visual_assets["selected_videos"]
|
|
@@ -358,9 +356,9 @@ class ContentAutomation:
|
|
| 358 |
async def _download_bg_music(self, try_next: bool = False):
|
| 359 |
logger.info("\nπ΅ STEP 1: Background Music")
|
| 360 |
if try_next:
|
| 361 |
-
self.
|
| 362 |
|
| 363 |
-
self.data_holder.visual_assets["background_music_url"] = self.
|
| 364 |
local_path = self.file_downloader.safe_download(self.data_holder.visual_assets["background_music_url"])
|
| 365 |
if local_path:
|
| 366 |
self.data_holder.visual_assets["background_music_local"] = str(local_path)
|
|
@@ -408,7 +406,7 @@ class ContentAutomation:
|
|
| 408 |
"""Generate visual assets in parallel (hook video + library videos)"""
|
| 409 |
tasks = {
|
| 410 |
"hook_video": self._generate_hook_video(content_strategy),
|
| 411 |
-
"selected_videos": self.
|
| 412 |
tts_script=self.data_holder.tts_script,
|
| 413 |
timed_transcript=self.data_holder.visual_assets["timed_transcript"]),
|
| 414 |
}
|
|
@@ -468,52 +466,6 @@ class ContentAutomation:
|
|
| 468 |
traceback.print_exc()
|
| 469 |
raise
|
| 470 |
|
| 471 |
-
async def health_check(self) -> Dict[str, bool]:
|
| 472 |
-
"""Comprehensive health check of all components"""
|
| 473 |
-
logger.info("π₯ Running comprehensive health check...")
|
| 474 |
-
|
| 475 |
-
# Check API clients
|
| 476 |
-
api_health = await self.api_clients.health_check()
|
| 477 |
-
|
| 478 |
-
# Check asset selector
|
| 479 |
-
try:
|
| 480 |
-
asset_selector_healthy = len(self.asset_selector.video_library) > 0
|
| 481 |
-
if not asset_selector_healthy:
|
| 482 |
-
logger.warning(" β οΈ Asset Selector: Video library is empty")
|
| 483 |
-
except Exception as e:
|
| 484 |
-
asset_selector_healthy = False
|
| 485 |
-
logger.error(f" β Asset Selector: {e}")
|
| 486 |
-
|
| 487 |
-
# Check video renderer
|
| 488 |
-
try:
|
| 489 |
-
video_renderer_healthy = self.video_renderer.temp_dir.exists()
|
| 490 |
-
if not video_renderer_healthy:
|
| 491 |
-
logger.warning(" β οΈ Video Renderer: Temp directory issue")
|
| 492 |
-
except Exception as e:
|
| 493 |
-
video_renderer_healthy = False
|
| 494 |
-
logger.error(f" β Video Renderer: {e}")
|
| 495 |
-
|
| 496 |
-
# Combine all health statuses
|
| 497 |
-
health_status = {
|
| 498 |
-
**api_health,
|
| 499 |
-
"asset_selector": asset_selector_healthy,
|
| 500 |
-
"video_renderer": video_renderer_healthy,
|
| 501 |
-
}
|
| 502 |
-
|
| 503 |
-
operational_services = sum(health_status.values())
|
| 504 |
-
total_services = len(health_status)
|
| 505 |
-
|
| 506 |
-
print(f"\nπ Health Summary: {operational_services}/{total_services} services operational")
|
| 507 |
-
|
| 508 |
-
if operational_services == total_services:
|
| 509 |
-
print("π System is fully operational and ready for production!")
|
| 510 |
-
elif operational_services >= total_services - 2:
|
| 511 |
-
print("β οΈ System is mostly operational, but some features may be limited")
|
| 512 |
-
else:
|
| 513 |
-
print("β System has significant issues that need attention")
|
| 514 |
-
|
| 515 |
-
return health_status
|
| 516 |
-
|
| 517 |
def extend_beats_to_audio_end(
|
| 518 |
self,
|
| 519 |
beats: List[float],
|
|
@@ -533,7 +485,6 @@ class ContentAutomation:
|
|
| 533 |
|
| 534 |
return beats
|
| 535 |
|
| 536 |
-
|
| 537 |
async def simple_demo(self):
|
| 538 |
"""Simple demo with proper audio handling"""
|
| 539 |
logger.info("π¬ Starting Simple Demo with Audio Fix...")
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
from api_clients import APIClients
|
| 11 |
from video_renderer import VideoRenderer
|
|
|
|
| 12 |
from utils import logger
|
| 13 |
import utils
|
| 14 |
from moviepy.config import change_settings
|
|
|
|
| 26 |
from file_downloader import FileDownloader
|
| 27 |
from data_holder import DataHolder
|
| 28 |
import setup_config
|
| 29 |
+
from asset_manager import get_asset_downloader, get_audio_lib, AssetProcessor
|
| 30 |
from file_downloader import FileDownloader
|
| 31 |
|
| 32 |
class ContentAutomation:
|
| 33 |
+
def __init__(self, config: Dict[str, Any], data_holder: DataHolder = None, api_clients: 'APIClients' = None):
|
| 34 |
self.config = config
|
| 35 |
self.data_holder = data_holder or DataHolder()
|
| 36 |
# Reuse provided api_clients or create new one
|
| 37 |
self.api_clients = api_clients or APIClients(config, self.data_holder)
|
| 38 |
self.video_renderer = VideoRenderer(config, self.data_holder)
|
| 39 |
+
# Use asset_manager singletons directly
|
| 40 |
+
self._audio_lib = get_audio_lib(config.get("current_audio_index", 0))
|
| 41 |
+
self._asset_processor = AssetProcessor(self.data_holder)
|
| 42 |
self.asset_downloader = get_asset_downloader()
|
| 43 |
self.file_downloader = FileDownloader()
|
| 44 |
self.pipeline_start_time = None
|
|
|
|
| 144 |
)
|
| 145 |
|
| 146 |
logger.info("\nπ΅ STEP 7: Background Music")
|
| 147 |
+
self.data_holder.visual_assets["background_music_url"] = self._audio_lib.select_background_music()
|
| 148 |
local_path = self.file_downloader.safe_download(self.data_holder.visual_assets["background_music_url"])
|
| 149 |
if local_path:
|
| 150 |
self.data_holder.visual_assets["background_music_local"] = str(local_path)
|
|
|
|
| 235 |
with AudioFileClip(self.data_holder.visual_assets["background_music_local"]) as audio_clip:
|
| 236 |
music_duration = audio_clip.duration - 0.5
|
| 237 |
|
| 238 |
+
beat_times = self._audio_lib.get_audio_beats(self.data_holder.visual_assets["background_music_url"])
|
| 239 |
if beat_times:
|
| 240 |
beat_times = self.extend_beats_to_audio_end(
|
| 241 |
beat_times,
|
|
|
|
| 263 |
logger.info(f"Beat times: {beat_times}")
|
| 264 |
|
| 265 |
num_videos_needed = len(beat_times) + 2
|
|
|
|
|
|
|
| 266 |
|
| 267 |
# Select enough videos
|
| 268 |
+
self.data_holder.visual_assets["selected_videos"] = self._asset_processor.select_random_videos(num_videos_needed)
|
| 269 |
logger.info(self.data_holder.visual_assets["selected_videos"])
|
| 270 |
|
| 271 |
videos = self.data_holder.visual_assets["selected_videos"]
|
|
|
|
| 356 |
async def _download_bg_music(self, try_next: bool = False):
|
| 357 |
logger.info("\nπ΅ STEP 1: Background Music")
|
| 358 |
if try_next:
|
| 359 |
+
self._audio_lib.inc_audio_index()
|
| 360 |
|
| 361 |
+
self.data_holder.visual_assets["background_music_url"] = self._audio_lib.select_background_music()
|
| 362 |
local_path = self.file_downloader.safe_download(self.data_holder.visual_assets["background_music_url"])
|
| 363 |
if local_path:
|
| 364 |
self.data_holder.visual_assets["background_music_local"] = str(local_path)
|
|
|
|
| 406 |
"""Generate visual assets in parallel (hook video + library videos)"""
|
| 407 |
tasks = {
|
| 408 |
"hook_video": self._generate_hook_video(content_strategy),
|
| 409 |
+
"selected_videos": self._asset_processor.select_videos(
|
| 410 |
tts_script=self.data_holder.tts_script,
|
| 411 |
timed_transcript=self.data_holder.visual_assets["timed_transcript"]),
|
| 412 |
}
|
|
|
|
| 466 |
traceback.print_exc()
|
| 467 |
raise
|
| 468 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
def extend_beats_to_audio_end(
|
| 470 |
self,
|
| 471 |
beats: List[float],
|
|
|
|
| 485 |
|
| 486 |
return beats
|
| 487 |
|
|
|
|
| 488 |
async def simple_demo(self):
|
| 489 |
"""Simple demo with proper audio handling"""
|
| 490 |
logger.info("π¬ Starting Simple Demo with Audio Fix...")
|
src/main.py
CHANGED
|
@@ -154,33 +154,6 @@ async def run_pipeline(
|
|
| 154 |
|
| 155 |
return result
|
| 156 |
|
| 157 |
-
|
| 158 |
-
async def health_check_command(automation: ContentAutomation):
|
| 159 |
-
"""Run health check on all services"""
|
| 160 |
-
try:
|
| 161 |
-
health_status = await automation.health_check()
|
| 162 |
-
|
| 163 |
-
print("\n" + "=" * 50)
|
| 164 |
-
print("π₯ SYSTEM HEALTH CHECK RESULTS")
|
| 165 |
-
print("=" * 50)
|
| 166 |
-
|
| 167 |
-
for service, status in health_status.items():
|
| 168 |
-
icon = "β
" if status else "β"
|
| 169 |
-
print(f"{icon} {service.upper():<15} {'OPERATIONAL' if status else 'ISSUE DETECTED'}")
|
| 170 |
-
|
| 171 |
-
if all(health_status.values()):
|
| 172 |
-
print("\nπ All systems are ready for production!")
|
| 173 |
-
return 0
|
| 174 |
-
else:
|
| 175 |
-
print("\nβ οΈ Some services need attention before running the pipeline.")
|
| 176 |
-
print(" Check the logs above for details.")
|
| 177 |
-
return 1
|
| 178 |
-
|
| 179 |
-
except Exception as e:
|
| 180 |
-
logger.error(f"Health check failed: {e}")
|
| 181 |
-
return 1
|
| 182 |
-
|
| 183 |
-
|
| 184 |
async def test_command(automation: ContentAutomation):
|
| 185 |
"""Run simple demo test"""
|
| 186 |
logger.info("\nπ§ͺ Running Simple Demo Test...")
|
|
|
|
| 154 |
|
| 155 |
return result
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
async def test_command(automation: ContentAutomation):
|
| 158 |
"""Run simple demo test"""
|
| 159 |
logger.info("\nπ§ͺ Running Simple Demo Test...")
|
src/process_csv.py
CHANGED
|
@@ -113,7 +113,7 @@ async def process_row(row, config: dict):
|
|
| 113 |
if SHARED_API_CLIENTS:
|
| 114 |
SHARED_API_CLIENTS.data_holder = dataHolder
|
| 115 |
|
| 116 |
-
#
|
| 117 |
automation = ContentAutomation(
|
| 118 |
config, dataHolder,
|
| 119 |
api_clients=SHARED_API_CLIENTS
|
|
|
|
| 113 |
if SHARED_API_CLIENTS:
|
| 114 |
SHARED_API_CLIENTS.data_holder = dataHolder
|
| 115 |
|
| 116 |
+
# ContentAutomation uses asset_manager singletons directly
|
| 117 |
automation = ContentAutomation(
|
| 118 |
config, dataHolder,
|
| 119 |
api_clients=SHARED_API_CLIENTS
|