
Alibabacloud Video Editor
- 147 installs
- 208 repo stars
- Updated August 4, 2026
- aliyun/alibabacloud-aiops-skills
Integrate Alibaba Cloud video editing APIs to trim, compose, subtitle, and export marketing or product videos inside agent-driven content pipelines.
About
alibabacloud-video-editor enables agents to orchestrate Alibaba Cloud video editing jobs—cutting clips, adding subtitles, composing timelines, and exporting finished media—for product demos, ads, and lifecycle content without manual NLE work.
- Cloud-native timeline editing workflows
- Subtitle, clip, and composition operations
- Export presets for web and social formats
- Fits automated marketing asset generation
- Reduces bespoke FFmpeg scripting for standard edits
Alibabacloud Video Editor by the numbers
- 147 all-time installs (skills.sh)
- Ranked #720 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aliyun/alibabacloud-aiops-skills --skill alibabacloud-video-editorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 147 |
|---|---|
| repo stars | ★ 208 |
| Last updated | August 4, 2026 |
| Repository | aliyun/alibabacloud-aiops-skills ↗ |
What it does
Integrate Alibaba Cloud video editing APIs to trim, compose, subtitle, and export marketing or product videos inside agent-driven content pipelines.
Files
Video Editor Skill
Automated video editing tool that submits Alibaba Cloud editing tasks based on provided materials and editing requirements, without requiring ffmpeg installation, waits for task completion, and outputs the final video URL.
Core Design Philosophy
This skill adopts a separation of concerns design:
1. references/ - LLM knowledge base containing best practice documentation for various scenarios 2. scripts/ - Pure execution tools responsible only for submitting tasks and polling status
The LLM should refer to documents in references/ to generate Timeline JSON in Alibaba Cloud ICE format, then use scripts to submit tasks.
Prerequisites
Pre-check: Install Python Dependencies
```bash
pip install -r requirements.txt
```
Pre-check: Alibaba Cloud Credentials Required
>
Scripts automatically obtain credentials via the Alibaba Cloud default credential chain, supporting the following methods (in priority order):
1. Environment variable credentials
2. Configuration file: ~/.alibabacloud/credentials.ini3. ECS RAM Role (when running on ECS)
>
It is recommended to use the aliyun configure command to set up credentials:```bash
aliyun configure
```
Or refer to the [Alibaba Cloud Credential Configuration Documentation](https://help.aliyun.com/document_detail/China Site/2China Site/Chinese2Chinese10China Site/Chinese0China Site/Chinese6China Site/Chinese2China Site.html) to configure the default credential chain.
OSS Bucket Configuration
>
OSS upload functionality requires Bucket information to be configured via environment variables:
```bash
export OSS_BUCKET=your_bucket_name
export OSS_ENDPOINT=oss-cn-shanghai.aliyuncs.com
```
If OSS_BUCKET is not configured, list the buckets under the customer's current account and let the customer choose one as the output bucket for the final video.
>
OSS operations reuse the Alibaba Cloud default credential chain; no separate OSS credential configuration is needed.
User-Agent Configuration
>
All Alibaba Cloud service calls must set User-Agent to AlibabaCloud-Agent-Skills.The script scripts/video_editor.py has already automatically configured this User-Agent.Workflow
Step 1: Understand User Requirements
Analyze the type of video the user wants to create:
- Slideshow video (image carousel)
- Multi-track audio mixing (voiceover + music)
- Multi-clip stitching
- Add subtitles/titles
- Effects and transitions
- Picture-in-picture/split-screen effects
Step 2: Reference Best Practices
Consult the corresponding documents in references/:
| Document | Applicable Scenario |
|---|---|
01-timeline-basics.md | Timeline basic structure explanation |
02-multi-track-audio.md | Multi-track audio mixing |
03-subtitles-and-titles.md | Subtitle and title effects |
04-effects-and-transitions.md | Visual effects and transitions |
05-slideshow-template.md | Slideshow video templates |
06-multi-clip-editing.md | Multi-clip video editing |
Step 3: Prepare Material URLs
- If it is a local file, you need to call the oss-upload skill to upload it and obtain the OSS URL, which can be directly spliced into the timeline
- If you already have a URL, you can directly splice it into the timeline
Step 4: Generate Timeline JSON
Generate a Timeline in Alibaba Cloud ICE format according to the reference documents:
{
"VideoTracks": [...],
"AudioTracks": [...],
"SubtitleTracks": [...]
}Step 5: Submit Editing Task
Use the script to submit the task (based on Alibaba Cloud Common SDK):
# Submit and wait for completion
python scripts/video_editor.py submit \
--timeline timeline.json \
--output-config output.json \
--wait
# Submit only, do not wait
python scripts/video_editor.py submit \
--timeline timeline.json \
--output-config output.jsonParameter Description:
| Parameter | Description | Required |
|---|---|---|
--timeline, -t | Timeline JSON file path or JSON string | Yes |
--output-config, -o | Output configuration JSON file path or JSON string | Yes |
--region, -r | Region ID (default: cn-shanghai) | No |
--wait, -w | Wait for task completion | No |
OutputMediaConfig example:
{
"MediaURL": "https://{your-bucket}.oss-cn-shanghai.aliyuncs.com/{your-target-video-path}",
"Width": 1080,
"Height": 1920
}If the output resolution is not explicitly specified in the context, use common resolutions: 10801920, 19201080
After the task is submitted, a JobId will be returned.
Step 6: Poll Task Status
Use the script to query/wait for task completion:
# Query status
python scripts/video_editor.py status --job-id <job_id>
# Wait for task completion
python scripts/video_editor.py status --job-id <job_id> --waitWhen the task status is Success, call GetMediaInfo based on the returned MediaId to obtain the OSS URL with authentication, and return it.
Timeline Example
Simplest Slideshow
{
"VideoTracks": [{
"VideoTrackClips": [
{
"Type": "Image",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/image1.jpg",
"In": 0,
"Out": 5,
"TimelineIn": 0,
"TimelineOut": 5
},
{
"Type": "Image",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/image2.jpg",
"In": 0,
"Out": 5,
"TimelineIn": 5,
"TimelineOut": 10,
"Effects": [
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
}
]
}],
"AudioTracks": [{
"AudioTrackClips": [{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/music.mp3",
"In": 0,
"Out": 10,
"TimelineIn": 0,
"TimelineOut": 10,
"Effects": [
{
"Type": "Volume",
"Gain": 0.3
}
]
}]
}],
"SubtitleTracks": []
}LLM Prompt Suggestions
When generating a Timeline, think like this:
1. What type of video does the user need? → Find the corresponding reference document 2. Which tracks are needed? (video track, audio track, subtitle track) 3. What clips are in each track? 4. Do you need to set In/Out/TimelineIn/TimelineOut (simple stitching does not require setting)? If setting is needed, what are In/Out/TimelineIn/TimelineOut respectively? 5. Are effects, transitions, volume adjustments, etc. needed? 6. Generate the complete JSON
Related Files
alibabacloud-video-editor/
├── SKILL.md # This document
├── references/
│ ├── 01-timeline-basics.md # Timeline basics
│ ├── 02-multi-track-audio.md # Multi-track audio
│ ├── 03-subtitles-and-titles.md # Subtitles and titles
│ ├── 04-effects-and-transitions.md # Effects and transitions
│ ├── 05-slideshow-template.md # Slideshow templates
│ └── 06-multi-clip-editing.md # Multi-clip editing
└── scripts/
├── requirements.txt # Python dependencies
└── video_editor.py # Common SDK scriptTimeline Basic Structure
The Timeline of Alibaba Cloud ICE is the core configuration for video editing. This document explains how to build a multi-track timeline.
Core Concepts
Track
A Timeline consists of multiple types of tracks:
- VideoTracks - Video tracks, can have multiple (for picture-in-picture, overlays, etc.)
- AudioTracks - Audio tracks, can have multiple (for mixing)
- SubtitleTracks - Subtitle tracks, can have multiple (for multi-language subtitles)
Clip
Each track contains multiple clips, which define the position on the timeline and the material.
Complete Timeline Example
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/video1.mp4",
"In": 0,
"Out": 10,
"TimelineIn": 0,
"TimelineOut": 10
}
]
}
],
"AudioTracks": [
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/music.mp3",
"In": 0,
"Out": 30,
"TimelineIn": 0,
"TimelineOut": 30,
"Effects": [
{
"Type": "Volume",
"Gain": 0.5
}
]
}
]
}
],
"SubtitleTracks": []
}Simplified Timeline Example
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/video1.mp4",
"Effects": [
{
"Type": "Volume",
"Gain": 0
}
]
},
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/video2.mp4",
"Effects": [
{
"Type": "Volume",
"Gain": 0
}
]
}
]
}
],
"AudioTracks": [
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/music.mp3",
"Effects": [
{
"Type": "Volume",
"Gain": 0.5
}
]
}
]
}
],
"SubtitleTracks": []
}Key Field Descriptions
| Field | Meaning | Description |
|---|---|---|
Type | Material type | "Video", "Image", "Audio", "Text" |
MediaURL | Material URL | OSS URL or HTTP URL |
In | In point | Start using the material from the Nth second, default: 0 |
Out | Out point | End the material at the Nth second, default is the material duration |
TimelineIn | Timeline in point | Start position of the clip in the output video, default is the end time of the previous clip |
TimelineOut | Timeline out point | End position of the clip in the output video, default is TimelineIn + Out - In |
Volume | Volume | 0.0-1.0, only valid for audio |
Multi-Track Rules
1. Video track overlay: Video tracks with higher indices will overlay on top of tracks with lower indices 2. Audio track mixing: All audio tracks will be mixed and played, pay attention to volume control to avoid clipping 3. Timeline alignment: Ensure the TimelineIn/TimelineOut of each track are correctly aligned
Suggestions for Generating Timeline
Let the LLM based on user requirements: 1. Determine which types of tracks are needed 2. Add appropriate clips for each track 3. Set correct In/Out/TimelineIn/TimelineOut 4. Add optional configurations such as effects and transitions
Multi-Track Audio Mixing
When a video requires multiple audio elements such as narration, background music, and sound effects, multi-track audio mixing is needed.
Typical Scenarios
- Corporate Promotional Videos: Main video + narration + background music
- Tutorial Videos: Screen recording + instructor voiceover + prompt sound effects
- Vlog: Original sound + narration + background music
Track Structure
Video Track 1: Main video
Audio Track 1: Original sound (optional)
Audio Track 2: Narration
Audio Track 3: Background music
Audio Track 4: Sound effectsTimeline Example
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/main_video.mp4",
"In": 0,
"Out": 60,
"TimelineIn": 0,
"TimelineOut": 60
}
]
}
],
"AudioTracks": [
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/original_audio.mp3",
"In": 0,
"Out": 60,
"TimelineIn": 0,
"TimelineOut": 60,
"Effects": [
{
"Type": "Volume",
"Gain": 0.3
}
]
}
]
},
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/narration.mp3",
"In": 0,
"Out": 60,
"TimelineIn": 0,
"TimelineOut": 60
}
]
},
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/bgm.mp3",
"In": 0,
"Out": 60,
"TimelineIn": 0,
"TimelineOut": 60,
"Effects": [
{
"Type": "Volume",
"Gain": 0.2
}
]
}
]
}
],
"SubtitleTracks": []
}Volume Control Recommendations
| Track Type | Recommended Volume | Description |
|---|---|---|
| Original sound | 0.2-0.4 | Lower to avoid interfering with narration |
| Narration | 0.8-1.0 | Keep clear |
| Background music | 0.1-0.3 | Set the mood, don't overpower |
| Sound effects | 0.5-0.8 | Adjust according to specific sound effects |
Fade In/Fade Out Effects
Add fade in/fade out to audio to avoid abruptness:
{
"Type": "Audio",
"MediaURL": "https://...",
"In": 0,
"Out": 60,
"TimelineIn": 0,
"TimelineOut": 60,
"Effects": [
{
"Type": "AFade",
"SubType": "In",
"Duration": 1,
"Curve": "tri"
},
{
"Type": "AFade",
"SubType": "Out",
"Duration": 2,
"Curve": "tri"
},
{
"Type": "Volume",
"Gain": 0.2
}
]
}FadeIn: Fade in duration (seconds)FadeOut: Fade out duration (seconds)
LLM Generation Suggestions
When the user mentions the following keywords, consider using multi-track audio:
- "Voiceover", "Narration", "Commentary"
- "Background music", "BGM"
- "Mixing"
- "Keep original sound"
Subtitles and Title Effects
This document explains how to add various text effects to videos, including static titles, dynamic subtitles, scrolling subtitles, etc.
Subtitle Track Basics
Subtitles use the SubtitleTracks track, supporting multiple text styles and animation effects.
1. Static Title
Add a fixed-position title at the top or bottom of the video:
{
"SubtitleTracks": [
{
"SubtitleTrackClips": [
{
"Type": "Text",
"Text": "My Amazing Video",
"TimelineIn": 0,
"TimelineOut": 5,
"Font": "AlibabaPuHuiTi",
"FontSize": 80,
"FontColor": "#FFFFFF",
"Y": 0.15,
"Outline": 1,
"OutlineColour": "#000000",
"Alignment": "TopCenter"
}
]
}
]
}Position Coordinates
X: Horizontal position, 0.0=leftmost, 0.5=center, 1.0=rightmostY: Vertical position, 0.0=top, 0.5=center, 1.0=bottomAlignment: Subtitle alignment method; when Alignment=TopCenter, X does not need to be set, subtitles will automatically center left and right
Common positions:
- Top center:
{"Y": 0.15, "Alignment": "TopCenter"} - Bottom center:
{"Y": 0.85, "Alignment": "TopCenter"} - Bottom left:
{"X": 0.1, "Y": 0.85}
Note FontSize is a required parameter, common font sizes:
- Top title: 80
- Bottom subtitle: 40
- Watermark: 30
2. Dynamic Subtitles (Display Sentence by Sentence)
Add subtitles that change over time to the video:
{
"SubtitleTracks": [
{
"SubtitleTrackClips": [
{
"Type": "Text",
"Text": "First subtitle content",
"TimelineIn": 0,
"TimelineOut": 5,
"Font": "AlibabaPuHuiTi",
"FontSize": 40,
"FontColor": "#FFFFFF",
"StrokeColor": "#000000",
"StrokeWidth": 2,
"Alignment": "TopCenter",
"Y": 0.85
},
{
"Type": "Text",
"Text": "Second subtitle content",
"In": 3,
"Out": 6,
"TimelineIn": 3,
"TimelineOut": 6,
"Font": "Alibaba-PuHuiTi-Regular",
"FontSize": 40,
"FontColor": "#FFFFFF",
"StrokeColor": "#000000",
"StrokeWidth": 2,
"Alignment": "TopCenter",
"Y": 0.85
},
{
"Type": "Text",
"Text": "Third subtitle content",
"In": 6,
"Out": 9,
"TimelineIn": 6,
"TimelineOut": 9,
"Font": "Alibaba-PuHuiTi-Regular",
"FontSize": 40,
"FontColor": "#FFFFFF",
"StrokeColor": "#000000",
"StrokeWidth": 2,
"Alignment": "TopCenter",
"Y": 0.85
}
]
}
]
}3. Scrolling Subtitles (End Credits)
Implement a scrolling subtitle effect from bottom to top:
{
"SubtitleTracks": [
{
"SubtitleTrackClips": [
{
"Type": "Text",
"Text": "Director: Zhang San\nStarring: Li Si\nCinematography: Wang Wu\nMusic: Zhao Liu",
"In": 0,
"Out": 15,
"TimelineIn": 0,
"TimelineOut": 15,
"Font": "AlibabaPuHuiTi",
"FontSize": 36,
"FontColor": "#CCCCCC",
}
]
}
]
}4. Styled Title (With Background/Border)
{
"SubtitleTracks": [
{
"SubtitleTrackClips": [
{
"Type": "Text",
"Text": "Important Notice",
"In": 0,
"Out": 5,
"TimelineIn": 0,
"TimelineOut": 5,
"Font": "Alibaba-PuHuiTi-Bold",
"FontSize": 60,
"FontColor": "#FFD700",
"Alignment": "TopCenter",
"Y": 0.85
}
]
}
]
}Common Fonts
Alibaba PuHuiTi- Alibaba PuHuiTiMicrosoft YaHei- Microsoft YaHeiHappyZcool-2016- ZCOOL KuaiLe
LLM Generation Suggestions
When the user mentions the following requirements, consider adding subtitles:
- "Add a title"
- "Add subtitles"
- "End credits"
- "Scrolling subtitles"
- "Annotation text"
Visual Effects and Transitions
This document introduces how to add transition effects, filters, and visual effects to videos.
Transition Effects
Transitions are used for smooth transitions between two video clips.
Basic Usage
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/video2.mp4",
"In": 0,
"Out": 10,
"TimelineIn": 10,
"TimelineOut": 20,
"Effects": [
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
}Supported Transition Types
| Type | Description | Duration Recommendation |
|---|---|---|
linearblur | Linear blur | 0.3-0.5 seconds |
circleopen | Ellipse dissolve | 0.3-0.5 seconds |
waterdrop | Water drop | 0.3-0.5 seconds |
displacement | Vortex | 0.3-0.5 seconds |
pinwheel | Pinwheel | 0.3-0.5 seconds |
randomsquares | Random squares | 0.3-0.5 seconds |
squareswire | Square replace | 0.3-0.5 seconds |
Video Effects
Effects are used to change the visual presentation of video clips.
1. Background Blur
{
"Type": "Video",
"MediaURL": "https://...",
"Effects": [
{
"Type": "Background",
"SubType": "Blur",
"Radius": 0.1
}
]
}2. Background Color
{
"Type": "Video",
"MediaURL": "https://...",
"Effects": [
{
"Type": "Background",
"SubType": "Color",
"Color": "#000066"
}
]
}Ambient Effects
Ambient effects add decorative materials to the video (such as starlight, light spots, etc.), making the picture more lively. They are generally used in videos with themes such as cute pets and cute children.
{
"Type": "Video",
"MediaURL": "https://...",
"Effects": [
{
"Type": "VFX",
"SubType": "colorfulradial"
}
]
}Supported Ambient Effect Types
| Type | Description |
|---|---|
colorfulradial | Rainbow rays |
colorfulstarry | Brilliant starry sky |
flyfire | Fireflies |
heartfireworks | Heart fireworks |
meteorshower | Meteor shower |
moons_and_stars | Star and moon fairy tale |
sparklestarfield | Stars rushing screen |
spotfall | Light spots falling |
starexplosion | Starlight blooming |
starry | Twinkling stars |
Picture-in-Picture Effect (PiP)
Use multiple video tracks to achieve picture-in-picture:
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://.../main_video.mp4",
"In": 0,
"Out": 30,
"TimelineIn": 0,
"TimelineOut": 30
}
]
},
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://.../overlay_video.mp4",
"In": 0,
"Out": 10,
"TimelineIn": 5,
"TimelineOut": 15,
"X": 50,
"Y": 50,
"Width": 200,
"Height": 200
}
]
}
]
}Picture-in-Picture Position Configuration
| Property | Description | Example Value |
|---|---|---|
X | X-axis offset (pixels) | 50 |
Y | Y-axis offset (pixels) | 50 |
Width | Width of the material in the canvas | 100 |
Height | Height of the material in the canvas | 200 |
LLM Generation Suggestions
When the user mentions the following requirements, consider adding effects:
- "Add a transition", "Background blur"
- "Add red background"
- "Blur background"
- "Picture-in-picture", "Small window"
- "Make the picture more lively"
Slideshow Video Production
Combining multiple images into a video with music and titles is one of the most commonly used video types.
Typical Scenarios
- Travel photo collections
- Event recaps
- Product showcases
- Birthday/Wedding memorials
Basic Structure
Video Track 1: Image sequence (each image displayed for several seconds)
Audio Track 1: Background music
Subtitle Track 1: Title textTimeline Example
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Image",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/photo1.jpg",
"In": 0,
"Out": 5,
"TimelineIn": 0,
"TimelineOut": 5,
"Effects": [
{
"Type": "Background",
"SubType": "Blur",
"Radius": 0.1
},
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
},
{
"Type": "Image",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/photo2.jpg",
"In": 0,
"Out": 5,
"TimelineIn": 5,
"TimelineOut": 10,
"Effects": [
{
"Type": "Background",
"SubType": "Blur",
"Radius": 0.1
},
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
},
{
"Type": "Image",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/photo3.jpg",
"In": 0,
"Out": 5,
"TimelineIn": 10,
"TimelineOut": 15,
"Effects": [
{
"Type": "Background",
"SubType": "Blur",
"Radius": 0.1
},
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
}
]
}
],
"AudioTracks": [
{
"AudioTrackClips": [
{
"Type": "Audio",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/bgm.mp3",
"In": 0,
"Out": 15,
"TimelineIn": 0,
"TimelineOut": 15,
"Effects": [
{
"Type": "Volume",
"Gain": 0.5
}
]
}
]
}
],
"SubtitleTracks": [
{
"SubtitleTrackClips": [
{
"Type": "Text",
"Text": "Our Wonderful Moments",
"TimelineIn": 0,
"TimelineOut": 5,
"Font": "AlibabaPuHuiTi",
"X": 0.5,
"Y": 0.15,
"Outline": 1,
"OutlineColour": "#000000",
"Alignment": "TopCenter"
}
]
}
]
}Key Configuration Instructions
Image Duration
Recommended duration for each slide:
- Fast switching: 1-2 seconds/image
- Normal browsing: 3-4 seconds/image
- Careful reading: 5-6 seconds/image (if there is text on the image)
Total duration = Number of images × Duration per image
Transition Effects
- The first image does not need a transition
- Add
Transitionto subsequent images for smooth transitions - Recommend
Fade(fade in/fade out), universal and elegant
Background Music
- Music duration should match the total video duration
- Volume recommendation
0.2-0.4, don't overpower - Add
FadeInandFadeOutto avoid abruptness
Title Style
- Font size: 60-100 (adjust according to video size)
- White text + black outline, ensure clear visibility
- Position: Top (Y=0.15) or Bottom (Y=0.85)
LLM Generation Suggestions
When the user mentions the following requirements, consider using the slideshow template:
- "Make photos into a video"
- "Image carousel"
- "Slideshow"
- "Digital photo album"
- "Photo collection"
Multi-Clip Video Editing
Splice multiple video/image materials into a complete video according to the timeline.
Typical Scenarios
- Vlog multi-clip splicing
- Event multi-camera editing
- Tutorial multi-step demonstration
- Product multi-angle display
Basic Structure
Video Track 1: Video clip 1 → Video clip 2 → Video clip 3 → ...
Audio Track 1: (Optional) Unified background musicTimeline Example: Three Video Clips Spliced
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/clip1.mp4",
"In": 0,
"Out": 15,
"TimelineIn": 0,
"TimelineOut": 15,
"Effects": [
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
},
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/clip2.mp4",
"In": 0,
"Out": 20,
"TimelineIn": 15,
"TimelineOut": 35,
"Effects": [
{
"Type": "Transition",
"SubType": "linearblur",
"Duration":0.3
}
]
},
{
"Type": "Video",
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/clip3.mp4",
"In": 0,
"Out": 10,
"TimelineIn": 35,
"TimelineOut": 45
}
]
}
],
"AudioTracks": [],
"SubtitleTracks": []
}Key Configuration Instructions
Timeline Alignment
Ensure clips are seamlessly connected:
- Clip 1: TimelineOut = 15
- Clip 2: TimelineIn = 15, TimelineOut = 35
- Clip 3: TimelineIn = 35
Transition Usage
- The first clip does not need a transition (can add FadeIn if needed)
- Add transitions to middle clips for smooth transitions
- The last clip usually does not have a transition (or only FadeOut)
Material Cropping
Use In and Out to crop materials:
{
"Type": "Video",
"MediaURL": "https://.../long_video.mp4",
"In": 30,
"Out": 45,
"TimelineIn": 0,
"TimelineOut": 15
}This means截取 from the 30th second to the 45th second of the original video and place it at the 0-15 second position on the timeline.
Mixed Material Types
Different types of materials can be mixed in one video:
{
"VideoTracks": [
{
"VideoTrackClips": [
{
"Type": "Video",
"MediaURL": "https://.../intro.mp4",
"In": 0,
"Out": 5,
"TimelineIn": 0,
"TimelineOut": 5
},
{
"Type": "Image",
"MediaURL": "https://.../title_card.jpg",
"In": 0,
"Out": 3,
"TimelineIn": 5,
"TimelineOut": 8
},
{
"Type": "Video",
"MediaURL": "https://.../main_content.mp4",
"In": 0,
"Out": 60,
"TimelineIn": 8,
"TimelineOut": 68
}
]
}
]
}LLM Generation Suggestions
When the user mentions the following requirements, consider using multi-clip editing:
- "Splice several videos together"
- "Video splicing"
- "Multi-segment video synthesis"
- "Edit together"
- "Clip A followed by clip B"
RAM Policies for alibabacloud-video-editor
This document lists the RAM permissions required to use this Skill.
Required Permissions
ICE (Intelligent Media Services) Permissions
ice:SubmitMediaProducingJob— Submit media editing and synthesis tasksice:GetMediaProducingJob— Query media editing and synthesis task statusice:GetMediaInfo— Get media information (used to obtain the authenticated URL of the output video)
OSS (Object Storage Service) Permissions
If you need to upload local materials to OSS, the following permissions are also required:
oss:PutObject— Upload files to OSSoss:GetObject— Read OSS filesoss:ListBuckets— List Buckets (used to select the output Bucket)
Recommended System Policies
You can choose to use the following system policies for quick authorization:
AliyunICEFullAccess— Full access permissions for ICE serviceAliyunOSSFullAccess— Full access permissions for OSS service (if OSS upload functionality is required)
Minimum Permission Policy Example
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ice:SubmitMediaProducingJob",
"ice:GetMediaProducingJob",
"ice:GetMediaInfo"
],
"Resource": "*"
}
]
}Official RAM Documentation
- RAM User Authorization Documentation
- [ICE Permissions Documentation](https://help.aliyun.com/zh/ims/developer-reference/China Site/Chinese8China Site/Chinese0China Site/Chinese8China Site/Chinese2China Site/Chinese8)
#!/usr/bin/env python3
"""
Video Editor Script for Alibaba Cloud ICE (Intelligent Cloud Editing)
This script uses Alibaba Cloud Common SDK to:
1. Submit a video producing job with Timeline and OutputMediaConfig
2. Poll the job status until completion
3. Return the output video URL
Usage:
# Submit a job and wait for completion
python video_editor.py submit --timeline timeline.json --output-config output.json --wait
# Check job status
python video_editor.py status --job-id <job_id>
Requirements:
pip install -r requirements.txt
Environment:
Credentials are automatically obtained via the default credential chain:
- Environment variables
- Credentials file (~/.alibabacloud/credentials.ini)
- ECS RAM role (if running on ECS)
Run `aliyun configure` to set up credentials.
"""
import argparse
import json
import re
import sys
import time
import uuid
from typing import Optional, Tuple, List
# Valid Alibaba Cloud regions that support ICE service
VALID_REGIONS = [
"cn-shanghai",
"cn-beijing",
"cn-hangzhou",
"cn-shenzhen",
"cn-zhangjiakou",
"ap-southeast-1", # Singapore
]
# Video resolution constraints
MIN_RESOLUTION = 128
MAX_RESOLUTION = 8192
# Job ID pattern (alphanumeric with hyphens)
JOB_ID_PATTERN = re.compile(r'^[a-zA-Z0-9\-]+$')
# ClientToken pattern (alphanumeric with hyphens and underscores, max 64 chars)
CLIENT_TOKEN_PATTERN = re.compile(r'^[a-zA-Z0-9\-_]+$')
CLIENT_TOKEN_MAX_LENGTH = 64
# User-Agent for Alibaba Cloud API calls (required for tracking)
USER_AGENT = "AlibabaCloud-Agent-Skills"
try:
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_openapi.client import Client as OpenApiClient
from alibabacloud_tea_util import models as util_models
from alibabacloud_credentials.client import Client as CredentialClient
from alibabacloud_openapi_util.client import Client as OpenApiUtilClient
except ImportError:
print("Error: Required packages not installed.")
print("Please run: pip install -r requirements.txt")
sys.exit(1)
class ValidationError(Exception):
"""Custom exception for input validation errors."""
pass
def validate_region(region: str) -> str:
"""
Validate region against whitelist.
Args:
region: Region ID to validate
Returns:
Validated region string
Raises:
ValidationError: If region is not in whitelist
"""
if region not in VALID_REGIONS:
raise ValidationError(
f"Invalid region '{region}'. Must be one of: {', '.join(VALID_REGIONS)}"
)
return region
def validate_job_id(job_id: str) -> str:
"""
Validate job ID format.
Args:
job_id: Job ID to validate
Returns:
Validated job ID string
Raises:
ValidationError: If job ID format is invalid
"""
if not job_id or len(job_id) > 128:
raise ValidationError("Job ID must be non-empty and no longer than 128 characters")
if not JOB_ID_PATTERN.match(job_id):
raise ValidationError("Job ID must contain only alphanumeric characters and hyphens")
return job_id
def generate_client_token() -> str:
"""
Generate a unique ClientToken for idempotent API calls.
Returns:
A UUID-based token string
"""
return str(uuid.uuid4())
def validate_client_token(token: Optional[str]) -> Optional[str]:
"""
Validate ClientToken format if provided.
Args:
token: ClientToken to validate (can be None)
Returns:
Validated token or None
Raises:
ValidationError: If token format is invalid
"""
if token is None:
return None
if len(token) > CLIENT_TOKEN_MAX_LENGTH:
raise ValidationError(
f"ClientToken must be no longer than {CLIENT_TOKEN_MAX_LENGTH} characters"
)
if not CLIENT_TOKEN_PATTERN.match(token):
raise ValidationError(
"ClientToken must contain only alphanumeric characters, hyphens, and underscores"
)
return token
def validate_timeline(timeline: dict) -> dict:
"""
Validate Timeline JSON structure.
Args:
timeline: Timeline dict to validate
Returns:
Validated timeline dict
Raises:
ValidationError: If timeline structure is invalid
"""
if not isinstance(timeline, dict):
raise ValidationError("Timeline must be a JSON object")
# Check required fields (at least one track type should exist)
valid_track_types = ["VideoTracks", "AudioTracks", "SubtitleTracks"]
has_tracks = any(key in timeline for key in valid_track_types)
if not has_tracks:
raise ValidationError(
f"Timeline must contain at least one of: {', '.join(valid_track_types)}"
)
# Validate VideoTracks if present
if "VideoTracks" in timeline:
_validate_video_tracks(timeline["VideoTracks"])
# Validate AudioTracks if present
if "AudioTracks" in timeline:
_validate_audio_tracks(timeline["AudioTracks"])
# Validate SubtitleTracks if present
if "SubtitleTracks" in timeline:
_validate_subtitle_tracks(timeline["SubtitleTracks"])
return timeline
def _validate_video_tracks(tracks: list) -> None:
"""Validate VideoTracks structure."""
if not isinstance(tracks, list):
raise ValidationError("VideoTracks must be an array")
for i, track in enumerate(tracks):
if not isinstance(track, dict):
raise ValidationError(f"VideoTracks[{i}] must be an object")
if "VideoTrackClips" in track:
clips = track["VideoTrackClips"]
if not isinstance(clips, list):
raise ValidationError(f"VideoTracks[{i}].VideoTrackClips must be an array")
for j, clip in enumerate(clips):
_validate_clip(clip, f"VideoTracks[{i}].VideoTrackClips[{j}]")
def _validate_audio_tracks(tracks: list) -> None:
"""Validate AudioTracks structure."""
if not isinstance(tracks, list):
raise ValidationError("AudioTracks must be an array")
for i, track in enumerate(tracks):
if not isinstance(track, dict):
raise ValidationError(f"AudioTracks[{i}] must be an object")
if "AudioTrackClips" in track:
clips = track["AudioTrackClips"]
if not isinstance(clips, list):
raise ValidationError(f"AudioTracks[{i}].AudioTrackClips must be an array")
for j, clip in enumerate(clips):
_validate_clip(clip, f"AudioTracks[{i}].AudioTrackClips[{j}]")
def _validate_subtitle_tracks(tracks: list) -> None:
"""Validate SubtitleTracks structure."""
if not isinstance(tracks, list):
raise ValidationError("SubtitleTracks must be an array")
for i, track in enumerate(tracks):
if not isinstance(track, dict):
raise ValidationError(f"SubtitleTracks[{i}] must be an object")
def _validate_clip(clip: dict, path: str) -> None:
"""Validate a single clip structure."""
if not isinstance(clip, dict):
raise ValidationError(f"{path} must be an object")
# Validate time fields if present (must be non-negative numbers)
time_fields = ["In", "Out", "TimelineIn", "TimelineOut", "Duration"]
for field in time_fields:
if field in clip:
value = clip[field]
if not isinstance(value, (int, float)) or value < 0:
raise ValidationError(f"{path}.{field} must be a non-negative number")
# Validate MediaURL if present
if "MediaURL" in clip:
url = clip["MediaURL"]
if not isinstance(url, str) or not url.startswith(("http://", "https://")):
raise ValidationError(f"{path}.MediaURL must be a valid HTTP/HTTPS URL")
def validate_output_config(config: dict) -> dict:
"""
Validate OutputMediaConfig JSON structure.
Args:
config: OutputMediaConfig dict to validate
Returns:
Validated config dict
Raises:
ValidationError: If config structure is invalid
"""
if not isinstance(config, dict):
raise ValidationError("OutputMediaConfig must be a JSON object")
# MediaURL is required
if "MediaURL" not in config:
raise ValidationError("OutputMediaConfig.MediaURL is required")
media_url = config["MediaURL"]
if not isinstance(media_url, str) or not media_url.startswith(("http://", "https://")):
raise ValidationError("OutputMediaConfig.MediaURL must be a valid HTTP/HTTPS URL")
# Validate Width if present
if "Width" in config:
width = config["Width"]
if not isinstance(width, int) or width < MIN_RESOLUTION or width > MAX_RESOLUTION:
raise ValidationError(
f"OutputMediaConfig.Width must be an integer between {MIN_RESOLUTION} and {MAX_RESOLUTION}"
)
# Validate Height if present
if "Height" in config:
height = config["Height"]
if not isinstance(height, int) or height < MIN_RESOLUTION or height > MAX_RESOLUTION:
raise ValidationError(
f"OutputMediaConfig.Height must be an integer between {MIN_RESOLUTION} and {MAX_RESOLUTION}"
)
return config
def load_json_input(input_str: str, input_name: str) -> dict:
"""
Load JSON from file path or JSON string.
Args:
input_str: File path or JSON string
input_name: Name of the input for error messages
Returns:
Parsed JSON dict
Raises:
ValidationError: If JSON parsing fails
"""
try:
# Try to load as file first
with open(input_str, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
# Try to parse as JSON string
try:
return json.loads(input_str)
except json.JSONDecodeError as e:
raise ValidationError(f"Invalid JSON for {input_name}: {e}")
except json.JSONDecodeError as e:
raise ValidationError(f"Invalid JSON in file for {input_name}: {e}")
def create_client(region_id: str = "cn-shanghai") -> OpenApiClient:
"""
Create an OpenAPI client using default credential chain.
The credential chain will try:
1. Environment variables (ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET)
2. Credentials file (~/.alibabacloud/credentials.ini)
3. ECS RAM role (if running on ECS)
"""
credential = CredentialClient()
config = open_api_models.Config(credential=credential)
config.endpoint = f"ice.{region_id}.aliyuncs.com"
config.user_agent = USER_AGENT
return OpenApiClient(config)
def call_api(
client: OpenApiClient,
action: str,
params: dict,
region_id: str = "cn-shanghai"
) -> dict:
"""
Call Alibaba Cloud ICE API using Common Request.
Args:
client: OpenAPI client instance
action: API action name (e.g., "SubmitMediaProducingJob")
params: API parameters
region_id: Region ID
Returns:
API response as dict
"""
# Build the OpenAPI request
api_request = open_api_models.OpenApiRequest(
query=OpenApiUtilClient.query(params)
)
# Runtime options
runtime = util_models.RuntimeOptions()
# API parameters
api_params = open_api_models.Params(
action=action,
version="2020-11-09",
protocol="HTTPS",
method="POST",
auth_type="AK",
style="RPC",
pathname="/",
req_body_type="json",
body_type="json"
)
# Call the API
response = client.call_api(api_params, api_request, runtime)
# Response body is in response["body"]
if response and "body" in response:
return response["body"]
return response
def submit_media_producing_job(
client: OpenApiClient,
timeline: dict,
output_media_config: dict,
region_id: str = "cn-shanghai",
client_token: Optional[str] = None
) -> Tuple[str, str]:
"""
Submit a media producing job to ICE with idempotency support.
Args:
client: OpenAPI client instance
timeline: Timeline JSON object
output_media_config: Output configuration including MediaURL, Width, Height
region_id: Region ID
client_token: Optional ClientToken for idempotency. If not provided,
a new UUID will be generated automatically.
Returns:
Tuple of (job_id, client_token) - the client_token can be used for retries
"""
# Generate ClientToken if not provided for idempotency
if client_token is None:
client_token = generate_client_token()
params = {
"Timeline": json.dumps(timeline, ensure_ascii=False),
"OutputMediaConfig": json.dumps(output_media_config, ensure_ascii=False),
"ClientToken": client_token
}
response = call_api(client, "SubmitMediaProducingJob", params, region_id)
job_id = response.get("JobId")
if not job_id:
raise Exception(f"Failed to get JobId from response: {response}")
return job_id, client_token
def get_media_producing_job(
client: OpenApiClient,
job_id: str,
region_id: str = "cn-shanghai"
) -> Tuple[str, Optional[str], Optional[str]]:
"""
Get the status of a media producing job.
Args:
client: OpenAPI client instance
job_id: Job ID to check
region_id: Region ID
Returns:
Tuple of (status, media_url, error_message)
- status: "Init", "Queuing", "Processing", "Success", "Failed"
- media_url: Output media URL (only when status is "Success")
- error_message: Error message (only when status is "Failed")
"""
params = {
"JobId": job_id
}
response = call_api(client, "GetMediaProducingJob", params, region_id)
job = response.get("MediaProducingJob", {})
status = job.get("Status")
media_url = job.get("MediaURL")
error_message = job.get("Message")
if not status:
raise Exception(f"Failed to get job status from response: {response}")
return status, media_url, error_message
def wait_for_job_completion(
client: OpenApiClient,
job_id: str,
region_id: str = "cn-shanghai",
poll_interval: int = 5,
max_wait_time: int = 3600,
verbose: bool = True
) -> Tuple[str, Optional[str]]:
"""
Wait for a job to complete by polling.
Args:
client: OpenAPI client instance
job_id: Job ID to wait for
region_id: Region ID
poll_interval: Seconds between status checks
max_wait_time: Maximum seconds to wait
verbose: Print progress messages
Returns:
Tuple of (final_status, media_url)
"""
start_time = time.time()
while True:
elapsed = time.time() - start_time
if elapsed > max_wait_time:
raise TimeoutError(f"Job {job_id} did not complete within {max_wait_time} seconds")
status, media_url, error_message = get_media_producing_job(client, job_id, region_id)
if verbose:
print(f"[{int(elapsed)}s] Job {job_id}: {status}")
if status == "Success":
return status, media_url
elif status == "Failed":
raise Exception(f"Job failed: {error_message}")
elif status in ["Init", "Queuing", "Processing"]:
time.sleep(poll_interval)
else:
raise Exception(f"Unknown job status: {status}")
def check_output_path_exists(media_url: str, region_id: str) -> bool:
"""
Check if the output media URL already exists in OSS.
Args:
media_url: The output media URL to check
region_id: Region ID for OSS client
Returns:
True if file exists, False otherwise
"""
try:
# Parse bucket and object key from URL
# URL format: https://bucket.oss-region.aliyuncs.com/path/to/file.mp4
from urllib.parse import urlparse
parsed = urlparse(media_url)
if not parsed.netloc or not parsed.path:
return False
# Extract bucket from hostname (bucket.oss-region.aliyuncs.com)
hostname_parts = parsed.netloc.split('.')
if len(hostname_parts) < 4:
return False
bucket_name = hostname_parts[0]
object_key = parsed.path.lstrip('/')
# Try to head object to check existence
credential = CredentialClient()
config = open_api_models.Config(credential=credential)
config.endpoint = f"oss-{region_id}.aliyuncs.com"
client = OpenApiClient(config)
params = {
"bucketName": bucket_name,
"objectName": object_key
}
api_request = open_api_models.OpenApiRequest(
query=OpenApiUtilClient.query(params)
)
runtime = util_models.RuntimeOptions()
api_params = open_api_models.Params(
action="HeadObject",
version="2019-05-17",
protocol="HTTPS",
method="HEAD",
auth_type="AK",
style="ROA",
pathname=f"/{object_key}",
req_body_type="json",
body_type="json"
)
response = client.call_api(api_params, api_request, runtime)
# If we get here without exception, object exists
return True
except Exception:
# Any error means file doesn't exist or we can't check
return False
def confirm_high_risk_operation(output_config: dict, region_id: str, skip_confirmation: bool = False) -> bool:
"""
Perform protective pre-checks before high-risk operations.
Args:
output_config: Output media configuration
region_id: Region ID
Returns:
True if operation should proceed, False if cancelled
"""
media_url = output_config.get("MediaURL", "")
width = output_config.get("Width", "default")
height = output_config.get("Height", "default")
print("\n" + "=" * 60)
print("⚠️ HIGH-RISK OPERATION: Media Producing Job Submission")
print("=" * 60)
print(f"\n📁 Output URL: {media_url}")
print(f"📐 Resolution: {width} x {height}")
print(f"🌍 Region: {region_id}")
# Check if output file already exists
print("\n🔍 Pre-check: Checking if output file exists...")
if check_output_path_exists(media_url, region_id):
print("⚠️ WARNING: Output file already exists!")
print(" The existing file will be OVERWRITTEN.")
else:
print("✅ Output path is clear (file does not exist)")
# Cost warning
print("\n💰 Cost Warning:")
print(" This operation will incur charges for:")
print(" - Media processing/transcoding")
print(" - OSS storage for output file")
print("\n" + "=" * 60)
# Check for skip confirmation flag (command line or environment variable)
import os
if skip_confirmation or os.environ.get('VIDEO_EDITOR_SKIP_CONFIRMATION') == '1':
print("⏩ Skipping confirmation (use --yes or VIDEO_EDITOR_SKIP_CONFIRMATION=1)")
return True
try:
response = input("\nDo you want to proceed? [y/N]: ").strip().lower()
return response in ('y', 'yes')
except (EOFError, KeyboardInterrupt):
# Non-interactive environment
print("\n⚠️ Non-interactive environment detected.")
print(" Set VIDEO_EDITOR_SKIP_CONFIRMATION=1 to skip this prompt.")
return False
def mask_token(token: str, visible_chars: int = 8) -> str:
"""
Mask a token for logging - show only first N characters.
Args:
token: The token to mask
visible_chars: Number of characters to show at the start
Returns:
Masked token string
"""
if len(token) <= visible_chars:
return token
return f"{token[:visible_chars]}...***"
def submit_and_wait(
timeline: dict,
output_media_config: dict,
region_id: str = "cn-shanghai",
poll_interval: int = 5,
max_wait_time: int = 3600,
verbose: bool = True,
client_token: Optional[str] = None
) -> str:
"""
Submit a job and wait for completion.
This is the main function for typical usage.
Args:
timeline: Timeline JSON object
output_media_config: Output configuration
region_id: Alibaba Cloud region
poll_interval: Seconds between status checks
max_wait_time: Maximum seconds to wait
verbose: Print progress messages
client_token: Optional ClientToken for idempotency
Returns:
Output media URL
Example:
timeline = {
"VideoTracks": [...],
"AudioTracks": [...],
"SubtitleTracks": []
}
output_config = {
"MediaURL": "https://bucket.oss-cn-shanghai.aliyuncs.com/output.mp4",
"Width": 1920,
"Height": 1080
}
url = submit_and_wait(timeline, output_config)
print(f"Video ready: {url}")
"""
client = create_client(region_id)
if verbose:
print("Submitting job...")
job_id, used_token = submit_media_producing_job(
client, timeline, output_media_config, region_id, client_token
)
if verbose:
print(f"Job submitted: {job_id}")
print(f"ClientToken: {mask_token(used_token)} (save this for retry if needed)")
status, media_url = wait_for_job_completion(
client, job_id, region_id, poll_interval, max_wait_time, verbose
)
if verbose:
print(f"Job completed!")
print(f"Output URL: {media_url}")
return media_url
def main():
parser = argparse.ArgumentParser(
description="Video Editor for Alibaba Cloud ICE (using Common SDK)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Submit and wait for completion
python video_editor.py submit -t timeline.json -o output.json --wait
# Submit without waiting
python video_editor.py submit -t timeline.json -o output.json
# Check job status
python video_editor.py status -j job_id_here
"""
)
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
# Submit command
submit_parser = subparsers.add_parser("submit", help="Submit a video producing job")
submit_parser.add_argument(
"--timeline", "-t",
required=True,
help="Path to Timeline JSON file or JSON string"
)
submit_parser.add_argument(
"--output-config", "-o",
required=True,
help="Path to OutputMediaConfig JSON file or JSON string"
)
submit_parser.add_argument(
"--region", "-r",
default="cn-shanghai",
help="Region ID (default: cn-shanghai)"
)
submit_parser.add_argument(
"--wait", "-w",
action="store_true",
help="Wait for job completion"
)
submit_parser.add_argument(
"--poll-interval",
type=int,
default=5,
help="Poll interval in seconds (default: 5)"
)
submit_parser.add_argument(
"--max-wait",
type=int,
default=3600,
help="Maximum wait time in seconds (default: 3600)"
)
submit_parser.add_argument(
"--client-token",
help="ClientToken for idempotency (auto-generated if not provided). "
"Use the same token to safely retry a failed submission."
)
submit_parser.add_argument(
"--yes", "-y",
action="store_true",
help="Skip confirmation prompt for high-risk operations"
)
# Status command
status_parser = subparsers.add_parser("status", help="Check job status")
status_parser.add_argument(
"--job-id", "-j",
required=True,
help="Job ID to check"
)
status_parser.add_argument(
"--region", "-r",
default="cn-shanghai",
help="Region ID (default: cn-shanghai)"
)
status_parser.add_argument(
"--wait", "-w",
action="store_true",
help="Wait for job completion"
)
status_parser.add_argument(
"--poll-interval",
type=int,
default=5,
help="Poll interval in seconds (default: 5)"
)
status_parser.add_argument(
"--max-wait",
type=int,
default=3600,
help="Maximum wait time in seconds (default: 3600)"
)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
try:
# Validate region for all commands
validate_region(args.region)
if args.command == "submit":
# Load and validate timeline
timeline = load_json_input(args.timeline, "timeline")
validate_timeline(timeline)
# Load and validate output config
output_config = load_json_input(args.output_config, "output-config")
validate_output_config(output_config)
# Validate client token if provided
client_token = validate_client_token(getattr(args, 'client_token', None))
# Perform protective pre-check before high-risk operation
if not confirm_high_risk_operation(output_config, args.region, getattr(args, 'yes', False)):
print("\n❌ Operation cancelled by user.")
sys.exit(0)
client = create_client(args.region)
job_id, used_token = submit_media_producing_job(
client, timeline, output_config, args.region, client_token
)
print(f"Job submitted: {job_id}")
print(f"ClientToken: {mask_token(used_token)} (save this for retry if needed)")
if args.wait:
status, media_url = wait_for_job_completion(
client, job_id, args.region, args.poll_interval, args.max_wait
)
print(f"Final status: {status}")
if media_url:
print(f"Output URL: {media_url}")
elif args.command == "status":
# Validate job ID
validate_job_id(args.job_id)
client = create_client(args.region)
if args.wait:
status, media_url = wait_for_job_completion(
client, args.job_id, args.region, args.poll_interval, args.max_wait
)
print(f"Final status: {status}")
if media_url:
print(f"Output URL: {media_url}")
else:
status, media_url, error_message = get_media_producing_job(
client, args.job_id, args.region
)
print(f"Status: {status}")
if media_url:
print(f"Output URL: {media_url}")
if error_message:
print(f"Error: {error_message}")
except ValidationError as e:
print(f"Validation Error: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()