mirror of
https://github.com/sun-guannan/CapCutAPI.git
synced 2025-11-24 19:13:01 +08:00
241 lines
11 KiB
Python
241 lines
11 KiB
Python
import os
|
|
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_video_track(
|
|
video_url: str,
|
|
draft_folder: Optional[str] = None,
|
|
width: int = 1080,
|
|
height: int = 1920,
|
|
start: float = 0,
|
|
end: Optional[float] = None,
|
|
target_start: float = 0,
|
|
draft_id: Optional[str] = None,
|
|
transform_y: float = 0,
|
|
scale_x: float = 1,
|
|
scale_y: float = 1,
|
|
transform_x: float = 0,
|
|
speed: float = 1.0,
|
|
track_name: str = "main",
|
|
relative_index: int = 0,
|
|
duration: Optional[float] = None, # Added duration parameter
|
|
transition: Optional[str] = None, # Transition type
|
|
transition_duration: Optional[float] = 0.5, # Transition duration (seconds)
|
|
# Mask related parameters
|
|
mask_type: Optional[str] = None, # Mask type
|
|
mask_center_x: float = 0.5, # Mask center X coordinate (0-1)
|
|
mask_center_y: float = 0.5, # Mask center Y coordinate (0-1)
|
|
mask_size: float = 1.0, # Mask size (0-1)
|
|
mask_rotation: float = 0.0, # Mask rotation angle (degrees)
|
|
mask_feather: float = 0.0, # Mask feather level (0-1)
|
|
mask_invert: bool = False, # Whether to invert mask
|
|
mask_rect_width: Optional[float] = None, # Rectangle mask width (only for rectangle mask)
|
|
mask_round_corner: Optional[float] = None, # Rectangle mask rounded corner (only for rectangle mask, 0-100)
|
|
volume: float = 1.0, # Volume level, default 1.0
|
|
background_blur: Optional[int] = None # Background blur level, optional values: 1 (light), 2 (medium), 3 (strong), 4 (maximum), default None (no background blur)
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Add video track to specified draft
|
|
:param draft_folder: Draft folder path, optional parameter
|
|
:param video_url: Video URL
|
|
:param width: Video width, default 1080
|
|
:param height: Video height, default 1920
|
|
:param start: Source video start time (seconds), default 0
|
|
:param end: Source video end time (seconds), default None (use total video duration)
|
|
:param target_start: Target video start time (seconds), default 0
|
|
:param draft_id: Draft ID, if None or corresponding zip file not found, create new draft
|
|
:param transform_y: Y-axis transform, default 0
|
|
:param scale_x: X-axis scale, default 1
|
|
:param scale_y: Y-axis scale, default 1
|
|
:param transform_x: X-axis transform, default 0
|
|
:param speed: Video playback speed, default 1.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 duration: Video duration (seconds), if provided, skip duration detection
|
|
:param transition: Transition type, optional parameter
|
|
:param transition_duration: Transition duration (seconds), default uses the default duration of transition type
|
|
:param mask_type: Mask type (linear, mirror, circle, rectangle, heart, star), optional parameter
|
|
:param mask_center_x: Mask center X coordinate (0-1), default 0.5
|
|
:param mask_center_y: Mask center Y coordinate (0-1), default 0.5
|
|
:param mask_size: Mask size (0-1), default 1.0
|
|
:param mask_rotation: Mask rotation angle (degrees), default 0.0
|
|
:param mask_feather: Mask feather level (0-1), default 0.0
|
|
:param mask_invert: Whether to invert mask, default False
|
|
: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 volume: Volume level, default 1.0 (0.0 is mute, 1.0 is original volume)
|
|
:param background_blur: Background blur level, optional values: 1 (light), 2 (medium), 3 (strong), 4 (maximum), default None (no background blur)
|
|
: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 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)
|
|
|
|
# If duration parameter is passed, use it preferentially; otherwise use default duration of 0 seconds, and get the real duration when downloading the draft
|
|
if duration is not None:
|
|
# Use the passed duration, skip duration retrieval and check
|
|
video_duration = duration
|
|
else:
|
|
# Use default duration of 0 seconds, and get the real duration when downloading the draft
|
|
video_duration = 0.0 # Default video duration is 0 seconds
|
|
# duration_result = get_video_duration(video_url)
|
|
# if not duration_result["success"]:
|
|
# print(f"Failed to get video duration: {duration_result['error']}")
|
|
|
|
# # Check if video duration exceeds 2 minutes
|
|
# if duration_result["output"] > 120: # 120 seconds = 2 minutes
|
|
# raise Exception(f"Video duration exceeds 2-minute limit, current duration: {duration_result['output']} seconds")
|
|
|
|
# video_duration = duration_result["output"]
|
|
|
|
# Generate local filename
|
|
material_name = f"video_{url_to_hash(video_url)}.mp4"
|
|
# local_video_path = download_video(video_url, draft_dir)
|
|
|
|
# Build draft_video_path
|
|
draft_video_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_video_path = os.path.join(windows_drive, *parts, draft_id, "assets", "video", material_name)
|
|
# Normalize path (ensure consistent separators)
|
|
draft_video_path = draft_video_path.replace('/', '\\')
|
|
else:
|
|
# macOS/Linux path processing
|
|
draft_video_path = os.path.join(draft_folder, draft_id, "assets", "video", material_name)
|
|
|
|
# Print path information
|
|
print('replace_path:', draft_video_path)
|
|
|
|
# Set video end time
|
|
video_end = end if end is not None else video_duration
|
|
|
|
# Calculate source video duration
|
|
source_duration = video_end - start
|
|
# Calculate target video duration (considering speed factor)
|
|
target_duration = source_duration / speed
|
|
|
|
# Create video clip
|
|
if draft_video_path:
|
|
video_material = draft.Video_material(material_type='video', replace_path=draft_video_path, remote_url=video_url, material_name=material_name, duration=video_duration, width=0, height=0)
|
|
else:
|
|
video_material = draft.Video_material(material_type='video', remote_url=video_url, material_name=material_name, duration = video_duration, width=0, height=0)
|
|
|
|
# Create source_timerange and target_timerange
|
|
source_timerange = trange(f"{start}s", f"{source_duration}s")
|
|
target_timerange = trange(f"{target_start}s", f"{target_duration}s")
|
|
|
|
video_segment = draft.Video_segment(
|
|
video_material,
|
|
target_timerange=target_timerange,
|
|
source_timerange=source_timerange,
|
|
speed=speed,
|
|
clip_settings=Clip_settings(
|
|
transform_y=transform_y,
|
|
scale_x=scale_x,
|
|
scale_y=scale_y,
|
|
transform_x=transform_x
|
|
),
|
|
volume=volume
|
|
)
|
|
|
|
# Add transition effect
|
|
if transition:
|
|
try:
|
|
# Get transition type
|
|
if IS_CAPCUT_ENV:
|
|
transition_type = getattr(draft.CapCut_Transition_type, transition)
|
|
else:
|
|
transition_type = getattr(draft.Transition_type, transition)
|
|
|
|
# Set transition duration (convert to microseconds)
|
|
duration_microseconds = int(transition_duration * 1e6)
|
|
|
|
# Add transition
|
|
video_segment.add_transition(transition_type, duration=duration_microseconds)
|
|
except AttributeError:
|
|
raise ValueError(f"Unsupported transition type: {transition}, transition setting skipped")
|
|
|
|
# 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)
|
|
video_segment.add_mask(
|
|
script,
|
|
mask_type_enum,
|
|
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:
|
|
# Validate if background blur level is valid
|
|
if background_blur not in [1, 2, 3, 4]:
|
|
raise ValueError(f"Invalid background blur level: {background_blur}, valid values are: 1, 2, 3, 4")
|
|
|
|
# Map blur level to specific blur values
|
|
blur_values = {
|
|
1: 0.0625, # Light blur
|
|
2: 0.375, # Medium blur
|
|
3: 0.75, # Strong blur
|
|
4: 1.0 # Maximum blur
|
|
}
|
|
|
|
# Add background blur
|
|
video_segment.add_background_filling("blur", blur=blur_values[background_blur])
|
|
|
|
# Add video segment to track
|
|
# if imported_track is not None:
|
|
# imported_track.add_segment(video_segment)
|
|
# else:
|
|
script.add_segment(video_segment, track_name=track_name)
|
|
|
|
return {
|
|
"draft_id": draft_id,
|
|
"draft_url": generate_draft_url(draft_id)
|
|
}
|