{"id":51157336,"url":"https://github.com/risenxxx/dubsync","last_synced_at":"2026-06-26T11:30:22.241Z","repository":{"id":357233588,"uuid":"1235927032","full_name":"risenxxx/dubsync","owner":"risenxxx","description":"Sync localized dubs from a donor release onto a master video using FFT cross-correlation.","archived":false,"fork":false,"pushed_at":"2026-05-11T22:39:36.000Z","size":770,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T00:12:43.394Z","etag":null,"topics":["audio","audio-sync","cli","cross-correlation","dubbing","ffmpeg","fft","gui","home-theater","mkv","rust","subtitle-sync","video"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/risenxxx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T19:40:04.000Z","updated_at":"2026-05-11T22:39:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/risenxxx/dubsync","commit_stats":null,"previous_names":["risenxxx/dubsync"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/risenxxx/dubsync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/risenxxx%2Fdubsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/risenxxx%2Fdubsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/risenxxx%2Fdubsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/risenxxx%2Fdubsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/risenxxx","download_url":"https://codeload.github.com/risenxxx/dubsync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/risenxxx%2Fdubsync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34815669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-26T02:00:06.560Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audio","audio-sync","cli","cross-correlation","dubbing","ffmpeg","fft","gui","home-theater","mkv","rust","subtitle-sync","video"],"created_at":"2026-06-26T11:30:19.161Z","updated_at":"2026-06-26T11:30:22.221Z","avatar_url":"https://github.com/risenxxx.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/logo-512.png\" alt=\"dubsync\" width=\"180\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003edubsync\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  Sync localized dub tracks from a lower-quality donor release (e.g. 720p Web-DL) onto a high-quality master video (e.g. 4K Blu-ray) using FFT-based audio cross-correlation.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/risenxxx/dubsync/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/risenxxx/dubsync?style=flat\u0026color=blue\" alt=\"Latest release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/risenxxx/dubsync/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/risenxxx/dubsync/ci.yml?branch=main\u0026style=flat\u0026label=CI\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue?style=flat\" alt=\"License\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey?style=flat\" alt=\"Platforms\"\u003e\n\u003c/p\u003e\n\nBoth releases must share an \"anchor\" track in the same language (typically English).\n`dubsync` correlates the two anchors **once** to derive a piecewise Time Offset Map,\nthen applies that map to every selected donor dub. Each segment boundary is snapped\ninto a moment when the master is silent (so the splice is invisible behind a quiet\nscene transition), with optional ambient gap-filling via the `rubberband` time-\nstretcher when the dub itself has continuous music or room-tone you don't want\ninterrupted.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/screenshot.png\" alt=\"dubsync running on macOS\" width=\"820\"\u003e\n\u003c/p\u003e\n\n## Installation\n\n### From release (recommended)\n\nDownload the latest archive for your platform from the [Releases page](https://github.com/risenxxx/dubsync/releases):\n\n- **Windows installer** — `dubsync-setup-vX.Y.Z.exe` (Inno Setup, Start-Menu shortcut, optional desktop icon, per-user install — no admin elevation required).\n- **Windows portable** — `dubsync-vX.Y.Z-x86_64-pc-windows-msvc.zip` (extract anywhere, run `dubsync-gui.exe` or `dubsync.exe`).\n- **macOS (Apple Silicon)** — `dubsync-vX.Y.Z-aarch64-apple-darwin.dmg`. **Not signed with a Developer ID** — see \"Installing on macOS\" below for the one-time Gatekeeper bypass.\n- **Linux** — `dubsync-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz` (CLI only; build the GUI locally with `cargo build --release --features gui`).\n\nEach archive bundles:\n\n- `dubsync` / `dubsync.exe` — CLI binary.\n- `dubsync-gui` / `dubsync-gui.exe` — desktop GUI with drag-and-drop, persistent selections, live progress (Windows release; Linux users build from source).\n- `ffmpeg` + `ffprobe` (BtbN LGPL build on Windows/Linux, Homebrew on macOS) — required at runtime.\n- `rubberband` — used by the optional `--smooth-gaps` mode. Windows: ships with `sndfile.dll` next door (rubberband links it dynamically). macOS: every dylib dependency is bundled inside the `.app` and rewritten to `@executable_path/...` so no Homebrew is required on the target machine. Linux: dynamically linked against `libsndfile1` and `libsamplerate0` from your distro (preinstalled on most desktops; `apt install libsndfile1 libsamplerate0` if missing).\n- `FFMPEG-LICENSE.txt` (LGPL) and `RUBBERBAND-LICENSE.txt` (GPL-2.0+).\n\nExtract anywhere and run — no Rust toolchain, no separate ffmpeg/rubberband install required.\n\n#### Installing on Windows\n\nThe installer and portable archive are **not signed with a code-signing\ncertificate**. Windows SmartScreen (and sometimes Defender's heuristic scanner)\nwill warn on first launch. You only need to clear this once per file:\n\n1. Download `dubsync-setup-vX.Y.Z.exe` (or the portable `.zip`) from the\n   [Releases page](https://github.com/risenxxx/dubsync/releases).\n2. If Edge / Chrome warns *\"can harm your device\"* when downloading, click\n   **Keep** → **Keep anyway** (or **Show more → Keep** in newer Edge).\n3. Double-click the installer. SmartScreen shows *\"Windows protected your PC\"*.\n   Click **More info** → **Run anyway**.\n4. Walk through the Inno Setup wizard normally. Subsequent launches work without\n   warnings.\n\nIf Defender quarantines the file outright as a generic `Trojan:Win32/Wacatac` or\nsimilar heuristic detection (a known false positive for unsigned Rust/eframe\nbinaries), restore it from **Windows Security → Protection history** and add an\nexclusion for the install folder.\n\n#### Installing on macOS\n\nThe `.dmg` is **not signed with an Apple Developer ID** (and not notarized). On\nfirst launch macOS Gatekeeper will refuse to open the app with a *\"Apple could\nnot verify...\"* dialog. You only need to do this once:\n\n1. Open the `.dmg`, drag `Dubsync.app` into `/Applications`.\n2. Double-click `Dubsync.app` — Gatekeeper blocks it. Click **Done**.\n3. Open **System Settings → Privacy \u0026 Security**, scroll to the bottom. You will\n   see a line saying *\"Dubsync was blocked to protect your Mac\"* with an\n   **Open Anyway** button. Click it, confirm with Touch ID or your password.\n4. Subsequent launches work normally with a double-click.\n\n(If you prefer the terminal route: `xattr -cr /Applications/Dubsync.app`\nstrips the quarantine attribute in one shot.)\n\n### From source\n\n```sh\ncargo install --path .                     # installs `dubsync` (CLI)\ncargo build --release --features gui        # also produces `dubsync-gui`\n```\n\nRequirements:\n\n- Rust 1.75+\n- `ffmpeg` and `ffprobe` on `PATH` (Homebrew, apt, choco) — required.\n- `rubberband` on `PATH` — required only if you use `--smooth-gaps`.\n\n`dubsync` looks for these binaries next to its own executable first (so the\ndistributed archives just work), then falls back to `PATH`.\n\n## Usage\n\n### Desktop GUI\n\nDrag a master + donor file onto labelled drop zones, pick anchor + dub tracks,\nchoose an output folder + filename, click **Run**. Selections persist across\nsessions, so re-running on the next episode of a series is a one-click operation.\n\nThe right-hand pane shows a **chat-style pipeline timeline** — one card per phase\n(extract → fps-normalize → correlate → splice → remux) with live progress bars\nand an ETA. A final summary card lists the offset-map stats (segments, anchors\nkept, largest jump, total silence inserted, FPS handling, output path). The full\nraw `tracing` log is available under \"Show full log\" if you want the underlying\nffmpeg invocations.\n\nThe donor zone has a collapsible **\"Subtitle tracks\"** picker showing every\ntext-based donor sub for explicit selection. Image-based codecs (PGS / DVD-SUB)\nare shown as disabled with an explanation tooltip — they need OCR to time-shift,\nwhich is out of scope.\n\n### Interactive CLI\n\n```sh\ndubsync\n```\n\nWalks you through file selection, uses `ffprobe` to list audio tracks in each file,\nand presents pickers for the master anchor, donor anchor, and dub multi-select.\nAfter the basic flow, an **opt-in Advanced options** prompt covers everything that\ndoesn't fit the main flow — subtitle pass-through + donor sub picker, output\ncodec/bitrate, FPS handling mode, workflow modes (anchor-only validation, solo-dub,\ndiagnostic anchor swap), report file path, and a nested \"Tune correlation/splice\nparameters?\" sub-gate for the rare numeric knobs. Every prompt is pre-filled with\nthe current default, so Enter accepts.\n\nA copy-pasteable headless command is printed at the end so you can re-run with the\nsame selection without going through the prompts again.\n\n### Headless\n\n```sh\ndubsync \\\n  --master-file /path/master.mkv \\\n  --donor-file /path/donor.mkv \\\n  --master-anchor-track 1 \\\n  --donor-anchor-track 1 \\\n  --donor-dub-tracks 2,3,4 \\\n  --output-file /path/out.mkv\n```\n\nStream indices come from `ffprobe -show_streams \u003cfile\u003e`.\n\n### Common recipes\n\n**Cast a single dub to a TV** — output one audio track so the TV auto-plays it\ninstead of defaulting to English:\n\n```sh\ndubsync ... --donor-dub-tracks 5 --solo-dub --output-file cast.mkv\n```\n\n**Verify the offset map is correct** — replace the master English with the synced\ndonor English so you can A/B against master video lips:\n\n```sh\ndubsync ... --include-donor-anchor\n```\n\nIf the diagnostic English track drifts against the master video, the *boundaries* are\nwrong (not the dubs).\n\n**Smooth audible gaps with stretched ambient** — when the dub has continuous music\nor room-tone that gets interrupted by the inserted silence at splices:\n\n```sh\ndubsync ... --smooth-gaps true\n```\n\nOff by default. Speech is never stretched: the splicer probes the neighbour buffer\non each side of the gap and falls back to literal silence if either side is dialog,\npreserving lip-sync.\n\n**Anchor-only validation** — confirm the offset map is correct before committing\nto a full multi-dub run. Outputs an MKV with *only* the synced donor anchor so you\ncan A/B against the master video lips:\n\n```sh\ndubsync ... --anchor-only-validation --output-file validate.mkv\n```\n\n`--donor-dub-tracks` becomes optional in this mode — only the anchor is processed.\n\n**Override the output audio codec** — `eac3` is the default (matches the typical\ndonor and plays back natively on TVs / AVRs over HDMI). Switch to `flac` for a\nlossless re-encode or for 7.1 sources, `ac3` for maximum S/PDIF-bitstream\ncompatibility with older receivers, or `aac` for the smallest stereo files:\n\n```sh\ndubsync ... --dub-codec flac                 # lossless, 2–3× larger\ndubsync ... --dub-codec ac3                  # AC3 5.1 → 640 kbps auto\ndubsync ... --dub-codec aac --dub-bitrate 192\n```\n\n`ac3` and `eac3` are capped at 6 channels — for a 7.1 source dubsync rejects the\nconfig upfront and tells you to use FLAC.\n\n**Save a report file** — HTML / CSV / JSON describing the offset map, segment table,\nsplice strategy decisions, and run summary:\n\n```sh\ndubsync ... --report run.html                # styled HTML\ndubsync ... --report run.csv                 # spreadsheet-ready\ndubsync ... --report run.json                # structured JSON dump\n```\n\nA high-level run summary always prints to stderr regardless of `--report` (and\nappears as a final card in the GUI).\n\n**Override the auto fps-stretch** — when the donor's `r_frame_rate` lies (VFR mess,\nmis-encoded WEB-DL, broken muxer) and the audio is already at master speed:\n\n```sh\ndubsync ... --disable-fps-normalize          # skip the probe + stretch entirely\ndubsync ... --force-fps-ratio 1.0417         # bypass probe, use this ratio (PAL→film)\n```\n\nThese are mutually exclusive. With `--force-fps-ratio` you can also opt into\n`--pal-pitch-correction` to undo the PAL speed-up's pitch raise.\n\n**Carry subtitles through to the output** — master subtitle streams pass through\nby default (`-c:s copy`); donor forced subs (typical localised signs / on-screen\ntext) opt-in via `--include-donor-forced-subs`, with timecodes shifted through the\noffset map. Or pick specific donor sub tracks by index:\n\n```sh\ndubsync ... --include-donor-forced-subs                    # all forced auto\ndubsync ... --include-donor-subs 11,14                     # explicit indices\ndubsync ... --no-master-subs --include-donor-forced-subs   # donor-only subs\n```\n\nImage-based codecs (PGS / DVD-SUB / DVB-SUB) are detected and skipped with a\nwarning — they need OCR to time-shift, which is out of scope.\n\n### Flags\n\n#### Source / output\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--solo-dub`              | off     | Output a single audio track (first dub only). For \"Cast to device\". Drops the master anchor and any other selected dubs. |\n| `--include-donor-anchor`  | off     | Diagnostic: replace master anchor with synced donor anchor in output. |\n| `--anchor-only-validation`| off     | Sync only the donor anchor (no other dubs). Output MKV has just that synced track for A/B-against-master verification. `--donor-dub-tracks` becomes optional. |\n| `--keep-temp`             | off     | Keep the workspace dir + diagnostic JSON dumps.    |\n| `--temp-dir PATH`         |         | Override workspace root.                           |\n| `--threads N`             | auto    | Rayon worker count.                                |\n\n#### Output codec\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--dub-codec CODEC`       | eac3    | One of `eac3` / `ac3` / `flac` / `aac`. ac3 / eac3 capped at 6 channels — pre-pipeline validation rejects 7.1+ sources with a clear error. |\n| `--dub-bitrate KBPS`      | auto    | Override per-codec / per-channel default. Ignored for FLAC. Auto picks consumer-quality rates (e.g. eac3 5.1 → 640k, eac3 stereo → 256k, aac stereo → 192k). |\n| `--report PATH`           |         | Optional detailed report file. Format dispatched on extension: `.html` (styled page), `.csv` (spreadsheet), `.json` (structured dump). |\n\n#### FPS handling\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--disable-fps-normalize` | off     | Skip the auto fps-stretch entirely. Use when the donor's `r_frame_rate` lies (VFR sources, mis-encoded WEB-DL, broken muxers) and the audio is already at master speed. Mutually exclusive with `--force-fps-ratio`. |\n| `--force-fps-ratio RATIO` |         | Force a specific donor/master fps ratio for the stretch, bypassing the probe. `1.0` = no stretch; `25/24 ≈ 1.0417` corrects PAL→film. Mutually exclusive with `--disable-fps-normalize`. |\n| `--pal-pitch-correction BOOL` | false | When fps mismatch is corrected, additionally lower donor pitch by `12·log2(master_fps/donor_fps)` semitones to undo the PAL speed-up's pitch raise. |\n\n#### Subtitles\n\n| Flag                            | Default | Notes                                              |\n|---------------------------------|---------|----------------------------------------------------|\n| `--no-master-subs`              | off     | Drop master subtitle tracks from the output. By default every master sub stream is copied through unchanged (`-c:s copy`). |\n| `--include-donor-forced-subs`   | off     | Auto-extract donor subtitles marked `forced`, time-shift their timecodes through the offset map, mux them alongside master subs. |\n| `--include-donor-subs IDX[,IDX...]` |     | Explicit donor subtitle track indices to include (composes with `--include-donor-forced-subs` as a union). Image-based codecs are rejected with a clear error at validation time. |\n\n#### Correlation tuning\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--anchor-rate HZ`        | 16000   | Resample rate for anchor FFT (mono).               |\n| `--correlation-window-s S`| 30      | Master window length for sliding correlation. Lower this for short test clips (e.g. `10` for a 1-minute sample). |\n| `--max-drift-s S`         | 60      | Per-window donor search radius around the seed. Lower this when you know the offset is small. |\n| `--max-segment-jump-s S`  | 10      | Maximum offset jump accepted between consecutive segments. Larger jumps are suppressed as likely false positives — typically GCC-PHAT confused by repetitive end-credits music. Raise for content with genuinely large mid-show edits. |\n\n#### Splice tuning\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--silence-db DBFS`       | -45     | VAD threshold for silence detection.               |\n| `--silence-min-ms MS`     | 200     | Minimum silence run length.                        |\n| `--snap-radius-s S`       | 30      | Maximum master-time radius searched around each refined boundary for a master-anchor silence wide enough to absorb \\|Δ\\|. |\n| `--crossfade-ms MS`       | 10      | Equal-power sin crossfade applied at every splice. Range [1, 50]. |\n\n#### Optional gap-filling (rubberband)\n\n| Flag                      | Default | Notes                                              |\n|---------------------------|---------|----------------------------------------------------|\n| `--smooth-gaps BOOL`      | false   | Replace literal silence at splices with time-stretched ambient from the neighbouring dub. Requires `rubberband` (bundled in releases). |\n| `--gap-fill-margin-s S`   | 0.5     | Length of dub audio sampled before AND after each gap as the stretch source. |\n| `--speech-db DBFS`        | -25     | Above this RMS level a neighbour buffer is treated as speech and the gap stays as silence (preserves lip-sync). |\n\n## How it works\n\n1. **Extract** master + donor anchors (mono, 16 kHz, f32le PCM via `ffmpeg`),\n   plus each requested dub at native rate / channel count, plus optionally any\n   forced or explicit donor subtitle tracks (extracted as SRT via `-c:s srt` so\n   ASS / WebVTT / etc. all funnel into one parser downstream).\n2. **FPS normalize** (auto): probe master + donor `r_frame_rate` via `ffprobe`.\n   When they differ by \u003e0.1 % (e.g. PAL 25 vs film 24), apply a global rubberband\n   time-stretch to the donor anchor + every dub by `donor_fps / master_fps`\n   before correlation runs. Optional pitch correction undoes PAL speed-up. Can\n   be disabled with `--disable-fps-normalize` or overridden with\n   `--force-fps-ratio` for sources where the probe lies.\n3. **Correlate** with chunked-parallel sliding-window **GCC-PHAT** (`rustfft` +\n   `rayon`), adaptively re-seeding the search window from the previous chunk's\n   result. Confidence is reported as a peak-to-sidelobe ratio (PSR with ±20 ms\n   guard around the peak), so spectral colour or sidelobe noise doesn't bias it.\n4. **Build offset map**: filter low-confidence anchors, median-filter, detect\n   frame-level offset jumps. Refine each transition in two passes (coarse 0.25 s\n   sweep, then sub-frame 0.05 s sweep) with adaptive bracket extension when the\n   cut sits past the initial bracket — final boundary lands within ~50 ms of the\n   true content cut. Jumps larger than `--max-segment-jump-s` (default 10 s)\n   are suppressed as likely false positives — typically GCC-PHAT confused by\n   repetitive end-credits music with multiple equally-strong correlation peaks.\n5. **Detect master-anchor silences** via RMS VAD on the already-loaded master PCM\n   so the splicer can place each splice in a moment when the master video is\n   visually quiet.\n6. **Apply** the map to each dub in parallel. For every interior boundary the\n   splicer tries three strategies in priority order:\n   - **InMasterSilence** *(preferred)* — find a master-anchor silence within\n     `--snap-radius-s` of the refined boundary that's wide enough to absorb \\|Δ\\|.\n     Snap the splice into its centre and insert a literal \\|Δ\\|-second gap. Donor\n     reads are mathematically continuous through the gap, so no donor frames are\n     dropped or duplicated.\n   - **InDubSilence** — fallback: same idea, but using the dub's own silences.\n     The splice is inaudible for *that* track but lands at a different master time\n     per dub.\n   - **AtBoundary** — last resort: hard cut + crossfade + silence-gap at the\n     refined boundary itself. Audible muting but never glitches.\n\n   Every splice gets a 10 ms (configurable) equal-power sin crossfade. There is\n   **no** in-process WSOLA path: stretching across hard donor edits caused worse\n   artefacts than the silence it would have replaced.\n\n7. **Optional gap-fill** (`--smooth-gaps true`): for each silent gap, sample the\n   `gap_fill_margin_s` of dub audio just before and just after the gap, stretch\n   it with `rubberband -D \u003ctarget_s\u003e` to cover the gap (plus crossfade headroom),\n   and overlap-add it on top of the zeros with sin crossfades into the\n   surrounding segments. The neighbour is checked for speech first; if the dub\n   is talking right at the splice, the stretch is skipped and the gap stays as\n   literal silence (lip-sync preserved). When the surrounding dub has only\n   continuous room-tone or music — exactly the case where the literal gap reads\n   as a \"silence pit\" — the stretched ambient blends in seamlessly.\n8. **Subtitle shift**: every selected donor SRT is parsed, each event's start /\n   end seconds are walked through the offset map (segment lookup by the event's\n   midpoint in donor time), shifted into master time, and re-serialised. Events\n   whose midpoint falls outside any segment's donor coverage (intro / outro\n   mismatches without anchor evidence) are dropped.\n9. **Remux**: copy master video + master anchor (or replace with synced donor\n   anchor under `--include-donor-anchor`), plus synced dubs encoded with\n   `--dub-codec` (default E-AC3, matching the typical donor codec; the dub PCM\n   is the spliced output, not the donor original, so a lossless codec preserves\n   the spliced PCM rather than the donor's bits), plus master subtitle tracks\n   (`-c:s copy` pass-through unless `--no-master-subs`),\n   plus shifted donor subs as new SRT inputs. Language / title / forced / default\n   metadata is preserved on every track.\n\n## Diagnostics\n\nWith `--keep-temp` the workspace dir contains:\n\n- `anchors.json` — every coarse correlation window's `(master_t, offset, confidence)`.\n- `offset_map.json` — final segments + valid-evidence range.\n- `transition_traces.json` — per-fine-position `(s_a, s_b)` scores around each\n  boundary, plus the sub-frame trace.\n- `master_silences.json` — every master-anchor silence interval the splicer\n  considered for snapping.\n- `master_onsets.json` / `donor_onsets.json` — RMS-jump events in each anchor for\n  cross-referencing the *true* offset against the algorithm's choice.\n- The extracted anchor + dub PCM WAVs and the synced dub WAVs.\n\nThe repeat-command line printed at the end of the interactive flow round-trips every\nnon-default flag, so you can iterate on tuning without losing context.\n\n## Tips for fast iteration\n\nWhen debugging on a multi-hour source, cut a small matching pair of samples first:\n\n```sh\nffmpeg -i master.mkv -ss 00:16:00.320 -to 00:16:51.080 -map 0 -c copy master.sample.mkv\nffmpeg -i donor.mkv  -ss 00:15:57.320 -to 00:16:47.080 -map 0 -c copy donor.sample.mkv\n```\n\n(Pick start/end timestamps that you know straddle a boundary in the offset map.)\nThen run `dubsync` against the samples with reduced correlation parameters so the\n50-second clip still produces enough anchors to detect the boundary:\n\n```sh\ndubsync ... --correlation-window-s 10 --max-drift-s 5 --keep-temp\n```\n\nA full pipeline run on a 50-second sample takes ~1–2 seconds vs ~30 seconds for a\n50-minute episode — useful for tuning splice flags or auditing diagnostics.\n\n## Non-goals\n\n- **Variable drift / clock-rate mismatch within a segment** — offsets are modelled\n  as piecewise constant. FPS-normalize handles a global ratio mismatch up front,\n  but per-segment continuous drift (bad encodes with floating sample clock, etc.)\n  is not corrected.\n- **Re-encoding the master video** — `-c:v copy` always.\n- **Image-based subtitles** (PGS / DVD-SUB / DVB-SUB) — text-based subs (SRT /\n  ASS / WebVTT) are extracted, time-shifted, and muxed; image-based codecs are\n  detected and skipped because shifting them would require OCR.\n\n## License\n\n`dubsync` itself: **MIT OR Apache-2.0**.\n\nDistributed archives bundle:\n\n- **FFmpeg** (BtbN LGPL build) — LGPL-2.1+, license shipped as `FFMPEG-LICENSE.txt`.\n- **Rubber Band** — GPL-2.0+, license shipped as `RUBBERBAND-LICENSE.txt`. Invoked\n  as a subprocess only — \"mere aggregation\" per FSF guidance, so dubsync's own\n  permissive licensing is unaffected.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frisenxxx%2Fdubsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frisenxxx%2Fdubsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frisenxxx%2Fdubsync/lists"}