Spaces:
Running
Running
| import os | |
| import json | |
| import shutil | |
| import zipfile | |
| from .utils import json_to_srt, get_video_dims | |
| from .face_detection import detect_faces_jit | |
| from .rendering import render_segmented_overlays | |
| from .xml_generator import create_premiere_xml | |
| def export_pack(project_path, segment_index, output_format="premiere"): | |
| """ | |
| Generates a ZIP Pack for the segment. | |
| """ | |
| print(f"Starting Export Pack for Project: {os.path.basename(project_path)}, Segment: {segment_index}") | |
| # Paths | |
| proj_name = os.path.basename(project_path) | |
| cut_dir = os.path.join(project_path, "cuts") | |
| # 1. IDENTIFY VIDEO FILE | |
| video_file = None | |
| original_scale_file = None | |
| if os.path.exists(cut_dir): | |
| files = os.listdir(cut_dir) | |
| # Search for {index}_..._original_scale.mp4 or similar | |
| prefix_idx = f"{segment_index:03d}_" | |
| for f in files: | |
| if f.startswith(prefix_idx) and (f.endswith(".mp4") or f.endswith(".mov")): | |
| video_file = os.path.join(cut_dir, f) | |
| break | |
| if not video_file: | |
| print(f"Error: No video file found for segment {segment_index} in {cut_dir}") | |
| return | |
| print(f"Selected Video: {video_file}") | |
| # 2. IDENTIFY SUBTITLE FILES | |
| subs_dir = os.path.join(project_path, "subs_ass") | |
| ass_file = None | |
| if os.path.exists(subs_dir): | |
| sub_files = os.listdir(subs_dir) | |
| prefix_idx = f"{segment_index:03d}_" | |
| # Prioritize Clean Processed > Processed > Any | |
| patterns = [ | |
| (lambda f: f.endswith(".ass") and f.startswith(prefix_idx) and "processed" in f and "original" not in f), | |
| (lambda f: f.endswith(".ass") and f.startswith(prefix_idx) and "processed" in f), | |
| (lambda f: f.endswith(".ass") and f.startswith(prefix_idx)) | |
| ] | |
| for p in patterns: | |
| if ass_file: break | |
| for f in sub_files: | |
| if p(f): | |
| ass_file = os.path.join(subs_dir, f) | |
| break | |
| # JSON in 'subs' usually | |
| subs_json_dir = os.path.join(project_path, "subs") | |
| json_file = None | |
| if os.path.exists(subs_json_dir): | |
| sub_files = os.listdir(subs_json_dir) | |
| prefix_idx = f"{segment_index:03d}_" | |
| # Same pattern priority | |
| json_patterns = [ | |
| (lambda f: f.endswith(".json") and f.startswith(prefix_idx) and "processed" in f), | |
| (lambda f: f.endswith(".json") and f.startswith(prefix_idx)) | |
| ] | |
| for p in json_patterns: | |
| if json_file: break | |
| for f in sub_files: | |
| if p(f): | |
| json_file = os.path.join(subs_json_dir, f) | |
| break | |
| # 2.1 IDENTIFY FACE COORDS | |
| final_dir = os.path.join(project_path, "final") | |
| face_data = None | |
| if os.path.exists(final_dir): | |
| final_files = os.listdir(final_dir) | |
| prefix_idx = f"{segment_index:03d}_" | |
| for f in final_files: | |
| if f.startswith(prefix_idx) and f.endswith("_coords.json"): | |
| try: | |
| with open(os.path.join(final_dir, f), 'r') as fd: | |
| face_data = json.load(fd) | |
| print(f"Found Face Coordinates: {f}") | |
| except Exception as e: | |
| print(f"Face coords load error: {e}") | |
| break | |
| if face_data is None: | |
| print("No pre-computed face data found. Attempting JIT detection...") | |
| face_data = detect_faces_jit(video_file) | |
| # 3. PREPARE STAGING | |
| export_name = f"export_{proj_name}_seg{segment_index}" | |
| stage_dir = os.path.join(project_path, export_name) | |
| if os.path.exists(stage_dir): | |
| try: | |
| shutil.rmtree(stage_dir) | |
| except Exception: | |
| import random | |
| stage_dir += f"_{random.randint(1000,9999)}" | |
| os.makedirs(stage_dir, exist_ok=True) | |
| # 4. COPY VIDEO (Prefer Original Scale for XML editing) | |
| source_video_to_copy = video_file | |
| dest_filename = "video_cut.mp4" | |
| # Try to find original scale version in 'cuts' folder | |
| # video_file is usually in 'cuts', lets check there | |
| try: | |
| cuts_dir = os.path.dirname(video_file) | |
| # Attempt 1: Direct suffix replacement | |
| original_scale_candidate = video_file.replace(".mp4", "_original_scale.mp4") | |
| if not os.path.exists(original_scale_candidate): | |
| # Attempt 2: Search by prefix | |
| prefix_idx = f"{segment_index:03d}_" | |
| if os.path.exists(cuts_dir): | |
| for f in os.listdir(cuts_dir): | |
| if f.startswith(prefix_idx) and "original_scale" in f and f.endswith(".mp4"): | |
| original_scale_candidate = os.path.join(cuts_dir, f) | |
| break | |
| if os.path.exists(original_scale_candidate): | |
| print(f"Using Original Scale Source for Export: {original_scale_candidate}") | |
| source_video_to_copy = original_scale_candidate | |
| dest_filename = "video_source.mp4" # Distinct name | |
| except Exception as e: | |
| print(f"Error checking for original scale video: {e}") | |
| dest_video = os.path.join(stage_dir, dest_filename) | |
| shutil.copy2(source_video_to_copy, dest_video) | |
| # 5. RENDER OVERLAYS (SEGMENTED) | |
| overlay_segments = [] | |
| if ass_file and json_file: | |
| try: | |
| with open(json_file, 'r', encoding='utf-8') as f: | |
| jdata = json.load(f) | |
| # Extract segment list | |
| jdata_segs = [] | |
| if isinstance(jdata, dict) and "segments" in jdata: | |
| jdata_segs = jdata["segments"] | |
| elif isinstance(jdata, list): | |
| jdata_segs = jdata | |
| if jdata_segs: | |
| # Create 'captions' subfolder for organization | |
| captions_dir = os.path.join(stage_dir, "captions") | |
| os.makedirs(captions_dir, exist_ok=True) | |
| # Render into subfolder | |
| overlay_segments = render_segmented_overlays(ass_file, jdata_segs, video_file, captions_dir) | |
| except Exception as e: | |
| print(f"Error preparing overlay segments: {e}") | |
| else: | |
| print("Missing ASS or JSON for subtitles. Skipping overlays.") | |
| # 6. GENERATE SRT (Standard) | |
| dest_srt = os.path.join(stage_dir, f"{proj_name}_Seg{segment_index}.srt") | |
| if json_file: | |
| try: | |
| with open(json_file, 'r', encoding='utf-8') as f: | |
| jdata_srt = json.load(f) | |
| if isinstance(jdata_srt, dict) and "segments" in jdata_srt: | |
| jdata_srt = jdata_srt["segments"] | |
| srt_content = json_to_srt(jdata_srt) | |
| with open(dest_srt, 'w', encoding='utf-8') as f: | |
| f.write(srt_content) | |
| except Exception: pass | |
| # 7. GENERATE XML | |
| width_src, height_src, frames, fps = get_video_dims(dest_video) | |
| # Validation for resolution mismatch (same as before) | |
| if face_data: | |
| max_x = 0 | |
| for entry in face_data: | |
| for f in entry.get('faces', []): | |
| if len(f) >= 3 and f[2] > max_x: max_x = f[2] | |
| if max_x > width_src: | |
| print(f"Correction: Detecting 4K source based on face coords ({max_x} > {width_src})") | |
| width_src = 3840 | |
| height_src = 2160 | |
| # 6. XML GENERATION | |
| width, height, duration, fps = get_video_dims(video_file) | |
| print(f"DEBUG: Passing face_data to XML: {len(face_data) if face_data else 'None'}") | |
| # Logic to Determine Sequence Resolution | |
| # Default 1080p Vertical | |
| seq_w = 1080 | |
| seq_h = 1920 | |
| # If source is 4K (Width > 2000 or Height > 2000), upgrade to 4K Vertical | |
| # Note: width_src from 'get_video_dims' usually returns width. | |
| # Normal 4K is 3840x2160. | |
| if width_src > 3000 or height_src > 3000: | |
| print("Detected 4K Source Content. Setting Sequence to 4K Vertical (2160x3840).") | |
| seq_w = 2160 | |
| seq_h = 3840 | |
| else: | |
| print("Source is 1080p or lower. Setting Sequence to 1080p Vertical (1080x1920).") | |
| xml_content = create_premiere_xml( | |
| project_name=proj_name, | |
| video_path=dest_video, | |
| overlay_segments=overlay_segments, | |
| duration_frames=duration, | |
| width=seq_w, | |
| height=seq_h, | |
| timebase=int(fps), | |
| scale_value=100.0, | |
| face_data=face_data, | |
| source_width=width_src, | |
| source_height=height_src | |
| ) | |
| xml_output = os.path.join(stage_dir, "timeline.xml") | |
| with open(xml_output, "w", encoding="utf-8") as f: | |
| f.write(xml_content) | |
| print("Generated Custom Premiere XML (Opus-Style Segments).") | |
| # 8. ZIP IT | |
| zip_path = f"{stage_dir}.zip" | |
| shutil.make_archive(stage_dir, 'zip', stage_dir) | |
| print(f"SUCCESS: Export Pack created at {zip_path}") | |
| # Cleanup | |
| try: | |
| # shutil.rmtree(stage_dir) | |
| pass | |
| except: pass | |
| return zip_path | |