YTDock is a macOS terminal video tool I built to bring YouTube downloads, local video compression, and local SRT subtitle burning into one fullscreen interface. Based on the code, configuration, and documentation for version 0.8.1, this article covers its features and several key implementation decisions.
Source code: GrahamQuan/ytdock. Current installers target Apple Silicon devices running macOS 14 or later.
Three operations in one terminal interface
The download page accepts a single YouTube video or Shorts URL, retrieves its information, and then lets you choose a resolution and frame rate. It defaults to the highest available resolution at or below 1080p. Different frame rates at the same resolution appear separately, and options requiring transcoding are marked in advance.
The compression page offers “Prioritize size” and “Prioritize quality,” preserving the source dimensions, aspect ratio, and actual frame rate. It reports the size change afterward. If the output is larger, it still keeps the file and explains the result: completing a transcode does not guarantee a smaller file.
The subtitle page accepts a local video and a UTF-8 SRT file, including subtitles generated by external translation tools. YTDock validates and burns them locally; it does not perform translation or speech recognition itself.
The interface uses Python's prompt-toolkit. Tab and Shift+Tab move focus, and the left and right arrow keys switch pages when navigation has focus. Each page preserves its inputs and options, while switching pages is disabled during processing and cleanup. The task page records results, saved paths, and failure reasons for the current run; those records are cleared on exit.
English and Simplified Chinese interface strings live in separate JSON files. Ctrl+O switches languages at runtime. Translation affects only the interface: video titles, file paths, and output filenames remain unchanged.
Identify the audio track before choosing an encoding
Downloads use yt-dlp, but audio selection needs additional constraints. A single audio track can have AAC, Opus, and multiple bitrate variants. These are encoding candidates, not separate language tracks.
YTDock identifies the audio track first, then compares codec compatibility and bitrate. Explicitly identified original audio takes priority. A single available track can be selected automatically, but it is not described as original without supporting evidence. When multiple tracks exist, there must be enough information to uniquely identify the original-language track. Otherwise, the operation fails rather than guessing from the title, interface language, or region.
Optional subtitle downloads also require independent evidence of the original language. Human-created subtitles in that language take priority, followed by native automatic captions; automatically translated tracks are excluded. Being able to download audio does not necessarily mean the subtitle language can be identified reliably.
Downloaded and local subtitles follow different rules
YouTube subtitles are converted into single-line SRT cues with no overlaps and a maximum of 42 characters per cue before burning. The program saves the plain MP4 and the exact SRT used for burning first, then generates the subtitled video.
Locally imported SRT files retain their original text, line breaks, and cue boundaries. They do not go through the resegmentation used for downloaded captions. Import checks cover encoding, timestamp order, overlaps, fonts, and whether subtitles extend beyond the frame, avoiding unnecessary changes to subtitles users have already edited.
Both paths validate subtitles against the measured duration of the selected video stream, falling back to container duration only when that measurement is unavailable. Time uses integer milliseconds throughout. A cue that starts within the video and ends no more than two seconds beyond it is shortened with a warning. Cues entirely outside the video or substantially out of bounds are rejected. Corrected download timestamps are written to the SRT, preserving the same time basis for later local imports.
Ordinary downloads and compression produce H.264/AAC MP4 files. Subtitle burning produces H.265/HEVC with the hvc1 tag, using CRF 27, the medium preset, yuv420p, and faststart. Each output path has an explicit encoding contract.
Validate the output before calling it complete
A media tool cannot rely on a subprocess exit code alone to determine success. Before saving the final file, YTDock checks codecs, dimensions, frame rate, aspect ratio, audio, and duration, and performs a full decode check. Subtitled outputs also undergo sampled-frame comparison checks.
Download duration validation distinguishes two sources: YouTube metadata provides a coarse completeness check, while measured source audio and video stream durations support stricter output checks. Metadata may have only whole-second precision, so it receives a tolerance of at least one second to avoid reporting precision differences as damaged output.
Processing takes place in a separate hidden temporary directory under Downloads. Files are published to their final paths only after validation. Naming collisions receive numeric suffixes; existing files are not overwritten, and compression sources are not modified.
Ctrl+C waits for the worker and its subprocesses to stop before cleaning up the current task's partial files. If the ordinary video has already been saved and subsequent subtitle burning fails, that completed output is preserved, with its actual path listed in the task result.
The installer bundles the runtime; FFmpeg stays external
The project uses Python 3.11 or later, uv for development dependencies, and PyInstaller for packaging. macOS installers bundle Python, QuickJS, yt-dlp, yt-dlp-ejs, and interface dependencies. Users installing the packaged application do not need to set up Python or uv first.
FFmpeg and ffprobe must be installed externally. The program checks their executables and required decoding and encoding capabilities. Subtitle mode additionally requires libx265, subtitles/libass, available fonts, and HEVC decoding support. The installer does not install or update FFmpeg for users.
On a system with Homebrew, you can prepare and launch it with:
The installation script retrieves the latest compatible stable release, verifies its SHA256 checksum, and installs it in the user directory. SHA256 checks file integrity; it does not replace Apple signing or notarization. Alternatively, download an archive from Releases, extract it completely, and run Install.command.
From manual packaging to automatic releases from main
The current Release macOS workflow runs on pushes to main and also supports manual triggers. Publishing a new version requires updating the version in pyproject.toml and synchronizing uv.lock. The workflow does not increment versions automatically, and already published versions are skipped.
New versions first run tests and isolated packaging acceptance checks on macOS 14 arm64. The workflow then creates a draft release and uploads ZIP and tar.gz archives with their respective SHA256 files. The release becomes public only after all four files upload successfully, and its tag points to the commit that passed testing.
Failed uploads leave the draft intact so the same commit can be retried. If source changes are needed after a tag has been created, a new version number is used. This keeps public versions aligned with their validated code and installers.
I did not rerun YTDock's media or installation acceptance checks while preparing this article. The project documentation still lists these validation boundaries: CI does not verify live YouTube downloads, a second Mac, Apple notarization, or real self-updates across versions. Intel and Linux installers are not currently provided either.
As development continues, I will document these boundaries alongside the features themselves. For a tool that processes local files, clearly explaining what was saved, where a failure occurred, and which capabilities have been validated is part of the user experience.