mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-24 19:13:01 +08:00
169 lines
7.5 KiB
Python
169 lines
7.5 KiB
Python
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]:
|
|
"""
|
|
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
|
|
"""
|
|
# Get or create draft
|
|
draft_id, script = get_or_create_draft(
|
|
draft_id=draft_id
|
|
)
|
|
|
|
try:
|
|
# Get specified track
|
|
track = script.get_track(draft.Video_segment, track_name=track_name)
|
|
|
|
# Get segments in the track
|
|
segments = track.segments
|
|
if not segments:
|
|
raise Exception(f"No segments in track {track_name}")
|
|
|
|
# Determine the keyframes list to process
|
|
if property_types is not None or times is not None or values is not None:
|
|
# Batch mode: use three array parameters
|
|
if property_types is None or times is None or values is None:
|
|
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)):
|
|
raise Exception("property_types, times, values must all be list types")
|
|
|
|
if len(property_types) == 0:
|
|
raise Exception("In batch mode, parameter lists cannot be empty")
|
|
|
|
if not (len(property_types) == len(times) == len(values)):
|
|
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:
|
|
# Single mode: use original parameters
|
|
keyframes_to_process = [{
|
|
"property_type": property_type,
|
|
"time": time,
|
|
"value": value
|
|
}]
|
|
|
|
# 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:
|
|
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)
|
|
}
|
|
|
|
# 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:
|
|
raise Exception(f"Track named {track_name} not found")
|
|
except Exception as e:
|
|
raise Exception(f"Failed to add keyframe: {str(e)}")
|
|
|
|
|
|
def _add_single_keyframe(track, property_type: str, time: float, value: str):
|
|
"""
|
|
Internal function to add a single keyframe
|
|
"""
|
|
# Convert property type string to enum value, validate if property type is valid
|
|
try:
|
|
property_enum = getattr(draft.Keyframe_property, property_type)
|
|
except:
|
|
raise Exception(f"Unsupported keyframe property type: {property_type}")
|
|
|
|
# Parse value based on property type
|
|
try:
|
|
if property_type in ['position_x', 'position_y']:
|
|
# Handle position, range [0,1]
|
|
float_value = float(value)
|
|
if not -10 <= float_value <= 10:
|
|
raise ValueError(f"Value for {property_type} must be between -10 and 10")
|
|
elif property_type == 'rotation':
|
|
# Handle rotation angle
|
|
if value.endswith('deg'):
|
|
float_value = float(value[:-3])
|
|
else:
|
|
float_value = float(value)
|
|
elif property_type == 'alpha':
|
|
# Handle opacity
|
|
if value.endswith('%'):
|
|
float_value = float(value[:-1]) / 100
|
|
else:
|
|
float_value = float(value)
|
|
elif property_type == 'volume':
|
|
# Handle volume
|
|
if value.endswith('%'):
|
|
float_value = float(value[:-1]) / 100
|
|
else:
|
|
float_value = float(value)
|
|
elif property_type in ['saturation', 'contrast', 'brightness']:
|
|
# 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:
|
|
# Other properties directly convert to float
|
|
float_value = float(value)
|
|
except ValueError:
|
|
raise Exception(f"Invalid value format: {value}")
|
|
|
|
# If track object is provided, use the track's add_pending_keyframe method
|
|
track.add_pending_keyframe(property_type, time, value) |