jebin2 commited on
Commit
9d1dc2c
Β·
1 Parent(s): ea33c8c

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 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, asset_selector: 'AssetSelector' = None, api_clients: 'APIClients' = 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
- # Reuse provided asset_selector or create new one
41
- self.asset_selector = asset_selector or AssetSelector(config, self.data_holder)
 
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.asset_selector.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,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.asset_selector.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,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.asset_selector.select_random_videos(num_videos_needed)
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.asset_selector.inc_audio_index()
362
 
363
- self.data_holder.visual_assets["background_music_url"] = self.asset_selector.select_background_music()
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.asset_selector.select_videos(
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
- # AssetSelector uses singletons internally, no need to share
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