How ReClip Handles Playlist Downloads: A Complete Technical Guide
ReClip processes YouTube playlists by extracting individual video URLs server-side and downloading each video separately, never as a single batch operation.
This article explains how the averygan/reclip open-source tool implements playlist downloads. Understanding this workflow helps developers integrate ReClip's API or adapt its architecture for similar media downloader applications.
The Two-Stage Playlist Download Architecture
ReClip splits playlist handling into distinct enumeration and download phases. This design prevents yt-dlp from treating the playlist as a single entity, which gives the frontend granular control over each item.
Stage 1: URL Extraction via /api/playlist
When a client submits a playlist URL, the Flask server in app.py invokes yt-dlp with specific flags designed for metadata-only extraction:
# https://github.com/averygan/reclip/blob/main/app.py#L43-L63
@app.route("/api/playlist", methods=["POST"])
def get_playlist_info():
data = request.json
url = data.get("url", "").strip()
...
cmd = ["yt-dlp", "--flat-playlist", "-J", url]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
info = json.loads(result.stdout)
entries = info.get("entries", [])
urls = [entry.get("url") for entry in entries if entry.get("url")]
return jsonify({"urls": urls})
The --flat-playlist flag tells yt-dlp to return only basic entry information without fully extracting metadata for each video. The -J flag outputs machine-readable JSON. This combination completes quickly even for large playlists, returning a lightweight array of video URLs.
The response follows this structure:
{
"urls": [
"https://www.youtube.com/watch?v=abc123",
"https://www.youtube.com/watch?v=def456"
]
}
Stage 2: Individual Video Downloads via /api/download
The client iterates through the returned URLs and initiates separate download requests. Each request spawns a background thread via the start_download function:
# https://github.com/averygan/reclip/blob/main/app.py#L66-L84
@app.route("/api/download", methods=["POST"])
def start_download():
data = request.json
url = data.get("url", "").strip()
format_choice = data.get("format", "video")
format_id = data.get("format_id")
...
thread = threading.Thread(
target=run_download,
args=(job_id, url, format_choice, format_id)
)
thread.start()
return jsonify({"job_id": job_id})
The actual download worker enforces single-video behavior through a critical flag:
# https://github.com/averygan/reclip/blob/main/app.py#L32-L38
def run_download(job_id, url, format_choice, format_id):
...
cmd = ["yt-dlp", "--no-playlist", "-o", out_template]
...
The --no-playlist flag guarantees that even if a playlist URL somehow reaches this stage, yt-dlp downloads only the specific video referenced.
Client-Side Implementation
The frontend in templates/index.html orchestrates the two-stage workflow. First, it retrieves the playlist members:
// fetch playlist URLs
async function fetchPlaylist(url) {
const res = await fetch('/api/playlist', {
method: 'POST',
headers: { 'Content-Type':'application/json' },
body: JSON.stringify({ url })
});
const data = await res.json();
return data.urls; // array of video URLs
}
Then it processes each URL sequentially or in parallel:
async function downloadPlaylist(url) {
const videoUrls = await fetchPlaylist(url);
for (const videoUrl of videoUrls) {
const start = await fetch('/api/download', {
method: 'POST',
headers: { 'Content-Type':'application/json' },
body: JSON.stringify({ url: videoUrl, format: 'video' })
});
const { job_id } = await start.json();
// poll /api/status/:job_id and retrieve via /api/file/:job_id
}
}
This separation enables per-video progress tracking, individual error handling, and custom filename generation for each download.
Key Design Benefits
- Resilience: A single failed video does not abort the entire playlist
- Progress visibility: The UI can display status for each item independently
- Flexible formats: Each video can use different format selections
- Clean filenames: The
-ooutput template inrun_downloadensures safe, predictable naming without playlist-induced path collisions
Source Code Reference
| File | Responsibility |
|---|---|
[app.py](https://github.com/averygan/reclip/blob/main/app.py) |
Flask routes for /api/playlist, /api/download, and download workers |
[templates/index.html](https://github.com/averygan/reclip/blob/main/templates/index.html) |
Frontend JavaScript consuming both endpoints |
Dockerfile |
yt-dlp installation and container configuration |
Summary
- ReClip handles playlist downloads through URL enumeration followed by individual video processing
- The
/api/playlistendpoint usesyt-dlp --flat-playlist -Jfor fast metadata extraction - The
/api/downloadendpoint always passes--no-playlistto prevent accidental batch downloads - Each video receives its own job ID for independent tracking and retrieval
- This architecture prioritizes reliability and user control over download speed
Frequently Asked Questions
Can ReClip download an entire playlist as a single zip file?
No. The current implementation in app.py explicitly prevents this. The --no-playlist flag in run_download forces single-video processing, and the API returns separate job IDs for each item. Users download files individually through /api/file/:job_id.
What happens if a video in the playlist is unavailable?
The /api/playlist endpoint only extracts URLs, so unavailable videos still appear in the list. When the client requests /api/download for that specific URL, the run_download worker will fail and update the job status accordingly. Other playlist items continue processing normally.
Is there a limit to playlist size?
The enumeration timeout is 60 seconds as configured in subprocess.run. Extremely large playlists may hit this limit during the /api/playlist call. There is no explicit count limit, but practical constraints depend on yt-dlp performance and server resources.
Can I modify ReClip to download playlists without the two-stage approach?
You would need to remove --no-playlist from run_download in app.py and handle yt-dlp's native playlist output directly. However, this would break the per-video progress tracking and error isolation that ReClip's architecture provides.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →