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"""
"""
else:
track_overlay_block = ""
xml_content = f"""
{project_name}
{duration_frames}
{timebase}
FALSE
{timecode_block}
"""
return xml_content
if __name__ == "__main__":
pass