Files
CapCutAPI/add_video_keyframe_impl.py

169 lines
7.5 KiB
Python
Raw Normal View History

import pyJianYingDraft as draft
from pyJianYingDraft import exceptions
from create_draft import get_or_create_draft
from typing import Optional, Dict, List
from util import generate_draft_url
def add_video_keyframe_impl(
draft_id: Optional[str] = None,
track_name: str = "main",
property_type: str = "alpha",
time: float = 0.0,
value: str = "1.0",
property_types: Optional[List[str]] = None,
times: Optional[List[float]] = None,
values: Optional[List[str]] = None
) -> Dict[str, str]:
"""
2025-07-13 16:18:27 +08:00
Add keyframes to the specified segment
:param draft_id: Draft ID, if None or corresponding zip file not found, a new draft will be created
:param track_name: Track name, default "main"
:param property_type: Keyframe property type, supports the following values:
- position_x: Horizontal position, range [-1,1], 0 means center, 1 means rightmost
- position_y: Vertical position, range [-1,1], 0 means center, 1 means bottom
- rotation: Clockwise rotation angle
- scale_x: X-axis scale ratio (1.0 means no scaling), mutually exclusive with uniform_scale
- scale_y: Y-axis scale ratio (1.0 means no scaling), mutually exclusive with uniform_scale
- uniform_scale: Overall scale ratio (1.0 means no scaling), mutually exclusive with scale_x and scale_y
- alpha: Opacity, 1.0 means completely opaque
- saturation: Saturation, 0.0 means original saturation, range from -1.0 to 1.0
- contrast: Contrast, 0.0 means original contrast, range from -1.0 to 1.0
- brightness: Brightness, 0.0 means original brightness, range from -1.0 to 1.0
- volume: Volume, 1.0 means original volume
:param time: Keyframe time point (seconds), default 0.0
:param value: Keyframe value, format varies according to property_type:
- position_x/position_y: "0" means center position, range [-1,1]
- rotation: "45deg" means 45 degrees
- scale_x/scale_y/uniform_scale: "1.5" means scale up by 1.5 times
- alpha: "50%" means 50% opacity
- saturation/contrast/brightness: "+0.5" means increase by 0.5, "-0.5" means decrease by 0.5
- volume: "80%" means 80% of original volume
:param property_types: Batch mode: List of keyframe property types, e.g. ["alpha", "position_x", "rotation"]
:param times: Batch mode: List of keyframe time points (seconds), e.g. [0.0, 1.0, 2.0]
:param values: Batch mode: List of keyframe values, e.g. ["1.0", "0.5", "45deg"]
Note: property_types, times, values must be provided together and have equal lengths. If these parameters are provided, single keyframe parameters will be ignored
:return: Updated draft information
"""
2025-07-13 16:18:27 +08:00
# Get or create draft
draft_id, script = get_or_create_draft(
draft_id=draft_id
)
try:
2025-07-13 16:18:27 +08:00
# Get specified track
track = script.get_track(draft.Video_segment, track_name=track_name)
2025-07-13 16:18:27 +08:00
# Get segments in the track
segments = track.segments
if not segments:
2025-07-13 16:18:27 +08:00
raise Exception(f"No segments in track {track_name}")
2025-07-13 16:18:27 +08:00
# Determine the keyframes list to process
if property_types is not None or times is not None or values is not None:
2025-07-13 16:18:27 +08:00
# Batch mode: use three array parameters
if property_types is None or times is None or values is None:
2025-07-13 16:18:27 +08:00
raise Exception("In batch mode, property_types, times, values must be provided together")
if not (isinstance(property_types, list) and isinstance(times, list) and isinstance(values, list)):
2025-07-13 16:18:27 +08:00
raise Exception("property_types, times, values must all be list types")
if len(property_types) == 0:
2025-07-13 16:18:27 +08:00
raise Exception("In batch mode, parameter lists cannot be empty")
if not (len(property_types) == len(times) == len(values)):
2025-07-13 16:18:27 +08:00
raise Exception(f"property_types, times, values must have equal lengths, current lengths are: {len(property_types)}, {len(times)}, {len(values)}")
keyframes_to_process = [
{
"property_type": prop_type,
"time": t,
"value": val
}
for prop_type, t, val in zip(property_types, times, values)
]
else:
2025-07-13 16:18:27 +08:00
# Single mode: use original parameters
keyframes_to_process = [{
"property_type": property_type,
"time": time,
"value": value
}]
2025-07-13 16:18:27 +08:00
# Process each keyframe
added_count = 0
for i, kf in enumerate(keyframes_to_process):
try:
_add_single_keyframe(track, kf["property_type"], kf["time"], kf["value"])
added_count += 1
except Exception as e:
2025-07-13 16:18:27 +08:00
raise Exception(f"Failed to add keyframe #{i+1} (property_type={kf['property_type']}, time={kf['time']}, value={kf['value']}): {str(e)}")
result = {
"draft_id": draft_id,
"draft_url": generate_draft_url(draft_id)
}
2025-07-13 16:18:27 +08:00
# If in batch mode, return the number of added keyframes
if property_types is not None:
result["added_keyframes_count"] = added_count
return result
except exceptions.TrackNotFound:
2025-07-13 16:18:27 +08:00
raise Exception(f"Track named {track_name} not found")
except Exception as e:
2025-07-13 16:18:27 +08:00
raise Exception(f"Failed to add keyframe: {str(e)}")
def _add_single_keyframe(track, property_type: str, time: float, value: str):
"""
2025-07-13 16:18:27 +08:00
Internal function to add a single keyframe
"""
2025-07-13 16:18:27 +08:00
# Convert property type string to enum value, validate if property type is valid
try:
property_enum = getattr(draft.Keyframe_property, property_type)
except:
2025-07-13 16:18:27 +08:00
raise Exception(f"Unsupported keyframe property type: {property_type}")
2025-07-13 16:18:27 +08:00
# Parse value based on property type
try:
if property_type in ['position_x', 'position_y']:
2025-07-13 16:18:27 +08:00
# Handle position, range [0,1]
float_value = float(value)
if not -10 <= float_value <= 10:
2025-07-13 16:18:27 +08:00
raise ValueError(f"Value for {property_type} must be between -10 and 10")
elif property_type == 'rotation':
2025-07-13 16:18:27 +08:00
# Handle rotation angle
if value.endswith('deg'):
float_value = float(value[:-3])
else:
float_value = float(value)
elif property_type == 'alpha':
2025-07-13 16:18:27 +08:00
# Handle opacity
if value.endswith('%'):
float_value = float(value[:-1]) / 100
else:
float_value = float(value)
elif property_type == 'volume':
2025-07-13 16:18:27 +08:00
# Handle volume
if value.endswith('%'):
float_value = float(value[:-1]) / 100
else:
float_value = float(value)
elif property_type in ['saturation', 'contrast', 'brightness']:
2025-07-13 16:18:27 +08:00
# Handle saturation, contrast, brightness
if value.startswith('+'):
float_value = float(value[1:])
elif value.startswith('-'):
float_value = -float(value[1:])
else:
float_value = float(value)
else:
2025-07-13 16:18:27 +08:00
# Other properties directly convert to float
float_value = float(value)
except ValueError:
2025-07-13 16:18:27 +08:00
raise Exception(f"Invalid value format: {value}")
2025-07-13 16:18:27 +08:00
# If track object is provided, use the track's add_pending_keyframe method
track.add_pending_keyframe(property_type, time, value)