mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-24 19:13:01 +08:00
252 lines
12 KiB
Python
252 lines
12 KiB
Python
import os
|
|
import uuid
|
|
import pyJianYingDraft as draft
|
|
import time
|
|
from settings.local import IS_CAPCUT_ENV
|
|
from util import generate_draft_url, is_windows_path, url_to_hash
|
|
from pyJianYingDraft import trange, Clip_settings
|
|
import re
|
|
from typing import Optional, Dict
|
|
from pyJianYingDraft import exceptions
|
|
from create_draft import get_or_create_draft
|
|
|
|
def add_image_impl(
|
|
image_url: str,
|
|
draft_folder: Optional[str] = None,
|
|
width: int = 1080,
|
|
height: int = 1920,
|
|
start: float = 0,
|
|
end: float = 3.0, # Default display time: 3 seconds
|
|
draft_id: Optional[str] = None,
|
|
transform_y: float = 0,
|
|
scale_x: float = 1,
|
|
scale_y: float = 1,
|
|
transform_x: float = 0,
|
|
track_name: str = "main",
|
|
relative_index: int = 0,
|
|
animation: Optional[str] = None, # Entrance animation parameter (backward compatibility)
|
|
animation_duration: float = 0.5, # Entrance animation duration parameter, default 0.5 seconds
|
|
intro_animation: Optional[str] = None, # New entrance animation parameter, higher priority than animation
|
|
intro_animation_duration: float = 0.5, # New entrance animation duration parameter, default 0.5 seconds
|
|
outro_animation: Optional[str] = None, # Exit animation parameter
|
|
outro_animation_duration: float = 0.5, # Exit animation duration parameter, default 0.5 seconds
|
|
combo_animation: Optional[str] = None, # Combo animation parameter
|
|
combo_animation_duration: float = 0.5, # Combo animation duration parameter, default 0.5 seconds
|
|
transition: Optional[str] = None, # Transition type parameter
|
|
transition_duration: Optional[float] = 0.5, # Transition duration parameter (seconds), default 0.5 seconds
|
|
# Mask related parameters
|
|
mask_type: Optional[str] = None, # Mask type: Linear, Mirror, Circle, Rectangle, Heart, Star
|
|
mask_center_x: float = 0.0, # Mask center X coordinate
|
|
mask_center_y: float = 0.0, # Mask center Y coordinate
|
|
mask_size: float = 0.5, # Mask main size
|
|
mask_rotation: float = 0.0, # Mask rotation angle
|
|
mask_feather: float = 0.0, # Mask feather parameter (0-100)
|
|
mask_invert: bool = False, # Whether to invert the mask
|
|
mask_rect_width: Optional[float] = None, # Rectangle mask width (rectangle mask only)
|
|
mask_round_corner: Optional[float] = None, # Rectangle mask rounded corner (rectangle mask only, 0-100)
|
|
background_blur: Optional[int] = None # Background blur level, 1-4, corresponding to four blur intensity levels
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Add an image track to the specified draft
|
|
:param animation: Entrance animation name, supported animations include: Zoom Out, Fade In, Zoom In, Rotate, Kira Float, Shake Down, Mirror Flip, Rotate Open, Fold Open, Vortex Rotate, Jump Open, etc.
|
|
:param animation_duration: Entrance animation duration (seconds), default 0.5 seconds
|
|
:param intro_animation: New entrance animation parameter, higher priority than animation
|
|
:param intro_animation_duration: New entrance animation duration (seconds), default 0.5 seconds
|
|
:param outro_animation: Exit animation parameter
|
|
:param outro_animation_duration: Exit animation duration (seconds), default 0.5 seconds
|
|
:param combo_animation: Combo animation parameter
|
|
:param combo_animation_duration: Combo animation duration (seconds), default 0.5 seconds
|
|
:param transition: Transition type, supported transitions include: Dissolve, Move Up, Move Down, Move Left, Move Right, Split, Compress, Anime Cloud, Anime Vortex, etc.
|
|
:param transition_duration: Transition duration (seconds), default 0.5 seconds
|
|
:param draft_folder: Draft folder path, optional parameter
|
|
:param image_url: Image URL
|
|
:param width: Video width, default 1080
|
|
:param height: Video height, default 1920
|
|
:param start: Start time (seconds), default 0
|
|
:param end: End time (seconds), default 3 seconds
|
|
:param draft_id: Draft ID, if None or corresponding zip file not found, a new draft will be created
|
|
:param transform_y: Y-axis transformation, default 0
|
|
:param scale_x: X-axis scaling, default 1
|
|
:param scale_y: Y-axis scaling, default 1
|
|
:param transform_x: X-axis transformation, default 0
|
|
:param track_name: When there is only one video, track name can be omitted
|
|
:param relative_index: Track rendering order index, default 0
|
|
:param mask_type: Mask type, supports: Linear, Mirror, Circle, Rectangle, Heart, Star
|
|
:param mask_center_x: Mask center X coordinate (in material pixels), default set at material center
|
|
:param mask_center_y: Mask center Y coordinate (in material pixels), default set at material center
|
|
:param mask_size: Main size of the mask, represented as a proportion of material height, default is 0.5
|
|
:param mask_rotation: Clockwise rotation angle of the mask, default no rotation
|
|
:param mask_feather: Mask feather parameter, range 0~100, default no feathering
|
|
:param mask_invert: Whether to invert the mask, default not inverted
|
|
:param mask_rect_width: Rectangle mask width, only allowed when mask type is rectangle, represented as a proportion of material width
|
|
:param mask_round_corner: Rectangle mask rounded corner parameter, only allowed when mask type is rectangle, range 0~100
|
|
:param background_blur: Background blur level, 1-4, corresponding to four blur intensity levels (0.0625, 0.375, 0.75, 1.0)
|
|
:return: Updated draft information, including draft_id and draft_url
|
|
"""
|
|
# Get or create draft
|
|
draft_id, script = get_or_create_draft(
|
|
draft_id=draft_id,
|
|
width=width,
|
|
height=height
|
|
)
|
|
|
|
# Check if video track exists, if not, add a default video track
|
|
try:
|
|
script.get_track(draft.Track_type.video, track_name=None)
|
|
except exceptions.TrackNotFound:
|
|
script.add_track(draft.Track_type.video, relative_index=0)
|
|
except NameError:
|
|
# If multiple video tracks exist (NameError), do nothing
|
|
pass
|
|
|
|
# Add video track (only when track doesn't exist)
|
|
if track_name is not None:
|
|
try:
|
|
imported_track=script.get_imported_track(draft.Track_type.video, name=track_name)
|
|
# If no exception is thrown, the track already exists
|
|
except exceptions.TrackNotFound:
|
|
# Track doesn't exist, create a new track
|
|
script.add_track(draft.Track_type.video, track_name=track_name, relative_index=relative_index)
|
|
else:
|
|
script.add_track(draft.Track_type.video, relative_index=relative_index)
|
|
|
|
# Generate material_name but don't download the image
|
|
material_name = f"image_{url_to_hash(image_url)}.png"
|
|
|
|
# Build draft_image_path
|
|
draft_image_path = None
|
|
if draft_folder:
|
|
# Detect input path type and process
|
|
if is_windows_path(draft_folder):
|
|
# Windows path processing
|
|
windows_drive, windows_path = re.match(r'([a-zA-Z]:)(.*)', draft_folder).groups()
|
|
parts = [p for p in windows_path.split('\\') if p] # Split path and filter empty parts
|
|
draft_image_path = os.path.join(windows_drive, *parts, draft_id, "assets", "image", material_name)
|
|
# Normalize path (ensure consistent separators)
|
|
draft_image_path = draft_image_path.replace('/', '\\')
|
|
else:
|
|
# macOS/Linux path processing
|
|
draft_image_path = os.path.join(draft_folder, draft_id, "assets", "image", material_name)
|
|
|
|
# Print path information
|
|
print('replace_path:', draft_image_path)
|
|
|
|
# Create image material
|
|
if draft_image_path:
|
|
image_material = draft.Video_material(path=None, material_type='photo', replace_path=draft_image_path, remote_url=image_url, material_name=material_name)
|
|
else:
|
|
image_material = draft.Video_material(path=None, material_type='photo', remote_url=image_url, material_name=material_name)
|
|
|
|
# Create target_timerange (image)
|
|
duration = end - start
|
|
target_timerange = trange(f"{start}s", f"{duration}s")
|
|
source_timerange = trange(f"{0}s", f"{duration}s")
|
|
|
|
# Create image segment
|
|
image_segment = draft.Video_segment(
|
|
image_material,
|
|
target_timerange=target_timerange,
|
|
source_timerange=source_timerange,
|
|
clip_settings=Clip_settings(
|
|
transform_y=transform_y,
|
|
scale_x=scale_x,
|
|
scale_y=scale_y,
|
|
transform_x=transform_x
|
|
)
|
|
)
|
|
|
|
# Add entrance animation (prioritize intro_animation, then use animation)
|
|
intro_anim = intro_animation if intro_animation is not None else animation
|
|
intro_animation_duration = intro_animation_duration if intro_animation_duration is not None else animation_duration
|
|
if intro_anim:
|
|
try:
|
|
if IS_CAPCUT_ENV:
|
|
animation_type = getattr(draft.CapCut_Intro_type, intro_anim)
|
|
else:
|
|
animation_type = getattr(draft.Intro_type, intro_anim)
|
|
image_segment.add_animation(animation_type, intro_animation_duration * 1e6) # Use microsecond unit for animation duration
|
|
except AttributeError:
|
|
raise ValueError(f"Warning: Unsupported entrance animation type {intro_anim}, this parameter will be ignored")
|
|
|
|
# Add exit animation
|
|
if outro_animation:
|
|
try:
|
|
if IS_CAPCUT_ENV:
|
|
outro_type = getattr(draft.CapCut_Outro_type, outro_animation)
|
|
else:
|
|
outro_type = getattr(draft.Outro_type, outro_animation)
|
|
image_segment.add_animation(outro_type, outro_animation_duration * 1e6) # Use microsecond unit for animation duration
|
|
except AttributeError:
|
|
raise ValueError(f"Warning: Unsupported exit animation type {outro_animation}, this parameter will be ignored")
|
|
|
|
# Add combo animation
|
|
if combo_animation:
|
|
try:
|
|
if IS_CAPCUT_ENV:
|
|
combo_type = getattr(draft.CapCut_Group_animation_type, combo_animation)
|
|
else:
|
|
combo_type = getattr(draft.Group_animation_type, combo_animation)
|
|
image_segment.add_animation(combo_type, combo_animation_duration * 1e6) # Use microsecond unit for animation duration
|
|
except AttributeError:
|
|
raise ValueError(f"Warning: Unsupported combo animation type {combo_animation}, this parameter will be ignored")
|
|
|
|
# Add transition effect
|
|
if transition:
|
|
try:
|
|
if IS_CAPCUT_ENV:
|
|
transition_type = getattr(draft.CapCut_Transition_type, transition)
|
|
else:
|
|
transition_type = getattr(draft.Transition_type, transition)
|
|
# Convert seconds to microseconds (multiply by 1000000)
|
|
duration_microseconds = int(transition_duration * 1000000) if transition_duration is not None else None
|
|
image_segment.add_transition(transition_type, duration=duration_microseconds)
|
|
except AttributeError:
|
|
raise ValueError(f"Warning: Unsupported transition type {transition}, this parameter will be ignored")
|
|
|
|
# Add mask effect
|
|
if mask_type:
|
|
try:
|
|
if IS_CAPCUT_ENV:
|
|
mask_type_enum = getattr(draft.CapCut_Mask_type, mask_type)
|
|
else:
|
|
mask_type_enum = getattr(draft.Mask_type, mask_type)
|
|
image_segment.add_mask(
|
|
script,
|
|
mask_type_enum, # Remove keyword name, pass as positional argument
|
|
center_x=mask_center_x,
|
|
center_y=mask_center_y,
|
|
size=mask_size,
|
|
rotation=mask_rotation,
|
|
feather=mask_feather,
|
|
invert=mask_invert,
|
|
rect_width=mask_rect_width,
|
|
round_corner=mask_round_corner
|
|
)
|
|
except:
|
|
raise ValueError(f"Unsupported mask type {mask_type}, supported types include: Linear, Mirror, Circle, Rectangle, Heart, Star")
|
|
|
|
# Add background blur effect
|
|
if background_blur is not None:
|
|
# Background blur level mapping table
|
|
blur_levels = {
|
|
1: 0.0625, # Light blur
|
|
2: 0.375, # Medium blur
|
|
3: 0.75, # Heavy blur
|
|
4: 1.0 # Maximum blur
|
|
}
|
|
|
|
# Validate background blur level
|
|
if background_blur not in blur_levels:
|
|
raise ValueError(f"Invalid background blur level {background_blur}, valid values are 1-4")
|
|
|
|
# Add background blur effect
|
|
image_segment.add_background_filling("blur", blur=blur_levels[background_blur])
|
|
|
|
# Add image segment to track
|
|
script.add_segment(image_segment, track_name=track_name)
|
|
|
|
return {
|
|
"draft_id": draft_id,
|
|
"draft_url": generate_draft_url(draft_id)
|
|
}
|