Skip to main content

ytslice

Batch-extract time-bounded audio/video segments from long YouTube videos via a JSON config — cached, fail-soft, no manual yt-dlp + ffmpeg.


Prerequisites

Dependency Why Install
Python ≥ 3.10 runtime apt install -y python3 python3-pip
ffmpeg (+ ffprobe) cuts segments, parses durations apt install -y ffmpeg
Deno on PATH yt-dlp's n-parameter challenge solver (see below) curl -fsSL https://deno.land/install.sh | sh then export PATH="$HOME/.deno/bin:$PATH"

Why Deno

YouTube's player obfuscates stream URLs behind a JavaScript challenge (the n parameter). yt-dlp resolves it via the ejs:github remote-components plugin, which requires a JS runtime — deno is the default. Without deno on PATH, downloads of newer YouTube videos fail mid-stream with HTTP 403 Forbidden and the in-process fallback emits a warning. Install deno via the one-liner above, then deno --version should print a version before you proceed.


Install

pip install .
# or, for development:
pip install -e ".[dev]"

The ytslice CLI is installed via [project.scripts].


Quick start

config.json:

{
  "output_dir": "./output",
  "videos": [
    {
      "url": "https://www.youtube.com/watch?v=REPLACE_ME",
      "quality": "720",
      "segments": [
        { "name": "intro",     "start": "0:00:00", "end": "0:00:30", "mode": "audio" },
        { "name": "highlight", "start": "0:01:00", "end": "0:02:15", "mode": "both"  }
      ]
    }
  ]
}

Run:

ytslice --config config.json

Expected outputs (one segment per file, plus both produces a pair):

output/
└── <video_id>/
    ├── intro-<video_id>.mp3
    ├── highlight-<video_id>.mp3
    └── highlight-<video_id>.mp4

Config schema

Top level:

Field Type Required Notes
output_dir string no default ./output; outputs go to <output_dir>/<video_id>/
videos array yes non-empty list of video objects

Per video:

Field Type Required Notes
url string yes any URL yt-dlp accepts
quality string no one of `best
segments array yes non-empty list of segment objects

Per segment:

Field Type Required Notes
name string yes becomes the on-disk basename after sanitization
start string yes H:MM:SS or MM:SS, minutes/seconds 0–59
end string yes same format; must be > start
mode string yes one of audio (MP3), video (MP4 with audio), both (MP3 + MP4)

The quality enum is exactly what ytslice/config.py validates against. See config.example.json for a runnable example.


CLI flags

Flag Description
--config <path> path to JSON config (required)
--output-dir <path> override output_dir from config
--keep-cache keep source in cache after a successful run (disables auto-evict)
--no-cache bypass cache: do not look up, do not store; downloads to a tempdir and removes it after the run
--clear-cache empty the cache root before the run
--dry-run validate config and exit without downloading or cutting
-h, --help show help

--no-cache and --keep-cache are mutually exclusive.

--verbose was removed in 1.1.x — use PYTHONLOGLEVEL=DEBUG if you need more detail.

Environment variables

Variable Effect
YTSLICE_NO_PROGRESS=1 force-disable the rich progress UI (non-TTY opt-out)
YTSLICE_CACHE_DIR=<path> override cache root (default ~/.cache/ytslice/)
YTSLICE_E2E=1 enable network-using tests under pytest

The progress UI also auto-disables when stderr is not a TTY.


Cache lifecycle

Mode Behavior
default per-video auto-evict on success — the source file is removed from ~/.cache/ytslice/ once all that video's segments succeed
--keep-cache source files persist (useful when iterating on segment timestamps)
--no-cache source is downloaded to a tempdir, used once, and removed after the run
--clear-cache the entire cache root is purged at the start of the run (sentinel-guarded)

A failed segment keeps the source so a rerun does not re-download. Cache root is ~/.cache/ytslice/ (override via YTSLICE_CACHE_DIR); the directory is marked with a .ytslice-cache sentinel that --clear-cache refuses to purge directories without.


Output filename rules

The full V1 rule, in order:

<sanitize_filename(seg.name)>-<video_id><collision_suffix?><extension>
  • sanitize_filename strips path separators, control chars, leading/trailing dots+spaces; collapses whitespace; truncates at 200 chars; falls back to unnamed on empty input.
  • <video_id> is the YouTube ID resolved by yt-dlp (e.g. dQw4w9WgXcQ).
  • <collision_suffix?> is empty when the target path is free; otherwise _1, _2, … (1-indexed, no zero-pad), allocated to the lowest free index. For both-mode segments, the same _N is applied to both the .mp3 and the .mp4 so the pair never splits across different suffixes.
  • <extension> is .mp3 / .mp4 per mode.

Rerun caveat: re-running the same config without clearing ./output/<video_id>/ will produce _1, _2, … duplicates because pre-existing files are treated as collisions (by design — no silent overwrites). Either rm -rf ./output/<video_id>/ between runs, or rely on the _N accumulation if you want a versioned history.


Clean-Debian walkthrough

End-to-end in debian:stable-slim (container runs as root — no sudo; on a non-container Debian with a non-root user, prefix the apt commands with sudo yourself):

docker run --rm -it debian:stable-slim bash

# system deps
apt update
apt install -y python3 python3-pip python3-venv ffmpeg curl git

# deno (for yt-dlp's n-challenge solver)
curl -fsSL https://deno.land/install.sh | sh
export PATH="$HOME/.deno/bin:$PATH"
deno --version

# ytslice
git clone https://gitlab.com/MisterJB/ytslice.git
cd ytslice
python3 -m venv .venv
. .venv/bin/activate
pip install .

ytslice --help

# run the example (replace REPLACE_ME with a real YouTube ID first)
cp config.example.json config.json
sed -i 's/REPLACE_ME/dQw4w9WgXcQ/' config.json
sed -i 's/ANOTHER/dQw4w9WgXcQ/' config.json
ytslice --config config.json

# verify outputs
ls -la output/dQw4w9WgXcQ/
ffprobe -v error -show_entries format=duration output/dQw4w9WgXcQ/*.mp3 | head

# verify cache auto-evicted on success
ls -la ~/.cache/ytslice/
# Expected: directory exists, contains the .ytslice-cache sentinel,
# but no <video_id>.mp4 — auto-evict-on-success worked.

Testing

pytest                  # offline suite (default; mocks downloader + cutter)
YTSLICE_E2E=1 pytest    # additionally runs network-using tests

Done-criteria coverage lives in tests/test_done_criteria.py, mapping every PROJECT.md success metric to a runnable assertion.


License

GPL-3.0-or-later. See the LICENSE file for the full text.


Changelog (1.1.x)

  • Added --keep-cache, --no-cache, --clear-cache, --dry-run.
  • Added per-video quality field (best | 1080 | 720 | 480 | 360, default 1080).
  • Added rich progress UI (auto-disables in non-TTY; opt out via YTSLICE_NO_PROGRESS=1).
  • Filename collisions now resolved as _1, _2, … (pair-consistent for both mode).
  • LICENSE: GPL-3.0-or-later (PEP 639 SPDX).
  • Removed: --verbose (use PYTHONLOGLEVEL=DEBUG).

Metadata

Release files for ytslice 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ytslice 1.1.0
File Size Uploaded
ytslice-1.1.0.tar.gz 64.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ytslice 1.1.0
File Interpreter ABI Platform
ytslice-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 101.6 kB

Release files / ytslice-1.1.0.tar.gz

Download URL ytslice-1.1.0.tar.gz
Size 64.9 kB
Tags Source
SHA-256 checksum
How to use checksums
a474f488f9d94c8da4c6b6a6a9c0c7f338256339498c120547f723b2407fad5f
BLAKE2b-256 checksum
How to use checksums
765b0143d20f85dc080d9480746f38127b4a819b87ae77e66df8197b6fcac0c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / ytslice-1.1.0-py3-none-any.whl

Download URL ytslice-1.1.0-py3-none-any.whl
Size 36.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7ac4554cfe7263860f0e85a5a22a8874b5ff809b08a0be42f0f1417e4ee35d3e
BLAKE2b-256 checksum
How to use checksums
846f9d9a0f4f92006ce9a6826ab7867c493a3f110d5945abea79d46f4045416b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page