AI-powered video processing API with subtitle generation, karaoke effects, and editing tools.
curl -fsSL https://raw.githubusercontent.com/jdportugal/VideoEditorAPI/main/install-ghcr.sh | sudo bashgit clone https://github.com/jdportugal/VideoEditorAPI.git
cd VideoEditorAPI
docker-compose up -ddocker run -d -p 8080:8080 ghcr.io/jdportugal/videoeditorapi:latest- π€ AI Subtitles - Whisper-powered speech recognition
- π Karaoke Effects - Word-by-word highlighting
- βοΈ Video Editing - Split, join, trim videos
- π΅ Audio Mixing - Add background music
- π Job Tracking - Real-time processing status
- CPU: 2+ vCPUs (4+ recommended for faster processing)
- RAM: 4GB minimum (8GB+ for large videos)
- Storage: 25GB+ SSD
- OS: Ubuntu 20.04+, Docker installed
GET /health
curl http://localhost:8080/healthResponse:
{"status": "healthy", "timestamp": "2024-01-01T12:00:00"}POST /add-subtitles
curl -X POST http://localhost:8080/add-subtitles \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"settings": {
"font-size": 120,
"normal-color": "#FFF4E9"
}
}'Parameters:
url(required): Video URL or Google Drive linklanguage(optional): Language code (default: "en")settings(optional): Subtitle styling options
Styling Options:
{
"font-size": 120,
"font-family": "Luckiest Guy",
"normal-color": "#FFFFFF",
"outline-width": 10,
"position": "bottom-center"
}Response:
{
"job_id": "uuid-here",
"status": "pending",
"message": "Subtitle processing started"
}POST /split-video
curl -X POST http://localhost:8080/split-video \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"start_time": "00:00:10,000",
"end_time": "00:01:30,500"
}'Job Chaining - Split Previous Output:
curl -X POST http://localhost:8080/split-video \
-H "Content-Type: application/json" \
-d '{
"job_id": "previous-job-uuid",
"start_time": "00:00:05,000",
"end_time": "00:00:15,000"
}'Parameters:
urlORjob_id(required): Video URL or previous job ID to splitstart_time(required): Start time for splitend_time(required): End time for split
Time Formats Supported:
"00:01:30,500"(HH:MM:SS,mmm)"90.5"(seconds as string)90.5(numeric seconds)
POST /join-videos
curl -X POST http://localhost:8080/join-videos \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/video1.mp4",
"https://example.com/video2.mp4"
]
}'POST /add-music
curl -X POST http://localhost:8080/add-music \
-H "Content-Type: application/json" \
-d '{
"video_url": "https://example.com/video.mp4",
"music_url": "https://example.com/music.mp3",
"settings": {
"volume": 0.3,
"fade_in": 2,
"fade_out": 3,
"loop_music": true
}
}'Music Settings:
volume: 0.0 to 1.0 (default: 0.5)fade_in: Fade in duration in secondsfade_out: Fade out duration in secondsloop_music: Loop music to match video length
GET /job-status/<job_id>
curl http://localhost:8080/job-status/your-job-idResponse:
{
"job_id": "uuid-here",
"status": "completed",
"progress": 100,
"video_download_url": "/download/uuid-here",
"subtitle_download_url": "/download-subtitles/uuid-here"
}Status Values:
pending- Job queued for processingprocessing- Currently being processedcompleted- Successfully completedfailed- Processing failed (checkerrorfield)
POST /admin/cleanup
curl -X POST http://localhost:8080/admin/cleanup \
-H "Content-Type: application/json"Purpose: Comprehensive cleanup of all jobs and files (completed, failed, pending)
Removes:
- All job files (jobs directory)
- All temporary files (temp directory)
- All uploaded files (uploads directory)
- All static files (static directory)
Response:
{
"status": "success",
"message": "Comprehensive cleanup completed",
"timestamp": "2025-11-15T11:59:04.888763",
"cleanup_stats": {
"jobs_removed": 10,
"temp_files_removed": 18,
"upload_files_removed": 1,
"static_files_removed": 1,
"total_size_freed": 322308966,
"total_size_freed_formatted": "307.38 MB",
"directories_cleaned": ["jobs", "temp", "uploads", "static"]
}
}GET /download/<job_id>
curl http://localhost:8080/download/your-job-id -o result.mp4GET /download-subtitles/<job_id>
curl http://localhost:8080/download-subtitles/your-job-id -o subtitles.srt- font-family: "Luckiest Guy" (default), "Arial", "DejaVu-Sans-Bold"
- font-size: 80-200 pixels (default: 120)
- normal-color: Any hex color (default: "#FFFFFF")
- outline-width: 0-20 pixels (default: 10)
- top-left, top-center, top-right
- center-left, center-center, center-right
- bottom-left, bottom-center, bottom-right
- karaoke (default): Word-by-word highlighting
- off: Traditional sentence subtitles
- popup: Individual word pop-ups
- typewriter: Accumulating word display
curl -X POST http://localhost:8080/add-subtitles \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/video.mp4"}'curl -X POST http://localhost:8080/add-subtitles \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/video.mp4",
"settings": {
"font-size": 140,
"normal-color": "#FFD700",
"outline-width": 8,
"position": "top-center"
}
}'# 1. Add subtitles to original video
SUBTITLE_JOB=$(curl -s -X POST http://localhost:8080/add-subtitles \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/video.mp4"}' | jq -r '.job_id')
# 2. Wait for completion and get status
curl http://localhost:8080/job-status/$SUBTITLE_JOB
# 3. Split the subtitled video using job chaining
SPLIT_JOB=$(curl -s -X POST http://localhost:8080/split-video \
-H "Content-Type: application/json" \
-d "{\"job_id\": \"$SUBTITLE_JOB\", \"start_time\": \"00:00:10,000\", \"end_time\": \"00:00:30,000\"}" | jq -r '.job_id')
# 4. Download the final result
curl http://localhost:8080/download/$SPLIT_JOB -o final_video.mp4# 1. Split video to extract segment
curl -X POST http://localhost:8080/split-video \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/long-video.mp4",
"start_time": "00:02:00,000",
"end_time": "00:03:30,000"
}'
# 2. Get job ID from response, check status
curl http://localhost:8080/job-status/JOB_ID
# 3. Download split video
curl http://localhost:8080/download/JOB_ID -o segment.mp4
# 4. Add subtitles to segment
curl -X POST http://localhost:8080/add-subtitles \
-H "Content-Type: application/json" \
-d '{"url": "https://yourdomain.com/segment.mp4"}'- Create droplet (4GB+ RAM, Ubuntu 20.04+)
- Run installer:
curl -fsSL https://raw.githubusercontent.com/jdportugal/VideoEditorAPI/main/install-ghcr.sh | sudo bashgit clone https://github.com/jdportugal/VideoEditorAPI.git
cd VideoEditorAPI
docker-compose up -ddocker run -d \
--name videoeditor \
-p 5000:5000 \
-v ./temp:/app/temp \
-v ./uploads:/app/uploads \
ghcr.io/jdportugal/videoeditorapi:latest# Optional customization
FLASK_ENV=production
MAX_CONCURRENT_JOBS=4
VIDEO_MAX_DURATION=1800
WHISPER_MODEL=base
LOG_LEVEL=INFO/app/temp- Temporary processing files/app/uploads- User uploaded files/app/jobs- Job status tracking/app/logs- Application logs
# Docker Compose
docker-compose ps
docker-compose logs -f
# Direct Docker
docker ps
docker logs videoeditorPort 8080 not accessible:
# Check firewall
sudo ufw allow 8080/tcp
# Check if service is running
curl http://localhost:8080/healthOut of disk space:
# Clean up temporary files
docker system prune -f
sudo rm -rf ./temp/*Memory issues:
# Check resource usage
docker stats
# Increase droplet size or add swapJob stuck in processing:
# Restart service
docker-compose restart
# Check logs for errors
docker-compose logs video-editor-api- 1-minute video: 30-60 seconds
- 5-minute video: 2-4 minutes
- 10-minute video: 5-10 minutes
- Use CPU-optimized droplets for faster processing
- Keep videos under 500MB for best performance
- Ensure clear audio for better subtitle accuracy
- Use MP4 format for compatibility
cd /opt/shortscreator
./update.shdocker-compose pull
docker-compose up -d# Check API health
curl http://localhost:8080/health
# View recent logs
docker-compose logs --tail=50
# Monitor resources
docker statsconst axios = require('axios');
async function addSubtitles(videoUrl) {
const response = await axios.post('http://localhost:8080/add-subtitles', {
url: videoUrl,
settings: {
'font-size': 120,
'normal-color': '#FFD700'
}
});
return response.data.job_id;
}
async function checkJobStatus(jobId) {
const response = await axios.get(`http://localhost:8080/job-status/${jobId}`);
return response.data;
}import requests
import time
def add_subtitles(video_url):
response = requests.post('http://localhost:8080/add-subtitles', json={
'url': video_url,
'settings': {
'font-size': 120,
'normal-color': '#FFD700'
}
})
return response.json()['job_id']
def wait_for_completion(job_id):
while True:
response = requests.get(f'http://localhost:8080/job-status/{job_id}')
status = response.json()
if status['status'] == 'completed':
return status
elif status['status'] == 'failed':
raise Exception(f"Job failed: {status.get('error')}")
time.sleep(5)#!/bin/bash
# process_video.sh
VIDEO_URL="$1"
API_URL="http://localhost:8080"
# Submit job
JOB_ID=$(curl -s -X POST "$API_URL/add-subtitles" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$VIDEO_URL\"}" | jq -r '.job_id')
echo "Job ID: $JOB_ID"
# Wait for completion
while true; do
STATUS=$(curl -s "$API_URL/job-status/$JOB_ID" | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "completed" ]; then
echo "Downloading result..."
curl -o "result.mp4" "$API_URL/download/$JOB_ID"
curl -o "subtitles.srt" "$API_URL/download-subtitles/$JOB_ID"
break
elif [ "$STATUS" = "failed" ]; then
echo "Job failed!"
exit 1
fi
sleep 5
done- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
MIT License - Feel free to use in personal and commercial projects.
β Star this repository if it helped you!
Built with Python, Flask, Whisper AI, and MoviePy.