CoolFace
Apppublic

bhavisha21/trial_video_generation

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py783 linesDownload Raw Back to root
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