HLS .0 cache → MP4
Huawei Browser sniff-download stores each video as a UUID folder of encrypted HLS, then appends .0 to every filename. The phone player reads its own DB + these names. PC players and FFmpeg do not.
This skill converts that cache by hardlinking .ts.0→.ts and .key.0→.key, feeding the original playlist to FFmpeg (-c copy), then deleting only the temp names. Verified on Huawei Browser caches. Other vendors are untested.
When to use
Activate if any of these are true:
- Folder names look like
64f1723d-421b-495c-8a10-85ab7d74ddab - Files named
aq.m3u8.0,*.ts.0,*.key.0 - User says 嗅探下载 / 华为浏览器 / VideoData / 手机能播电脑不能
- Log lines mention
DLEngine,DLSegMgr,download_content_
Do not use for: downloading an m3u8 URL (that is N_m3u8DL-RE / 猫抓), Widevine/PSSH DRM, or remuxing already-plain .mp4.
Layout (do not trust workspace search)
Cursor/ripgrep omit binary *.0 files. Glob **/*.ts.0 returns 0 even when 73 segments exist. Prove files with a directory listing (Get-ChildItem / dir / Path.iterdir), or by reading a playlist-named path (binary read error = file exists).
Each video folder:
<uuid>/
aq.m3u8.0 # playlist; references foo.ts and foo.key (no .0)
<digits>.ts.0 # AES-128 MPEG-TS, ~10s each
<digits>.key.0 # 16-byte HLS key
Optional sibling log/<uuid>.txt is DLEngine text, not media.
Read references/layout.md if the tree differs (missing key, nested dirs, no aq.m3u8.0).
Workflow
Hard gate — do this before any convert or download.
0. Find ffmpeg, or stop and ask
Bounded search only (never recurse a whole drive). scripts/convert.py uses the same order:
--ffmpeg/ envHLS_DOT0_FFMPEG<cache-root>/ffmpeg.exeor<cache-root>/ffmpegPATH(where.exe ffmpeg/command -v ffmpeg)- A few common dirs if still missing:
%LOCALAPPDATA%\Microsoft\WinGet\Links\ffmpeg.exe,%USERPROFILE%\scoop\shims\ffmpeg.exe,C:\ffmpeg\bin\ffmpeg.exe,/opt/homebrew/bin/ffmpeg,/usr/local/bin/ffmpeg
Run ffmpeg -version (or that absolute path). Success → go to step 1.
If not found, stop. Ask the user exactly these two options; do not install or convert until they answer:
- A. They install or drop
ffmpegthemselves, then tell you when ready — re-run this search +-version. - B. You install it — detect OS and arch first (
win32+PROCESSOR_ARCHITECTURE/uname -s+uname -m). Then follow only the matching default in references/ffmpeg-install.md.
Install rules: one default command, one fallback if it fails, then ask again. Do not install Homebrew / Chocolatey / Scoop just to get ffmpeg unless the user agrees. Do not commit a downloaded binary into this skill repo. After a successful install, verify -version in a new shell or via the installer path (Windows PATH often stale).
1. Plan (print, then convert)
List UUID directories (8-4-4-4-12 hex). For each, count *.ts.0, *.key.0, and whether aq.m3u8.0 exists. Skip folders with 0 segments or no key.
Default order: folder name A–Z. Number outputs 01.mp4, 02.mp4, … in --out-dir (default <cache-root>/mp4) from --start (default 1). The script skips a name when that mp4 already exists (JSON skipped: true) unless --force. Re-run the same command after a partial batch — already-done numbers stay, the rest convert. Do not invent a second numbering scheme.
2. Run the bundled script (do not reinvent)
From this skill root:
python scripts/convert.py --root <cache-root> --dry-run
python scripts/convert.py --root <cache-root> --out-dir <cache-root>/mp4 --start 1
python scripts/convert.py --root <cache-root> --folder <uuid> --name 01 --force
Always pass --dry-run first on a new tree. Script stdout is JSON; progress goes to stderr.
Do not rewrite the playlist to *.ts.0. FFmpeg rejects that extension even with -allowed_extensions ALL (not in allowed_segment_extensions). Hardlink/copy to the names already in aq.m3u8.0.
Do not probe outputs with PowerShell $ErrorActionPreference = "Stop" plus ffmpeg -i file -f null -. FFmpeg writes banner to stderr; PowerShell treats it as a terminating error and a catch that deletes the mp4 will wipe a successful convert.
Exact FFmpeg flags (fragile — do not drop):
ffmpeg -y -hide_banner -loglevel warning -stats -allowed_extensions ALL -protocol_whitelist file,crypto,data -i play.m3u8 -c copy out.mp4
3. Cleanup and report
The script removes only temp names it created this run (plus leftover hardlinks that already point at the .0 files) and always removes play.m3u8. Original *.ts.0 / *.key.0 / aq.m3u8.0 stay. It does not delete a pre-existing .ts / .key that is not a hardlink to the .0 file.
Report a table: number, size, duration if known, sps_warning. See references/troubleshooting.md for SPS and other failures.
If the user asks to re-encode a SPS-warning file: ffmpeg -i 04.mp4 -c:v libx264 -crf 18 -c:a copy 04_fixed.mp4 — only those files, not the whole batch.
Checklist
- Directory listing shows
.ts.0and.key.0(search tools may lie) - ffmpeg found (bounded search) or user chose A/B;
-versionok; no full-disk scan - If installing: OS/arch detected; only the matching commands in
references/ffmpeg-install.md -
--dry-runplan looks right (counts, output names,SKIPon existing mp4s) - One folder converted and user can play it (unless they said convert all)
- Batch JSON
ok/skipped/ per-item errors; temp links gone - Mention SPS-warning numbers so the user can spot-check
Output format
Converted N/M videos → <out-dir>
01.mp4 101 MB (~12:10) ok
04.mp4 251 MB (~23:59) SPS warning — check start and ~10s cuts
FAIL <uuid> missing key.0
Gotchas
.0is the real media. Glob/index skipping binaries is not “files missing.”- Playlist
URI="….key"and….ts— on disk they are….key.0/….ts.0. - Hardlinks must stay on the same volume; if
os.linkfails, copy (slow, extra disk). non-existing SPS 0 referenced in buffering periodis usually cosmetic on-c copy. Check 10s boundaries; re-encode only if playback is broken.- GUI tools (N_m3u8DL-RE, m3u8-to-mp4) expect standard extensions. They work after rename/hardlink, not on raw Huawei names.
- This is standard HLS AES-128 with a local key (
no psshin DLEngine logs), not Widevine circumvention. - No ffmpeg → ask A/B first. Do not silently
winget/brew/aptor download an exe. - Existing
01.mp4… are skipped, not errors. Use--forceonly when the user wants those files overwritten.