2025-07-11 18:02:44 +08:00
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 ,
2025-07-13 16:12:23 +08:00
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)
2025-07-30 16:22:22 +08:00
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)
2025-07-11 18:02:44 +08:00
) - > Dict [ str , str ] :
"""
2025-07-13 16:12:23 +08:00
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 )
2025-07-30 16:22:22 +08:00
: param background_blur : Background blur level , optional values : 1 ( light ) , 2 ( medium ) , 3 ( strong ) , 4 ( maximum ) , default None ( no background blur )
2025-07-13 16:12:23 +08:00
: return : Updated draft information , including draft_id and draft_url
2025-07-11 18:02:44 +08:00
"""
2025-07-13 16:12:23 +08:00
# Get or create draft
2025-07-11 18:02:44 +08:00
draft_id , script = get_or_create_draft (
draft_id = draft_id ,
width = width ,
height = height
)
2025-07-13 16:12:23 +08:00
# Check if video track exists, if not, add a default video track
2025-07-11 18:02:44 +08:00
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 :
2025-07-13 16:12:23 +08:00
# If multiple video tracks exist (NameError), do nothing
2025-07-11 18:02:44 +08:00
pass
2025-07-13 16:12:23 +08:00
# Add video track (only when track doesn't exist)
2025-07-11 18:02:44 +08:00
if track_name is not None :
try :
imported_track = script . get_imported_track ( draft . Track_type . video , name = track_name )
2025-07-13 16:12:23 +08:00
# If no exception is thrown, the track already exists
2025-07-11 18:02:44 +08:00
except exceptions . TrackNotFound :
2025-07-13 16:12:23 +08:00
# Track doesn't exist, create new track
2025-07-11 18:02:44 +08:00
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 )
2025-07-13 16:12:23 +08:00
# If duration parameter is passed, use it preferentially; otherwise use default duration of 0 seconds, and get the real duration when downloading the draft
2025-07-11 18:02:44 +08:00
if duration is not None :
2025-07-13 16:12:23 +08:00
# Use the passed duration, skip duration retrieval and check
2025-07-11 18:02:44 +08:00
video_duration = duration
else :
2025-07-13 16:12:23 +08:00
# 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
2025-07-11 18:02:44 +08:00
# duration_result = get_video_duration(video_url)
# if not duration_result["success"]:
2025-07-13 16:12:23 +08:00
# print(f"Failed to get video duration: {duration_result['error']}")
2025-07-11 18:02:44 +08:00
2025-07-13 16:12:23 +08:00
# # 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")
2025-07-11 18:02:44 +08:00
# video_duration = duration_result["output"]
2025-07-13 16:12:23 +08:00
# Generate local filename
2025-07-11 18:02:44 +08:00
material_name = f " video_ { url_to_hash ( video_url ) } .mp4 "
# local_video_path = download_video(video_url, draft_dir)
2025-07-13 16:12:23 +08:00
# Build draft_video_path
2025-07-11 18:02:44 +08:00
draft_video_path = None
if draft_folder :
2025-07-13 16:12:23 +08:00
# Detect input path type and process
2025-07-11 18:02:44 +08:00
if is_windows_path ( draft_folder ) :
2025-07-13 16:12:23 +08:00
# Windows path processing
2025-07-11 18:02:44 +08:00
windows_drive , windows_path = re . match ( r ' ([a-zA-Z]:)(.*) ' , draft_folder ) . groups ( )
2025-07-13 16:12:23 +08:00
parts = [ p for p in windows_path . split ( ' \\ ' ) if p ] # Split path and filter empty parts
2025-07-11 18:02:44 +08:00
draft_video_path = os . path . join ( windows_drive , * parts , draft_id , " assets " , " video " , material_name )
2025-07-13 16:12:23 +08:00
# Normalize path (ensure consistent separators)
2025-07-11 18:02:44 +08:00
draft_video_path = draft_video_path . replace ( ' / ' , ' \\ ' )
else :
2025-07-13 16:12:23 +08:00
# macOS/Linux path processing
2025-07-11 18:02:44 +08:00
draft_video_path = os . path . join ( draft_folder , draft_id , " assets " , " video " , material_name )
2025-07-13 16:12:23 +08:00
# Print path information
2025-07-11 18:02:44 +08:00
print ( ' replace_path: ' , draft_video_path )
2025-07-13 16:12:23 +08:00
# Set video end time
2025-07-11 18:02:44 +08:00
video_end = end if end is not None else video_duration
2025-07-13 16:12:23 +08:00
# Calculate source video duration
2025-07-11 18:02:44 +08:00
source_duration = video_end - start
2025-07-13 16:12:23 +08:00
# Calculate target video duration (considering speed factor)
2025-07-11 18:02:44 +08:00
target_duration = source_duration / speed
2025-07-13 16:12:23 +08:00
# Create video clip
2025-07-11 18:02:44 +08:00
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 )
2025-07-13 16:12:23 +08:00
# Create source_timerange and target_timerange
2025-07-11 18:02:44 +08:00
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
)
2025-07-13 16:12:23 +08:00
# Add transition effect
2025-07-11 18:02:44 +08:00
if transition :
try :
2025-07-13 16:12:23 +08:00
# Get transition type
2025-07-11 18:02:44 +08:00
if IS_CAPCUT_ENV :
transition_type = getattr ( draft . CapCut_Transition_type , transition )
else :
transition_type = getattr ( draft . Transition_type , transition )
2025-07-13 16:12:23 +08:00
# Set transition duration (convert to microseconds)
2025-07-11 18:02:44 +08:00
duration_microseconds = int ( transition_duration * 1e6 )
2025-07-13 16:12:23 +08:00
# Add transition
2025-07-11 18:02:44 +08:00
video_segment . add_transition ( transition_type , duration = duration_microseconds )
except AttributeError :
2025-07-13 16:12:23 +08:00
raise ValueError ( f " Unsupported transition type: { transition } , transition setting skipped " )
2025-07-11 18:02:44 +08:00
2025-07-13 16:12:23 +08:00
# Add mask effect
2025-07-11 18:02:44 +08:00
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 :
2025-07-13 16:12:23 +08:00
raise ValueError ( f " Unsupported mask type { mask_type } , supported types include: linear, mirror, circle, rectangle, heart, star " )
2025-07-11 18:02:44 +08:00
2025-07-30 16:22:22 +08:00
# 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 ] )
2025-07-13 16:12:23 +08:00
# Add video segment to track
2025-07-11 18:02:44 +08:00
# 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 )
}