ishworrsubedii commited on
Commit
15a5b8a
·
1 Parent(s): 0c43391

add: endpoint product video gen --> one combined

Browse files
Files changed (2) hide show
  1. app.py +108 -4
  2. src/components/each_necklace_video_gen.py +182 -0
app.py CHANGED
@@ -2,6 +2,7 @@ import mimetypes
2
  import os
3
  import tempfile
4
  import time
 
5
 
6
  import requests
7
  from fastapi import FastAPI
@@ -9,6 +10,7 @@ from pydantic import BaseModel
9
  from starlette.responses import JSONResponse
10
  from supabase import create_client
11
 
 
12
  from src.components.vidgen import VideoCreator
13
 
14
  supabase_url = os.getenv('SUPABASE_URL')
@@ -16,7 +18,7 @@ supabase_key = os.getenv('SUPABASE_KEY')
16
  supabase = create_client(supabase_url, supabase_key)
17
  app = FastAPI()
18
 
19
- RESOURCES_DIR = "/app/resources"
20
 
21
  os.makedirs(RESOURCES_DIR, exist_ok=True)
22
  TEMP_VIDEO_DIR = f"{RESOURCES_DIR}/temp_video"
@@ -94,7 +96,7 @@ class VideoGenerator(BaseModel):
94
  makeup_image_title: str = "Makeup Try-On"
95
 
96
 
97
- @app.post("/create-video/")
98
  async def create_video(request: VideoGenerator):
99
  start_time = time.time()
100
  try:
@@ -134,11 +136,9 @@ async def create_video(request: VideoGenerator):
134
 
135
  )
136
 
137
- # Generate video
138
  video_creator.create_final_video()
139
  video_creation_time = time.time() - start_time - file_download_time
140
 
141
- # Upload to Supabase
142
  url = upload_to_supabase(video_path=output_path)
143
  supabase_upload_time = time.time() - start_time - file_download_time - video_creation_time
144
 
@@ -168,6 +168,110 @@ async def create_video(request: VideoGenerator):
168
  return JSONResponse(content={"status": "error", "message": str(e)}, status_code=500)
169
 
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  @app.get("/resources")
172
  async def get_infromation():
173
  music = os.listdir(RESOURCES_DIR + "/audio")
 
2
  import os
3
  import tempfile
4
  import time
5
+ from typing import List
6
 
7
  import requests
8
  from fastapi import FastAPI
 
10
  from starlette.responses import JSONResponse
11
  from supabase import create_client
12
 
13
+ from src.components.each_necklace_video_gen import EachVideoCreator
14
  from src.components.vidgen import VideoCreator
15
 
16
  supabase_url = os.getenv('SUPABASE_URL')
 
18
  supabase = create_client(supabase_url, supabase_key)
19
  app = FastAPI()
20
 
21
+ RESOURCES_DIR = "resources"
22
 
23
  os.makedirs(RESOURCES_DIR, exist_ok=True)
24
  TEMP_VIDEO_DIR = f"{RESOURCES_DIR}/temp_video"
 
96
  makeup_image_title: str = "Makeup Try-On"
97
 
98
 
99
+ @app.post("/create-single-product-video/")
100
  async def create_video(request: VideoGenerator):
101
  start_time = time.time()
102
  try:
 
136
 
137
  )
138
 
 
139
  video_creator.create_final_video()
140
  video_creation_time = time.time() - start_time - file_download_time
141
 
 
142
  url = upload_to_supabase(video_path=output_path)
143
  supabase_upload_time = time.time() - start_time - file_download_time - video_creation_time
144
 
 
168
  return JSONResponse(content={"status": "error", "message": str(e)}, status_code=500)
169
 
170
 
171
+ class EachNecklaceVideoGeneratorRequest(BaseModel):
172
+ intro_video_path: str = "JewelMirror_intro.mp4"
173
+ font_path: str = "PlayfairDisplay-VariableFont_wght.ttf"
174
+ background_audio_path: str = "TraditionalIndianVlogMusic.mp3"
175
+ image_display_duration: float = 2.5
176
+ fps: int = 10
177
+ necklace_title: List[str]
178
+ nto_image_title: List[str]
179
+ nto_cto_image_title: List[str]
180
+ makeup_image_title: List[str]
181
+ necklace_images: List[str]
182
+ necklace_try_on_output_images: List[List[str]]
183
+ clothing_output_images: List[List[str]]
184
+ makeup_output_images: List[List[str]]
185
+
186
+
187
+ @app.post("/create-multiple-product-video/")
188
+ async def create_video(request: EachNecklaceVideoGeneratorRequest):
189
+ start_time = time.time()
190
+ try:
191
+ def process_images(image_list):
192
+ if isinstance(image_list, list):
193
+ return [process_images(item) for item in image_list]
194
+ return download_image(image_list)
195
+
196
+ temp_files = {
197
+ 'necklaces': process_images(request.necklace_images),
198
+ 'necklace_tryon': process_images(request.necklace_try_on_output_images),
199
+ 'clothing': process_images(request.clothing_output_images),
200
+ 'makeup': process_images(request.makeup_output_images)
201
+ }
202
+ file_download_time = time.time() - start_time
203
+
204
+ def verify_files(files):
205
+ if isinstance(files, list):
206
+ return all(verify_files(item) for item in files) # Recurse for nested lists
207
+ return files is not None # Check single file
208
+
209
+ if not all(verify_files(files) for files in temp_files.values()):
210
+ return JSONResponse(
211
+ content={"status": "error", "message": "Failed to download all required images."},
212
+ status_code=400
213
+ )
214
+
215
+ # Prepare paths
216
+ intro_path = f"{RESOURCES_DIR}/intro/{request.intro_video_path}"
217
+ output_path = f"{TEMP_VIDEO_DIR}/video_{os.urandom(8).hex()}.mp4"
218
+ font_path = f"{RESOURCES_DIR}/fonts/{request.font_path}"
219
+ audio_path = f"{RESOURCES_DIR}/audio/{request.background_audio_path}"
220
+
221
+ video_creator = EachVideoCreator(
222
+ intro_video_path=intro_path,
223
+ necklace_image=temp_files['necklaces'],
224
+ nto_outputs=temp_files['necklace_tryon'],
225
+ nto_cto_outputs=temp_files['clothing'],
226
+ makeup_outputs=temp_files['makeup'],
227
+ font_path=font_path,
228
+ output_path=output_path,
229
+ audio_path=audio_path,
230
+ image_display_duration=request.image_display_duration,
231
+ fps=request.fps,
232
+ necklace_title=request.necklace_title,
233
+ nto_title=request.nto_image_title,
234
+
235
+ cto_title=request.nto_cto_image_title,
236
+ makeup_title=request.makeup_image_title
237
+
238
+ )
239
+
240
+ # Generate video
241
+ video_creator.create_final_video()
242
+ video_creation_time = time.time() - start_time - file_download_time
243
+
244
+ # Upload video to Supabase
245
+ url = upload_to_supabase(video_path=output_path)
246
+ supabase_upload_time = time.time() - start_time - file_download_time - video_creation_time
247
+
248
+ response = {
249
+ "status": "success",
250
+ "message": "Video created successfully.",
251
+ "video_url": url,
252
+ "timings": {
253
+ "file_download_time": file_download_time,
254
+ "video_creation_time": video_creation_time,
255
+ "supabase_upload_time": supabase_upload_time
256
+ }
257
+ }
258
+
259
+ def cleanup_files(files):
260
+ if isinstance(files, list):
261
+ for item in files:
262
+ cleanup_files(item)
263
+ elif os.path.exists(files):
264
+ os.unlink(files)
265
+
266
+ if os.path.exists(output_path):
267
+ os.remove(output_path)
268
+
269
+ return JSONResponse(content=response, status_code=200)
270
+
271
+ except Exception as e:
272
+ return JSONResponse(content={"status": "error", "message": str(e)}, status_code=500)
273
+
274
+
275
  @app.get("/resources")
276
  async def get_infromation():
277
  music = os.listdir(RESOURCES_DIR + "/audio")
src/components/each_necklace_video_gen.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ project @ images_to_video
3
+ created @ 2024-12-17
4
+ author @ github.com/ishworrsubedii
5
+ """
6
+ import os
7
+
8
+ from moviepy.audio.io.AudioFileClip import AudioFileClip
9
+ from moviepy.video.VideoClip import ImageClip, ColorClip, TextClip
10
+ from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
11
+ from moviepy.video.compositing.concatenate import concatenate_videoclips
12
+ from moviepy.video.fx.all import resize
13
+ from moviepy.video.io.VideoFileClip import VideoFileClip
14
+
15
+
16
+ class EachVideoCreator:
17
+ def __init__(self, necklace_title, nto_title, cto_title, makeup_title, intro_video_path=None, necklace_image=None,
18
+ nto_outputs=None,
19
+ nto_cto_outputs=None, makeup_outputs=None, font_path=None, output_path=None,
20
+ audio_path=None, image_display_duration=2.5, box_color=(131, 42, 48), box_opacity=0.8,
21
+ font_size=28, text_color="white", fps=1):
22
+ self.intro_video_path = intro_video_path
23
+ self.necklace_images = necklace_image if necklace_image else []
24
+ self.nto_outputs = nto_outputs if nto_outputs else []
25
+ self.nto_cto_outputs = nto_cto_outputs if nto_cto_outputs else []
26
+ self.makeup_outputs = makeup_outputs if makeup_outputs else []
27
+ self.output_video_path = output_path
28
+ self.font_path = font_path
29
+ self.audio_path = audio_path
30
+ self.image_display_duration = image_display_duration
31
+ self.box_color = box_color
32
+ self.box_opacity = box_opacity
33
+ self.font_size = font_size
34
+ self.text_color = text_color
35
+ self.fps = fps
36
+ self.necklace_title = necklace_title
37
+ self.nto_title = nto_title
38
+ self.cto_title = cto_title
39
+ self.makeup_title = makeup_title
40
+
41
+ def create_necklace_clips(self, necklace_image, index, label):
42
+ if not necklace_image:
43
+ print(f"Skipping necklace {index + 1}: No image provided.")
44
+ return []
45
+
46
+ backgrounds = [
47
+ (245, 245, 245), # Soft White
48
+ (220, 245, 245), # Light Blue
49
+ (230, 230, 235) # Pearl Gray
50
+ ]
51
+ necklace_clips = []
52
+ for bg_color in backgrounds:
53
+ bg_clip = ColorClip((1080, 1080), col=bg_color, duration=self.image_display_duration)
54
+ necklace = resize(ImageClip(necklace_image), width=650)
55
+ necklace = necklace.set_duration(self.image_display_duration).set_position('center')
56
+ txt_overlay = self.create_text_overlay(f"{label}")
57
+ final_clip = CompositeVideoClip([bg_clip, necklace, txt_overlay.set_position(('center', 'bottom'))])
58
+ necklace_clips.append(final_clip)
59
+ return necklace_clips
60
+
61
+ def create_grouped_clips(self, grouped_images, label):
62
+ clips = []
63
+ for idx, group in enumerate(grouped_images):
64
+ for img_path in group:
65
+ if os.path.exists(img_path) and img_path.lower().endswith(('.png', '.jpg', '.jpeg')):
66
+ print(f"Processing {label} image: {img_path}")
67
+ img_clip = resize(ImageClip(img_path), (1080, 1080))
68
+ txt_overlay = self.create_text_overlay(f"{label} {idx + 1}")
69
+ final_clip = CompositeVideoClip([
70
+ img_clip.set_duration(self.image_display_duration),
71
+ txt_overlay.set_position(('center', 'bottom'))
72
+ ])
73
+ clips.append(final_clip)
74
+ return clips
75
+
76
+ def create_text_overlay(self, text, duration=None):
77
+ box = ColorClip((1080, 80), col=self.box_color, duration=duration or self.image_display_duration)
78
+ box = box.set_opacity(self.box_opacity)
79
+ txt = TextClip(text, font=self.font_path, fontsize=self.font_size, color=self.text_color)
80
+ return CompositeVideoClip([box, txt.set_position('center')])
81
+
82
+ def create_final_video(self):
83
+ try:
84
+ print("Starting video creation...")
85
+ clips = []
86
+
87
+ # Step 1: Process Intro Video
88
+ if self.intro_video_path and os.path.exists(self.intro_video_path):
89
+ print(f"Adding intro video from path: {self.intro_video_path}")
90
+ intro_clip = resize(VideoFileClip(self.intro_video_path), (1080, 1080))
91
+ clips.append(intro_clip)
92
+ else:
93
+ print("Skipping intro video: Path not provided or invalid.")
94
+
95
+ # Step 2: Process Necklaces and Associated Outputs
96
+ for idx, necklace_image in enumerate(self.necklace_images):
97
+ print(f"Processing Necklace {idx + 1}...")
98
+ # Necklace preview clips
99
+ necklace_clips = self.create_necklace_clips(necklace_image, idx, self.necklace_title[idx])
100
+ if necklace_clips:
101
+ clips.extend(necklace_clips)
102
+ else:
103
+ print(f"Skipping Necklace {idx + 1} preview: No valid clips created.")
104
+
105
+ # NTO outputs
106
+ if idx < len(self.nto_outputs):
107
+ print(f"Adding NTO outputs for Necklace {idx + 1}")
108
+ nto_clips = self.create_grouped_clips([self.nto_outputs[idx]], self.nto_title[idx])
109
+ if nto_clips:
110
+
111
+ clips.extend(nto_clips)
112
+ else:
113
+ print(f"No valid NTO clips for Necklace {idx + 1}")
114
+
115
+ # CTO outputs
116
+ if idx < len(self.nto_cto_outputs):
117
+ print(f"Adding CTO outputs for Necklace {idx + 1}")
118
+ cto_clips = self.create_grouped_clips([self.nto_cto_outputs[idx]], self.cto_title[idx])
119
+ if cto_clips:
120
+ clips.extend(cto_clips)
121
+ else:
122
+ print(f"No valid CTO clips for Necklace {idx + 1}")
123
+
124
+ # Makeup outputs
125
+ if idx < len(self.makeup_outputs):
126
+ print(f"Adding Makeup outputs for Necklace {idx + 1}")
127
+ makeup_clips = self.create_grouped_clips([self.makeup_outputs[idx]], self.makeup_title[idx])
128
+ if makeup_clips:
129
+ clips.extend(makeup_clips)
130
+ else:
131
+ print(f"No valid Makeup clips for Necklace {idx + 1}")
132
+
133
+ final_clips = []
134
+ for clip in clips:
135
+ final_clips.append(clip.set_duration(self.image_display_duration))
136
+ clips = final_clips
137
+
138
+ if not clips:
139
+ print("No valid clips to combine. Exiting.")
140
+ return
141
+
142
+ print(f"Total clips to concatenate: {len(clips)}")
143
+ final_video = concatenate_videoclips(clips, method="compose")
144
+
145
+ # Step 4: Add Background Audio
146
+ if self.audio_path and os.path.exists(self.audio_path):
147
+ print(f"Adding background audio from path: {self.audio_path}")
148
+ try:
149
+ audio = AudioFileClip(self.audio_path).subclip(0, final_video.duration)
150
+ final_video = final_video.set_audio(audio)
151
+ except Exception as e:
152
+ print(f"Error adding audio: {e}")
153
+ else:
154
+ print("Skipping background audio: Path not provided or invalid.")
155
+
156
+ # Step 5: Export Final Video
157
+ print("Rendering final video...")
158
+ final_video.write_videofile(
159
+ self.output_video_path,
160
+ fps=self.fps,
161
+ codec="libx264",
162
+ audio_codec="aac",
163
+ threads=4,
164
+ preset="ultrafast"
165
+ )
166
+ print(f"Video successfully saved to: {self.output_video_path}")
167
+
168
+ except Exception as e:
169
+ print(f"An error occurred during video creation: {e}")
170
+
171
+ # if __name__ == "__main__":
172
+ # creator = EachVideoCreator(
173
+ # intro_video_path="intro.mp4",
174
+ # necklace_image=["necklace.jpg", "necklace2.png"],
175
+ # nto_outputs=[["nto1.jpg", "nto2.jpg"], ["nto3.jpg", "nto4.jpg"]],
176
+ # nto_cto_outputs=[["cto1.jpg", "cto2.jpg"], ["cto3.jpg", "cto4.jpg"]],
177
+ # makeup_outputs=[["makeup1.jpg", "makeup2.jpg"], ["makeup3.jpg", "makeup4.jpg"]],
178
+ # font_path="font.ttf",
179
+ # output_path="output/final_video.mp4",
180
+ # audio_path="audio.mp3"
181
+ # )
182
+ # creator.create_final_video()