Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Current DVD authoring capabilities include:
- authored menu routing for VMGM, titleset, and title-return paths, including keyboard-safe entry selection
- asset inspection with embedded metadata title surfacing, compatibility explanations, and fix-oriented validation
- DVD build planning and execution with diagnostics export and toolchain checks
- bitmap subtitle muxing, plus a developer option to skip unsupported text subtitle mappings during builds
- bitmap subtitle muxing plus first-pass text subtitle rendering for DVD authoring

## Workspace layout

Expand Down Expand Up @@ -52,7 +52,7 @@ If Rust tooling is not installed locally, run Rust and Tauri commands through `g
Current app behaviour also includes:

- a persistent thumbnail cache stored in the app cache directory, with Settings controls to inspect and clear cached previews
- developer toggles to prefer host `PATH` tools over bundled sidecars and to skip unsupported subtitle mappings during builds
- developer toggles to prefer host `PATH` tools over bundled sidecars and to skip unsupported subtitle mappings during builds, mainly for unknown subtitle types or debugging
- diagnostics bundle export including toolchain status, build logs, validation issues, project summary, and active developer options

Build the frontend bundle:
Expand Down
2 changes: 1 addition & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,7 @@ Because this app orchestrates binaries, the product should clearly communicate:
- direct titleset editing with compatibility guidance
- reversible subtitle track selection
- bitmap subtitle authoring and muxing
- first-pass text subtitle rendering and DVD-safe conversion with simplified styling
- `VIDEO_TS` export
- optional ISO generation
- build logs and diagnostics
Expand All @@ -1197,7 +1198,6 @@ Because this app orchestrates binaries, the product should clearly communicate:
- motion menus
- autogenerated title, chapter, audio, and subtitle menu creation
- menu themes and theme-aware generation
- text subtitle rendering and conversion
- advanced VM command logic exposure
- deep program/cell editing
- Blu-ray authoring
Expand Down
2 changes: 2 additions & 0 deletions apps/spindle/src/pages/BuildPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ function getJobIcon(job: BuildJob): string {
return '\u{1F517}';
case 'extractSubtitles':
return '\u{1F4DD}';
case 'renderTextSubtitles':
return '\u{1F5E8}';
case 'authorDvd':
return '\u{1F4BF}';
case 'createIso':
Expand Down
17 changes: 17 additions & 0 deletions apps/spindle/src/types/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ export interface BuildSettings {
generateIso: boolean;
safetyMarginBytes: number;
allocationStrategy: AllocationStrategy;
subtitleRenderMode?: 'one-pass' | 'two-pass';
}

// ── Validation ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -390,6 +391,22 @@ export type BuildJob =
command: string[];
label: string;
}
| {
type: 'renderTextSubtitles';
titleId: string;
titleName: string;
sourcePath: string;
sourceStreamIndex: number;
inputPath: string;
outputPath: string;
subtitlePath: string;
prepareCommand: string[];
spumuxXml: string;
command: string[];
label: string;
renderMode: 'one-pass' | 'two-pass';
fontFamily: string;
}
| {
type: 'authorDvd';
xmlPath: string;
Expand Down
52 changes: 26 additions & 26 deletions docs/core-dvd-authoring-completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ When all source streams are already mapped, the picker is disabled. The subtitle

### 4. Subtitle Authoring and Export Pipeline

**Build planner** — `planner.rs` generates `ExtractSubtitles` jobs for each title with bitmap subtitle mappings. Only bitmap subtitles (`dvd_subtitle`, `dvdsub`, `hdmv_pgs_subtitle`, `pgssub`) are extracted; text subtitle rendering is out of scope.
**Build planner** — `planner.rs` muxes bitmap subtitle mappings during `TranscodeTitle`. Text subtitle mappings generate explicit `RenderTextSubtitles` jobs that first normalise the source stream to SRT, then compose DVD subtitle streams onto the authored title MPEG with `spumux` text rendering.

**FFmpeg extraction** — `ffmpeg.rs` provides `build_ffmpeg_subtitle_extract_command()` which generates: `ffmpeg -i source -map 0:{index} -c:s dvd_subtitle output.sub`
**Rendering path** — `ffmpeg.rs` provides a text-subtitle preparation command that exports supported text subtitle streams to SRT. `spumux` then renders that text into DVD subpictures using a host font and DVD-safe defaults.

**dvdauthor XML** — `authoring.rs` generates `<subpicture>` declarations with language attributes for each title's subtitle mappings within the titleset's `<titles>` section.
**dvdauthor XML** — `authoring.rs` generates `<subpicture>` declarations with language attributes for each title's subtitle mappings within the titleset's `<titles>` section. The first-pass text subtitle path keeps that seam by producing a final title MPEG that already contains the rendered subtitle streams.

**Build job type** — `ExtractSubtitles` variant added to `BuildJob` enum in `types.rs`, with title_id, title_name, source_path, output_path, command, and label fields. The frontend `BuildJob` union and `BuildPage.tsx` handle this job type for progress display.
**Build job types** — `TranscodeTitle` remains the bitmap subtitle mux point, and `RenderTextSubtitles` adds the explicit text subtitle rendering stage surfaced on the Build page.

---

Expand Down Expand Up @@ -117,28 +117,28 @@ All fields use `#[serde(default)]` for backwards compatibility with existing pro

**Fix descriptions by rule:**

| Code | Suggested fix |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `disc.no-titlesets` | Add at least one titleset to the disc. |
| `disc.no-titles` | Add titles in the Titles page to define the disc's playback structure. |
| `disc.no-first-play` | Set a first-play action on the overview page so the disc has a defined startup behaviour. |
| `title.no-source` | Open the title and assign a source asset from the Assets library. |
| `title.dangling-source` | Re-import the missing asset or assign a different source. |
| `title.no-video-mapping` | Select a video stream in the title's track mapping section. |
| `title.no-output-profile` | Choose a video output profile (resolution and aspect ratio) for this title. |
| `chapter.non-increasing` | Reorder or adjust chapter timestamps so they are strictly increasing. |
| `chapter.beyond-duration` | Move this chapter to a timestamp within the asset's duration or remove it. |
| `menu.no-buttons` | Add at least one button to define user interaction. |
| `menu.no-default-button` | Set a default button so the player knows which button to highlight on entry. |
| `menu.button-no-action` | Assign an action (play title, show menu, etc.) to this button. |
| `menu.dangling-title-ref` | Update the button action to point to an existing title or remove it. |
| `menu.dangling-menu-ref` | Update the button action to point to an existing menu or remove it. |
| `menu.dangling-nav-ref` | Remove the broken nav link or use auto-generate navigation to rebuild all links. |
| `menu.button-no-navigation` | Use the auto-generate navigation feature to create directional links for all buttons. |
| `titleset.format-mismatch` | Ensure all titles in this titleset use the same resolution and aspect ratio, or move mismatched titles to a separate titleset. |
| `build.no-output-dir` | Set an output directory in the build settings to avoid being prompted each time. |
| `subtitle.dangling-stream` | The source file may have changed. Remove this subtitle mapping or relink the asset. |
| `subtitle.text-only-unsupported` | Text subtitle rendering is not yet supported. Remove text subtitles or provide bitmap subtitle sources. |
| Code | Suggested fix |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `disc.no-titlesets` | Add at least one titleset to the disc. |
| `disc.no-titles` | Add titles in the Titles page to define the disc's playback structure. |
| `disc.no-first-play` | Set a first-play action on the overview page so the disc has a defined startup behaviour. |
| `title.no-source` | Open the title and assign a source asset from the Assets library. |
| `title.dangling-source` | Re-import the missing asset or assign a different source. |
| `title.no-video-mapping` | Select a video stream in the title's track mapping section. |
| `title.no-output-profile` | Choose a video output profile (resolution and aspect ratio) for this title. |
| `chapter.non-increasing` | Reorder or adjust chapter timestamps so they are strictly increasing. |
| `chapter.beyond-duration` | Move this chapter to a timestamp within the asset's duration or remove it. |
| `menu.no-buttons` | Add at least one button to define user interaction. |
| `menu.no-default-button` | Set a default button so the player knows which button to highlight on entry. |
| `menu.button-no-action` | Assign an action (play title, show menu, etc.) to this button. |
| `menu.dangling-title-ref` | Update the button action to point to an existing title or remove it. |
| `menu.dangling-menu-ref` | Update the button action to point to an existing menu or remove it. |
| `menu.dangling-nav-ref` | Remove the broken nav link or use auto-generate navigation to rebuild all links. |
| `menu.button-no-navigation` | Use the auto-generate navigation feature to create directional links for all buttons. |
| `titleset.format-mismatch` | Ensure all titles in this titleset use the same resolution and aspect ratio, or move mismatched titles to a separate titleset. |
| `build.no-output-dir` | Set an output directory in the build settings to avoid being prompted each time. |
| `subtitle.dangling-stream` | The source file may have changed. Remove this subtitle mapping or relink the asset. |
| `subtitle.text-rendering-simplified` | Text subtitle rendering uses first-pass DVD-safe styling with a host font. |

**Clickable issue navigation** — Validation issues in OverviewPage and BuildPage are clickable. Clicking an issue navigates to the relevant page (titles, menus, build, etc.) and auto-selects the affected entity using a `NavigationContext` provided by `App.tsx`. Entity type determines the target route:

Expand Down
40 changes: 11 additions & 29 deletions docs/text-subtitle-rendering-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ Enable text-based subtitle streams (SRT, ASS/SSA, WebVTT, `mov_text`) to be rend

### Current state

The bitmap subtitle path is fully functional: extraction via FFmpeg, muxing via spumux, and `<subpicture>` declarations in dvdauthor XML. Text subtitles are detected during inspection and classified as `SubtitleType::Text`, but the build pipeline filters them out. A `subtitle.text-only-unsupported` validation warning tells users that text subtitles cannot yet be authored.
The shipped first pass now keeps the current `dvdauthor` seam intact by:

1. transcoding titles to DVD MPEG as before
2. normalising text subtitle mappings to SRT with FFmpeg
3. composing rendered DVD subtitle streams onto the authored title MPEG with `spumux` text subtitle rendering

Text subtitles are no longer blocked outright, but the first pass stays honest about simplified styling and host-font dependence.

### DVD subtitle constraints

Expand Down Expand Up @@ -38,36 +44,12 @@ From `inspect.rs::classify_subtitle_type`:

### Rendering approach

Use FFmpeg's subtitle filter chain to render text subtitles into bitmap subpictures. FFmpeg can:

1. Decode all recognised text formats (SRT, ASS, SSA, WebVTT, `mov_text`).
2. Render styled text onto a transparent canvas via the `subtitles` or `ass` filter.
3. Output the result as a VOBsub stream (`-c:s dvd_subtitle`).

The rendering pipeline is a two-step process per text subtitle mapping:
The implemented first pass uses a hybrid seam:

**Step 1 — Render text to bitmap video overlay**

Generate a short video stream where each frame is a transparent canvas with the subtitle text rendered on it. FFmpeg's `subtitles` filter handles this when applied to a blank video input:

```
ffmpeg -f lavfi -i "color=c=black@0:s=720x480:d={duration}" \
-vf "subtitles={source}:si={stream_index}:force_style='{style}'" \
-c:v rawvideo -pix_fmt yuva420p \
{temp_overlay}.nut
```

**Step 2 — Convert overlay to VOBsub**

Quantise the rendered overlay to DVD's 4-colour palette and package as VOBsub:

```
ffmpeg -i {temp_overlay}.nut \
-c:s dvd_subtitle \
{output}.sub
```
1. FFmpeg decodes the mapped text subtitle stream and exports it to SRT.
2. `spumux` renders that text input into DVD subtitle graphics while multiplexing the subtitle stream into the authored title MPEG.

An alternative single-pass approach may be viable depending on FFmpeg version capabilities. The two-step approach is safer and allows intermediate inspection.
This keeps the current authoring path compatible with `dvdauthor` and avoids relying on a direct FFmpeg text-to-`dvd_subtitle` path that is not available in the current toolchain.

### Style defaults

Expand Down
1 change: 1 addition & 0 deletions plugins/tauri-plugin-spindle-project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ serde_json = "1"
thiserror = "2"
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
isolang = "2.4"

[build-dependencies]
tauri-plugin = { version = "2", features = ["build"] }
Loading
Loading