yt2text-watch
v0.1.0
Published
Watch YouTube channels and transcribe new videos with yt2text.
Maintainers
Readme
yt2text Watch
This is a scheduled watcher fork of lopleec/yt2text. It keeps the original URL/file transcription CLI and adds yt2text-watch watch for checking YouTube channels on a schedule.
Download audio from YouTube or another yt-dlp supported URL, transcribe it locally or through the platform speech interface, and save the result as txt, json, or srt.
Windows support is included but currently untested. macOS and Linux are the primary targets.
Features
- Downloads audio only through
yt-dlp. - Uses macOS Speech.framework by default on macOS.
- Uses local Whisper ASR by default on Linux.
- Includes a best-effort Windows SAPI helper, marked untested.
- Supports local multilingual ASR with
--multilingualor-l auto. - Optional anonymous speaker diarization with
--diarize. - Browser cookie auto-detection, manual cookies files, proxy, retries, timeouts, and fragment concurrency.
- Progress output for dependency downloads, media download, conversion, and transcription.
- JSON config file with CLI flags taking precedence.
- Output filename templates and optional audio retention.
Requirements
- Node.js 20 or newer.
- Network access on first use to download
yt-dlpand, when local ASR is enabled, ASR models. - macOS system ASR requires Speech Recognition permission and Xcode Command Line Tools for
swiftc; the CLI automatically opens Apple'sxcode-select --installinstaller whenswiftcis missing. - Linux local ASR requires the optional
sherpa-onnx-nodepackage to install correctly. ffmpegis bundled through@ffmpeg-installer/ffmpeg; a systemffmpegis used as fallback.
Install
Recommended for this fork:
npm install -g yt2text-watchFrom this repository:
git clone https://github.com/lopleec/yt2text_watch.git
cd yt2text_watch
npm install
npm run build
npm linkRun a local checkout without linking:
npm run dev -- "https://www.youtube.com/watch?v=ROrOGwJDneo" -l en-USQuick Start
macOS default: browser cookies are auto-detected, the system Speech interface runs in offline/on-device mode, and the transcript is saved as txt in ~/Downloads.
yt2text-watch "https://www.youtube.com/watch?v=ROrOGwJDneo" -l en-USChinese video using the macOS default language:
yt2text-watch "https://www.youtube.com/watch?v=muAgkhaoLDA"Mixed-language or unknown-language video:
yt2text-watch "https://youtu.be/..." --multilingualLocal file:
yt2text-watch file ./audio.wav -l en-USInteractive terminal mode:
yt2text-watchCheck dependencies and effective configuration:
yt2text-watch doctor --fix --show-config--fix downloads missing small runtime tools such as yt-dlp. On macOS it also opens the Xcode Command Line Tools installer if swiftc is missing. That Apple installer still requires the user to finish the system dialog, then rerun the CLI.
Platform Defaults
| Platform | Default ASR | Notes |
| --- | --- | --- |
| macOS | system | Uses Speech.framework with --system-mode offline; browser cookies default to auto. |
| Linux | local | No single cross-distro system speech API is assumed; first local ASR run downloads models. |
| Windows | system | Uses a best-effort offline SAPI helper. This path is included but not tested. |
Use --asr local to force local ASR, or --fallback local to try local ASR if system ASR fails.
Common Commands
yt2text-watch "https://youtu.be/..." # direct run
yt2text-watch "https://youtu.be/..." -l en-US # English locale
yt2text-watch "https://youtu.be/..." --asr local # force local Whisper ASR
yt2text-watch "https://youtu.be/..." --multilingual # local ASR with language auto-detect
yt2text-watch "https://youtu.be/..." --diarize # anonymous speaker labels
yt2text-watch "https://youtu.be/..." -f srt # subtitle output
yt2text-watch "https://youtu.be/..." --audio-quality small # smaller download
yt2text-watch "https://youtu.be/..." --parallel-downloads 8 # faster fragmented downloads
yt2text-watch "https://youtu.be/..." --keep-original-audio --keep-audio
yt2text-watch file ./meeting.m4a -l zh-CN
yt2text-watch config
yt2text-watch config --print
yt2text-watch doctor
yt2text-watch watch --init ./yt2text-watch.json
yt2text-watch watch ./yt2text-watch.json --run-once
yt2text-watch watch ./yt2text-watch.jsonImportant Options
ASR:
--asr system|local--system-mode online|offline|auto--fallback local|fail-l, --language <locale>such aszh-CN,zh-TW,en-US,ja-JP, orauto--multilingual--model tiny|base|small|medium--diarize--chunk-seconds <seconds>
Output:
-f, --format txt|json|srt-o, --output <dir>--filename-template <template>--keep-original-audio--keep-audio--log-file <file>
Filename template tokens:
{title}{date}{datetime}{language}{engine}{source}
Download and cookies:
--cookies <file>--cookies-from-browser auto|chrome|safari|firefox|edge|brave|chromium--no-browser-cookies--audio-quality best|balanced|small--parallel-downloads <n>--concurrent-fragments <n>--retries <n>--fragment-retries <n>--socket-timeout <seconds>--download-timeout <seconds>--proxy <url>--rate-limit <rate>--user-agent <ua>--update-ytdlp
Runtime:
--config <file>--cache-dir <dir>--offline--convert-timeout <seconds>--asr-chunk-timeout <seconds>-v, --verbose
Daily Channel Watcher
yt2text-watch watch checks one or more YouTube channel pages, records which videos have already been seen, and transcribes new videos when they appear.
Create a watch config in the current folder:
yt2text-watch watch --init ./yt2text-watch.jsonEdit yt2text-watch.json and replace the example channel:
{
"schedule": {
"times": ["03:00"],
"runOnStart": false
},
"channels": [
{
"name": "Bloomberg TV",
"url": "https://www.youtube.com/@markets/videos",
"enabled": true
}
],
"check": {
"maxScanVideosPerChannel": 30,
"maxNewVideosPerRun": 0,
"markExistingAsSeenOnFirstRun": true,
"newestFirst": false
},
"paths": {
"outputDir": "./transcripts",
"logDir": "./logs",
"stateFile": "./yt2text-watch-state.json"
},
"notify": {
"enabled": true,
"mode": "terminal",
"onSuccess": true,
"onFailure": true,
"title": "yt2text Watch",
"terminalMessage": "Done"
},
"transcription": {
"language": "zh-CN",
"format": "txt",
"cookiesFromBrowser": "auto",
"audioQuality": "balanced",
"filenameTemplate": "{date}-{title}"
}
}Useful commands:
yt2text-watch watch ./yt2text-watch.json --run-once
yt2text-watch watch ./yt2text-watch.json --mark-seen --run-once
yt2text-watch watch ./yt2text-watch.jsonBehavior:
--run-oncechecks now and exits.- Without
--run-once, the process stays open and runs every day at the configuredschedule.times. --mark-seen --run-oncerecords the currently listed videos without transcribing them.- By default, the first run marks existing videos as seen, so future runs only process new videos.
- Set
markExistingAsSeenOnFirstRuntofalseif you want the first run to transcribe existing listed videos. maxScanVideosPerChannelcontrols how many latest videos are scanned per channel.maxNewVideosPerRun: 0means no limit.notify.enabledsends a completion signal after each completed or failed watch run.- Relative
outputDir,logDir, andstateFilepaths are resolved relative to the watch JSON file. - Logs default to
./logs/yt2text-watch.lognext to the JSON file. - State defaults to
./yt2text-watch-state.jsonnext to the JSON file.
Scheduling modes:
- Built-in daemon:
yt2text-watch watch ./yt2text-watch.jsonkeeps running, sleeps until the next localHH:mm, runs, and repeats daily. Keep the terminal open, or run it undernohup,launchd,systemd, or another supervisor. - OS scheduler: run
yt2text-watch watch ./yt2text-watch.json --run-oncefromcron,launchd, orsystemd. In this mode the terminal does not need to stay open, and the OS scheduler controls the time.
Notification modes:
mode: "terminal"opens Terminal.app on macOS and printsDoneplus a short run summary. This is the default generated by--initon macOS.mode: "system"uses macOS Notification Center throughosascript, Linuxnotify-send, and a best-effort Windows PowerShell balloon.mode: "both"runs both notification methods.terminalmode is macOS-only. Usesystemon Linux.- If macOS asks for Automation or notification permission the first time, allow it for Terminal or osascript.
Simple background run on macOS/Linux:
mkdir -p ./logs
nohup yt2text-watch watch ./yt2text-watch.json >> ./logs/watch.out 2>&1 &For a machine that reboots often, prefer the OS scheduler mode.
macOS launchd
Use launchd when you want the job to run daily without keeping Terminal open. launchd should run the CLI with --run-once; the plist time controls the schedule, not schedule.times in the JSON.
Create the config and edit your channel list first:
mkdir -p "$HOME/Documents/yt2text-watch"
yt2text-watch watch --init "$HOME/Documents/yt2text-watch/yt2text-watch.json"
open -a TextEdit "$HOME/Documents/yt2text-watch/yt2text-watch.json"Create a LaunchAgent that runs every day at 03:00:
COMMAND="$(command -v yt2text-watch || true)"
NODE="$(command -v node || true)"
CONFIG="$HOME/Documents/yt2text-watch/yt2text-watch.json"
LOGDIR="$HOME/Documents/yt2text-watch/logs"
PLIST="$HOME/Library/LaunchAgents/com.lopleec.yt2text-watch.plist"
test -n "$COMMAND" || { echo "yt2text-watch is not installed or linked"; exit 1; }
test -n "$NODE" || { echo "node is not installed"; exit 1; }
NODEDIR="$(dirname "$NODE")"
PATH_VALUE="$NODEDIR:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
mkdir -p "$LOGDIR" "$HOME/Library/LaunchAgents"
cat > "$PLIST" <<PLIST
<?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.lopleec.yt2text-watch</string>
<key>ProgramArguments</key>
<array>
<string>$COMMAND</string>
<string>watch</string>
<string>$CONFIG</string>
<string>--run-once</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>$PATH_VALUE</string>
</dict>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>3</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>$LOGDIR/launchd.out.log</string>
<key>StandardErrorPath</key>
<string>$LOGDIR/launchd.err.log</string>
</dict>
</plist>
PLISTLoad it and run one manual test:
launchctl bootout "gui/$(id -u)" "$PLIST" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST"
launchctl enable "gui/$(id -u)/com.lopleec.yt2text-watch"
launchctl kickstart -k "gui/$(id -u)/com.lopleec.yt2text-watch"Check logs:
tail -f "$HOME/Documents/yt2text-watch/logs/launchd.out.log"
tail -f "$HOME/Documents/yt2text-watch/logs/launchd.err.log"
tail -f "$HOME/Documents/yt2text-watch/logs/yt2text-watch.log"Unload it:
launchctl bootout "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.lopleec.yt2text-watch.plist"Linux systemd User Timer
Use a user timer for a reboot-safe daily run. Replace paths if your config lives somewhere else.
COMMAND="$(command -v yt2text-watch || true)"
NODE="$(command -v node || true)"
WORKDIR="$HOME/Documents/yt2text-watch"
CONFIG="$WORKDIR/yt2text-watch.json"
test -n "$COMMAND" || { echo "yt2text-watch is not installed or linked"; exit 1; }
test -n "$NODE" || { echo "node is not installed"; exit 1; }
NODEDIR="$(dirname "$NODE")"
PATH_VALUE="$NODEDIR:$HOME/.npm-global/bin:/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin"
mkdir -p "$WORKDIR/logs" "$HOME/.config/systemd/user"
yt2text-watch watch --init "$CONFIG"Create the service:
cat > "$HOME/.config/systemd/user/yt2text-watch.service" <<UNIT
[Unit]
Description=yt2text Watch run
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
WorkingDirectory=$WORKDIR
Environment=PATH=$PATH_VALUE
ExecStart=$COMMAND watch $CONFIG --run-once
UNITCreate the timer for 03:00 every day:
cat > "$HOME/.config/systemd/user/yt2text-watch.timer" <<UNIT
[Unit]
Description=Run yt2text Watch daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Unit=yt2text-watch.service
[Install]
WantedBy=timers.target
UNITEnable it and run one manual test:
systemctl --user daemon-reload
systemctl --user enable --now yt2text-watch.timer
systemctl --user start yt2text-watch.service
systemctl --user list-timers yt2text-watch.timer
journalctl --user -u yt2text-watch.service -fIf the machine should run the timer while you are logged out, enable lingering:
sudo loginctl enable-linger "$USER"Desktop notifications from systemd user timers require a graphical user session. If you only use SSH or a server, rely on logs instead.
cron Fallback
Cron is simpler but less reliable for desktop notifications. Use absolute paths.
PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
0 3 * * * cd /Users/YOU/Documents/yt2text-watch && /opt/homebrew/bin/yt2text-watch watch /Users/YOU/Documents/yt2text-watch/yt2text-watch.json --run-once >> /Users/YOU/Documents/yt2text-watch/logs/cron.log 2>&1On Linux the line looks like:
PATH=/home/YOU/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
0 3 * * * cd /home/YOU/Documents/yt2text-watch && /home/YOU/.npm-global/bin/yt2text-watch watch /home/YOU/Documents/yt2text-watch/yt2text-watch.json --run-once >> /home/YOU/Documents/yt2text-watch/logs/cron.log 2>&1Find the correct binary path with:
command -v yt2text-watchConfiguration
Create a config file:
yt2text-watch configDefault paths:
- macOS/Linux:
~/.config/yt2text/config.json - Windows:
%APPDATA%\yt2text\config.json
Print the platform-specific default config without writing:
yt2text-watch config --printExample:
{
"asr": "system",
"systemMode": "offline",
"fallback": "fail",
"language": "zh-CN",
"outputDir": "~/Downloads",
"format": "txt",
"cookiesFromBrowser": "auto",
"browserCookies": true,
"model": "small",
"offline": false,
"diarize": false,
"chunkSeconds": 55,
"filenameTemplate": "{date}-{title}",
"audioQuality": "balanced",
"keepAudio": false,
"keepOriginalAudio": false,
"parallelDownloads": 8,
"concurrentFragments": 8,
"retries": 20,
"fragmentRetries": 20,
"socketTimeout": 30,
"downloadTimeoutSeconds": 1800,
"convertTimeoutSeconds": 600,
"asrChunkTimeoutSeconds": 180,
"logFile": "~/Downloads/yt2text.log"
}CLI flags override config values. Environment variables:
YT2TEXT_CONFIGYT2TEXT_OUTPUT_DIRYT2TEXT_LANGUAGEYT2TEXT_CACHE_DIR
Cookies
For many YouTube videos, cookies are not needed. When YouTube asks for sign-in or bot verification, use browser cookies:
yt2text-watch "https://youtu.be/..." --cookies-from-browser chromemacOS defaults to --cookies-from-browser auto. You can disable this:
yt2text-watch "https://youtu.be/..." --no-browser-cookiesManual Netscape cookies file:
yt2text-watch "https://youtu.be/..." --cookies ./cookies.txtLocal ASR Models
Local ASR uses sherpa-onnx-node and Whisper-style models from the sherpa-onnx project. First use downloads model files into the cache directory. Larger models usually improve accuracy but cost more disk, memory, and CPU time.
yt2text-watch "https://youtu.be/..." --asr local --model tiny
yt2text-watch "https://youtu.be/..." --asr local --model small
yt2text-watch "https://youtu.be/..." --multilingual --model mediumUse --offline only after dependencies and models are already cached.
Speaker Labels
--diarize adds anonymous labels such as Speaker 1 and Speaker 2. It does not identify real people by name. Diarization downloads extra local models on first use.
yt2text-watch "https://youtu.be/..." --asr local --diarizeTroubleshooting
Run:
yt2text-watch doctor --fix --show-configCommon fixes:
npmsayspackage.jsonis missing: run commands from the project directory, or install/link the CLI globally first.- YouTube requires sign-in: try
--cookies-from-browser chrome,safari, orfirefox. - Browser cookie database is locked: fully quit the browser and retry.
- 403 or bot verification: use cookies, then try
--update-ytdlp; if needed, configure--proxy. - macOS Speech permission denied: allow Speech Recognition for the helper when macOS prompts.
swiftcis missing: the CLI automatically opens the Xcode Command Line Tools installer on macOS. Finish the system installer and rerun the command; if it did not open, runxcode-select --install.- Local ASR dependency missing: reinstall without
--no-optional. - Local model extraction fails: make sure
tarwith bzip2 support is available. - Long files time out: increase
--download-timeout,--convert-timeout, or--asr-chunk-timeout.
Development
npm install
npm run check
npm run build
npm test
npm pack --dry-runUseful local commands:
node dist/cli.js doctor --show-config
node dist/cli.js "https://www.youtube.com/watch?v=ROrOGwJDneo" -l en-US
node dist/cli.js file ./audio.wav -l en-US -o ./outPrivacy
The CLI stores downloaded tools, ASR models, temporary audio, and optional logs on your machine. Temporary work files are removed after each run. If you pass browser cookies or a manual cookies file, yt-dlp reads them locally to access the requested media. Be careful when sharing logs, configs, transcripts, and retained audio files.
Acknowledgements
- Original project: lopleec/yt2text.
yt-dlpfor media extraction and download support.FFmpegand@ffmpeg-installer/ffmpegfor audio conversion.sherpa-onnxandsherpa-onnx-nodefor local ASR and diarization runtime support.- OpenAI Whisper model architecture and the broader open speech recognition ecosystem.
- Apple Speech.framework and Windows SAPI for platform speech interfaces.
Disclaimer
This project is not affiliated with YouTube, Google, Apple, Microsoft, FFmpeg, yt-dlp, sherpa-onnx, or OpenAI.
Use this tool only for media you are allowed to access, download, and transcribe. You are responsible for complying with copyright law, platform terms of service, privacy rules, consent requirements, and any laws that apply in your jurisdiction. The software is provided as-is under the MIT License, without warranty.
License
MIT.
