narrator-ai-cli-video-narration
Create AI-narrated movie commentary videos using the narrator-ai-cli tool with automated script generation, voice synthesis, and video composition
How do I install this agent skill?
npx skills add https://github.com/reason-machines/devtools-skills --skill narrator-ai-cli-video-narrationIs this agent skill safe to install?
- Gen Agent Trust Hubfail
The skill instructs the installation of code from an unverified remote source and provides automation scripts that are vulnerable to shell injection attacks due to improper handling of user input.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
narrator-ai-cli-video-narration
Skill by ara.so — Devtools Skills collection.
What It Does
narrator-ai-cli is a command-line tool that automates the creation of movie narration and commentary videos. It handles the entire pipeline: searching movies, generating scripts, selecting background music and voiceovers, and composing final videos. The tool provides two workflow paths:
- Original Narration: Fast path for creating new commentary from scratch
- Adapted Narration: Standard path for adapting existing movie content
Installation
Install via pip from the GitHub repository:
pip install "narrator-ai-cli @ git+https://github.com/NarratorAI-Studio/narrator-ai-cli.git"
Requirements:
- Python 3.10+
- Dependencies: typer, httpx[socks], httpx-sse, pyyaml, rich
Configuration
Set API Key
You must configure your API key before using the tool:
# Set the API key
narrator-ai-cli config set app_key YOUR_APP_KEY
# Verify configuration
narrator-ai-cli config show
The API key is stored in ~/.narrator-ai-cli/config.yaml and can also be set via environment variable:
export NARRATOR_APP_KEY=your_api_key_here
Configuration File Location
- Linux/macOS:
~/.narrator-ai-cli/config.yaml - Windows:
%USERPROFILE%\.narrator-ai-cli\config.yaml
Core Concepts
Key Identifiers
- file_id: Unique identifier for uploaded or generated files (video clips, audio, scripts)
- task_id: Identifier for asynchronous tasks (script generation, video composition)
- task_order_num: Order number for tracking task status
- movie_id: Identifier for movies in the database
- template_id: Identifier for narration templates
- bgm_id: Background music identifier
- dubbing_id: Voice/dubbing identifier
File Types
- Video clips:
.mp4,.mov,.avi(source material) - Audio:
.mp3,.wav(BGM, voiceovers, TTS output) - Scripts:
.txt,.json(generated narration scripts) - Clip data:
.json(timeline and segment information)
Key Commands
Search and Browse
# Search for movies
narrator-ai-cli search-movie "Inception"
# List available templates
narrator-ai-cli list-template --page 1 --page_size 20
# List background music
narrator-ai-cli list-bgm --page 1 --page_size 50
# List available voices
narrator-ai-cli list-dubbing --page 1 --page_size 50
# List user templates
narrator-ai-cli list-user-template
Original Narration Workflow (Fast Path)
This workflow creates new narration from scratch without adapting existing movies.
Step 0: Select Resources
# Select a template
narrator-ai-cli list-template --page 1
# Select BGM
narrator-ai-cli list-bgm --style "轻快" --page 1
# Select voice
narrator-ai-cli list-dubbing --gender "男" --page 1
Step 1: Generate Original Script
# Create a new original narration script task
narrator-ai-cli create-original-narration-script-task \
--drama_name "都市霸总复仇记" \
--drama_intro "一个被背叛的霸总重返巅峰的故事" \
--template_id 12345
# Poll task status
narrator-ai-cli query-original-narration-script-task \
--task_id abc123 \
--task_order_num 1
# Download the generated script (when task completes)
narrator-ai-cli download-file --file_id def456 --output script.txt
Step 2: Create Original Clip Data
# Generate clip/timeline data from the script
narrator-ai-cli create-original-clip-data-task \
--narration_script_file_id def456 \
--drama_name "都市霸总复仇记"
# Poll until complete
narrator-ai-cli query-original-clip-data-task \
--task_id xyz789 \
--task_order_num 2
# Download clip data
narrator-ai-cli download-file --file_id ghi012 --output clip_data.json
Step 3: Text-to-Speech
# Convert script to speech
narrator-ai-cli create-tts-task \
--text_file_id def456 \
--dubbing_id 67890
# Poll TTS task
narrator-ai-cli query-tts-task \
--task_id tts123 \
--task_order_num 3
# Download audio
narrator-ai-cli download-file --file_id jkl345 --output narration.mp3
Step 4: Compose Final Video
# Compose the final video
narrator-ai-cli create-original-compose-task \
--dubbing_file_id jkl345 \
--clip_data_file_id ghi012 \
--bgm_id 11111 \
--template_id 12345
# Poll compose task
narrator-ai-cli query-original-compose-task \
--task_id comp456 \
--task_order_num 4
# Download final video
narrator-ai-cli download-file --file_id mno678 --output final_video.mp4
Adapted Narration Workflow (Standard Path)
This workflow adapts existing movie content into narration videos.
Step 0: Search and Select Movie
# Find the movie
narrator-ai-cli search-movie "The Shawshank Redemption"
# Record the movie_id from results
Step 1: Generate Adapted Script
# Create adapted narration script task
narrator-ai-cli create-adapted-narration-script-task \
--movie_id 99999 \
--template_id 12345
# Poll task
narrator-ai-cli query-adapted-narration-script-task \
--task_id adp123 \
--task_order_num 1
# Download script
narrator-ai-cli download-file --file_id scr789 --output adapted_script.txt
Step 2: Create Adapted Clip Data
# Generate clip data for adapted narration
narrator-ai-cli create-adapted-clip-data-task \
--narration_script_file_id scr789 \
--movie_id 99999
# Poll task
narrator-ai-cli query-adapted-clip-data-task \
--task_id clp456 \
--task_order_num 2
# Download clip data
narrator-ai-cli download-file --file_id cld123 --output adapted_clip_data.json
Step 3: Text-to-Speech
# Same as Original workflow
narrator-ai-cli create-tts-task \
--text_file_id scr789 \
--dubbing_id 67890
narrator-ai-cli query-tts-task \
--task_id tts789 \
--task_order_num 3
narrator-ai-cli download-file --file_id aud456 --output adapted_narration.mp3
Step 4: Upload Source Video
# Upload the source movie file
narrator-ai-cli upload-file --file_path /path/to/movie.mp4
# Record the returned file_id
Step 5: Compose Adapted Video
# Compose final video with source material
narrator-ai-cli create-adapted-compose-task \
--dubbing_file_id aud456 \
--clip_data_file_id cld123 \
--source_video_file_id vid789 \
--bgm_id 11111 \
--template_id 12345
# Poll compose task
narrator-ai-cli query-adapted-compose-task \
--task_id acp999 \
--task_order_num 5
# Download final video
narrator-ai-cli download-file --file_id fin000 --output adapted_final.mp4
Standalone Tasks
Voice Cloning
# Upload reference audio for voice cloning
narrator-ai-cli upload-file --file_path reference_voice.mp3
# Create voice clone task
narrator-ai-cli create-voice-clone-task \
--audio_file_id ref123 \
--voice_name "Custom Voice"
# Poll task
narrator-ai-cli query-voice-clone-task \
--task_id vcl123 \
--task_order_num 1
# The cloned voice gets a new dubbing_id for future use
Direct TTS (without full pipeline)
# Upload a text file
narrator-ai-cli upload-file --file_path script.txt
# Generate speech
narrator-ai-cli create-tts-task \
--text_file_id txt123 \
--dubbing_id 67890
narrator-ai-cli query-tts-task \
--task_id tts456 \
--task_order_num 1
narrator-ai-cli download-file --file_id tts789 --output speech.mp3
Common Patterns
Complete Original Narration Pipeline
#!/usr/bin/env python3
import subprocess
import json
import time
def run_cmd(cmd):
"""Run CLI command and return output"""
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.stdout.strip()
def poll_task(query_cmd, task_id, order_num):
"""Poll task until completion"""
while True:
output = run_cmd(f"{query_cmd} --task_id {task_id} --task_order_num {order_num}")
data = json.loads(output)
if data['status'] == 'completed':
return data['file_id']
elif data['status'] == 'failed':
raise Exception(f"Task failed: {data.get('error')}")
time.sleep(5)
# Step 1: Generate script
print("Generating script...")
script_task = json.loads(run_cmd(
'narrator-ai-cli create-original-narration-script-task '
'--drama_name "复仇之路" '
'--drama_intro "一个关于复仇的故事" '
'--template_id 12345'
))
script_file_id = poll_task(
'narrator-ai-cli query-original-narration-script-task',
script_task['task_id'],
script_task['task_order_num']
)
# Step 2: Generate clip data
print("Generating clip data...")
clip_task = json.loads(run_cmd(
f'narrator-ai-cli create-original-clip-data-task '
f'--narration_script_file_id {script_file_id} '
f'--drama_name "复仇之路"'
))
clip_file_id = poll_task(
'narrator-ai-cli query-original-clip-data-task',
clip_task['task_id'],
clip_task['task_order_num']
)
# Step 3: Generate TTS
print("Generating voice...")
tts_task = json.loads(run_cmd(
f'narrator-ai-cli create-tts-task '
f'--text_file_id {script_file_id} '
f'--dubbing_id 67890'
))
audio_file_id = poll_task(
'narrator-ai-cli query-tts-task',
tts_task['task_id'],
tts_task['task_order_num']
)
# Step 4: Compose video
print("Composing final video...")
compose_task = json.loads(run_cmd(
f'narrator-ai-cli create-original-compose-task '
f'--dubbing_file_id {audio_file_id} '
f'--clip_data_file_id {clip_file_id} '
f'--bgm_id 11111 '
f'--template_id 12345'
))
video_file_id = poll_task(
'narrator-ai-cli query-original-compose-task',
compose_task['task_id'],
compose_task['task_order_num']
)
# Download final video
print("Downloading video...")
run_cmd(f'narrator-ai-cli download-file --file_id {video_file_id} --output final.mp4')
print("Done! Video saved as final.mp4")
Resource Selection Strategy
#!/bin/bash
# Function to select resources interactively
select_resources() {
echo "=== Selecting Template ==="
narrator-ai-cli list-template --page 1 | jq '.templates[] | {id, name, style}'
read -p "Enter template_id: " TEMPLATE_ID
echo -e "\n=== Selecting BGM ==="
narrator-ai-cli list-bgm --style "轻快" --page 1 | jq '.bgm[] | {id, name, style}'
read -p "Enter bgm_id: " BGM_ID
echo -e "\n=== Selecting Voice ==="
narrator-ai-cli list-dubbing --gender "男" --page 1 | jq '.voices[] | {id, name, gender}'
read -p "Enter dubbing_id: " DUBBING_ID
echo -e "\nSelected resources:"
echo "Template: $TEMPLATE_ID"
echo "BGM: $BGM_ID"
echo "Voice: $DUBBING_ID"
}
select_resources
Batch Processing Multiple Movies
#!/usr/bin/env python3
import subprocess
import json
movies = [
{"name": "Inception", "template_id": 12345},
{"name": "The Matrix", "template_id": 12346},
{"name": "Interstellar", "template_id": 12347},
]
for movie in movies:
print(f"\nProcessing {movie['name']}...")
# Search movie
search_result = subprocess.run(
['narrator-ai-cli', 'search-movie', movie['name']],
capture_output=True, text=True
)
movie_data = json.loads(search_result.stdout)
if not movie_data.get('movies'):
print(f"Movie {movie['name']} not found, skipping...")
continue
movie_id = movie_data['movies'][0]['id']
# Create adapted script task
script_task = subprocess.run(
['narrator-ai-cli', 'create-adapted-narration-script-task',
'--movie_id', str(movie_id),
'--template_id', str(movie['template_id'])],
capture_output=True, text=True
)
task_info = json.loads(script_task.stdout)
print(f"Started script generation: {task_info['task_id']}")
# Continue with remaining steps...
Troubleshooting
Common Error Codes
| Code | Error | Solution |
|---|---|---|
| 1001 | Invalid API key | Verify narrator-ai-cli config show or check $NARRATOR_APP_KEY |
| 1002 | Insufficient credits | Contact support to add credits |
| 2001 | File not found | Check file_id is correct and file exists |
| 2002 | Invalid file format | Ensure file format matches API requirements (.mp4 for video, .mp3/.wav for audio) |
| 3001 | Task not found | Verify task_id and task_order_num are correct |
| 3002 | Task failed | Check task error message with query command |
| 4001 | Movie not found | Use search-movie to find valid movie_id |
| 5001 | Template not found | Use list-template to find valid template_id |
| 6001 | Invalid parameters | Review command syntax and required parameters |
Task Polling Best Practices
import time
import json
import subprocess
def safe_poll_task(query_command, task_id, order_num, max_attempts=120, interval=5):
"""
Safely poll a task with timeout and error handling
Args:
query_command: Base CLI command (e.g., 'narrator-ai-cli query-tts-task')
task_id: Task identifier
order_num: Task order number
max_attempts: Maximum polling attempts (default 120 = 10 minutes at 5s interval)
interval: Seconds between polls
Returns:
dict: Task result data
"""
for attempt in range(max_attempts):
try:
result = subprocess.run(
[*query_command.split(), '--task_id', task_id, '--task_order_num', str(order_num)],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
print(f"Query failed: {result.stderr}")
time.sleep(interval)
continue
data = json.loads(result.stdout)
status = data.get('status')
if status == 'completed':
return data
elif status == 'failed':
raise Exception(f"Task failed: {data.get('error_message', 'Unknown error')}")
elif status == 'processing':
print(f"Attempt {attempt + 1}/{max_attempts}: Task still processing...")
except subprocess.TimeoutExpired:
print(f"Query timeout on attempt {attempt + 1}")
except json.JSONDecodeError as e:
print(f"Invalid JSON response: {e}")
time.sleep(interval)
raise TimeoutError(f"Task did not complete within {max_attempts * interval} seconds")
# Usage
try:
result = safe_poll_task(
'narrator-ai-cli query-tts-task',
'task_abc123',
1,
max_attempts=60,
interval=10
)
print(f"Task completed! File ID: {result['file_id']}")
except TimeoutError as e:
print(f"Timeout: {e}")
except Exception as e:
print(f"Error: {e}")
File Download Issues
# Check file exists before downloading
narrator-ai-cli list-files | grep "file_id_here"
# Download with explicit output path
mkdir -p output
narrator-ai-cli download-file \
--file_id abc123 \
--output output/video_$(date +%Y%m%d_%H%M%S).mp4
# Verify download succeeded
if [ -f output/video_*.mp4 ]; then
echo "Download successful"
ls -lh output/video_*.mp4
else
echo "Download failed"
fi
Handling Network Errors
import subprocess
import time
def retry_command(cmd, max_retries=3, backoff=2):
"""Retry CLI command with exponential backoff"""
for attempt in range(max_retries):
try:
result = subprocess.run(
cmd.split(),
capture_output=True,
text=True,
timeout=60
)
if result.returncode == 0:
return result.stdout
print(f"Attempt {attempt + 1} failed: {result.stderr}")
except subprocess.TimeoutExpired:
print(f"Attempt {attempt + 1} timed out")
if attempt < max_retries - 1:
wait = backoff ** attempt
print(f"Retrying in {wait} seconds...")
time.sleep(wait)
raise Exception(f"Command failed after {max_retries} attempts")
# Usage
output = retry_command('narrator-ai-cli search-movie Inception', max_retries=5)
Important Notes
-
Task Polling: All
create-*-taskcommands are asynchronous. Always poll with the correspondingquery-*-taskcommand until status iscompleted. -
File ID Chaining: Each step produces a
file_idthat feeds into the next step. Track these carefully:- Script file_id → Clip data task
- Script file_id → TTS task
- Clip data file_id + Audio file_id → Compose task
-
Resource Selection Order:
- Template first (defines narration style)
- BGM second (matches template mood)
- Voice third (matches template tone)
-
Original vs Adapted: Choose workflow based on source material:
- Original: Creating from scratch (short dramas, new content)
- Adapted: Using existing movies from database
-
API Rate Limits: The API may have rate limits. Implement exponential backoff for retries.
-
File Retention: Uploaded and generated files are retained for a limited time. Download important outputs promptly.
-
Template Compatibility: Not all templates work with all movie types. Check template descriptions for genre compatibility.
Environment Variables
# API Authentication
export NARRATOR_APP_KEY=your_api_key_here
# Optional: Custom config location
export NARRATOR_CLI_CONFIG_DIR=/custom/path/to/config
# Optional: HTTP proxy
export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
Working with JSON Output
All commands return JSON output. Use jq for parsing:
# Pretty print search results
narrator-ai-cli search-movie "Inception" | jq '.'
# Extract specific fields
narrator-ai-cli list-template --page 1 | jq '.templates[] | {id, name}'
# Filter by criteria
narrator-ai-cli list-bgm --page 1 | jq '.bgm[] | select(.style == "史诗")'
# Count results
narrator-ai-cli list-dubbing --page 1 | jq '.voices | length'
Agent Integration Tips
When helping users create narration videos:
- Always confirm resources before starting: Show template, BGM, and voice options and let user choose
- Provide progress updates: Narrate each step completion and estimated time remaining
- Save intermediate file IDs: Store script_file_id, clip_file_id, etc. in conversation context
- Estimate costs: Warn user about credit consumption before creating tasks
- Handle failures gracefully: If a task fails, suggest alternative templates or parameters
- Offer previews: After script generation, offer to show script content before proceeding
- Batch operations: When user requests multiple videos, create tasks in parallel but track separately
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/reason-machines/devtools-skills/narrator-ai-cli-video-narration">View narrator-ai-cli-video-narration on skillZs</a>