bhavisha21/trial_video_generation
0
1import base642import json3import os4import re5import shutil6from io import BytesIO7import gizeh as gz8import moviepy.editor as mp9import requests10from PIL import Image11from fastapi import FastAPI, UploadFile, File, HTTPException12from fastapi.middleware.cors import CORSMiddleware13from fastapi.staticfiles import StaticFiles14from pydantic import BaseModel15from gtts import gTTS16from fastapi.responses import FileResponse17import moviepy.config as mp_config18from fastapi.responses import JSONResponse19import uuid20import ffmpeg21from bs4 import BeautifulSoup22 23import ssl24import urllib325from sendgrid import SendGridAPIClient, Personalization, Email26from sendgrid.helpers.mail import Mail27from aeneas.executetask import ExecuteTask28from aeneas.task import Task29mp_config.change_settings({"IMAGEMAGIC_BINARY": r"/usr/bin/convert"})30mp_config.TEMP_DIR = '/tmp'31os.environ["CACHE_DIR"] = "/tmp"32 33from faster_whisper import WhisperModel34 35MASTER_API = os.getenv("MASTER_API")36 37app = FastAPI()38#235 and 407 39# Enable CORS for external access (important for Spaces)40app.add_middleware(41 CORSMiddleware,42 allow_origins=["*"], # Allow all origins43 allow_credentials=True,44 allow_methods=["*"], # Allow all methods45 allow_headers=["*"], # Allow all headers46)47 48# Mount the directory for serving static files (videos)49app.mount("/static", StaticFiles(directory="/tmp"), name="static")50 51 52class Step(BaseModel):53 action: str54 description: str55 imageData: str = None56 57 58class CombinedObject(BaseModel):59 name: str60 description: str61 steps: list[Step]62 63 64# Define the input model65class VideoData(BaseModel):66 combined_object: CombinedObject67 action_steps: list[int]68 intro_video_path: str69 ending_video_path: str70 71 72TEXT_COLOR = (10 / 255, 18 / 255, 145 / 255)73REC_COLOR = (103 / 255, 158 / 255, 0 / 255)74VIDEO_SIZE = (1920, 1080)75DURATION = 476FPS = 2477VIDEO_BACKGROUND = (255, 255, 255)78 79def clean_strong_tags(soup):80 html = str(soup)81 82 # Remove leading/trailing spaces inside <strong>83 html = re.sub(r'<strong>\s*(.*?)\s*</strong>', r'<strong>\1</strong>', html)84 85 # Move punctuation inside </strong>86 html = re.sub(r'<strong>(.*?)</strong>\s*([;,.!?])', r'<strong>\1\2</strong>', html)87 88 # Remove space after </strong> if next is a word89 html = re.sub(r'</strong>\s+(?=\w)', r'</strong>', html)90 91 # Remove space before <strong>92 html = re.sub(r'\s+<strong>', r'<strong>', html)93 94 return BeautifulSoup(html, 'html.parser')95 96 97def send_credit_alert_email(error_message):98 # Disable SSL warnings (⚠️ only for dev)99 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)100 101 p = Personalization()102 p.add_to(Email("admin@instancy.com"))103 p.add_cc(Email("ysrinivas@instancy.com"))104 105 message = Mail(106 from_email='support@instancy.com',107 to_emails='admin@instancy.com',108 subject='ElevenLabs Credit Quota Exceeded!',109 plain_text_content=f"""110 We regret to inform you that we are unable to generate video due to ElevenLabs Text-to-Speech credits being over.111 Error Details: {error_message}112 Please check your ElevenLabs account balance and consider purchasing additional credits or upgrading your plan to avoid service interruptions.113 Thank you, Your Support Team"""114 )115 116 message.add_personalization(p)117 118 # Custom SSL context to skip verification119 ssl._create_default_https_context = ssl._create_unverified_context120 try:121 sg = SendGridAPIClient(os.getenv('SENDGRID_API_KEY'))122 sg.send(message)123 print("📧 Credit alert email sent successfully!")124 except Exception as e:125 print("❌ Failed to send alert email:", e)126 127 128def extract_action_and_description(html):129 # Parse the HTML content using BeautifulSoup130 soup = BeautifulSoup(html, 'html.parser')131 132 # Extract the first h1 tag as the action133 action = soup.find('h1').get_text() if soup.find('h1') else ''134 135 # Extract all other text as the description (excluding the first h1)136 h1_tag = soup.find('h1')137 if h1_tag:138 h1_tag.extract() # Remove the first <h1> tag to focus on the remaining content139 140 cleaned_html = clean_strong_tags(soup)141 142 print("Cleaned Soup:")143 print(cleaned_html)144 145 print("soup.get_text(separator=' '):", cleaned_html.get_text(separator=" "))146 147 # Extract description text, remove all non-printable characters, and normalize whitespace148 description = ''.join(ch for ch in cleaned_html.get_text(separator=" ") if ch.isprintable()).strip()149 150 # Extract the image URL (first <img> tag 'src' attribute)151 img_tag = soup.find('img')152 image_url = img_tag['src'] if img_tag else ''153 154 return action, description, image_url155 156 157def text_to_speech_eleven_labs(text_to_speak, filename):158 # Constants159 CHUNK_SIZE = 1024 # Size of chunks to read/write at a time160 XI_API_KEY = os.getenv("XI_API_KEY")161 VOICE_ID = "0rfzWkFI8tX1ryG1G27h" # ID of the voice model to use162 163 # URL for the Text-to-Speech API request164 tts_url = f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}/stream"165 166 # Headers for the API request167 headers = {168 "Accept": "application/json",169 "xi-api-key": XI_API_KEY170 }171 172 # Data payload for the API request173 data = {174 "text": text_to_speak,175 "model_id": "eleven_multilingual_v2",176 "voice_settings": {177 "stability": 0.5,178 "similarity_boost": 0.8,179 "style": 0.0,180 "use_speaker_boost": True181 }182 }183 184 try:185 # Make the POST request to the TTS API with headers and data, enabling streaming response186 response = requests.post(tts_url, headers=headers, json=data, stream=True)187 188 if response.status_code == 401 or response.status_code == 402:189 try:190 error_json = response.json()191 error_message = error_json.get("detail", {}).get("message", "")192 if "quota_exceeded" in error_json.get("detail", {}).get("status","") or "exceeds your quota" in error_message:193 print("⚠️ Quota error detected!")194 send_credit_alert_email(error_message)195 return196 except Exception as parse_err:197 print("❌ Failed to parse error message:", parse_err)198 199 # Check if the request was successful200 response.raise_for_status() # Raise an exception for HTTP errors201 # Open the output file in write-binary mode202 with open(filename, "wb") as f:203 # Read the response in chunks and write to the file204 for chunk in response.iter_content(chunk_size=CHUNK_SIZE):205 f.write(chunk)206 print("Audio stream saved successfully.")207 except requests.RequestException as e:208 print(f"Error during TTS API request: {e}")209 except IOError as e:210 print(f"Error saving audio file: {filename} - {e}")211 212 213def download_video_intro(url, save_path):214 try:215 response = requests.get(url, stream=True)216 response.raise_for_status() # Check for any errors217 with open(save_path, 'wb') as f:218 for chunk in response.iter_content(chunk_size=8192):219 f.write(chunk)220 print(f"Video downloaded successfully: {save_path}")221 except requests.RequestException as e:222 print(f"Error downloading video from {url}: {e}")223 except IOError as e:224 print(f"Error saving video to {save_path}: {e}")225 226 227def calculate_position_animation(start_pos, end_pos, time, middle_time, direction='forward'):228 if direction == 'forward':229 if time <= middle_time:230 return start_pos + (end_pos - start_pos) * (time / middle_time)231 else:232 return end_pos233 elif direction == 'backward':234 if time <= middle_time:235 return start_pos + (end_pos - start_pos) * (time / middle_time)236 else:237 return end_pos238 else:239 raise ValueError("Invalid direction. Use 'forward' or 'backward'.")240 241 242def calculate_position(start_pos, end_pos, time, middle_time, direction='forward'):243 if direction == 'forward':244 return end_pos245 elif direction == 'backward':246 return end_pos247 else:248 raise ValueError("Invalid direction. Use 'forward' or 'backward'.")249 250 251def text_to_speech(text, filename):252 try:253 tts = gTTS(text=text, lang='en')254 tts.save(filename)255 return filename256 except Exception as e:257 print(f"Error during text-to-speech: {e}")258 259 260def calculate_font_size(text_length, base_font_size, max_text_width, min_font_size):261 """262 Adjust font size based on text length and maximum text width.263 """264 average_char_width_factor = 0.9265 266 def get_text_width(font_size):267 return font_size * average_char_width_factor * text_length268 269 font_size = base_font_size270 while get_text_width(font_size) > max_text_width and font_size > min_font_size:271 font_size -= 2272 return font_size273 274 275def render_animation(time, text_input, mode='line'):276 middle_time = 100277 base_font_size = 60278 min_font_size = 20279 max_text_width = VIDEO_SIZE[0] - 100280 281 text_length = len(text_input)282 font_size = calculate_font_size(text_length, base_font_size, max_text_width, min_font_size)283 text_width = font_size * 0.6 * text_length284 text_height = font_size * 1.2285 286 text_x_position = calculate_position(-text_width, VIDEO_SIZE[0] / 2, time, middle_time, 'forward')287 x_position = calculate_position(VIDEO_SIZE[0], VIDEO_SIZE[0] / 2, time, middle_time, 'backward')288 289 surface = gz.Surface(*VIDEO_SIZE, bg_color=(0, 0, 0, 0))290 y_position = VIDEO_SIZE[1] / 2291 292 try:293 if mode == 'line':294 line_y_position = y_position + text_height / 2 + 10295 gz.polyline(296 points=[(-text_width / 2, 0), (text_width / 2, 0)],297 stroke_width=15,298 stroke=REC_COLOR,299 xy=(x_position, line_y_position)300 ).draw(surface)301 elif mode == 'rectangle':302 rectangle_x = x_position - (text_width + 40) / 2303 rectangle_y = y_position - (text_height + 40) / 2304 gz.rectangle(305 lx=text_width + 120,306 ly=text_height + 50,307 xy=(x_position, y_position),308 stroke_width=10,309 stroke=REC_COLOR310 ).draw(surface)311 else:312 raise ValueError("Invalid mode. Use 'line' or 'rectangle'.")313 314 gz.text(text_input, fontfamily="./Arial.ttf", fontsize=font_size, fontweight='bold', fill=TEXT_COLOR,315 xy=(text_x_position, y_position)).draw(surface)316 except Exception as e:317 print("Error rendering animation: ", e)318 319 return surface.get_npimage(transparent=True)320 321 322def set_transparency(drawable, DURATION=0):323 """324 Apply transparency to the video clip.325 """326 print(DURATION)327 clip_mask = mp.VideoClip(lambda t: drawable(t)[:, :, 3] / 255.0, duration=DURATION, ismask=True)328 return mp.VideoClip(lambda t: drawable(t)[:, :, :3], duration=DURATION).set_mask(clip_mask)329 330 331def create_action_frame(action, filename):332 try:333 action_text = set_transparency(drawable=lambda t: render_animation(t, action, mode="rectangle"), DURATION=3)334 335 video_clip = mp.CompositeVideoClip([action_text.set_position(('center', 0))], size=VIDEO_SIZE).on_color(336 color=VIDEO_BACKGROUND, col_opacity=1).set_duration(3)337 338 return video_clip339 except Exception as e:340 print(f"Error creating action frame: {e}")341 342 343def extract_audio_from_video(input_video, audio_file):344 """345 Extracts the audio from a video using ffmpeg.346 """347 try:348 (349 ffmpeg350 .input(input_video)351 .output(audio_file,acodec='libmp3lame')352 .run(overwrite_output=True)353 )354 print("Audio extracted successfully from video.")355 except ffmpeg.Error as e:356 print(f"Error extracting audio: {e}")357 358 359def generate_vtt_from_segments(segments):360 """361 Generates VTT file content from the transcription segments, respecting the actual start time.362 """363 try:364 vtt_content = "WEBVTT\n\n"365 366 # Convert the generator to a list so it can be accessed like a regular list367 segments = list(segments)368 369 # Check if there are any segments to process370 if not segments:371 print("No segments found for VTT generation.")372 return None373 374 # Calculate the offset using the first segment's start time375 start_offset = segments[0].start376 377 # Loop through the segments and adjust the timing378 for segment in segments:379 # Adjust the segment start and end times by subtracting the start_offset380 start_time = segment.start - start_offset381 end_time = segment.end - start_offset382 383 # Format the start and end times to the VTT format (HH:MM:SS.mmm)384 start_str = f"{int(start_time // 3600):02}:{int((start_time % 3600) // 60):02}:{int(start_time % 60):02}.{int(start_time * 1000) % 1000:03}"385 end_str = f"{int(end_time // 3600):02}:{int((end_time % 3600) // 60):02}:{int(end_time % 60):02}.{int(end_time * 1000) % 1000:03}"386 387 # Add the timestamp and the transcription text to the VTT content388 vtt_content += f"{start_str} --> {end_str}\n{segment.text}\n\n"389 390 return vtt_content391 392 except Exception as e:393 print(f"Error generating VTT content: {e}")394 return None395 396 397 398def save_vtt_to_file(vtt_content, output_vtt_file):399 """400 Saves the VTT content to a file.401 """402 if vtt_content is None:403 print("No VTT content to save.")404 return405 406 try:407 with open(output_vtt_file, "w") as vtt_file:408 vtt_file.write(vtt_content)409 print(f"VTT file generated successfully: {output_vtt_file}")410 except Exception as e:411 print(f"Error saving VTT file: {e}")412 413 414def process_video_for_transcription(input_video, output_vtt_file, temp_audio_file,transcription_script):415 # Paths for audio extraction416 audio_file = temp_audio_file417 418 temp_script_path = os.path.join("/tmp", "text_script.txt")419 420 with open(temp_script_path, "w", encoding="utf-8") as f:421 f.write(transcription_script)422 423 # Step 1: Extract audio from the video424 try:425 extract_audio_from_video(input_video, audio_file)426 except Exception as e:427 print(f"Error extracting audio from video: {e}")428 return429 430 # Step 2: Transcribe audio using faster-whisper431 try:432 vtt_output_path = output_vtt_file433 config_string = "task_language=eng|is_text_type=plain|os_task_file_format=vtt"434 task = Task(config_string=config_string)435 task.audio_file_path_absolute = audio_file436 task.text_file_path_absolute = temp_script_path437 task.sync_map_file_path_absolute = vtt_output_path438 439 ExecuteTask(task).execute()440 task.output_sync_map_file()441 except Exception as e:442 print(f"Error during transcription: {e}")443 return444 445 446def create_video_with_synchronized_audio_from_dict(447 combined_object, output_video_path, output_vtt_path, steps, client_url,448 intro_video_path=None, ending_video_path=None, fps=24, temp_folder="/tmp"449):450 clips = []451 audio_clips = []452 intro_video_save_path = None453 ending_video_save_path = None454 transcript_script = ""455 456 # Handle intro video457 if intro_video_path:458 try:459 if intro_video_path.startswith(("http://", "https://")):460 download_video_intro(intro_video_path, os.path.join(temp_folder, "intro_video.mp4"))461 intro_video_save_path = os.path.join(temp_folder, "intro_video.mp4")462 else:463 intro_video_save_path = intro_video_path464 intro_video_clip = mp.VideoFileClip(intro_video_save_path)465 clips.append(intro_video_clip)466 if intro_video_clip.audio is not None:467 audio_clips.append(intro_video_clip.audio)468 else:469 print("No audio found in the intro video.")470 silent_audio_duration = intro_video_clip.duration471 silent_audio_clip = mp.AudioClip(lambda t: 0, duration=silent_audio_duration).set_fps(44100)472 audio_clips.append(silent_audio_clip)473 except Exception as e:474 print(f"Error processing intro video: {e}")475 else:476 print("Intro video path is None. Skipping intro video.")477 478 # Extract introduction479 try:480 introduction = combined_object["introduction"]481 intro_html_content = introduction["htmlContent"]482 483 # Separate name and description from htmlContent using regex484 name, description, img = extract_action_and_description(intro_html_content)485 print("intro name and description: ", name, description)486 transcript_script += f"{description}\n\n"487 488 # Create the introduction clip based on the combined_object intro489 intro_audio_path = os.path.join(temp_folder, "intro_audio.mp3")490 try:491 text_to_speech_eleven_labs(description, intro_audio_path)492 except Exception as e:493 print(f"Quota error at intro: {e}")494 return495 intro_audio_clip = mp.AudioFileClip(intro_audio_path)496 intro_audio_clip.set_duration(intro_audio_clip.duration)497 498 silent_audio_clip = mp.AudioClip(lambda t: 0, duration=2).set_fps(44100)499 500 print("Intro audio duration: ", intro_audio_clip.duration)501 intro_text = set_transparency(drawable=lambda t: render_animation(t, name, mode="rectangle"),502 DURATION=intro_audio_clip.duration + 2)503 intro_clip = mp.CompositeVideoClip([intro_text.set_position(('center', 0))], size=VIDEO_SIZE).on_color(504 color=VIDEO_BACKGROUND, col_opacity=1).set_duration(intro_audio_clip.duration + 2)505 506 clips.append(intro_clip)507 audio_clips.append(intro_audio_clip)508 audio_clips.append(silent_audio_clip)509 except Exception as e:510 print(f"Error processing introduction: {e}")511 512 # 2. Process each card from the combined_object['cards']513 for i, obj in enumerate(combined_object.get("steps", [])):514 try:515 action, description, image_data = extract_action_and_description(obj['htmlContent'])516 print("step action and description: ", action, description, image_data)517 transcript_script += f"{description}\n\n"518 519 # Add action animation only if the current index is in the steps list520 if i in steps:521 action_image_path = os.path.join(temp_folder, f"tmp_action_{i}.png")522 action_clip = create_action_frame(action, action_image_path)523 clips.append(action_clip)524 525 silent_audio_duration = action_clip.duration526 silent_audio_clip = mp.AudioClip(lambda t: 0, duration=silent_audio_duration).set_fps(44100)527 audio_clips.append(silent_audio_clip)528 529 audio_file_path = os.path.join(temp_folder, f"audio_{i}.mp3")530 try:531 text_to_speech_eleven_labs(description, audio_file_path)532 except Exception as e:533 print(f"Quota error at step {i}: {e}")534 return535 audio_clip = mp.AudioFileClip(audio_file_path)536 audio_clips.append(audio_clip)537 538 tmp_image_path = os.path.join(temp_folder, f"tmp_image_{i}.png")539 540 if image_data:541 print(" in if image data: ","http" in image_data)542 if isinstance(image_data, str) and image_data.startswith("data:image/"):543 print("in if base 64")544 # Handle Base64-encoded image545 image_data = image_data.split(",")[1]546 image_bytes = base64.b64decode(image_data)547 image = Image.open(BytesIO(image_bytes))548 elif "http" in image_data or "http" in client_url + image_data:549 550 if "http" in image_data:551 full_url = client_url + image_data552 else:553 # If it's not a full URL, prepend client_url554 full_url = client_url + image_data555 556 print("In file download section: ", full_url)557 558 # Define temporary path for the downloaded image559 tmp_image_path = os.path.join(temp_folder, f"tmp_image_{i}{os.path.splitext(image_data)[1]}")560 561 # Download the image562 response = requests.get(full_url, stream=True)563 if response.status_code == 200:564 # Save the image to the temp path565 with open(tmp_image_path, 'wb') as f:566 f.write(response.content)567 print("Image downloaded to:", tmp_image_path)568 569 # Open the downloaded image570 image = Image.open(tmp_image_path)571 else:572 print("Failed to download the image; status code:", response.status_code)573 continue574 else:575 print("invalid image data: ",client_url + image_data)576 print(f"Invalid image data for step {i}. Default action image will be used.")577 continue # Skip if image data is invalid578 if image:579 # Resize and convert to RGB if needed580 if image.mode == "RGBA":581 image = image.convert("RGB")582 resized_image = image.resize((1920, 1080))583 584 # Save resized image back to the temp path585 resized_image.save(tmp_image_path, format="JPEG")586 print(f"Image resized and saved to {tmp_image_path}")587 588 # Create an ImageClip with the resized image589 img_clip = mp.ImageClip(tmp_image_path).set_duration(audio_clip.duration)590 else:591 # Default action image if no image is provided592 create_action_frame(action, tmp_image_path)593 img_clip = mp.ImageClip(tmp_image_path).set_duration(audio_clip.duration)594 595 if img_clip:596 clips.append(img_clip)597 except Exception as e:598 print(f"Error processing step {i}: {e}")599 600 # 3. Check if the ending video exists and add it if available601 if ending_video_path:602 try:603 if ending_video_path.startswith(("http://", "https://")):604 download_video_intro(ending_video_path, os.path.join(temp_folder, "ending_video.mp4"))605 ending_video_save_path = os.path.join(temp_folder, "ending_video.mp4")606 else:607 ending_video_save_path = ending_video_path608 end_video_clip = mp.VideoFileClip(ending_video_save_path)609 clips.append(end_video_clip)610 if end_video_clip.audio is not None:611 audio_clips.append(end_video_clip.audio)612 else:613 print("No audio found in the intro video.")614 silent_audio_duration = end_video_clip.duration615 silent_audio_clip = mp.AudioClip(lambda t: 0, duration=silent_audio_duration).set_fps(44100)616 audio_clips.append(silent_audio_clip)617 except Exception as e:618 print(f"Error processing ending video: {e}")619 else:620 print("Ending video path is None. Skipping ending video.")621 622 # Concatenate video and audio clips623 try:624 video = mp.concatenate_videoclips(clips, method="compose")625 total_video_duration = sum(clip.duration for clip in clips)626 final_audio = mp.concatenate_audioclips(audio_clips).set_duration(total_video_duration)627 628 # Set final video with audio629 video = video.set_audio(final_audio)630 video.write_videofile(output_video_path, codec="libx264", fps=fps,631 temp_audiofile=os.path.join(temp_folder, "temp_audio.mp3"), remove_temp=False)632 process_video_for_transcription(output_video_path, output_vtt_path,633 os.path.join(temp_folder, "extracted_audio.mp3"),transcript_script)634 except Exception as e:635 print(f"Error creating final video: {e}")636 637 638currently_processing_videos = {}639 640 641def update_video_progress(content_id, client_url, status, to_details="",fromDetails=""):642 # The updated API URL as per your cURL request643 url = f"{MASTER_API}ClientData/UpdateUserGuideVideoProgress"644 645 # Generate a new UID (token) dynamically for each request646 token = str(uuid.uuid4())647 648 # JSON payload with the data structure you provided649 data = {650 "ContentID": content_id,651 "FromDetails": fromDetails, # Keep FromDetails empty as requested652 "ToDetails": to_details, # Empty when status is 1, file path when status is 2653 "ClientURL": client_url,654 "Status": status # 1 for processing started, 2 for processing done655 }656 657 # Headers including the dynamically generated Authorization token658 headers = {659 "Content-Type": "application/json",660 "Authorization": f"Bearer {token}" # Use the dynamic token661 }662 663 # Make the POST request with data and headers664 response = requests.post(url, json=data, headers=headers)665 666 # Return the JSON response667 return response.json()668 669 670@app.post("/generate_video")671async def generate_video(json_data: dict):672 client_url = json_data.get("clienturl", "")673 content_id = json_data.get("contentId", "")674 json_object = json_data.get("json_object", {})675 676 if "steps" not in json_object:677 raise KeyError("Error: 'steps' not found in the object")678 679 if not all(all(key in obj for key in ['action', 'description', 'imageData']) for obj in json_object["steps"]):680 raise KeyError("Error: 'action', 'description', or 'imageData' not found in one of the steps")681 682 if not client_url or not content_id:683 return {"message": "clienturl or ContentID is missing, defaulting to empty"}684 685 print("client url", client_url, "content_id", content_id)686 687 setting_str = json_object.get("setting", "{}") # Get string or "{}" if missing688 setting = json.loads(setting_str) if isinstance(setting_str, str) else {}689 selected_steps = setting.get("selectedSteps", []) if isinstance(setting, dict) else []690 conclusion_video_obj = setting.get("conclusionVideoObj", {}).get('url', "") if isinstance(setting, dict) else ""691 intro_video_obj = setting.get("introVideoObj", {}).get('url', "") if isinstance(setting, dict) else ""692 693 for existing_content_id, video_info in currently_processing_videos.items():694 if video_info['status'] == 'processing':695 return f"Already processing video for {existing_content_id}. Try again after some time."696 697 # If not processing, add or update the video in the dictionary698 currently_processing_videos[content_id] = {699 "status": "processing",700 "clientUrl": client_url701 }702 703 # Notify the start of video processing with status 1704 705 print("client url and content id for status 1:",content_id,client_url)706 #update_video_progress(content_id, client_url, status=1)707 print("after update video")708 709 try:710 # Create a folder using content_id711 folder_path = f"/tmp/{content_id}"712 if os.path.exists(folder_path):713 shutil.rmtree(folder_path) # Remove the existing folder and its contents714 os.makedirs(folder_path)715 output_video_path = os.path.join(folder_path, f"{content_id}_video.mp4")716 output_vtt_path = os.path.join(folder_path, f"{content_id}_vtt.vtt")717 718 create_video_with_synchronized_audio_from_dict(combined_object=json_object, output_video_path=output_video_path,719 output_vtt_path=output_vtt_path, steps=selected_steps,720 intro_video_path=intro_video_obj,721 ending_video_path=conclusion_video_obj, temp_folder=folder_path,722 client_url=client_url)723 724 # Notify that video processing is done with status 2 and provide the video path725 print("client url and content id for status 2:",content_id,client_url)726 #update_video_progress(content_id, client_url, status=2, to_details= "https://instancy-video-generation-docker.hf.space/download-video/?file_path=" + output_video_path,fromDetails= "https://instancy-video-generation-docker.hf.space/download-video/?file_path=" + output_vtt_path)727 728 # Once done, remove the contentId from the processing list729 del currently_processing_videos[content_id]730 731 return {"message": "Video generated successfully", "video_file_path": output_video_path}732 733 except Exception as e:734 # In case of an error, ensure contentId is removed from the processing list735 if content_id in currently_processing_videos:736 del currently_processing_videos[content_id]737 return {"error": str(e)}738 739 740@app.get("/download-video/")741async def download_video(file_path: str):742 # Check if file exists743 if not os.path.exists(file_path):744 raise HTTPException(status_code=404, detail="File not found")745 746 # Extract filename from the path for the response747 filename = os.path.basename(file_path)748 749 return FileResponse(path=file_path, filename=filename)750 751 752@app.get("/download-video-base64/")753async def download_video(file_path: str):754 # Check if file exists755 if not os.path.exists(file_path):756 raise HTTPException(status_code=404, detail="File not found")757 758 # Read the file as binary and encode it to base64759 with open(file_path, "rb") as video_file:760 video_data = video_file.read()761 encoded_video = base64.b64encode(video_data).decode('utf-8')762 763 # Return the base64-encoded video data as JSON response764 return JSONResponse(content={"file_name": os.path.basename(file_path), "file_data": encoded_video})765 766 767@app.post("/delete_folder/")768async def delete_folder(video_file_path: str):769 try:770 # Extract folder path from the video file path771 folder_path = os.path.dirname(video_file_path)772 773 # Check if the folder exists774 if not os.path.exists(folder_path):775 raise HTTPException(status_code=404, detail="Folder not found")776 777 # Delete the folder and its contents778 shutil.rmtree(folder_path)779 return {"message": f"Folder {folder_path} and its contents deleted successfully."}780 781 except Exception as e:782 raise HTTPException(status_code=500, detail=f"An error occurred while deleting the folder: {str(e)}")783 