## changelog

Changelog

v0.9.1

// 2026-08-20
  • Subtitle files next to your video are found properly — a subtitle named exactly like the video ("Show.srt" beside "Show.avi") was being skipped entirely, which is the most common way they're named. Tags with extra words ("movie.en.forced.srt") are read correctly now, and .vtt files are recognised.
  • Subtitles get the right language even when the filename doesn't say — the language is worked out from the subtitle text itself, so a plain "Show.srt" next to a Russian rip arrives as Russian instead of "Unknown". Files saved in older Windows encodings are read correctly. If there isn't enough text to be sure, it stays Unknown rather than guessing.
  • Add a subtitle yourself — drag a .srt, .ass, .ssa or .vtt onto a file, or use "Add subtitle…". You can also set any subtitle's language by hand when the guess is wrong.
  • Russian season folders are understood — "Фонари (Сезон 1)" and "1 сезон" are read as season 1, in either word order. Episodes in a season folder also used to all land in season 1 regardless of what the folder said, which made the TV app draw a duplicate season heading.
  • More episode names recognised — files that repeat the show name then the episode number, with quality tags after ("Show 1.WEB-DLRip.avi"), are now matched. A folder that names its season needs only one episode in it, so single-episode drops work.
  • Fixed: subtitles you'd ticked could be dropped when a file was remuxed, and re-asking for a film match repeated the search that had just failed instead of offering better guesses.
  • A film misfiled as a TV episode is no longer stuck — the show picker has a "Not a show…" button that hands it back to the movie editor.

v0.9.0

// 2026-08-10
  • Whole seasons in one go — syncing a few dozen files at once used to fail after about a second with "No AssetManifest received from device", while one or two files worked fine. That's fixed: a 39-file, 73.6 GB batch (two full anime seasons plus five movies) now lands complete and playable.
  • Season folders are recognised — episodes named only by number, with the show name in the folder ("E1 «Beast Titan».mkv" inside "Shingeki no Kyojin S2 60 FPS"), now get the right show, season and episode instead of being treated as movies. Quality tags in the folder name (60 FPS, 1080p, WEB-DL, release groups) are ignored.
  • Better film matching — results are ranked by how closely they match the filename rather than taking the first hit, so "Rambo.First.Blood…" finds First Blood (1982) and not the sequel. Years jammed against release tags ("GoldenEye (1995)BDRip720p") are read correctly, and scene tags no longer end up in the search.
  • Pick a different film — when the match is a judgement call the row shows an "N matches" badge; click it to see the alternatives with year and synopsis, or search for something else.
  • Interrupted runs don't re-encode — finished transcodes are reused when you add the same files again, as long as nothing about the source or your settings changed.
  • Honest leftover cleanup — the leftover-transcodes banner now checks the device before offering to recover, instead of promising files it can't restore, and half-written files from an interrupted encode can finally be cleared.
  • Faster repeat imports — film and show data is cached between launches, so re-adding the same library doesn't re-query the metadata service.
  • Also: a Clear all button for the file list, and no more flicker in the status line between files.

v0.8.2

// 2026-07-26
  • Cold-start handshake tolerance — right after a device is connected or unlocked, its media daemon can take tens of seconds to answer the sync handshake. The first Send no longer fails here: it retries with a growing back-off, shows "Device still waking up — retrying…" so the wait reads as progress, and if it still can't connect it explains the likely cause (unlock the device, wait a few seconds, Send again) instead of a bare error.
  • Graceful on an incompatible macOS — if a future macOS update changes the private device APIs MediaPorter relies on, the app now shows a clear "not compatible yet" message and keeps analyze/transcode working, instead of crashing on launch.
  • No more mid-sync crashes on unexpected device data — malformed values from the device during the handshake now fail the sync with an error instead of hard-crashing.
  • Clearer result when a sync can't be confirmed — if the device drops the connection or never confirms the final commit, the run says so ("check TV.app") rather than reporting success.
  • Duplicate check tells you when it's off — if the device's library can't be read at the start of a run, the device panel shows a note that already-on-device files won't be flagged.
  • Smaller fixes — a Wi-Fi SSL-context stall is caught immediately instead of hanging ~60 s; a possible hang reading very large device libraries is fixed; the file list auto-scrolls to follow the file currently transcoding or uploading.

v0.8.1

// 2026-06-08
  • Audio loudness normalization — Settings → Transcode → "Normalize audio loudness (EBU R128)" evens out quiet-dialogue / loud-action mixes via the ffmpeg `loudnorm` filter. Off by default, persisted. Only applies to tracks being re-encoded; copied tracks keep their original levels.
  • Pre-Send size estimate — the Send button now shows "≈ N GB to send", the summed predicted output size across the files about to sync.
  • Clearer Wi-Fi sync failures — a failed sync to a Wi-Fi device now hints that it may have gone to sleep (a sleeping device stops advertising over the network and drops the connection); shown only for genuine transport failures, not cancels, disk-full, or USB.

v0.8.0

// 2026-06-08
  • Sync over Wi-Fi — the full transcode → upload → register flow now works with the device on Wi-Fi, no cable. The blocker was a single API choice: the legacy service-start skips the SSL handshake network lockdown sessions require, so AFC now uses a secure service-start + the connection's socket fd + the SSL I/O context (one code path serves USB and Wi-Fi). Keep the device awake and unlocked — a sleeping device stops advertising and drops off Wi-Fi within ~2 min.
  • Multi-device USB/Wi-Fi picker — when more than one iPhone/iPad is attached, the device column shows a picker; each device carries a real per-transport badge (USB / Wi-Fi). Transports are tracked per device (prefers USB, promotes Wi-Fi if USB drops).
  • Reliability: mid-sync connection loss now aborts fast instead of stalling ~2 min — the send return code was proven meaningless as a liveness signal, so the session keys on the drainer thread observing transport death. Disk preflight sizes on predicted output (excludes stream-copied files), fixing false "not enough space" rejections of big drops.
  • Performance: VideoToolbox capability is probed once per run instead of a subprocess per file; the debug-only output probe is compiled out of release builds; the show-extras list is cached instead of re-sorted on every progress tick.
  • Bitrate hints — the per-file row shows the measured source bitrate, and the recommendation banner adds "these files run ~N Mbps at full resolution" when a downscale is on the table.
  • Diagnostics: `bench-upload` reports the transport (Wi-Fi measured ~56–63 MB/s on a test device, above the older ~30 MB/s USB-2 baseline); poster-fallback branch logging; faster Wi-Fi discovery; analyze no longer pins the cooperative thread pool during ffprobe.

v0.7.0

// 2026-05-18
  • TV.app finally renders TV episodes correctly. `series_name` / `season_number` / `episode_sort_id` moved from the `item` top-level (where they were silently dropped) into the `video_info` sub-dict, per the AMPDevicesAgent canonical key cluster. Header now shows the show name, season cards no longer read "Season 0", and episode rows lose the spurious `0.` prefix.
  • Show portrait per episode. Each episode ships the show's portrait poster as `item.posterData` (per-episode landscape still as fallback). The TV.app show-detail hero renders correctly; episode-row thumbs are portrait tiles. Phantom-`representative_item_pid` approach explored and closed — research log in `PHANTOM_REP_PID_TESTS.md`.
  • Pipeline reliability: remote AFC size verified before `FileComplete` (catches truncation before the row binds and the file gets swept), `FileError(0)` for thrown assets so they don't block `SyncFinished`, graceful cancel that flushes pending abandons before tearing down the session.
  • Adaptive AFC chunk size — `recommendedChunkSize(forFileBytes:)` picks 16 MB on files ≥ 300 MB and 4 MB below. Bench on a 1.21 GB file: +1.7% throughput at 16 MB. Lightning cable plateau ~36 MB/s — USB-3 cable should re-bench higher.
  • New `mediaporterctl` CLI: `sync <file>` (full pipeline with live progress), `delete <title>` (ATC `delete_track` + AFC remove), `bench-upload` with sweepable chunk sizes, `smoke-test` (release-readiness end-to-end check on a connected device). 75/75 unit tests; smoke verified on iPhone.
  • Send Diagnostic + anonymous heartbeat now route to self-hosted Bugsink at `bugs.porter.md`. Default-on, toggleable in Settings. Privacy doc rewritten with the full inventory of what's sent.
  • Cluster-extras (external dub/sub tracks) now survive Apply-to-all + re-analyze. Previously a TMDb pick or show rename wiped the per-job picks because cluster intent only captured audio / sub indexes. Regression tests added.
  • TMDb client honors `HTTPS_PROXY` / `HTTP_PROXY`, banners distinguish "no API key" from "no match", show-picker keyboard-navigates with Enter-to-confirm.
  • USB pill iPhone-filter: the Wi-Fi sync hint only shows for iPhones; iPads get the right copy. Release-readiness recipe in `research/docs/RELEASE_TESTING.md`.

v0.6.2

// 2026-05-14
  • Fixed a phantom "iOS Device · 1024p" that surfaced when MobileDevice.framework reported an attach without readable lockdown values (Wi-Fi-discovered device with stale trust, or USB device mid-handshake). Hidden until trust completes; the polling loop recovers it automatically.
  • AVI / MPEG-4 ASP (DivX / Xvid) → matroska copy: `-fflags +genpts` + `-avoid_negative_ts make_zero` + codec-gated `mpeg4_unpack_bframes` bitstream filter unblock the muxer.
  • Folder drop: dragging a season directory now expands into its videos (previously filtered out because the folder itself had no video extension). Depth-6 walk.
  • Plain-numbered TV detection: `Show Name 01.mkv` parses as `S01E01` when ≥3 siblings share the prefix. Single-file drops enrich the sibling set from the parent directory.
  • Audio-language inference for AVI dubs: titles with Cyrillic / Hiragana-Katakana / Hangul / CJK bytes infer `rus` / `jpn` / `kor` / `chi` when `TAG:language` is missing. New per-row picker (14-language shortlist) overrides cluster-wide.
  • USB-speed hint: connection pill reads `● CONNECTED over USB-C 2.0 🐢` when the cable bottlenecks a device capable of more (iPhone 15/16 Pro 10 Gbps, iPad Pro M1+ Thunderbolt). USB-2-only devices suppress.
  • Settings → Appearance gains `Auto` (follows macOS Appearance). Settings → FFmpeg shows resolved source (`.bundled` / `.system` / `.missing`) + install hint.
  • Apple Unified Logging mirror under subsystem `md.porter.MediaPorter` with `.debug` / `.info` / `.notice` / `.error` levels. Recovery actions emit at `.notice`, hard failures at `.error` — survive `log show --last 1h` without `--info`. README has the inspection commands.
  • Parallel transcode lookahead (`TaskGroup` K=2–4) so transcodes run ahead of uploads while preserving send order.
  • Multi-device picker now leads with a `Sync target — other devices stay idle` header so users with ≥2 attached devices understand `Send` doesn't fan out.
  • DMG packaging: contained `.app` is always `MediaPorter.app` (download filename keeps `-with-ffmpeg`), in-app glyph matches the dock + DMG brand mark, File → New Window removed.

v0.6.1

// 2026-05-14
  • Hotfix: large-file syncs landed the row in the TV app but the file was unbound and got swept by background GC — title visible, Play did nothing. Root cause: `SyncAllowed` (sent early, accumulates in the inbox during a long upload) was treated as terminal-equivalent to `SyncFinished`.
  • Strict `SyncFinished` wait with 120 s cap + 30 s `SyncAllowed` fallback grace. Mini-clips were unaffected because they finished uploading before `SyncAllowed` arrived.
  • `Send N to iPhone` no longer no-ops when every file is already on the device — counts files matching the actual pipeline filter and surfaces a disabled, explanatory button.
  • `atc.FileProgress` heartbeat sent every 5 s / 10 % during the AFC upload to keep medialibraryd's asset slot warm.

v0.6.0

// 2026-05-14
  • Cluster-extras — drop a folder of episodes with dub / sub subfolders, pick studios / labels once at cluster level, MediaPorter muxes each episode's extras into the transcode pass with the user-chosen default audio surviving into the TV app.
  • External-track scanner — finds sibling dubs (`.mka` / `.ac3` / `.eac3` / `.flac` / `.aac` / `.m4a` / `.opus`) and subs (`.srt` / `.ass` / `.ssa` / `.vtt`), groups by parent folder = label, infers language from path, detects "forced" subs by token.
  • Burn-in for cluster-extras subs is end-to-end: auto-included for the mux, language carried through, post-mux re-probe rewrites burn-in index to the embedded stream.
  • OpenSubtitles auto-fetch — analyze pulls missing-language SRTs via moviehash / TMDb id, includes them in the next run.
  • Burn-in for text, sidecar SRT, and bitmap (PGS / VOBSUB via `filter_complex overlay`). Bitmap canvas sized per codec; downscale applied after overlay so glyphs scale with the picture.
  • Streaming registration, mid-sync disk watchdog, parallel analyze, duplicate skip against the device's `MediaLibrary.sqlitedb`.
  • Cluster-scoped selection — audio / sub / resolution / burn-in changes raise an "Apply to all N episodes?" popover (auto-dismiss 5 s); "Always" toggle silences it.
  • Help menu — bug report + diagnostic info: app version, OS, device, debug log tail into a pre-filled GitHub issue.
  • Sign-and-notarize pipeline — Developer ID signed, notarized, stapled DMG. First public DMG shipping from porter.md.
  • Cancel + Retry per row reaches every in-flight ffmpeg (mux pass, ass→srt, main transcode). Zombie ffmpeg sweep at launch.
  • Two-artwork TV sync (show portrait + episode still). Multi-device support with override persistence. CLI `pull` command.

v0.3.2

// 2026-04-13
  • Corrected the iPad audio-language-switcher rule: AAC and EAC-3 are listable, AC-3 is silently filtered. AC-3 → AAC; AAC + EAC-3 pass through bit-perfect (typical `ac3 + eac3` releases keep 5.1 EAC-3 untouched).
  • Exactly one default audio track enforced at ffmpeg invocation (`-disposition:a:0 default` + `-disposition:a:N 0`), regardless of source — multiple defaults kill the switcher entirely.
  • Mixed-codec files no longer force-remux: per-stream codec decisions are authoritative. `aac + eac3` flavors copy through.
  • Full experimental matrix at `research/docs/AUDIO_SWITCHER_RULE.md` (11 variants A–K).

# Older entries live in CHANGELOG.md in the repository.