ismdrobiul489 commited on
Commit
1a02e5e
·
1 Parent(s): 0f86d28

Add optional HF Hub cloud storage for videos with auto folders per module

Browse files
.env.example CHANGED
@@ -59,4 +59,13 @@ GEMINI_API_KEY=your_gemini_key_here
59
  # Groq API Key (Primary for AI script generation)
60
  # Get from: https://console.groq.com/keys
61
  GROQ_API=gsk_your_groq_key_here
 
 
 
 
 
 
 
 
 
62
  ```
 
59
  # Groq API Key (Primary for AI script generation)
60
  # Get from: https://console.groq.com/keys
61
  GROQ_API=gsk_your_groq_key_here
62
+
63
+ # ===================
64
+ # HF Cloud Storage (Optional - Recommended)
65
+ # ===================
66
+ # If set, videos will upload to HF Hub Dataset repo
67
+ # Format: username/repo-name (e.g., robiul487/ncakit-videos)
68
+ # Uses HF_TOKEN for authentication (already set above)
69
+ # Folders auto-created: short_video/, story_reels/
70
+ HF_REPO=your-username/ncakit-videos
71
  ```
app.py CHANGED
@@ -65,6 +65,17 @@ async def startup_event():
65
  # Ensure directories exist
66
  config.ensure_directories()
67
 
 
 
 
 
 
 
 
 
 
 
 
68
  # Register all modules
69
  num_modules = registry.register_all(app, config)
70
  logger.info(f"Loaded {num_modules} module(s)")
 
65
  # Ensure directories exist
66
  config.ensure_directories()
67
 
68
+ # Initialize HF Hub storage (optional)
69
+ if config.hf_repo:
70
+ from modules.shared.services.hf_storage import init_hf_storage
71
+ import os
72
+ hf_token = os.getenv("HF_TOKEN")
73
+ hf_storage = init_hf_storage(repo_id=config.hf_repo, token=hf_token)
74
+ if hf_storage.enabled:
75
+ logger.info(f"HF Cloud storage enabled: {config.hf_repo}")
76
+ else:
77
+ logger.warning("HF credentials set but storage failed to initialize")
78
+
79
  # Register all modules
80
  num_modules = registry.register_all(app, config)
81
  logger.info(f"Loaded {num_modules} module(s)")
config.py CHANGED
@@ -78,6 +78,11 @@ class NCAkitConfig(BaseConfig):
78
  gemini_api_key: Optional[str] = None # For AI script generation (fallback)
79
  groq_api: Optional[str] = None # Groq API key (primary for script generation)
80
 
 
 
 
 
 
81
  @property
82
  def videos_dir_path(self) -> Path:
83
  """Directory for storing generated videos"""
 
78
  gemini_api_key: Optional[str] = None # For AI script generation (fallback)
79
  groq_api: Optional[str] = None # Groq API key (primary for script generation)
80
 
81
+ # ===================
82
+ # HF Cloud Storage (Optional)
83
+ # ===================
84
+ hf_repo: Optional[str] = None # HF repo for videos (e.g., "username/ncakit-videos")
85
+ # Note: Uses HF_TOKEN from environment for auth
86
  @property
87
  def videos_dir_path(self) -> Path:
88
  """Directory for storing generated videos"""
modules/shared/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ Shared utilities and services for NCAkit modules
3
+ """
modules/shared/services/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """
2
+ Shared services for NCAkit modules
3
+ """
4
+ from .hf_storage import HFStorageClient, get_hf_storage, init_hf_storage
5
+
6
+ __all__ = ['HFStorageClient', 'get_hf_storage', 'init_hf_storage']
modules/shared/services/hf_storage.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HF Hub Storage Client - Optional cloud storage for videos
3
+ Uploads videos to Hugging Face Hub Dataset repository
4
+ """
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ # Will be imported if available
12
+ try:
13
+ from huggingface_hub import HfApi, create_repo
14
+ HF_HUB_AVAILABLE = True
15
+ except ImportError:
16
+ HF_HUB_AVAILABLE = False
17
+ logger.warning("huggingface_hub not installed. Cloud storage disabled.")
18
+
19
+
20
+ class HFStorageClient:
21
+ """
22
+ Optional HF Hub storage for videos.
23
+
24
+ If HF_REPO and HF_TOKEN are set, uploads videos to HF Dataset repo.
25
+ Otherwise, does nothing (graceful degradation).
26
+
27
+ Structure:
28
+ - your-repo/
29
+ - short_video/
30
+ - video_id.mp4
31
+ - story_reels/
32
+ - video_id.mp4
33
+ - [future_module]/
34
+ - video_id.mp4
35
+ """
36
+
37
+ def __init__(self, repo_id: str = None, token: str = None):
38
+ """
39
+ Initialize HF storage client.
40
+
41
+ Args:
42
+ repo_id: HF repo ID (e.g., "username/ncakit-videos")
43
+ token: HF token with write access
44
+ """
45
+ self.enabled = bool(repo_id and token and HF_HUB_AVAILABLE)
46
+ self.repo_id = repo_id
47
+ self.token = token
48
+ self.api = None
49
+
50
+ if self.enabled:
51
+ self._initialize()
52
+ else:
53
+ if repo_id and token and not HF_HUB_AVAILABLE:
54
+ logger.warning("HF credentials provided but huggingface_hub not installed")
55
+ elif not repo_id or not token:
56
+ logger.info("HF cloud storage disabled (HF_REPO or HF_TOKEN not set)")
57
+
58
+ def _initialize(self):
59
+ """Initialize HF API and create repo if needed"""
60
+ try:
61
+ self.api = HfApi(token=self.token)
62
+
63
+ # Create repo if not exists
64
+ try:
65
+ create_repo(
66
+ repo_id=self.repo_id,
67
+ repo_type="dataset",
68
+ token=self.token,
69
+ exist_ok=True,
70
+ private=False # Public for easy access
71
+ )
72
+ logger.info(f"HF storage enabled: {self.repo_id}")
73
+ except Exception as e:
74
+ logger.warning(f"Could not create/verify repo: {e}")
75
+
76
+ except Exception as e:
77
+ logger.error(f"Failed to initialize HF storage: {e}")
78
+ self.enabled = False
79
+
80
+ def upload_video(
81
+ self,
82
+ local_path: Path,
83
+ video_id: str,
84
+ folder: str = "videos"
85
+ ) -> Optional[str]:
86
+ """
87
+ Upload video to HF Hub.
88
+
89
+ Args:
90
+ local_path: Path to local video file
91
+ video_id: Unique video ID
92
+ folder: Folder name (e.g., "short_video", "story_reels")
93
+
94
+ Returns:
95
+ Public URL if successful, None otherwise
96
+ """
97
+ if not self.enabled:
98
+ return None
99
+
100
+ if not local_path.exists():
101
+ logger.error(f"Video file not found: {local_path}")
102
+ return None
103
+
104
+ try:
105
+ # Upload file
106
+ path_in_repo = f"{folder}/{video_id}.mp4"
107
+
108
+ self.api.upload_file(
109
+ path_or_fileobj=str(local_path),
110
+ path_in_repo=path_in_repo,
111
+ repo_id=self.repo_id,
112
+ repo_type="dataset"
113
+ )
114
+
115
+ # Generate public URL
116
+ public_url = f"https://huggingface.co/datasets/{self.repo_id}/resolve/main/{path_in_repo}"
117
+
118
+ logger.info(f"Uploaded to HF: {public_url}")
119
+ return public_url
120
+
121
+ except Exception as e:
122
+ logger.error(f"Failed to upload to HF Hub: {e}")
123
+ return None
124
+
125
+ def delete_video(self, video_id: str, folder: str = "videos") -> bool:
126
+ """
127
+ Delete video from HF Hub.
128
+
129
+ Args:
130
+ video_id: Video ID to delete
131
+ folder: Folder name
132
+
133
+ Returns:
134
+ True if successful
135
+ """
136
+ if not self.enabled:
137
+ return False
138
+
139
+ try:
140
+ path_in_repo = f"{folder}/{video_id}.mp4"
141
+
142
+ self.api.delete_file(
143
+ path_in_repo=path_in_repo,
144
+ repo_id=self.repo_id,
145
+ repo_type="dataset"
146
+ )
147
+
148
+ logger.info(f"Deleted from HF: {path_in_repo}")
149
+ return True
150
+
151
+ except Exception as e:
152
+ logger.error(f"Failed to delete from HF Hub: {e}")
153
+ return False
154
+
155
+
156
+ # Singleton instance (initialized in modules/__init__.py or app startup)
157
+ _hf_storage: Optional[HFStorageClient] = None
158
+
159
+
160
+ def get_hf_storage() -> Optional[HFStorageClient]:
161
+ """Get the global HF storage client"""
162
+ return _hf_storage
163
+
164
+
165
+ def init_hf_storage(repo_id: str = None, token: str = None) -> HFStorageClient:
166
+ """Initialize the global HF storage client"""
167
+ global _hf_storage
168
+ _hf_storage = HFStorageClient(repo_id=repo_id, token=token)
169
+ return _hf_storage
modules/story_reels/services/story_creator.py CHANGED
@@ -473,11 +473,31 @@ class StoryCreator:
473
  )
474
 
475
  job["video_url"] = f"/videos/{job_id}.mp4"
 
476
  job["duration"] = audio_duration
477
  job["progress"] = 100
478
 
479
  logger.info(f"[{job_id}] Video saved to {output_path}")
480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  finally:
482
  # Cleanup temp files
483
  for temp_file in temp_files:
 
473
  )
474
 
475
  job["video_url"] = f"/videos/{job_id}.mp4"
476
+ job["storage"] = "local"
477
  job["duration"] = audio_duration
478
  job["progress"] = 100
479
 
480
  logger.info(f"[{job_id}] Video saved to {output_path}")
481
 
482
+ # Optional: Upload to HF Hub (if configured)
483
+ try:
484
+ from modules.shared.services.hf_storage import get_hf_storage
485
+ hf_storage = get_hf_storage()
486
+ if hf_storage and hf_storage.enabled:
487
+ cloud_url = hf_storage.upload_video(
488
+ local_path=output_path,
489
+ video_id=job_id,
490
+ folder="story_reels" # Module-specific folder
491
+ )
492
+ if cloud_url:
493
+ job["video_url"] = cloud_url
494
+ job["storage"] = "cloud"
495
+ # Delete local file to save Docker space
496
+ output_path.unlink()
497
+ logger.info(f"[{job_id}] Uploaded to HF Hub, local file deleted")
498
+ except Exception as e:
499
+ logger.warning(f"[{job_id}] HF upload failed, keeping local: {e}")
500
+
501
  finally:
502
  # Cleanup temp files
503
  for temp_file in temp_files:
modules/video_creator/services/short_creator.py CHANGED
@@ -327,6 +327,24 @@ class ShortCreator:
327
  if temp_output_path.exists():
328
  temp_output_path.rename(output_path)
329
  logger.info(f"Video {video_id} created successfully at {output_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  else:
331
  raise Exception("Rendered file not found at temp path")
332
 
 
327
  if temp_output_path.exists():
328
  temp_output_path.rename(output_path)
329
  logger.info(f"Video {video_id} created successfully at {output_path}")
330
+
331
+ # Optional: Upload to HF Hub (if configured)
332
+ try:
333
+ from modules.shared.services.hf_storage import get_hf_storage
334
+ hf_storage = get_hf_storage()
335
+ if hf_storage and hf_storage.enabled:
336
+ cloud_url = hf_storage.upload_video(
337
+ local_path=output_path,
338
+ video_id=video_id,
339
+ folder="short_video" # Module-specific folder
340
+ )
341
+ if cloud_url:
342
+ # Store cloud URL for status response
343
+ # Note: video_creator uses file-based status, so we just log
344
+ output_path.unlink()
345
+ logger.info(f"Video {video_id} uploaded to HF Hub, local file deleted")
346
+ except Exception as e:
347
+ logger.warning(f"HF upload failed for {video_id}, keeping local: {e}")
348
  else:
349
  raise Exception("Rendered file not found at temp path")
350
 
requirements.txt CHANGED
@@ -23,3 +23,4 @@ groq
23
 
24
  # Utilities
25
  python-multipart
 
 
23
 
24
  # Utilities
25
  python-multipart
26
+ huggingface_hub