From 151693fd9739efb1b4cde402482a0e71e6707fd0 Mon Sep 17 00:00:00 2001 From: GodsQuantum <193283917+GodsQuantum@users.noreply.github.com> Date: Fri, 18 Sep 2026 09:39:19 +0200 Subject: [PATCH] feat: add short-form punctuation cleanup and word-by-word captions --- CHANGELOG.md | 12 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.fr.md | 6 +- README.md | 6 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- .../src/lib/components/FormatPreview.svelte | 18 +- frontend/src/lib/i18n.ts | 8 +- frontend/src/lib/preview.js | 24 +++ frontend/src/lib/subtitle-edit.js | 17 ++ frontend/src/lib/types.ts | 2 +- frontend/src/lib/views/EditorView.svelte | 23 ++- frontend/src/lib/views/PresetsView.svelte | 2 +- frontend/tests/integration-docs.test.mjs | 8 +- frontend/tests/preview.test.mjs | 18 +- frontend/tests/subtitle-edit.test.mjs | 24 +++ src/domain.rs | 1 + src/subtitle/ass.rs | 170 +++++++++++++++++- 19 files changed, 309 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0a9ff..2653a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable released changes are documented here. AutoSubs follows semantic vers ## [Unreleased] +## [3.2.0] - 2026-09-18 + +### Added + +- New **Word by word** caption animation: only the currently spoken unit is displayed, using canonical word timings. +- Word-by-word grouping keeps French elisions and split compounds together (for example `l'` + `amour` and `rendez-` + `vous`) instead of flashing orphan fragments. +- Editor action to remove sentence-ending full stops in one pass while preserving commas, `!`, `?`, and ellipses, with a one-step undo before saving. + +### Changed + +- Preset and live video previews now mirror the word-by-word grouping used by the ASS/libass renderer. + ## [3.1.2] - 2026-09-17 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 8f53ce2..ecd839a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,7 +97,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autosubs" -version = "3.1.2" +version = "3.2.0" dependencies = [ "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index 831863b..2b4d4b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "autosubs" -version = "3.1.2" +version = "3.2.0" edition = "2024" rust-version = "1.98" authors = ["GodsQuantum"] diff --git a/README.fr.md b/README.fr.md index 85815db..289ce8c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -47,8 +47,8 @@ Le navigateur n'est pas le moteur métier. Rust possède la normalisation des ti - **Correction LLM optionnelle** — orthographe et ponctuation sans laisser le modèle modifier directement les timings. - **Un seul moteur de timing** — Rust répare plages invalides, chevauchements, gaps et timings de mots. Le frontend ne possède pas une deuxième implémentation divergente. - **Découpage Unicode/français** — comptage par graphèmes, opportunités Unicode et règles de non-coupure françaises. -- **Styles ASS/libass** — Pop, Surbrillance, Rebond, Karaoké, Fondu, Slide-up ou aucun effet; polices, contour, ombre, position et couleur d'accent. -- **Timings mot-à-mot durables** — la timeline canonique survit aux regroupements et modifications visuelles ; les animations utilisent les timings originaux. +- **Styles ASS/libass** — Pop, Surbrillance, Rebond, Karaoké, **Mot par mot**, Fondu, Slide-up ou aucun effet; polices, contour, ombre, position et couleur d'accent. +- **Timings mot-à-mot durables** — la timeline canonique survit aux regroupements et modifications visuelles ; les animations utilisent les timings originaux. Le mode Mot par mot garde les élisions (`l'` + mot) et mots composés avec tiret dans une seule unité visuelle. - **Mise en page française** — segmentation syntaxique française et `maxLines` strict garantissent des retours explicites sans ligne cachée supplémentaire. - **Cycle de vie complet** — édition, scission, fusion, suppression, retranscription et rerendu depuis la file et l'éditeur. Supprimer un job conserve la source et la sortie finale. - **Invariant Source** — Source + Conserver préserve la géométrie primaire sans scale, pad, crop ni bandes noires. @@ -122,7 +122,7 @@ volumes: 3. La correction LLM optionnelle ne touche qu'au texte. 4. Le moteur Rust normalise timings et regroupement. 5. Le job arrive à **Prêt**. Aucun encodage vidéo n'a encore été dépensé si le rendu immédiat n'a pas été demandé. -6. Corrige/scinde/fusionne/supprime/recherche/remplace/regroupe/décale dans l'Éditeur ; la timeline canonique reste disponible pour les regroupements ultérieurs. +6. Corrige/scinde/fusionne/supprime/recherche/remplace/regroupe/décale dans l'Éditeur. **Supprimer les points finaux** nettoie en lot les captions short-form tout en conservant virgules, !, ?, et points de suspension ; l'action peut être annulée avant enregistrement. La timeline canonique reste disponible pour les regroupements ultérieurs. 7. Exporte SRT/ASS/JSON sans réencoder, ou lance **Rendre la vidéo**. 8. FFmpeg/libass écrit d'abord un staging `.partial`. `.partial` est strictement interne au média incomplet, jamais un format de sous-titres. 9. Vidéo et sidecars sont publiés ensemble ; l'archive éventuelle de la source arrive en dernier. Les jobs existants peuvent être retranscrits ou rerendus depuis la file. diff --git a/README.md b/README.md index 0db9672..1638f92 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,8 @@ It is deliberately not a browser-only subtitle toy. The Rust backend owns timing - **Optional LLM correction** — spelling/punctuation correction after import/transcription while preserving line count and timings. - **Canonical subtitle timing engine** — one Rust implementation repairs invalid ranges, overlaps, gaps and word timings. The browser does not maintain a competing copy. - **Unicode-aware line grouping** — grapheme counting, Unicode line-break opportunities and French no-break rules instead of UTF-8 byte counting. -- **ASS/libass styling** — Pop, Highlight, Bounce, Karaoke, Fade, Slide-up and plain subtitle modes; custom fonts, outline, shadow, placement and highlight colors. -- **Durable word timing** — the canonical word-by-word timeline survives regrouping and visual edits; timed animations use the original word timings. +- **ASS/libass styling** — Pop, Highlight, Bounce, Karaoke, **Word by word**, Fade, Slide-up and plain subtitle modes; custom fonts, outline, shadow, placement and highlight colors. +- **Durable word timing** — the canonical word-by-word timeline survives regrouping and visual edits; timed animations use the original word timings. Word-by-word display keeps apostrophe prefixes and split hyphen compounds attached to their word. - **French-safe visual layout** — French syntax-aware segmentation and hard `maxLines` enforcement use explicit line breaks, so rendered captions never gain hidden extra lines. - **Complete job lifecycle** — edit, split, merge, delete, retranscribe and re-render jobs from the queue/editor. Deleting a job keeps its source and final output files. - **Source geometry invariant** — Source + Preserve keeps the primary video dimensions and aspect ratio without scale, pad, crop, or black bars. @@ -136,7 +136,7 @@ For migration, the old `SPEACHES_URL` variable is still accepted as a first-boot 3. Optional LLM correction runs on text only. 4. The canonical Rust engine normalizes timings and grouping. 5. The job reaches **Ready**. Nothing has been re-encoded yet unless you explicitly chose immediate render. -6. Review/edit/split/merge/delete/search/replace/regroup/shift timings in Editor. The canonical word timeline remains available for later regrouping. +6. Review/edit/split/merge/delete/search/replace/regroup/shift timings in Editor. Use **Remove final periods** for short-form caption cleanup; it preserves commas, !, ?, and ellipses and can be undone before saving. The canonical word timeline remains available for later regrouping. 7. Export SRT/ASS/JSON without touching the video, or click **Render video**. 8. FFmpeg/libass renders to a `.partial` staging file. `.partial` is internal media staging and is never treated as a subtitle file. 9. Video and sidecars publish together; an optional source archive happens last. Existing jobs can be retranscribed or re-rendered from the queue. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bd8dc5b..284b8a7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "autosubs-frontend", - "version": "3.1.2", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autosubs-frontend", - "version": "3.1.2", + "version": "3.2.0", "devDependencies": { "@sveltejs/adapter-static": "3.0.10", "@sveltejs/kit": "2.70.3", diff --git a/frontend/package.json b/frontend/package.json index 01409dc..9ddcdc8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "autosubs-frontend", "private": true, - "version": "3.1.2", + "version": "3.2.0", "type": "module", "scripts": { "dev": "vite dev", diff --git a/frontend/src/lib/components/FormatPreview.svelte b/frontend/src/lib/components/FormatPreview.svelte index 8066ec3..25b698c 100644 --- a/frontend/src/lib/components/FormatPreview.svelte +++ b/frontend/src/lib/components/FormatPreview.svelte @@ -1,7 +1,7 @@