
Youtube To Bookplayer
- 66 installs
- 62 repo stars
- Updated August 3, 2026
- terrylica/cc-skills
Helps with ai & agent building tasks.
About
youtube-to-bookplayer is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- youtube-to-bookplayer
- AI & Agent Building
- AI-coding skill
Youtube To Bookplayer by the numbers
- 66 all-time installs (skills.sh)
- Ranked #6,006 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/terrylica/cc-skills --skill youtube-to-bookplayerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 66 |
|---|---|
| repo stars | ★ 62 |
| Last updated | August 3, 2026 |
| Repository | terrylica/cc-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
youtube-to-bookplayer
Download audio from a YouTube video, tag metadata, and push to BookPlayer on iPhone via USB.
BookPlayer is an iOS audiobook player that resumes playback position — ideal for long-form YouTube content (lectures, audiobooks, podcasts). Files pushed to its /Documents/ directory are auto-imported on next app launch.
---
Self-Evolving Skill: This skill improves through use. If instructions are wrong, parameters drifted, or a workaround was needed — fix this file immediately, don't defer. Only update for real, reproducible issues.
Task Template
Execute phases 0–5 sequentially. Each phase has a [Preflight], [Ask], [Execute], or [Verify] tag indicating its nature. Do not skip phases.
---
Phase 0: Preflight [Preflight]
Check all required tools and device connectivity. Fail fast — do not proceed if any check fails.
# Tool availability
TOOLS_OK=true
for tool in yt-dlp ffmpeg exiftool; do
if command -v "$tool" &>/dev/null; then
echo "$tool: OK ($(command -v "$tool"))"
else
echo "$tool: MISSING"
TOOLS_OK=false
fi
done
# pymobiledevice3 (may only be available via uvx)
if command -v pymobiledevice3 &>/dev/null; then
echo "pymobiledevice3: OK ($(command -v pymobiledevice3))"
else
if uvx --python 3.14 --from pymobiledevice3 pymobiledevice3 --help &>/dev/null 2>&1; then
echo "pymobiledevice3: OK (via uvx)"
else
echo "pymobiledevice3: MISSING"
TOOLS_OK=false
fi
fi
echo "---"
[ "$TOOLS_OK" = true ] && echo "All tools OK" || echo "BLOCKED: Install missing tools (see table below)"If tools are missing:
| Tool | Install Command |
|---|---|
yt-dlp | brew install yt-dlp |
ffmpeg | brew install ffmpeg |
exiftool | brew install exiftool |
pymobiledevice3 | uvx --python 3.14 --from pymobiledevice3 pymobiledevice3 --help |
Device check (only after tools pass):
# Check for connected iOS device
pymobiledevice3 usbmux list 2>/dev/null || uvx --python 3.14 --from pymobiledevice3 pymobiledevice3 usbmux list
# Check BookPlayer is installed
pymobiledevice3 apps list --no-color 2>/dev/null | grep -i "audiobookplayer\|bookplayer" || \
uvx --python 3.14 --from pymobiledevice3 pymobiledevice3 apps list --no-color 2>/dev/null | grep -i "audiobookplayer\|bookplayer"If no device found: ask user to connect iPhone via USB, unlock it, and tap "Trust This Computer". If BookPlayer not found: ask user to install BookPlayer from the App Store.
---
Phase 1: Accept URL & Confirm [Ask]
If `$ARGUMENTS[0]` is provided, use it as the YouTube URL. Otherwise, use AskUserQuestion to ask for the URL.
Preview metadata before proceeding:
yt-dlp --dump-json --no-download "$URL" 2>/dev/null | python3 -c "
import json, sys
d = json.load(sys.stdin)
hrs, rem = divmod(int(d.get('duration', 0)), 3600)
mins, secs = divmod(rem, 60)
print(f\"Title: {d.get('title', 'Unknown')}\")
print(f\"Channel: {d.get('channel', 'Unknown')}\")
print(f\"Duration: {hrs}h {mins}m {secs}s\")
print(f\"Upload: {d.get('upload_date', 'Unknown')}\")
"Use AskUserQuestion to confirm:
- Title, channel, duration look correct
- Whether to customize the metadata (title/artist/album) or use defaults from yt-dlp
---
Phase 2: Download Audio [Execute]
WORK_DIR=$(mktemp -d)
echo "Working directory: $WORK_DIR"
yt-dlp -x --audio-format m4a --audio-quality 0 --no-playlist \
-o "$WORK_DIR/%(title).100B.%(ext)s" \
"$URL"
# Show result
ls -lh "$WORK_DIR"/*.m4aNotes:
--audio-quality 0= best available quality%(title).100Btruncates filename to 100 bytes (prevents filesystem issues)--no-playlistensures single video download even from playlist URLs- ffmpeg is auto-invoked by yt-dlp for M4A conversion
---
Phase 3: Tag Metadata [Execute]
Extract metadata from yt-dlp JSON and apply to the M4A file:
# Get the downloaded file path
M4A_FILE=$(ls "$WORK_DIR"/*.m4a | head -1)
# Apply metadata (use values confirmed in Phase 1, or yt-dlp defaults)
exiftool -overwrite_original \
-Title="$TITLE" \
-Artist="$ARTIST" \
-Album="YouTube Audio" \
"$M4A_FILE"
# Verify tags
exiftool -Title -Artist -Album "$M4A_FILE"Variables (from Phase 1 confirmation):
$TITLE— Video title (or user-customized)$ARTIST— Channel name (or user-customized)- Album defaults to "YouTube Audio" unless user specifies otherwise
---
Phase 4: Push to BookPlayer [Execute]
CRITICAL: Use the Python API withdocuments_only=True. The CLIpymobiledevice3 apps pushuses VendContainer mode and will not work with BookPlayer.
M4A_FILE=$(ls "$WORK_DIR"/*.m4a | head -1)
FILENAME=$(basename "$M4A_FILE")
uvx --python 3.14 --from pymobiledevice3 python3 << 'PYEOF'
import sys
from pathlib import Path
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.house_arrest import HouseArrestService
local_path = sys.argv[1] if len(sys.argv) > 1 else None
if not local_path:
# Find the m4a file from environment
import glob, os
work_dir = os.environ.get("WORK_DIR", "/tmp")
files = glob.glob(os.path.join(work_dir, "*.m4a"))
if not files:
print("ERROR: No .m4a file found in work directory")
sys.exit(1)
local_path = files[0]
file_path = Path(local_path)
filename = file_path.name
file_data = file_path.read_bytes()
size_mb = len(file_data) / (1024 * 1024)
print(f"Pushing: {filename} ({size_mb:.1f} MB)")
lockdown = create_using_usbmux()
service = HouseArrestService(
lockdown=lockdown,
bundle_id="com.tortugapower.audiobookplayer",
documents_only=True # CRITICAL: VendDocuments mode
)
service.set_file_contents(f"/Documents/{filename}", file_data)
print(f"SUCCESS: {filename} pushed to BookPlayer /Documents/")
PYEOFAnti-pattern — DO NOT USE:
# WRONG: This uses VendContainer mode and fails silently on BookPlayer
pymobiledevice3 apps push com.tortugapower.audiobookplayer /path/to/file.m4a---
Phase 5: Verify [Verify]
List BookPlayer's /Documents/ directory to confirm the file arrived:
uvx --python 3.14 --from pymobiledevice3 python3 << 'PYEOF'
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.house_arrest import HouseArrestService
lockdown = create_using_usbmux()
service = HouseArrestService(
lockdown=lockdown,
bundle_id="com.tortugapower.audiobookplayer",
documents_only=True
)
files = service.listdir("/Documents/")
print("BookPlayer /Documents/ contents:")
for f in sorted(files):
if f.startswith('.'):
continue
try:
info = service.stat(f"/Documents/{f}")
size_mb = info.get('st_size', 0) / (1024 * 1024)
print(f" {f} ({size_mb:.1f} MB)")
except Exception:
print(f" {f}")
PYEOFReport to user:
- File name and size in BookPlayer
- Duration (from Phase 1 metadata)
- Remind: open BookPlayer on iPhone to see the new file (force-quit and reopen if it doesn't appear)
Cleanup:
# Remove temp working directory
rm -rf "$WORK_DIR"
echo "Cleaned up: $WORK_DIR"---
Troubleshooting Quick Reference
| Problem | Quick Fix |
|---|---|
| No device found | Unlock iPhone, re-plug USB, tap "Trust" |
| File not in BookPlayer | You used the CLI — must use Python API with documents_only=True |
| Wrong metadata shown | Re-run Phase 3 with correct -Title/-Artist values |
Full troubleshooting: references/troubleshooting.md
---
References
- Tool Reference — yt-dlp flags, pymobiledevice3 API, exiftool tags
- Troubleshooting — Known issues, diagnostic commands
- Evolution Log — Origin and key discoveries
---
Post-Change Checklist
When modifying this skill, verify:
- [ ] Phase 0 preflight catches all missing tools with correct install commands
- [ ] Phase 4 uses Python API with
documents_only=True(never CLIapps push) - [ ] No hardcoded paths — uses
$HOME,mktemp,command -v,create_using_usbmux() - [ ] Python commands use
--python 3.14(per global policy) - [ ] Anti-pattern warning is preserved in Phase 4
Post-Execution Reflection
After this skill completes, reflect before closing the task:
0. Locate yourself. — Find this SKILL.md's canonical path (Glob for this skill's name) before editing. All corrections target THIS file and its sibling references/ — never other documentation. 1. What failed? — Fix the instruction that caused it. If it could recur, add it as an anti-pattern. 2. What worked better than expected? — Promote it to recommended practice. Document why. 3. What drifted? — Any script, reference, or external dependency that no longer matches reality gets fixed now. 4. Log it. — Every change gets an evolution-log entry with trigger, fix, and evidence.
Do NOT defer. The next invocation inherits whatever you leave behind
Evolution Log
2026-03-02 — Initial Creation
Origin: Manual workflow developed during a session to download ~12-hour YouTube audiobook content and transfer to BookPlayer on iPhone via USB.
Pipeline: yt-dlp → exiftool → pymobiledevice3 HouseArrest API
Key Discoveries:
1. VendDocuments vs VendContainer: The pymobiledevice3 apps push CLI command uses VendContainer mode internally. BookPlayer exposes its Documents directory via VendDocuments mode. The CLI command fails silently — no error, but the file never appears in BookPlayer. The Python API with documents_only=True parameter correctly uses VendDocuments mode and works.
2. BookPlayer auto-import: Files placed in /Documents/ are automatically detected by BookPlayer on next app launch. No special naming convention required — BookPlayer reads M4A metadata (title, artist, album) for display.
3. Large file handling: ~12-hour audiobooks produce ~300-500MB M4A files. The set_file_contents() API handles these without issue, but reading entire files into memory is required. For extremely large files (>1GB), chunked approaches may be needed.
4. yt-dlp audio quality: --audio-quality 0 gives the best available quality. Combined with --audio-format m4a, yt-dlp auto-invokes ffmpeg for conversion. The %(title).100B template truncates filenames to 100 bytes to avoid filesystem issues.
5. Metadata tagging: exiftool's -Title, -Artist, -Album tags map directly to what BookPlayer displays. The -overwrite_original flag prevents .m4a_original backup files cluttering the temp directory.
Codified as: media-tools:youtube-to-bookplayer skill in cc-skills marketplace.
Tool Reference
yt-dlp
YouTube audio extraction tool. Handles authentication, format selection, and download.
Key Flags
| Flag | Purpose |
|---|---|
-x / --extract-audio | Extract audio only (no video) |
--audio-format m4a | Convert to M4A (AAC) — BookPlayer's preferred format |
--audio-quality 0 | Best available audio quality |
--no-playlist | Download single video even if URL is part of a playlist |
--dump-json | Print metadata JSON without downloading (for preview) |
--no-download | Skip download (combine with --dump-json for metadata only) |
-o TEMPLATE | Output filename template |
Output Template
yt-dlp -x --audio-format m4a --audio-quality 0 --no-playlist \
-o "$WORK_DIR/%(title).100B.%(ext)s" "$URL"%(title).100B— Video title, truncated to 100 bytes (avoids filesystem path limits)%(ext)s— File extension (will bem4aafter conversion)
Metadata JSON (Key Fields)
yt-dlp --dump-json --no-download "$URL"Useful fields: title, duration (seconds), channel, upload_date, description, thumbnail.
---
pymobiledevice3
Python library and CLI for iOS device communication via USB.
Critical: VendDocuments API (Python)
BookPlayer uses VendDocuments mode. The CLI apps push command uses VendContainer and will not work.
Correct approach — Python API:
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.house_arrest import HouseArrestService
lockdown = create_using_usbmux()
bundle_id = "com.tortugapower.audiobookplayer"
service = HouseArrestService(lockdown=lockdown, bundle_id=bundle_id, documents_only=True)
service.set_file_contents(f"/Documents/{filename}", file_data)Run via uvx:
uvx --python 3.14 --from pymobiledevice3 python3 -c '
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.house_arrest import HouseArrestService
# ... script body
'Anti-Pattern (WRONG — VendContainer)
# THIS DOES NOT WORK FOR BOOKPLAYER
pymobiledevice3 apps push com.tortugapower.audiobookplayer /path/to/file.m4aThe CLI uses VendContainer mode internally. BookPlayer's container is not accessible this way.
Useful CLI Commands
| Command | Purpose |
|---|---|
pymobiledevice3 usbmux list | List connected iOS devices |
pymobiledevice3 apps list --no-color | List installed apps (grep for BookPlayer) |
Listing Files (Verification)
service = HouseArrestService(lockdown=lockdown, bundle_id=bundle_id, documents_only=True)
files = service.listdir("/Documents/")---
exiftool
Metadata tagging for M4A/AAC audio files.
Key Tags
| Tag | Maps To | BookPlayer Display |
|---|---|---|
-Title | Track title | Main title |
-Artist | Artist/author | Author line |
-Album | Album name | Collection grouping |
Usage
exiftool -overwrite_original \
-Title="Video Title" \
-Artist="Channel Name" \
-Album="YouTube Audio" \
"/path/to/file.m4a"-overwrite_originalprevents creation of.m4a_originalbackup files
---
ffmpeg
Audio format conversion engine. Not invoked directly — yt-dlp calls ffmpeg automatically when --audio-format m4a is specified.
Prerequisite
Must be installed (brew install ffmpeg) for yt-dlp's audio extraction to work. yt-dlp will error clearly if ffmpeg is missing.
Troubleshooting
Known Issues
| Problem | Cause | Solution |
|---|---|---|
pymobiledevice3 usbmux list returns empty | No device connected, or missing USB trust | Connect iPhone via USB cable, unlock device, tap "Trust This Computer" if prompted |
pymobiledevice3 apps push succeeds but file not in BookPlayer | CLI uses VendContainer mode; BookPlayer uses VendDocuments | Use Python API with documents_only=True — see tool-reference.md |
| BookPlayer shows file but wrong title/artist | Missing or incorrect metadata tags | Run exiftool -Title="..." -Artist="..." file.m4a before pushing |
| BookPlayer doesn't see newly pushed file | App needs restart to scan /Documents/ | Force-quit BookPlayer and reopen; file should appear |
yt-dlp returns HTTP 403 | YouTube rate limiting or geo-restriction | Wait a few minutes and retry; try with --cookies-from-browser safari if persistent |
yt-dlp errors about ffmpeg | ffmpeg not installed | brew install ffmpeg |
Python script fails with ConnectionFailedError | Device locked or USB not trusted | Unlock iPhone, re-plug USB, tap "Trust" |
lockdown creation fails with pairing error | Device has never been paired with this Mac | Open Finder, click the device, confirm trust on both Mac and iPhone |
| Out of memory during large file push | File read into memory exceeds available RAM | Rare for audio files (<1GB); if hit, close other apps or use chunked read |
uvx not found | mise/uv not in PATH | Ensure mise is activated in your shell profile |
Diagnostic Commands
# Check all tool availability
command -v yt-dlp && echo "yt-dlp: OK" || echo "yt-dlp: MISSING"
command -v ffmpeg && echo "ffmpeg: OK" || echo "ffmpeg: MISSING"
command -v exiftool && echo "exiftool: OK" || echo "exiftool: MISSING"
command -v pymobiledevice3 && echo "pmd3: OK" || echo "pmd3: MISSING (use uvx)"
# List connected iOS devices
pymobiledevice3 usbmux list
# Check if BookPlayer is installed
pymobiledevice3 apps list --no-color 2>/dev/null | grep -i bookplayer
# Test HouseArrest access (Python)
uvx --python 3.14 --from pymobiledevice3 python3 -c '
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.house_arrest import HouseArrestService
lockdown = create_using_usbmux()
svc = HouseArrestService(lockdown=lockdown, bundle_id="com.tortugapower.audiobookplayer", documents_only=True)
print("Documents:", svc.listdir("/Documents/"))
'
# Check yt-dlp can reach a URL (metadata only, no download)
yt-dlp --dump-json --no-download "https://www.youtube.com/watch?v=EXAMPLE"