Command Palette

Search for a command to run...

Scriptable Mode

Scriptable Mode

Download videos without the interactive picker — no full-screen takeover, no arrow keys. Just a one-line command that probes, downloads, and exits. Scriptable mode is designed for automation, shell pipelines, cron jobs, and any scenario where you want fetchit to run without human interaction.

Overview: when to use scriptable mode vs interactive mode

fetchit offers two distinct modes of operation. The interactive mode provides a full-screen TUI with a URL input screen, format picker, live progress bars, and clipboard detection. It is ideal for ad-hoc downloads where you want to browse available formats, see file sizes, and make a selection visually. Scriptable mode, on the other hand, is a non-interactive mode that accepts all parameters as command-line flags and exits immediately after the download completes (or fails).

Use scriptable mode when:

  • Running fetchit from a shell script, cron job, or CI/CD pipeline
  • Downloading many URLs in a loop without manual intervention
  • Integrating fetchit with other tools via stdout/stderr parsing
  • Running on a headless server or over SSH without a terminal TUI
  • You already know exactly which quality or format you want

Use interactive mode when:

  • You want to browse available formats and compare file sizes before choosing
  • You are unsure which resolution or codec is available for a given URL
  • You want to preview thumbnails, see codec information, or quickly toggle chapters
  • You prefer a visual keyboard-driven interface over memorising flags
Scriptable mode is also triggered automatically when stdin is not a TTY (e.g. when piped into another command or run inside a Docker container). If fetchit detects it is running non-interactively, it behaves as if --best was passed.

Complete CLI reference

Every flag and option is listed below. Flags can appear before or after the URL. Short flags (single dash) and long flags (double dash) are interchangeable unless noted otherwise.

Positional arguments

PositionArgumentRequiredDescription
1URLYes (unless --help)The media URL to download. Supports all sites that yt-dlp handles: YouTube, Twitch, Twitter/X, Instagram, TikTok, Facebook, Vimeo, Reddit, SoundCloud, and thousands more.
2QualityNoA specific quality string like <Code>1080p</Code>, <Code>720p</Code>, <Code>480p</Code>, <Code>360p</Code>, <Code>mp3</Code>, or <Code>best</Code>. Overrides <Code>--best</Code> and <Code>--mp3</Code> if both are present.

Flags

FlagAliasDescription
--bestDownload the best available quality automatically. Selects the highest resolution video with the best audio track. If the config has a preferred quality, that is respected.
--mp3Download audio only and convert to MP3. Fetches the best available audio stream (Opus or M4A) and transcodes it using the bundled FFmpeg.
--chaptersEmbed chapter markers into the output file. Chapters appear as named timestamps in VLC, MPV, macOS QuickTime, and other chapter-aware media players. Works on YouTube and other sites that expose chapter data.
--fromStart time for clipping. Accepts <Code>MM:SS</Code> or <Code>HH:MM:SS</Code> format. Combined with <Code>--to</Code> to download a segment. Uses lossless stream copy — cuts at the nearest keyframe.
--toEnd time for clipping. Must be used with <Code>--from</Code>. If omitted, clipping runs from <Code>--from</Code> to the end of the video.
-o--outputOutput directory. Overrides the default download location. The directory is created automatically if it does not exist. Accepts both absolute and relative paths.
--concurrencyNumber of parallel downloads for playlists or multi-video URLs. Overrides the default of 3. Note that YouTube may rate-limit concurrent connections; values above 5 rarely improve throughput.
--themeForce a theme: <Code>dark</Code>, <Code>light</Code>, or <Code>auto</Code>. In scriptable mode this only affects the spinner and progress output. Defaults to <Code>auto</Code>.
--cookies-from-browserExtract cookies from a browser profile for authenticated downloads. Supported values: <Code>chrome</Code>, <Code>firefox</Code>, <Code>brave</Code>, <Code>edge</Code>, <Code>chromium</Code>, <Code>opera</Code>, <Code>vivaldi</Code>. Useful for downloading age-restricted or member-only content.
--helpPrint the full help menu and exit. Lists all flags with descriptions. Does not require a URL.
--versionPrint the installed fetchit version number and exit. Useful for verifying installation and reporting bugs.

Quick reference examples

CLI
fetchit --best https://youtu.be/dQw4w9WgXcQ
fetchit --mp3 https://youtu.be/dQw4w9WgXcQ
fetchit https://youtu.be/dQw4w9WgXcQ 1080p
fetchit --chapters --best https://youtu.be/dQw4w9WgXcQ
fetchit --from 1:30 --to 4:45 https://youtu.be/dQw4w9WgXcQ 720p
fetchit -o ~/Videos --best https://youtu.be/dQw4w9WgXcQ
fetchit --cookies-from-browser firefox --best https://patreon.com/posts/...
fetchit --concurrency 5 --best https://youtube.com/playlist?list=PL...
fetchit --help
fetchit --version

Quality specification

You can specify quality as a positional argument after the URL or via the --best and --mp3 flags. Quality arguments are matched against the formats yt-dlp returns for the given URL.

Resolution values

Pass a resolution string like 1080p as the second positional argument. fetchit matches it to the closest available stream.

ValueTargetFallback behaviour
2160p4K UHD (3840×2160)Next highest resolution available
1440pQuad HD (2560×1440)Next highest resolution available
1080pFull HD (1920×1080)Next highest resolution available
720pHD (1280×720)Next highest resolution available
480pSD (854×480)Next highest resolution available
360pSD (640×360)Next highest resolution available
240pLow (426×240)Next highest resolution available
144pLowest (256×144)No fallback — exact match only
Resolution matching is approximate. If 1080p is not available but 1080p60 (60 FPS) is, fetchit selects that and prints a notice. If no close match is found, it falls back to the highest available resolution and warns on stderr.

Shorthand flags

FlagBehaviour
--bestSelect the single best video+audio combination. Picks the highest resolution combined format if available; otherwise downloads the best video-only and best audio-only streams and muxes them.
--mp3Download the best audio stream and transcode to MP3. Output file has a .mp3 extension regardless of the source codec.

Audio quality

When you pass mp3 as the quality argument or use --mp3, fetchit downloads the best available audio stream (typically Opus at 160 kbps or M4A at 128 kbps) and transcodes it to MP3 at 192 kbps CBR using the bundled FFmpeg. The transcode preserves as much quality as possible from the source; if the source is already MP3 at a lower bitrate, no upscaling is performed.

Output path templates

fetchit uses a structured output naming scheme to keep your downloads organised. The default pattern is:

JSON
{title} [{id}].{ext}

For example: Rick Astley - Never Gonna Give You Up [dQw4w9WgXcQ].mp4. The video ID in square brackets guarantees uniqueness — two different videos with the same title never collide.

Custom output directory

Use the -o or --output flag to specify a download directory. The directory is created automatically if it does not exist:

CLI
fetchit --best https://youtu.be/dQw4w9WgXcQ -o ~/Videos
fetchit --best https://youtu.be/dQw4w9WgXcQ -o "D:\Media\Downloads"
fetchit --best https://youtu.be/dQw4w9WgXcQ -o /mnt/nas/videos

Playlist output structure

When downloading playlists, fetchit creates a subfolder named after the playlist title and numbers each file sequentially:

BASH
~/Downloads/My Awesome Playlist/
  01-First Video [abc123].mp4
  02-Second Video [def456].mp4
  03-Third Video [ghi789].mp4

Output template variables

fetchit supports yt-dlp's output template variables for advanced naming. These can be set via the --output-template config option or environment variable:

VariableDescriptionExample
<Code>{title}</Code>Video title (sanitized for filesystem)Rick Astley - Never Gonna Give You Up
<Code>{id}</Code>Video ID from URLdQw4w9WgXcQ
<Code>{ext}</Code>Output file extensionmp4
<Code>{uploader}</Code>Channel / uploader nameRick Astley
<Code>{upload_date}</Code>Upload date in YYYYMMDD format20091025
<Code>{duration}</Code>Duration in seconds212
<Code>{playlist_index}</Code>Index within a playlist (zero-padded)01
<Code>{playlist_title}</Code>Playlist name (playlist downloads only)My Mix
<Code>{resolution}</Code>Actual downloaded resolution1920x1080
<Code>{fps}</Code>Frames per second30

Custom templates are configured in the config file:

JSON
{
  "output_template": "{uploader} - {title} [{id}].{ext}"
}

This would produce: Rick Astley - Rick Astley - Never Gonna Give You Up [dQw4w9WgXcQ].mp4. Be careful with variable collisions.

Understanding exit codes

Every fetchit process exits with a numeric code that scripts can check to determine what happened. Always check the exit code in automated scripts (see error handling in scripts).

CodeMeaningTypical cause
0SuccessDownload completed normally. The output file exists at the reported path.
1Generic errorAn unspecified error occurred. Check stderr for details. This covers most failure modes: invalid URL, network timeout, disk full, etc.
2Parse / usage errorThe command-line arguments could not be parsed. For example, combining <Code>--best</Code> and <Code>--mp3</Code>, or passing an unrecognised flag.
3Probe failurefetchit could not extract metadata from the URL. The site may be down, the video may be private or geo-blocked, or yt-dlp may need an update.
4Format not availableThe requested quality (e.g. 1080p, mp3) is not available for this URL. Try again without a quality argument to see available formats.
5Download interruptedThe download started but did not complete. Possible causes: network disconnection, server timeout, storage full, or file write permission denied.
6FFmpeg / muxing errorThe download succeeded but merging video and audio streams failed. This usually indicates a corrupt stream or an FFmpeg compatibility issue.
7Rate limited / blockedThe remote server rate-limited the request or blocked the client. Wait before retrying, or use <Code>--cookies-from-browser</Code> to authenticate.
130Cancelled (Ctrl+C)The process was interrupted by the user via SIGINT (Ctrl+C). The partial file is deleted automatically.
137Killed (OOM)The process was killed by the system (SIGKILL), typically due to out-of-memory. Reduce concurrency or download smaller files.
Always check $? (or $LASTEXITCODE in PowerShell) after running fetchit in a script. Exit code 0 is the only code that guarantees a usable output file.

Parsing fetchit output in scripts

fetchit writes machine-parseable information to specific streams. Understanding the output stream layout is essential for reliable script integration.

stdout vs stderr

fetchit follows the Unix convention:

  • stdout (file descriptor 1) — The final output file path on success. Nothing else. This makes it trivial to capture the file path in a variable or pipe it to another command.
  • stderr (file descriptor 2) — Everything else: progress bars, status messages, warnings, and error messages. If you see text in your terminal during a download, it is almost certainly on stderr.
BASH
# Capture only the file path
file=$(fetchit --best https://youtu.be/dQw4w9WgXcQ 2>/dev/null)
echo "Downloaded to: $file"

# Capture file path and keep stderr visible
file=$(fetchit --best https://youtu.be/dQw4w9WgXcQ)
echo "File saved at: $file"

# Merge both streams (not recommended for parsing)
output=$(fetchit --best https://youtu.be/dQw4w9WgXcQ 2>&1)

Machine-readable output patterns

When the download succeeds, stdout contains exactly one line: the absolute path to the downloaded file. This is guaranteed to be a valid path on the local filesystem.

BASH
C:UsersaliceDownloadsRick Astley - Never Gonna Give You Up [dQw4w9WgXcQ].mp4

Parsing in different shells

Bash / Zsh

BASH
file=$(fetchit --best "https://youtu.be/dQw4w9WgXcQ" 2>/dev/null)
if [ -f "$file" ]; then
  echo "OK: $file"
else
  echo "Download failed" >&2
fi

PowerShell

BASH
$file = & fetchit --best "https://youtu.be/dQw4w9WgXcQ" 2>$null
if (Test-Path $file) {
  Write-Output "OK: $file"
} else {
  Write-Error "Download failed"
}

Fish

BASH
set file (fetchit --best "https://youtu.be/dQw4w9WgXcQ" 2>/dev/null)
if test -f "$file"
  echo "OK: $file"
else
  echo "Download failed" >&2
end

Suppressing stderr in scripts

In scriptable mode, stderr contains progress spinners and status lines that are useful in interactive use but noisy in logs. Redirect or suppress them as needed:

BASH
# Suppress all stderr (only get the file path)
file=$(fetchit --best url 2>/dev/null)

# Log stderr to a file for debugging
file=$(fetchit --best url 2>>fetchit-errors.log)

# Discard stderr but still capture exit code
file=$(fetchit --best url 2>&-)
exit_code=$?
Never rely on parsing stderr text for critical logic. The format of status messages may change between versions. Always use stdout for the file path and the exit code for success/failure detection.

Piping and redirection patterns

fetchit is designed to compose well with standard Unix pipelines. Here are common patterns:

Pipe file path to a media player

BASH
# Open directly in mpv
mpv "$(fetchit --best https://youtu.be/dQw4w9WgXcQ 2>/dev/null)"

# Open in VLC (macOS)
open -a VLC "$(fetchit --best https://youtu.be/dQw4w9WgXcQ 2>/dev/null)"

# Open in VLC (Linux)
vlc "$(fetchit --best https://youtu.be/dQw4w9WgXcQ 2>/dev/null)"

# Open in Windows Media Player (PowerShell)
& "C:Program FilesWindows Media Playerwmplayer.exe" (fetchit --best url 2>$null)

Copy to clipboard

BASH
# macOS — copy path to clipboard
fetchit --best url 2>/dev/null | pbcopy

# Linux (X11) — copy path to clipboard
fetchit --best url 2>/dev/null | xclip -selection clipboard

# Linux (Wayland) — copy path to clipboard
fetchit --best url 2>/dev/null | wl-copy

# Windows (PowerShell) — copy path to clipboard
fetchit --best url 2>$null | Set-Clipboard

Copy file after download

BASH
# Move to a NAS after download
file=$(fetchit --best url 2>/dev/null)
cp "$file" /mnt/nas/videos/
echo "Copied to NAS"

# Upload via rsync
file=$(fetchit --best url 2>/dev/null)
rsync -avP "$file" user@server:/media/incoming/

Pipe URLs from a file

BASH
# Download every URL in urls.txt
while IFS= read -r url; do
  [ -z "$url" ] && continue
  fetchit --best "$url" -o ~/Downloads
done < urls.txt

Error handling in scripts

Robust scripts always check exit codes and handle failures gracefully. Here is a comprehensive error-handling template:

Check exit code

CLI
fetchit --best "https://youtu.be/dQw4w9WgXcQ"
case $? in
  0)  echo "Download succeeded" ;;
  1)  echo "Generic error — check stderr" >&2 ;;
  2)  echo "Usage error — bad arguments" >&2 ;;
  3)  echo "Probe failed — video may be private or unavailable" >&2 ;;
  4)  echo "Requested quality not available" >&2 ;;
  5)  echo "Download interrupted — network or disk issue" >&2 ;;
  6)  echo "Muxing failed — corrupt stream or FFmpeg error" >&2 ;;
  7)  echo "Rate limited — wait before retrying" >&2 ;;
  130) echo "Cancelled by user" >&2 ;;
  137) echo "Out of memory — reduce concurrency" >&2 ;;
  *)  echo "Unknown exit code $?" >&2 ;;
esac

Parse error messages

When an error occurs, fetchit writes a human-readable message to stderr. You can capture and inspect it:

BASH
# Capture stderr to a variable
output=$(fetchit --best "https://youtu.be/dQw4w9WgXcQ" 2>&1)
exit_code=$?

if [ $exit_code -ne 0 ]; then
  # Extract the last line of stderr for the error message
  error_msg=$(echo "$output" | tail -1)
  echo "Error: $error_msg" >&2
  logger -t fetchit "Download failed: $error_msg"
  exit $exit_code
fi

file=$(echo "$output" | head -1)
echo "Downloaded: $file"

Retry with backoff

Transient errors (network timeouts, rate limiting) can often be resolved by retrying with exponential backoff:

BASH
url="https://youtu.be/dQw4w9WgXcQ"
max_attempts=5
attempt=1
delay=10

while [ $attempt -le $max_attempts ]; do
  fetchit --best "$url" 2>/dev/null && break
  exit_code=$?

  if [ "$exit_code" -eq 7 ] || [ "$exit_code" -eq 5 ]; then
    echo "Attempt $attempt/$max_attempts failed (code $exit_code). Retrying in ${delay}s..."
    sleep $delay
    attempt=$((attempt + 1))
    delay=$((delay * 2))
  else
    echo "Non-retriable error (code $exit_code). Aborting." >&2
    exit $exit_code
  fi
done

if [ $attempt -gt $max_attempts ]; then
  echo "All $max_attempts attempts failed." >&2
  exit 1
fi

Fail-fast with set -e

BASH
set -e  # Exit immediately if any command fails
set -u  # Error on undefined variables
set -o pipefail  # Pipelines fail if any component fails

file=$(fetchit --best "https://youtu.be/dQw4w9WgXcQ" 2>/dev/null)
echo "Downloaded: $file"
When using set -e, be aware that $(fetchit ... 2>/dev/null) in a variable assignment does not trigger set -e on failure. Always check $? or use a separate statement.

Integration with other tools

fetchit works well with media players, transcoders, and file management tools. Here are common integrations:

FFmpeg

Pipe the output path directly into FFmpeg for further processing:

BASH
# Transcode downloaded video to H.265
file=$(fetchit --best url 2>/dev/null)
ffmpeg -i "$file" -c:v libx265 -c:a copy "${file%.*}_h265.mp4"

# Extract audio as FLAC
file=$(fetchit --best url 2>/dev/null)
ffmpeg -i "$file" -vn -c:a flac "${file%.*}.flac"

# Create a GIF from a segment
file=$(fetchit --best url 2>/dev/null)
ffmpeg -i "$file" -ss 00:00:05 -t 3 -vf "fps=10,scale=320:-1" "${file%.*}.gif"

MPV

Download and watch immediately:

BASH
# One-liner: download then play
mpv "$(fetchit --best url 2>/dev/null)"

# Download a clip and play just that segment
file=$(fetchit --from 10:00 --to 12:30 url 2>/dev/null)
mpv --start=0 "$file"

# Queue multiple downloads in mpv playlist
for url in "url1" "url2" "url3"; do
  file=$(fetchit --best "$url" 2>/dev/null)
  echo "$file" >> playlist.txt
done
mpv --playlist=playlist.txt

VLC

BASH
# Linux
vlc "$(fetchit --best url 2>/dev/null)"

# macOS
open -a VLC "$(fetchit --best url 2>/dev/null)"

# Windows (PowerShell)
$file = fetchit --best url 2>$null
& "C:Program FilesVideoLANVLClc.exe" $file

File managers

BASH
# Open containing folder in Finder (macOS)
open "$(fetchit --best url 2>/dev/null)"

# Reveal in File Explorer (Windows, PowerShell)
$file = fetchit --best url 2>$null
Invoke-Item (Split-Path $file -Parent)

# Open in Nautilus (Linux)
nautilus "$(dirname "$(fetchit --best url 2>/dev/null)")"

Automation patterns

fetchit is built for automation. Below are patterns for every major automation platform.

Shell scripts (Bash / Zsh)

Download a list of URLs

BASH
#!/bin/bash
set -euo pipefail

URLS=(
  "https://youtu.be/dQw4w9WgXcQ"
  "https://youtu.be/jNQXAC9IVRw"
  "https://youtu.be/9bZkp7q19f0"
)

for url in "${URLS[@]}"; do
  fetchit --best "$url" -o ~/Videos 2>/dev/null
  echo "Finished: $url (exit code: $?)"
done

Download with logging

BASH
#!/bin/bash
logfile="fetchit-$(date +%Y%m%d).log"

fetchit --best "$1" -o ~/Videos >>"$logfile" 2>&1
exit_code=$?

echo "[$(date)] $1 -> exit $exit_code" >>"$logfile"
exit $exit_code

Watch a URL list file for changes

BASH
#!/bin/bash
# Downloads new URLs added to urls.txt
touch urls.txt urls_done.txt

while true; do
  while IFS= read -r url; do
    if ! grep -qxF "$url" urls_done.txt 2>/dev/null; then
      echo "Downloading: $url"
      if fetchit --best "$url" -o ~/Downloads 2>/dev/null; then
        echo "$url" >> urls_done.txt
      fi
    fi
  done < urls.txt
  sleep 30
done

Cron / launchd / systemd timers

Cron (Linux / macOS)

Schedule recurring downloads with cron. Run crontab -e and add a line:

BASH
# Daily podcast download at 6 AM
0 6 * * * /usr/local/bin/fetchit --mp3 https://youtu.be/... -o ~/Podcasts

# Weekly playlist sync every Sunday at 2 AM
0 2 * * 0 /usr/local/bin/fetchit --best --concurrency 3 https://youtube.com/playlist?list=PL... -o ~/Playlists

# Hourly check for new videos in a channel
0 * * * * /usr/local/bin/fetchit --best "https://www.youtube.com/@channel" -o ~/ChannelVids 2>>~/fetchit-hourly.log
Always use absolute paths in cron jobs. The PATH environment in cron is minimal. Specify the full binary path (e.g. /usr/local/bin/fetchit) or set PATH at the top of the crontab: PATH=/usr/local/bin:/usr/bin:/bin.

launchd (macOS)

Create a plist file at ~/Library/LaunchAgents/com.user.fetchit-podcast.plist:

BASH
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.user.fetchit-podcast</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/fetchit</string>
    <string>--mp3</string>
    <string>https://youtu.be/...</string>
    <string>-o</string>
    <string>/Users/alice/Podcasts</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>6</integer>
    <key>Minute</key>
    <integer>0</integer>
  </dict>
  <key>StandardOutPath</key>
  <string>/tmp/fetchit-stdout.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/fetchit-stderr.log</string>
</dict>
</plist>

Load and start the service:

BASH
launchctl load ~/Library/LaunchAgents/com.user.fetchit-podcast.plist
launchctl start com.user.fetchit-podcast

systemd timer (Linux)

Create two files. First the service unit at ~/.config/systemd/user/fetchit-download.service:

JSON
[Unit]
Description=Download podcast via fetchit

[Service]
Type=oneshot
ExecStart=/usr/bin/fetchit --mp3 https://youtu.be/... -o ~/Podcasts
StandardOutput=append:~/.local/share/fetchit/download.log
StandardError=append:~/.local/share/fetchit/error.log

Then the timer unit at ~/.config/systemd/user/fetchit-download.timer:

JSON
[Unit]
Description=Daily podcast download timer

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

Enable and start:

BASH
systemctl --user daemon-reload
systemctl --user enable fetchit-download.timer
systemctl --user start fetchit-download.timer

GitHub Actions

Use fetchit in CI/CD pipelines to download test media assets or archive videos. Here is a workflow that downloads a video and uploads it as a release asset:

BASH
name: Download and Release Media

on:
  schedule:
    - cron: '0 6 * * 1'  # Every Monday at 6 AM
  workflow_dispatch:  # Allow manual trigger

jobs:
  download:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install fetchit
        run: |
          curl -fsSL https://fetchit-cli.vercel.app/install.sh | sh
          echo "$HOME/.fetchit/bin" >> $GITHUB_PATH

      - name: Download media
        run: |
          fetchit --best "https://youtu.be/dQw4w9WgXcQ" -o ./media 2>&1 | tee download.log
          echo "file=$(ls ./media/*.mp4)" >> $GITHUB_OUTPUT
        id: download

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: downloaded-media
          path: ./media/*

      - name: Create release
        uses: softprops/action-gh-release@v1
        if: startsWith(github.ref, 'refs/tags/')
        with:
          files: ./media/*

Watch folders / directory triggers

macOS — Folder Actions

Use macOS Folder Actions to trigger a download when a file containing URLs is added to a folder. In Script Editor:

BASH
on adding folder items to this_folder after receiving added_items
  repeat with an_item in added_items
    set item_path to POSIX path of an_item
    if item_path ends with ".txt" then
      do shell script "while read url; do
        /usr/local/bin/fetchit --best "$url" -o ~/Downloads
      done < " & quoted form of item_path
    end if
  end repeat
end adding folder items to

inotify (Linux)

BASH
#!/bin/bash
# Watch ~/watch-folder for new .url files and download them
WATCH_DIR=~/watch-folder
mkdir -p "$WATCH_DIR"

inotifywait -m "$WATCH_DIR" -e create -e moved_to --format '%f' |
while read filename; do
  case "$filename" in
    *.txt|*.urls)
      filepath="$WATCH_DIR/$filename"
      while IFS= read -r url; do
        [ -z "$url" ] && continue
        fetchit --best "$url" -o ~/Downloads
      done < "$filepath"
      mv "$filepath" "$filepath.done"
      ;;
  esac
done

Windows PowerShell FileSystemWatcher

BASH
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "$env:USERPROFILEwatch-folder"
$watcher.Filter = "*.txt"
$watcher.EnableRaisingEvents = $true

Register-ObjectEvent $watcher "Created" -Action {
  $path = $Event.SourceEventArgs.FullPath
  Get-Content $path | ForEach-Object {
    if ($_) {
      fetchit --best $_ -o "$env:USERPROFILEDownloads" 2>$null
    }
  }
  # Rename to mark as processed
  Rename-Item $path -NewName "$([IO.Path]::GetFileNameWithoutExtension($path)).done"
}

# Keep the script running
while ($true) { Start-Sleep 60 }

Advanced patterns

Download from URL list file

Maintain a plain text file with one URL per line and process it with a script:

BASH
# urls.txt — one URL per line, blank lines and # comments are ignored
https://youtu.be/dQw4w9WgXcQ
https://youtu.be/jNQXAC9IVRw
# https://youtu.be/9bZkp7q19f0  (commented out — skipped)
BASH
#!/bin/bash
input="urls.txt"
output_dir=~/Downloads

while IFS= read -r line; do
  # Trim whitespace
  line="${line#"${line%%[![:space:]]*}"}"
  line="${line%"${line##*[![:space:]]}"}"
  # Skip blank lines and comments
  [ -z "$line" ] && continue
  [ "${line:0:1}" = "#" ] && continue

  echo "Downloading: $line"
  if fetchit --best "$line" -o "$output_dir" 2>/dev/null; then
    echo "  OK"
  else
    echo "  FAILED (exit code: $?)" >&2
  fi
done < "$input"

Parallel downloads across multiple terminals

When downloading many independent files, you can run multiple fetchit processes in parallel across different terminal windows or tmux panes:

BASH
# Terminal 1 — 4K video
fetchit --best "https://youtu.be/video1" -o ~/Downloads/4K

# Terminal 2 — Audio podcast
fetchit --mp3 "https://youtu.be/podcast1" -o ~/Podcasts

# Terminal 3 — Playlist (override concurrency)
fetchit --best --concurrency 4 "https://youtube.com/playlist?list=PL..." -o ~/Playlists

For coordinated parallel execution from a single script:

BASH
#!/bin/bash
# Download three videos in parallel using background processes
urls=(
  "https://youtu.be/dQw4w9WgXcQ"
  "https://youtu.be/jNQXAC9IVRw"
  "https://youtu.be/9bZkp7q19f0"
)

pids=()
for url in "${urls[@]}"; do
  (fetchit --best "$url" -o ~/Downloads 2>/dev/null) &
  pids+=($!)
done

# Wait for all downloads and check their exit codes
failed=0
for pid in "${pids[@]}"; do
  wait "$pid" || failed=$((failed + 1))
done

echo "$failed downloads failed"
exit $failed

Parallel downloads with GNU Parallel

BASH
# Install GNU Parallel, then:
parallel -j 3 'fetchit --best {} -o ~/Downloads' :::: urls.txt

# With retry on failure
parallel --retries 3 'fetchit --best {} -o ~/Downloads' :::: urls.txt

# With progress bar
parallel --bar 'fetchit --best {} -o ~/Downloads 2>/dev/null' :::: urls.txt

Conditional logic — check if already downloaded

Avoid re-downloading videos that already exist. Use filename pattern matching:

BASH
#!/bin/bash
url="https://youtu.be/dQw4w9WgXcQ"
output_dir=~/Downloads

# Extract video ID from URL
video_id=$(echo "$url" | sed -n 's/.*/\([a-zA-Z0-9_-]\{11\}\).*/\1/p')

# Check if any file already contains this video ID
if ls "$output_dir"/*"[$video_id]"* >/dev/null 2>&1; then
  echo "Already downloaded: $video_id"
  exit 0
fi

# Download
file=$(fetchit --best "$url" -o "$output_dir" 2>/dev/null)
if [ -f "$file" ]; then
  echo "Downloaded: $file"
else
  echo "Download failed" >&2
  exit 1
fi

Conditional by file size

BASH
#!/bin/bash
# Skip downloads that would produce a file smaller than 1 MB
url="https://youtu.be/dQw4w9WgXcQ"

file=$(fetchit --best "$url" 2>/dev/null)
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)

if [ "$size" -lt 1048576 ]; then
  echo "Warning: file is only $((size / 1024)) KB, may be corrupt" >&2
  rm "$file"
  exit 1
fi

echo "OK: $file ($((size / 1048576)) MB)"

Configuration via environment variables

fetchit respects several environment variables that override config file settings. Flags always take precedence over environment variables, which take precedence over the config file.

VariableTypeDescriptionExample
<Code>FETCHIT_THEME</Code>stringOverride theme: <Code>auto</Code>, <Code>light</Code>, or <Code>dark</Code>FETCHIT_THEME=dark
<Code>FETCHIT_DOWNLOAD_DIR</Code>stringDefault download directoryFETCHIT_DOWNLOAD_DIR=~/Movies
<Code>FETCHIT_LOG_LEVEL</Code>stringSet log verbosity: <Code>debug</Code>, <Code>info</Code>, <Code>warn</Code>, <Code>error</Code>FETCHIT_LOG_LEVEL=debug
<Code>FETCHIT_CONCURRENCY</Code>integerDefault playlist concurrencyFETCHIT_CONCURRENCY=5
<Code>FETCHIT_OUTPUT_TEMPLATE</Code>stringOutput filename template (see output templates above)FETCHIT_OUTPUT_TEMPLATE="{uploader} - {title}.{ext}"
<Code>FETCHIT_COOKIES_BROWSER</Code>stringDefault browser for cookie extractionFETCHIT_COOKIES_BROWSER=firefox
<Code>YTDLP_PATH</Code>stringPath to a custom yt-dlp binaryYTDLP_PATH=/opt/yt-dlp/yt-dlp
<Code>FFMPEG_PATH</Code>stringPath to a custom FFmpeg binaryFFMPEG_PATH=/usr/local/bin/ffmpeg
<Code>NO_COLOR</Code>anyDisable colour output (see no-color.org)NO_COLOR=1
<Code>TERM</Code>stringUsed for colour capability detectionTERM=xterm-256color
<Code>HOME</Code>stringUsed to resolve <Code>~</Code> in paths(set by system)

Setting per-command

BASH
FETCHIT_DOWNLOAD_DIR=~/Movies FETCHIT_THEME=dark fetchit --best url

Setting in shell profile

Add to ~/.zshrc, ~/.bashrc, or PowerShell profile:

BASH
# ~/.zshrc or ~/.bashrc
export FETCHIT_DOWNLOAD_DIR="$HOME/Downloads/fetchit"
export FETCHIT_THEME="dark"
export FETCHIT_LOG_LEVEL="warn"
BASH
# PowerShell $PROFILE
$env:FETCHIT_DOWNLOAD_DIR = "$env:USERPROFILEDownloadsetchit"
$env:FETCHIT_THEME = "dark"

Setting in a .env file

If you use direnv or dotenv, create a .env file in your project:

BASH
FETCHIT_DOWNLOAD_DIR=./downloads
FETCHIT_LOG_LEVEL=debug
FETCHIT_CONCURRENCY=2

Rules and validation

Scriptable mode enforces argument validation rules to prevent ambiguous or impossible commands. The table below lists every validation rule and its error message:

RuleErrorExplanation
<Code>--best</Code> and <Code>--mp3</Code> togethercan't be combined — pick oneThese flags conflict. <Code>--best</Code> downloads video+audio; <Code>--mp3</Code> downloads audio only and transcodes to MP3. They cannot both be active.
<Code>--best</Code> or <Code>--mp3</Code> without URLscriptable mode needs a urlAll scriptable-mode flags require a URL. Without a URL, fetchit would start interactive mode.
Unknown quality stringunknown quality "..."The quality positional argument does not match any known resolution or shorthand. Valid values: <Code>2160p</Code>, <Code>1440p</Code>, <Code>1080p</Code>, <Code>720p</Code>, <Code>480p</Code>, <Code>360p</Code>, <Code>240p</Code>, <Code>144p</Code>, <Code>best</Code>, or <Code>mp3</Code>.
More than 2 positional argumentsexpected a url and optional qualityfetchit accepts at most two positional arguments: the URL (required) and an optional quality string. Extra positional arguments are rejected.
Invalid URL formatcould not parse urlThe URL does not appear to be valid. Ensure it starts with <Code>http://</Code> or <Code>https://</Code> and points to a supported site.
<Code>--from</Code> without <Code>--to</Code> after itclipping requires --to with --from<Code>--from</Code> can be used alone (downloads from start time to end of video). If <Code>--to</Code> appears before <Code>--from</Code>, the argument parser rejects it.
Negative <Code>--concurrency</Code>concurrency must be a positive integerConcurrency values must be 1 or greater. Zero or negative values are invalid.

Exit code reference for scripts

The following patterns help you interpret exit codes in script logic:

BASH
# Success
fetchit --best url && echo "Downloaded"

# Failure with error classification
fetchit --best url
case $? in
  0)  ;;  # success, nothing to do
  2|4) echo "Fix arguments and retry" ;;
  3|7) echo "Remote issue, retry later" ;;
  5|6) echo "Local issue, check disk/ffmpeg" ;;
  130) echo "User cancelled" ;;
  *)   echo "Unknown error" ;;
esac

# Retry on transient failures
until fetchit --best url; do
  case $? in
    0) break ;;
    130) exit 130 ;;  # respect user cancellation
    137) exit 137 ;;  # OOM won't resolve by retrying
    *) sleep 10 ;;
  esac
done

Rules of thumb

  • stdout is the file path, stderr is human-readable output. Never parse stderr for automation logic.
  • Always check the exit code. Exit code 0 is the only guarantee of success.
  • Use absolute paths in cron/systemd/launchd. Relative paths depend on the working directory, which may not be what you expect.
  • Set a PATH in cron. Cron runs with a minimal environment. Add PATH=/usr/local/bin:/usr/bin:/bin to your crontab.
  • Use 2>/dev/null to capture only the file path. Without this, stderr status lines intermix with your captured output.
  • Test with a single URL first. When building a script, run the fetchit command manually on one URL to verify the output before scaling up.
  • Resume interrupted downloads. Re-running the same fetchit command on a partially downloaded file resumes where it left off (yt-dlp handles partial file detection automatically).

See also

Was this page helpful?