Super lightweight utility to record a live stream from an M3U playlist (or a direct URL) using FFmpeg stream copy. This allows for lossless capture of a live stream without re-encoding.
- Manual early stop (for example Ctrl+C in the FFmpeg console, or ending the FFmpeg process) is now treated as a supported success path when output data exists.
- Early-stopped
.tsrecordings now run a normalization/remux pass (timestamp repair + corruption-tolerant copy) before caption finalization. - This was added to improve strict-player compatibility, especially VLC, for partial recordings.
- Caption sidecar behavior (
off,post_only,live_ccextractor) with optional per-job post scan/repair. - Optional per-job Comskip commercial detection (
.tsonly) with chapter sidecars.
From the repo root, this script downloads missing bundled tools (skips FFmpeg and CCExtractor when already installed), runs go test / go build, and produces iptvrecord.exe and gui\iptv-gui.exe. Close any running gui\iptv-gui.exe before rebuilding.
cd "C:\Visual Studio\IPTV-DVR"
powershell -ExecutionPolicy Bypass -File .\scripts\build.ps1Optional flags: -SkipGo, -SkipGui, -SkipCCExtractor, -SkipComskip, -SkipTests, -ForceFfmpeg, -ForceCCExtractor, -ForceComskip. scripts/build_gui_exe.ps1 calls the same script for backward compatibility.
Manual downloads (only if you are not using build.ps1):
powershell -ExecutionPolicy Bypass -File .\scripts\download_ffmpeg.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\download_ccextractor.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\download_comskip.ps1The Tkinter GUI and frozen iptv-gui.exe expect:
| Tool | Path (dev) | Notes |
|---|---|---|
| FFmpeg | gui\ffmpeg\ffmpeg.exe |
GPL build from BtbN/FFmpeg-Builds; see ffmpeg.org/legal.html. |
| CCExtractor (optional) | gui\tools\ccextractor\ccextractor.exe |
Post-record .srt via Captions = post_only + Post = ccextractor. Runtime must include sibling DLLs (libgpac.dll, FFmpeg DLLs, etc.). |
| Comskip (optional) | gui\tools\comskip\comskip.exe |
Post-record commercial detection via Post - Detect commercials (Comskip) (.ts jobs only). Requires comskip.ini beside the exe. |
While recording, FFmpeg and live CCExtractor subprocesses started by the app may receive the Windows console safeguards described under Recording console and accidental close. The Tkinter GUI itself is never modified.
Headless recording for Task Scheduler uses the same executable:
iptv-gui.exe run-job --job-id <uuid>
(config.json and logs\ live in the same folder as iptv-gui.exe when you run the frozen build.)
On Windows, the app tries to prevent accidental closure of FFmpeg and live CCExtractor recording consoles (the ConsoleWindowClass window owned by each child process, found by PID). It does not lock down unrelated windows.
| Window / process | Protected from accidental close? |
|---|---|
| FFmpeg recording console (when guard attaches) | Yes — system-menu Close removed; title shows [FFmpeg - PROTECTED - do not close]; grayed menu line Close disabled while FFmpeg is recording |
Live CCExtractor console (live_ccextractor mode, when guard attaches) |
Yes — title shows [CCExtractor - PROTECTED - do not close]; grayed menu line Close disabled while CCExtractor is extracting captions |
Tkinter main window (iptv-gui.exe / python gui\main.py) |
No — close/quit works normally (aside from save/sync prompts on exit) |
| GUI dialogs (Job Editor, channel picker, wake settings, etc.) | No |
| Post-record CCExtractor (file-mode extraction) | Yes — dedicated console with the same CCExtractor guard when the HWND appears |
Your shell (cmd/PowerShell where you launched iptvrecord) |
No |
| Other apps (VLC, Explorer, etc.) | No |
You may close the GUI and leave a detached Run selected job now or scheduled run-job recording running; stopping the recording means ending the FFmpeg (and live CCExtractor, if running) processes, not closing the Tk window.
| Path | How the process is started | Console behavior |
|---|---|---|
GUI run-job, Test 15s capture, caption repair/extract FFmpeg |
gui/recorder.py run_ffmpeg — stdout/stderr piped to log files |
FFmpeg may open a console HWND; guard polls up to ~5s and applies User32 tweaks if that HWND exists |
GUI / run-job live and post CCExtractor |
gui/caption_worker.py / gui/recorder.py run_tool — CREATE_NEW_CONSOLE, stdout piped to logs |
Dedicated CCExtractor console; guard attaches to that window |
iptvrecord record FFmpeg and Go post-record caption FFmpeg |
internal/winffmpeg — CREATE_NEW_CONSOLE |
Dedicated visible FFmpeg console; guard attaches to that window |
iptvrecord record live and post CCExtractor |
internal/ccextractor, post.go — CREATE_NEW_CONSOLE |
Dedicated CCExtractor console; guard attaches to that window |
| macOS / Linux | Plain exec / piped subprocess |
No console guard |
Shared User32 logic in gui/console_guard.py, internal/winffmpeg, and live worker startup in internal/ccextractor. Short-lived FFmpeg used only for post-record caption extract or TS repair gets the same guard while that FFmpeg process runs.
iptvrecord from cmd/PowerShell: your shell prints running: ffmpeg ...; FFmpeg itself runs in its own console window (not in the shell window).
Still allowed (not blocked by the guard): ending FFmpeg or live CCExtractor with Ctrl+C in its console, Task Manager, taskkill, or killing the parent Python/Go host. If no child console HWND appears within ~5s, the guard never attaches (recording still works; output goes to logs).
Ending the FFmpeg recording process early is supported (for example Ctrl+C in the FFmpeg console, not the disabled system-menu Close).
- If FFmpeg exits with a user-stop code (Windows
STATUS_CONTROL_C_EXIT, etc.) and output bytes exist, the run is treated as a successful early stop. - The app performs a best-effort TS normalization pass before finalizing sidecars:
-fflags +genpts+discardcorrupt-err_detect ignore_err-avoid_negative_ts make_zero- video timestamp rewrite via
settsbitstream filter (using probedavg_frame_rate)
- This specifically improves strict-player compatibility (for example, VLC) for early-stopped
.tsrecordings.
- Add / edit / remove M3U sources (local file or
http(s)URL). - Define recording jobs: channel name, duration (
90m,1h30m, …), output folder, filename pattern ({date},{time},{channel}), output format (ts,mp4,mkv,mov). - Daily or weekly (weekday checkboxes) at a wall-clock time.
- Save config writes
config.json(next to the app: repo root when usingpython gui\main.py, or thegui\folder when usingiptv-gui.exe). Sync Windows tasks registers recurring tasks that run either
pythonw.exe "…\gui\main.py" run-job --job-id <uuid>(dev) or
"…\gui\iptv-gui.exe" run-job --job-id <uuid>(frozen build), with working directory set accordingly. - Run selected job now starts the selected job immediately in background (handy for validation before waiting on a schedule).
- Test 15s capture uses the selected job’s source/channel.
- Optional per job: Captions mode (
off,post_only,live_ccextractor) — see Closed captions. Legacyauto/ download closed captions load aspost_only.
Run (Python 3.10+ with Tk on Windows):
cd "C:\Visual Studio\IPTV-DVR"
python .\gui\main.py- Windows (primary target; FFmpeg invocation is standard and works elsewhere too).
- FFmpeg on
PATH, or pass--ffmpegtoffmpeg.exe(e.g. a Gyan.dev build). - On Windows,
iptvrecord recordstarts FFmpeg in a dedicated console and applies the same User32 guard on that FFmpeg window (Recording console and accidental close). Live CCExtractor uses the same guard when enabled.
Use scripts/build.ps1 for iptvrecord.exe and the GUI, or build the CLI only:
go build -o iptvrecord.exe ./cmd/iptvrecordCopy iptvrecord.exe anywhere you like.
.\iptvrecord.exe list-channels --m3u C:\path\playlist.m3u
.\iptvrecord.exe list-channels --m3u "https://example.com/playlist.m3u"On Windows, FFmpeg and live CCExtractor run in separate consoles when applicable; accidental-close protection applies to those child windows only (details).
From playlist (match channel name exactly, case-insensitive, or a unique substring):
.\iptvrecord.exe record --m3u C:\path\playlist.m3u --channel "BBC One" --duration 90m --out D:\recordings\show.tsDirect URL (no M3U):
.\iptvrecord.exe record --url "http://example.com/stream" --duration 1h --out D:\rec.tsOptional overrides:
.\iptvrecord.exe record ... --ffmpeg "C:\ffmpeg\bin\ffmpeg.exe" --user-agent "VLC/3.0" --referer "https://provider/"Enable captions (--captions is shorthand for --caption-mode auto):
.\iptvrecord.exe record ... --out D:\rec.ts --captions
.\iptvrecord.exe record ... --out D:\rec.ts --caption-mode live_ccextractor --ccextractor C:\tools\ccextractor.exeRegisters a one-time Windows scheduled task that runs the same record command at --at:
.\iptvrecord.exe schedule --at 2026-05-02T20:00:00-05:00 --m3u C:\path\playlist.m3u --channel "BBC One" --duration 90m --out D:\rec.tsLocal time without zone:
.\iptvrecord.exe schedule --at 2026-05-02T20:00:00 --m3u ... --channel ... --duration 90m --out ...Optional task name:
.\iptvrecord.exe schedule --task-name "MyRecording" --at ....ts (MPEG-TS) is recommended with -c copy for broad compatibility. To remux to MP4 without re-encoding (when the bitstream allows):
ffmpeg -i recorded.ts -c copy recorded.mp4Recording uses stream copy (-c copy) for video and audio. Caption behavior depends on mode and how the broadcaster delivers captions:
| Mode | Behavior |
|---|---|
off |
No sidecar work. |
post_only |
After record: run the selected post processor (ffmpeg or ccextractor) to produce .srt (.ts only). |
live_ccextractor |
During record: CCExtractor stream mode (--stream 15 -out=srt) writes .srt.partial → validated .srt on finish. |
Legacy configs with caption_mode: auto or download_captions: true load as post_only.
| Delivery | What you get in .ts |
Sidecar |
|---|---|---|
| Embedded in video (CEA-608 / ATSC A53) | CC stays in H.264 (VLC can show CC). | Post-extract .srt after recording (FFmpeg or CCExtractor). |
| HLS subtitles (WebVTT renditions) | Video+audio copy. | Live .vtt when ffprobe sees a subtitle stream; post muxed copy otherwise. |
Post processor selector: In Job Editor, choose Post = ffmpeg or ccextractor. This dropdown is editable only when Captions mode is post_only.
Post scan/repair: Optional per-job checkbox Post - Scan for broken stream/repair. When checked, the finished .ts is scanned after recording and repaired only if issues are detected. When unchecked, no post-record TS repair runs.
Optional per-job checkbox Post - Detect commercials (Comskip) runs after recording completes (and after optional TS repair and caption finalize). The original .ts is never modified. The commercial cutlist .edl is written beside the recording (next to the .srt). Comskip work files (its .log, .logo, frame .txt, chapters, and merge manifest) stay under logs/comskip_work/<recording-stem>/.
| Artifact | Location | Purpose |
|---|---|---|
<stem>.edl |
Beside recording | Kodi/MPlayer-style commercial breaks (edl_skip_field=3) |
<stem>.txt |
logs/comskip_work/<stem>/ |
Comskip frame cutlist (v2 header) |
<stem>.chapters.ffmeta |
logs/comskip_work/<stem>/ |
FFmpeg chapter metadata (episode blocks + commercial markers) |
<stem>.comskip.json |
logs/comskip_work/<stem>/ |
Merge manifest (mode, segments, fps) |
<stem>.log / <stem>.logo.txt |
logs/comskip_work/<stem>/ |
Comskip run log and logo mask |
Requirements: Output format must be .ts. Install Comskip with:
powershell -ExecutionPolicy Bypass -File .\scripts\download_comskip.ps1Bundled layout: gui\tools\comskip\comskip.exe + comskip.ini + DLLs from the official Windows zip.
Multi-episode recordings: Long back-to-back blocks are pre-segmented using broadcast-style black/silence gaps plus duration heuristics. Comskip runs per segment when multiple episodes are detected; commercial markers are merged onto the full timeline.
CLI equivalent: iptvrecord record ... --comskip (.ts output only).
Failure contract: Comskip errors are logged to the job log and do not fail the recording job.
Practical takeaway: Set job Captions to post_only, select your preferred Post processor (ccextractor recommended for broadcast .ts), and use .ts output for automated .srt sidecar generation after each recording.
Caption/post CLI flags: --caption-post-processor ffmpeg|ccextractor (applies to post-record extraction in post_only). --post-scan-repair enables the optional TS scan/repair step.
If you can see embedded CC in playback but no .srt sidecar appears:
- Check the job log:
gui\logs\job_<job-id>.log. - Look for these signatures:
captions: live worker produced no valid SRT
(live worker ran, but no valid timed-caption output was produced)a value is required for '--stream <STREAM>'
(CCExtractor invocation mismatch; worker args must use--stream 15)libgpac.dll was not found
(CCExtractor runtime install incomplete; rerunscripts\download_ccextractor.ps1)Error reading HTTP response: End of file/ HLS parse errors
(provider/network instability; FFmpeg reconnect logic retries but long failures can still abort)
- Confirm output format is
.tsfor live embedded-caption extraction. - If needed, switch mode to
post_only, then set Post to eitherffmpegorccextractorto force that post extractor after recording.
If a run was stopped manually and you see a .failed.txt marker:
- Check
gui\logs\job_<job-id>.logfor FFmpeg exit code3221225786(Windows control-close / Ctrl+C path). - Verify whether a non-empty
.tswas produced. - If a non-empty
.tsexists, current behavior should keep that file and treat the stop as a successful early stop after TS normalization.
Playlist tip: If an M3U lists the same channel name more than once, the app matches the first matching entry (#EXTINF then URL).
Record only streams you are allowed to record. DRM-protected sources are not supported.