import os import json import urllib.parse import gradio as gr # Setup Virals Dir relative to this file # This file is in webui/library.py # VIRALS dir is in ../VIRALS (root of project) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) import sys sys.path.append(BASE_DIR) from i18n.i18n import I18nAuto i18n = I18nAuto() VIRALS_DIR = os.path.join(BASE_DIR, "VIRALS") # URL Mode: "fastapi" (default) or "gradio" URL_MODE = "fastapi" def set_url_mode(mode): global URL_MODE URL_MODE = mode def get_existing_projects(): if not os.path.exists(VIRALS_DIR): return [] try: projects = [d for d in os.listdir(VIRALS_DIR) if os.path.isdir(os.path.join(VIRALS_DIR, d))] projects.sort(key=lambda x: os.path.getctime(os.path.join(VIRALS_DIR, x)), reverse=True) return projects except: return [] def refresh_projects(): projs = get_existing_projects() return gr.update(choices=projs, value=None) def generate_project_gallery(project_path_name, is_full_path=False): """ Generates HTML gallery for a given project folder using FastAPI Static Files mounting. """ if not project_path_name: return f'
{i18n("No project selected.")}
' # Determine absolute path to project folder if is_full_path: project_folder_path = project_path_name else: project_folder_path = os.path.join(VIRALS_DIR, project_path_name) if not os.path.exists(project_folder_path): return f'
{i18n("Project path not found: {}").format(project_folder_path)}
' try: # Load JSON json_path = os.path.join(project_folder_path, "viral_segments.txt") segments_data = {} if os.path.exists(json_path): with open(json_path, 'r', encoding='utf-8') as f: segments_data = json.load(f) segments_list = segments_data.get("segments", []) # Fallback if list is empty if not segments_list: found_files = [] for subdir in ["burned_sub", "cuts", "."]: d = os.path.join(project_folder_path, subdir) if os.path.exists(d): for f in os.listdir(d): if f.endswith(".mp4") and "input" not in f.lower(): found_files.append(os.path.join(d, f)) found_files = sorted(list(set(found_files))) segments_list = [{"title": os.path.basename(f), "score": "N/A", "description": "No metadata found.", "filepath": f} for f in found_files] html_cards = "" for i, seg in enumerate(segments_list): title = seg.get("title", f"{i18n('Segment')} {i+1}") score = seg.get("score", "N/A") description = seg.get("description", i18n("No description available.")) video_path = seg.get("filepath", None) # Smart search if not video_path: idx_str = f"{i:03d}" potential_paths = [ os.path.join(project_folder_path, "burned_sub", f"final-output{idx_str}_processed_subtitled.mp4"), os.path.join(project_folder_path, "burned_sub", f"output{idx_str}.mp4"), os.path.join(project_folder_path, f"final-output{idx_str}_processed.mp4"), os.path.join(project_folder_path, f"output{idx_str}_original_scale.mp4"), os.path.join(project_folder_path, f"output{idx_str}.mp4"), os.path.join(project_folder_path, "cuts", f"output{idx_str}_original_scale.mp4"), os.path.join(project_folder_path, "cuts", f"segment_{idx_str}.mp4"), os.path.join(project_folder_path, "cuts", f"{idx_str}.mp4") ] if isinstance(seg.get("filename"), str): potential_paths.insert(0, os.path.join(project_folder_path, seg["filename"])) potential_paths.insert(0, os.path.join(project_folder_path, "burned_sub", seg["filename"])) for p in potential_paths: if os.path.exists(p): video_path = p break # Loose search if not video_path: sub_dirs = [os.path.join(project_folder_path, "burned_sub"), os.path.join(project_folder_path, "cuts")] for sd in sub_dirs: if os.path.exists(sd): for f in sorted(os.listdir(sd)): idx_str = f"{i:03d}" if f.endswith(".mp4") and idx_str in f: video_path = os.path.join(sd, f) break if video_path: break video_tag = "" download_link = "" if video_path: try: abs_video = os.path.abspath(video_path) if URL_MODE == "gradio": # Gradio Launch Mode # Strategy: SMART PATH (Relative preferred, Absolute fallback) try: cwd = os.getcwd() abs_video_path = os.path.abspath(video_path) # Try relative path first rel_path = os.path.relpath(abs_video_path, cwd) if not rel_path.startswith(".."): # Inside CWD, use relative final_path = rel_path.replace("\\", "/") # Debug print(f"DEBUG: URL Generation (Relative): {final_path}") else: # Outside CWD, use absolute final_path = abs_video_path.replace("\\", "/") print(f"DEBUG: URL Generation (Absolute fallback): {final_path}") # Encode path_encoded = urllib.parse.quote(final_path, safe="/:") video_src = f"/file/{path_encoded}" except Exception as e: print(f"DEBUG: Error pathing: {e}") video_src = "" if os.path.exists(abs_video): print(f"DEBUG: File Exists.") else: print(f"DEBUG: File NOT FOUND.") video_tag = f""" """ download_link = f'' else: # Use Relative Path through /virals mount # Calculate relative path from VIRALS_DIR # video_path needs to be under VIRALS_DIR for this to work abs_virals = os.path.abspath(VIRALS_DIR) if abs_video.startswith(abs_virals): rel_path = os.path.relpath(abs_video, abs_virals) # Replace backslashes for URL url_path = rel_path.replace("\\", "/") url_path = urllib.parse.quote(url_path) # Add timestamp to force cache refresh import time timestamp = int(time.time()) video_src = f"/virals/{url_path}?t={timestamp}" video_tag = f""" """ download_link = f'' # Export XML Link # project_path_name might be full path or folder name proj_name_api = os.path.basename(project_path_name) def make_export_btn(fmt, label, color_hover, svg_path): src = f"/export_xml_api?project={proj_name_api}&segment={i}&format={fmt}" return f'{svg_path}' # Premiere (Pr) export_pr = make_export_btn("premiere", "Export Premiere XML (Split Screen – known bug)", "#d064ff", '') # Resolve (Dv) # export_dv = make_export_btn("resolve", "Export DaVinci Resolve XML", "#ff6464", '') # Final Cut (Fc) # export_fc = make_export_btn("final-cut-pro", "Export FCP XML", "#64d0ff", '') export_link = f"{export_pr}" #{export_dv}{export_fc}" else: video_tag = f'
⚠️
{i18n("External Video")}
' except Exception as e: video_tag = f'
⚠️
{i18n("Error: {}").format(str(e))}
' else: video_tag = f'
⚠️
{i18n("Not Found")}
' # Score score_color = "#22c55e" try: if isinstance(score, int) or (isinstance(score, str) and score.isdigit()): val = int(score) if val < 70: score_color = "#ef4444" elif val < 85: score_color = "#eab308" except: pass # Card HTML - Dark Grid Style like Opus.pro (Inline Styles) if 'export_link' not in locals(): export_link = "" # Fallback if URL mode didn't trigger card_html = f"""
{video_tag}
{score}
{export_link} {download_link}

{title}

""" html_cards += card_html if not html_cards: return f'
{i18n("No viral segments found.")}
' # Gallery Container return f"""
{html_cards}
""" except Exception as e: return i18n("Error loading gallery: {}").format(e)