import os import uuid def create_premiere_xml(project_name, video_path, overlay_path, duration_frames, width=1080, height=1920, timebase=30, face_data=None, source_width=1920, source_height=1080): """ Generates a Premiere Pro XML (xmeml version 4) compliant with vertical restrictions. video_path: Path to the main video (Track 1) overlay_path: Path to the transparent overlay video (Track 2), can be None. face_data: List of dicts [{"frame": i, "faces": [[x1,y1,x2,y2]]}, ...] source_width/height: Resolution of the source video for coordinate normalization. """ # Generate unique IDs sequence_uuid = str(uuid.uuid4()) video_clip_id = "clipitem-video-1" overlay_clip_id = "clipitem-overlay-1" audio_clip_id = "clipitem-audio-1" video_file_id = f"file-video-{os.path.basename(video_path)}" audio_file_id = f"file-audio-{os.path.basename(video_path)}" overlay_file_id = f"file-overlay-{os.path.basename(overlay_path)}" if overlay_path else None # Scale Calculation # We want to fill the Vertical Screen (Height 1920) with the Source Height (source_height) # Scale = Target / Source * 100 scale_value = (height / source_height) * 100.0 # ensure it fills width too (if source is super tall? unlikely 16:9) if (source_width * (scale_value/100)) < width: scale_value = (width / source_width) * 100.0 # Keyframe Generation for Center/Position (Absolute Pixels) center_keyframes = "" default_horiz = width / 2.0 default_vert = height / 2.0 if face_data: kf_blocks = [] sorted_data = sorted(face_data, key=lambda x: x['frame']) s_factor = scale_value / 100.0 src_cx = source_width / 2.0 src_cy = source_height / 2.0 last_h = default_horiz last_v = default_vert for entry in sorted_data: frame_idx = entry['frame'] if frame_idx >= duration_frames: break # Simple Logic: Focus on the "First" face faces = entry.get('faces', []) current_h = last_h current_v = last_v if faces and len(faces) > 0: f = faces[0] if len(f) == 4: cx = (f[0] + f[2]) / 2.0 cy = (f[1] + f[3]) / 2.0 off_x = cx - src_cx off_y = cy - src_cy # Target = CenterSeq - (OffsetSrc * Scale) target_h = default_horiz - (off_x * s_factor) target_v = default_vert - (off_y * s_factor) # CLAMP SAFEGUARDS (Keep center within logical bounds) # Don't let the anchor point go way off screen. # The video needs to cover the screen (1080x1920) # At scale 88% of 4K, video is ~3400px wide. # We can move it quite a bit. # Limit target center to be within -1000 to +2000 roughly? # Let's clamp to strict +/- 2000 of center to avoid overflow errors target_h = max(-3000, min(4000, target_h)) target_v = max(-3000, min(5000, target_v)) current_h = target_h current_v = target_v last_h = current_h last_v = current_v kf_blocks.append(f""" {frame_idx} {current_h:.2f} {current_v:.2f} """) center_keyframes = "\n".join(kf_blocks) timecode_block = f""" {timebase} FALSE 00:00:00:00 0 NDF """ def get_file_block(fid, fpath, is_audio_only=False): # File defines Source properties width_f = source_width height_f = source_height audio_blk = """ """ if not is_audio_only else "" return f""" {os.path.basename(fpath)} {fpath} {timebase} FALSE {duration_frames} {timebase} FALSE 00:00:00:00 0 NDF {audio_blk} """ track_overlay_block = "" if overlay_path: track_overlay_block = f""" {os.path.basename(overlay_path)} {duration_frames} {timebase} FALSE 0 {duration_frames} {get_file_block(overlay_file_id, overlay_path)} normal """ else: track_overlay_block = "" xml_content = f""" {project_name} {duration_frames} {timebase} FALSE {timecode_block} """ return xml_content if __name__ == "__main__": pass