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''
# 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)