Merge integration track i18n

This commit is contained in:
aiongg
2026-10-31 02:17:34 +00:00
40 changed files with 22168 additions and 202 deletions
+6 -1
View File
@@ -9,12 +9,17 @@ iconography, images or other Adobe assets; open licences only; every asset attri
| Asset | Author | Source | Licence |
|---|---|---|---|
| `crates/ui-egui/src/i18n/sv.tsv` | FilmCraft contributors | Original work: clean-room Swedish interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/it.tsv` | FilmCraft contributors | Original work: clean-room Italian interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/tr.tsv` | FilmCraft contributors | Original work: clean-room Turkish interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/id.tsv` | FilmCraft contributors | Original work: clean-room Indonesian interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/ko.tsv` | FilmCraft contributors | Original work: clean-room Korean interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/cs.tsv` | FilmCraft contributors | Original work: AI-assisted Czech interface translations; not yet proofread by a native speaker | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/fr.tsv` | FilmCraft contributors | Original work: clean-room French interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/pl.tsv` | FilmCraft contributors | Original work: clean-room Polish interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/pt-br.tsv` | FilmCraft contributors | Original work: clean-room Brazilian Portuguese interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/ru.tsv` | FilmCraft contributors | Original work: clean-room Russian interface translations | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/bs.tsv` | FilmCraft contributors | Original work: AI-assisted clean-room Bosnian interface translations, not yet native-reviewed | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/ru.tsv` | FilmCraft contributors | Original work: clean-room Russian interface translation (full catalog) | MIT OR Apache-2.0 |
| `crates/ui-egui/src/i18n/uk.tsv` | FilmCraft contributors | Original work: clean-room Ukrainian interface translations | MIT OR Apache-2.0 |
| `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
| `assets/fonts/Inter-Medium.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
+7 -3
View File
@@ -5,7 +5,7 @@
**Stage: alpha** · next: beta, ~19 points of ready-for-real-work and ~400–700 h away, plus opening Premiere's `.prproj` ([why](docs/target-app-parity.md#stage); passes the [core-workflow gate](docs/roadmap.md#alpha-gate))
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (ProRes alpha final-run conformance and native macOS validation) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (ProRes alpha final-run conformance and native macOS validation; language table: Czech, #1156) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
Progress toward parity with Adobe Premiere Pro. This page is the summary; the detail is in
[docs/target-app-parity.md](docs/target-app-parity.md) (assessment, methodology, calibration),
@@ -72,7 +72,7 @@ Evidence for every row: [docs/target-app-parity.md](docs/target-app-parity.md#by
## Languages
Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings):
Premiere ships 10 interface languages. Measured from our catalogs (3,593 strings, last measured 2026-10-19):
| Language | Status | Strings |
|---|---|---|
@@ -85,12 +85,15 @@ Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings
| Portuguese (Brazil) | partial (whole interface; native review welcome) | ~100% |
| Indonesian | partial (whole interface; native review welcome) | ~100% |
| Italian | partial (whole interface; native review welcome) | ~100% |
| Bosnian | partial (whole interface; AI-assisted, native review welcome) | ~100% |
| Korean | partial (whole interface; needs an installed Hangul font; native review welcome) | ~100% |
| Czech | partial (whole interface; native review needed) | 100% |
| Russian | partial (whole interface; native review welcome) | ~100% |
| Japanese | partial (whole interface) | 99.7% |
| German | partial (whole interface; menus native-reviewed, the rest welcome) | ~100% |
| Vietnamese | none | 0% |
Also shipped: Ukrainian (menus only, 10%). Detail: [docs/localization-parity.md](docs/localization-parity.md).
Also shipped: Swedish (whole interface; native review welcome, ~100%), Ukrainian (menus only, 10%) and Polish (whole interface; AI-drafted, native review welcome). Detail: [docs/localization-parity.md](docs/localization-parity.md).
## Upcoming
@@ -169,6 +172,7 @@ Ranked; detail and "done when" in [docs/roadmap.md](docs/roadmap.md) and [docs/g
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | ProRes alpha: write the final singleton run code; 4444/XQ HD clips that failed native macOS decoding now decode, with syntax regression tests (EffectCraft #293) |
| 2026-10-19 | minor | Language table: Czech (#1156, 100%) |
| 2026-10-13 | minor | Language table: German covers the whole interface (~100%) |
| 2026-10-12 | trivial | Progress log: Transform shutter on adjustment layers (#1088) |
| 2026-10-11 | minor | Language table: German ships the menus (#630, 10.6%) |
+10
View File
@@ -145,8 +145,18 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Men
filmcraft_ui_egui::i18n::Language::Id
} else if checked("app.language.italian") {
filmcraft_ui_egui::i18n::Language::It
} else if checked("app.language.bosnian") {
filmcraft_ui_egui::i18n::Language::Bs
} else if checked("app.language.korean") {
filmcraft_ui_egui::i18n::Language::Ko
} else if checked("app.language.czech") {
filmcraft_ui_egui::i18n::Language::Cs
} else if checked("app.language.polish") {
filmcraft_ui_egui::i18n::Language::Pl
} else if checked("app.language.swedish") {
filmcraft_ui_egui::i18n::Language::Sv
} else if checked("app.language.turkish") {
filmcraft_ui_egui::i18n::Language::Tr
} else {
filmcraft_ui_egui::i18n::Language::En
};
+1 -1
View File
@@ -8,7 +8,7 @@ use super::{Deserialize, FrameRate, Serialize, Tick};
#[serde(rename_all = "camelCase", default)]
pub struct GeneralPrefs {
/// Interface language (persisted independently of projects): `en`, `ja`, `es`, `pt-br`, `uk`,
/// `zh-cn`, `de`, `ru`, `fr`, `id`, `it`, or `system` (the default: the operating system's preferred language when the
/// `zh-cn`, `de`, `ru`, `fr`, `id`, `it`, `ko`, `cs`, `pl`, `sv`, `tr`, or `system` (the default: the operating system's preferred language when the
/// interface has it, otherwise English).
pub interface_language: String,
/// "At Startup": `showHome` (FilmCraft: the demo project), `openMostRecent`, `emptyProject`.
+15 -8
View File
@@ -213,16 +213,23 @@ pub(super) static CATEGORIES: &[Category] = &[
Kind::Choice(&[
("system", "System Language"),
("en", "English"),
("ja", "日本語"),
("es", "Español"),
("pt-br", "Português (Brasil)"),
("uk", "Українська"),
("zh-cn", "简体中文"),
("de", "Deutsch"),
("ru", "Русский"),
("fr", "Français"),
// Alphabetical by the language's own name after English; Cyrillic, then
// CJK names follow the Latin ones. Edit ▸ Language lists them the same way.
("id", "Bahasa Indonesia"),
("bs", "Bosanski"),
("cs", "Čeština"),
("de", "Deutsch"),
("es", "Español"),
("fr", "Français"),
("it", "Italiano"),
("pl", "Polski"),
("pt-br", "Português (Brasil)"),
("sv", "Svenska"),
("tr", "Türkçe"),
("ru", "Русский"),
("uk", "Українська"),
("ja", "日本語"),
("zh-cn", "简体中文"),
("ko", "한국어"),
]),
true,
+25
View File
@@ -50,6 +50,31 @@ fn interface_language_persists_and_rejects_hostile_values() {
}
set(&mut s, "general.interfaceLanguage", json!("it"));
assert_eq!(Preferences::load(&path).general.interface_language, "it");
// Korean and Czech likewise store only their bare codes.
for value in [json!("ko-KR"), json!("KO")] {
assert!(s.execute("prefs.set", json!({"key": "general.interfaceLanguage", "value": value})).is_err());
}
set(&mut s, "general.interfaceLanguage", json!("ko"));
assert_eq!(Preferences::load(&path).general.interface_language, "ko");
for value in [json!("cs-CZ"), json!("CS")] {
assert!(s.execute("prefs.set", json!({"key": "general.interfaceLanguage", "value": value})).is_err());
}
set(&mut s, "general.interfaceLanguage", json!("cs"));
assert_eq!(Preferences::load(&path).general.interface_language, "cs");
set(&mut s, "general.interfaceLanguage", json!("pl"));
assert_eq!(Preferences::load(&path).general.interface_language, "pl");
// Swedish is stored as `sv`, not a locale tag
for value in [json!("sv-SE"), json!("sv-FI"), json!("SV")] {
assert!(s.execute("prefs.set", json!({"key": "general.interfaceLanguage", "value": value})).is_err());
}
set(&mut s, "general.interfaceLanguage", json!("sv"));
assert_eq!(Preferences::load(&path).general.interface_language, "sv");
// Turkish is stored as `tr`, not a locale tag
for value in [json!("tr-TR"), json!("TR")] {
assert!(s.execute("prefs.set", json!({"key": "general.interfaceLanguage", "value": value})).is_err());
}
set(&mut s, "general.interfaceLanguage", json!("tr"));
assert_eq!(Preferences::load(&path).general.interface_language, "tr");
// back to following the operating system (#218)
set(&mut s, "general.interfaceLanguage", json!("system"));
assert_eq!(Preferences::load(&path).general.interface_language, "system");
+5 -4
View File
@@ -38,8 +38,8 @@ the committed span and undo. Set `FILMCRAFT_UI_SNAPSHOT_DIR` to render screensho
## Localisation
Edit > Language offers English, Japanese, Spanish, Brazilian Portuguese, Ukrainian, Simplified
Chinese, German, Russian, French, Indonesian, Korean and Italian (all but Ukrainian and Russian cover
the whole interface; those two cover the core menus). The language is stored in the engine's `general.interfaceLanguage` preference,
Chinese, German, Russian, French, Indonesian, Korean, Italian, Czech, Polish, Swedish and Turkish (all but Ukrainian cover
the whole interface; Ukrainian covers the core menus). The language is stored in the engine's `general.interfaceLanguage` preference,
restored on startup, and also available in Settings > General.
Its default, System Language (`system`), follows the operating system: the first of the user's
preferred languages that the interface has (the host supplies them through
@@ -53,10 +53,11 @@ fills translated templates, and `i18n::t` translates names from registries. Plac
(including user filenames containing braces) are inserted literally. Catalog translations are
original work using ordinary language, without proprietary localisation resources.
Spanish, Japanese, Simplified Chinese, French, Brazilian Portuguese and German cover menus, panels, dialogs, settings and registry labels. Searches accept both the
Spanish, Japanese, Simplified Chinese, French, Brazilian Portuguese, German and Russian cover
menus, panels, dialogs, settings and registry labels. Searches accept both the
translated label and its English source, including Unicode capitals. Project content, command ids
and preference values retain their original values. Engine errors, CLI and MCP messages remain
English. Ukrainian and Russian currently cover the core menus; they fall back to English elsewhere.
English. Ukrainian currently covers the core menus; it falls back to English elsewhere.
A string a catalog lacks shows in English, so a missing translation never fails a test or a
build. `cargo xtask i18n-report` prints each language's missing strings (menus, `tl!` literals and
+13 -1
View File
@@ -118,7 +118,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
let ws_resp = btn(ui, Icon::Workspaces, "workspaces", tl!("Workspaces"), app);
// workspace name (caps)
let ws = crate::i18n::t(&app.ui.workspace).to_uppercase();
let ws = crate::i18n::to_uppercase(crate::i18n::t(&app.ui.workspace));
let wg = p.layout_no_wrap(ws.clone(), Tokens::ui(11.0), t.text_dim);
let wr = Rect::from_min_size(pos2(rx - wg.size().x + 10.0, rect.center().y - 10.0), vec2(wg.size().x + 8.0, 20.0));
let wresp = ui.interact(wr, egui::Id::unique("hdr-ws-name"), Sense::click());
@@ -186,3 +186,15 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn workspace_name_uppercasing_is_turkish_aware() {
crate::i18n::set_current(crate::i18n::Language::Tr);
assert_eq!(crate::i18n::to_uppercase("Birleştirme"), "BİRLEŞTİRME");
assert_eq!(crate::i18n::to_uppercase("Altyazılar ve Grafikler"), "ALTYAZILAR VE GRAFİKLER");
crate::i18n::set_current(crate::i18n::Language::En);
assert_eq!(crate::i18n::to_uppercase("Birleştirme"), "BIRLEŞTIRME");
}
}
+121 -5
View File
@@ -47,8 +47,17 @@ pub enum Language {
Fr,
Id,
It,
/// Bosnian, Latin script (Ijekavian); `bs-Cyrl` locales use it too.
Bs,
/// Korean; Hangul needs an installed system font ([`korean_font_available`]).
Ko,
/// Czech; Latin with háčky and čárky, covered by the bundled fonts.
Cs,
/// Polish.
Pl,
/// Swedish.
Sv,
Tr,
}
static JAPANESE: OnceLock<Catalog> = OnceLock::new();
@@ -60,11 +69,17 @@ static GERMAN: OnceLock<Catalog> = OnceLock::new();
static RUSSIAN: OnceLock<Catalog> = OnceLock::new();
static FRENCH: OnceLock<Catalog> = OnceLock::new();
static KOREAN: OnceLock<Catalog> = OnceLock::new();
static POLISH: OnceLock<Catalog> = OnceLock::new();
static INDONESIAN: OnceLock<Catalog> = OnceLock::new();
static ITALIAN: OnceLock<Catalog> = OnceLock::new();
static BOSNIAN: OnceLock<Catalog> = OnceLock::new();
static CZECH: OnceLock<Catalog> = OnceLock::new();
static SWEDISH: OnceLock<Catalog> = OnceLock::new();
static TURKISH: OnceLock<Catalog> = OnceLock::new();
impl Language {
pub const ALL: [Self; 12] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko, Self::It];
pub const ALL: [Self; 17] =
[Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko, Self::It, Self::Cs, Self::Pl, Self::Sv, Self::Tr, Self::Bs];
/// BCP 47 tag for AccessKit and the web IME's hidden input. Keep the persisted spelling
/// (`pt-br`, `zh-cn`) out of the protocol-facing value.
@@ -81,7 +96,12 @@ impl Language {
Self::Fr => "fr",
Self::Id => "id",
Self::It => "it",
Self::Bs => "bs",
Self::Ko => "ko",
Self::Cs => "cs",
Self::Pl => "pl",
Self::Sv => "sv",
Self::Tr => "tr",
}
}
@@ -98,7 +118,12 @@ impl Language {
Self::Fr => "Français",
Self::Id => "Bahasa Indonesia",
Self::It => "Italiano",
Self::Bs => "Bosanski",
Self::Ko => "한국어",
Self::Cs => "Čeština",
Self::Pl => "Polski",
Self::Sv => "Svenska",
Self::Tr => "Türkçe",
}
}
@@ -115,7 +140,12 @@ impl Language {
"fr" => Some(Self::Fr),
"id" => Some(Self::Id),
"it" => Some(Self::It),
"bs" => Some(Self::Bs),
"ko" => Some(Self::Ko),
"cs" => Some(Self::Cs),
"pl" => Some(Self::Pl),
"sv" => Some(Self::Sv),
"tr" => Some(Self::Tr),
_ => None,
}
}
@@ -126,11 +156,11 @@ impl Language {
/// any German (`de-AT`, `de_CH.UTF-8`…) gets the German one and any French (`fr-CA`, `fr_BE.UTF-8`…)
/// the French one; any Indonesian (`id-ID`, `id_ID.UTF-8`, and the legacy `in-ID` that older Java
/// and Android runtimes report) the Indonesian one; any Italian (`it-IT`, `it-CH`, `it_IT.UTF-8`…)
/// the Italian one. Chinese gets the Simplified catalog
/// the Italian one; any Bosnian (`bs-BA`, `bs_BA.UTF-8`, `bs-Latn-BA`…) the Bosnian one; any Swedish (`sv-SE`, `sv-FI`, `sv_SE.UTF-8`…) the Swedish one; any Turkish (`tr`, `tr-TR`, `tr_TR.UTF-8`…) the Turkish one. Chinese gets the Simplified catalog
/// only for Simplified locales (`zh`, `zh-CN`, `zh-SG`, `zh-Hans…`); Traditional ones (`zh-TW`,
/// `zh-HK`, `zh-MO`, `zh-Hant…`) are skipped.
pub fn from_locales(tags: &[String]) -> Self {
const PRIMARY: [(&str, Language); 12] = [
const PRIMARY: [(&str, Language); 17] = [
("en", Language::En),
("ja", Language::Ja),
("es", Language::Es),
@@ -142,7 +172,12 @@ impl Language {
("id", Language::Id),
("in", Language::Id),
("it", Language::It),
("bs", Language::Bs),
("ko", Language::Ko),
("cs", Language::Cs),
("pl", Language::Pl),
("sv", Language::Sv),
("tr", Language::Tr),
];
tags.iter()
.find_map(|tag| {
@@ -174,7 +209,12 @@ impl Language {
Self::Fr => "fr",
Self::Id => "id",
Self::It => "it",
Self::Bs => "bs",
Self::Ko => "ko",
Self::Cs => "cs",
Self::Pl => "pl",
Self::Sv => "sv",
Self::Tr => "tr",
}
}
@@ -192,7 +232,12 @@ impl Language {
Self::Fr => Some(FRENCH.get_or_init(|| Catalog::parse(include_str!("i18n/fr.tsv")))),
Self::Id => Some(INDONESIAN.get_or_init(|| Catalog::parse(include_str!("i18n/id.tsv")))),
Self::It => Some(ITALIAN.get_or_init(|| Catalog::parse(include_str!("i18n/it.tsv")))),
Self::Bs => Some(BOSNIAN.get_or_init(|| Catalog::parse(include_str!("i18n/bs.tsv")))),
Self::Ko => Some(KOREAN.get_or_init(|| Catalog::parse(include_str!("i18n/ko.tsv")))),
Self::Cs => Some(CZECH.get_or_init(|| Catalog::parse(include_str!("i18n/cs.tsv")))),
Self::Pl => Some(POLISH.get_or_init(|| Catalog::parse(include_str!("i18n/pl.tsv")))),
Self::Sv => Some(SWEDISH.get_or_init(|| Catalog::parse(include_str!("i18n/sv.tsv")))),
Self::Tr => Some(TURKISH.get_or_init(|| Catalog::parse(include_str!("i18n/tr.tsv")))),
}
}
@@ -205,6 +250,43 @@ impl Language {
pub fn has(self, text: &str) -> bool {
self.catalog().and_then(|c| c.plain(text)).is_some()
}
/// Uppercase `text`, using Turkish-aware rules (`i` → `İ`, `ı` → `I`) when the language is
/// Turkish.
pub fn to_uppercase(self, text: &str) -> String {
if self == Self::Tr {
let mapped: String = text
.chars()
.map(|c| match c {
'i' => 'İ',
'ı' => 'I',
other => other,
})
.collect();
mapped.to_uppercase()
} else {
text.to_uppercase()
}
}
/// Lowercase `text`, using Turkish-aware rules (`İ` → `i`, `I` → `ı`) when the language is
/// Turkish. Decomposed `I` followed by `\u{0307}` is normalized to `i`.
pub fn to_lowercase(self, text: &str) -> String {
if self == Self::Tr {
let normalized = text.replace("I\u{0307}", "i");
let mapped: String = normalized
.chars()
.map(|c| match c {
'İ' => 'i',
'I' => 'ı',
other => other,
})
.collect();
mapped.to_lowercase()
} else {
text.to_lowercase()
}
}
}
thread_local! {
@@ -229,9 +311,43 @@ pub fn t(text: &str) -> &str {
current().tr(text)
}
/// Match a lowercased search query against both the source and the displayed label.
/// Uppercase `text` in the current interface language (with Turkish-aware casing when the
/// language is Turkish).
pub fn to_uppercase(text: &str) -> String {
current().to_uppercase(text)
}
/// Lowercase `text` in the current interface language (with Turkish-aware casing when the
/// language is Turkish).
pub fn to_lowercase(text: &str) -> String {
current().to_lowercase(text)
}
fn fold_query(s: &str) -> String {
s.replace('İ', "i").replace('\u{0307}', "").to_lowercase()
}
fn fold_turkish(s: &str) -> String {
let normalized = s.replace("I\u{0307}", "i");
let mapped: String = normalized
.chars()
.map(|c| match c {
'İ' => 'i',
'I' => 'ı',
other => other,
})
.collect();
mapped.replace('\u{0307}', "").to_lowercase()
}
/// Match a search query against both the source and the displayed label.
pub fn matches_query(text: &str, query: &str) -> bool {
text.to_lowercase().contains(query) || t(text).to_lowercase().contains(query)
let q = fold_query(query);
if fold_query(text).contains(&q) || fold_query(t(text)).contains(&q) {
return true;
}
let q_tr = fold_turkish(query);
fold_turkish(t(text)).contains(&q_tr) || fold_turkish(text).contains(&q_tr)
}
/// Like [`t`], for an English string that needs a disambiguating `context` (a catalog row whose
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
asset: bs.tsv
title: Bosnian interface translation catalog
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT and LICENSE-APACHE
added: 2026-10-30 by nine-codes
notes: AI-assisted clean-room English-to-Bosnian (Latin, Ijekavian) translations, shared with storytold/craft-i18n locales/bs/; not yet reviewed by a native speaker.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
asset: cs.tsv
title: Czech interface translation catalog
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT, LICENSE-APACHE
added: 2026-10-11 by Losminek
notes: AI-assisted original Czech translations, manually rewritten as complete phrases from English meanings with a binding editing glossary; not yet proofread by a native speaker.
+8
View File
@@ -3631,6 +3631,14 @@
H.265 (HEVC) needs a hardware encoder (NVENC on NVIDIA GPUs, VideoToolbox on macOS); none was found on this machine, and FilmCraft has no software H.265 encoder yet. Choose H.264 or ProRes instead. H.265 (HEVC) nécessite un encodeur matériel (NVENC sur les GPU NVIDIA, VideoToolbox sur macOS) ; aucun n'a été trouvé sur cet ordinateur, et FilmCraft n'a pas encore d'encodeur H.265 logiciel. Choisissez plutôt H.264 ou ProRes.
AV1 needs a hardware encoder; none was found on this machine, and FilmCraft has no software AV1 encoder yet. AV1 nécessite un encodeur matériel ; aucun n'a été trouvé sur cet ordinateur, et FilmCraft n'a pas encore d'encodeur AV1 logiciel.
Hardware decoding: Auto uses the system's video decoder (VideoToolbox on macOS, Media Foundation on Windows, VA-API for H.264 and HEVC on Linux) for the streams it supports, and FilmCraft's own decoder for everything else or if the hardware fails. Media that is already open keeps its decoder until it is reopened. Décodage matériel : Auto utilise le décodeur vidéo du système (VideoToolbox sur macOS, Media Foundation sur Windows, VA-API pour H.264 et HEVC sous Linux) pour les flux qu'il prend en charge, et le décodeur de FilmCraft pour tout le reste ou en cas d'échec du matériel. Un média déjà ouvert garde son décodeur jusqu'à sa réouverture.
Check for Updates… Rechercher les mises à jour…
Checking for updates… Recherche de mises à jour…
Could not check for updates:\n{error} Impossible de rechercher les mises à jour :\n{error}
FilmCraft {version} is currently the newest version available. FilmCraft {version} est actuellement la version la plus récente disponible.
Looks for a newer FilmCraft release and offers to update. Rechercher une version plus récente de FilmCraft et proposer la mise à jour.
Set Work Area Bar In Point Définir le point d'entrée de la barre de zone de travail
Set Work Area Bar Out Point Définir le point de sortie de la barre de zone de travail
Transparency Grid Grille de transparence
# Control server: the status-bar notice when --control cannot serve.
The control server is disabled: FILMCRAFT_CONTROL_TOKEN must contain 64 random hex characters (see docs/control-protocol.md) Le serveur de contrôle est désactivé : FILMCRAFT_CONTROL_TOKEN doit contenir 64 caractères hexadécimaux aléatoires (voir docs/control-protocol.md)
Can't render this file because it contains an unexpected character in line 5 and column 83.
+9
View File
@@ -17,6 +17,11 @@
# - "Horizontal"/"Vertical" are 水平/垂直 (guides, blur, axes); vertical text is "Vertical Text".
About FilmCraft FilmCraft について
Check for Updates… アップデートを確認…
Checking for updates… アップデートを確認中…
Could not check for updates:\n{error} アップデートを確認できませんでした:\n{error}
FilmCraft Updates FilmCraft のアップデート
FilmCraft {version} is currently the newest version available. FilmCraft {version} は最新バージョンです。
Add Audio Submix Track オーディオサブミックストラックを追加
Add Caption at Playhead 再生ヘッドの位置にキャプションを追加
Add Chapter Marker… チャプターマーカーを追加…
@@ -3238,6 +3243,9 @@
QuickTime, ProRes 4444 (4:4:4) with alpha channel, 24-bit 48 kHz PCM QuickTime、ProRes 4444 (4:4:4) アルファチャンネル付き、24-bit 48 kHz PCM
QuickTime, ProRes 4444 XQ (4:4:4), 24-bit 48 kHz PCM QuickTime、ProRes 4444 XQ (4:4:4)、24-bit 48 kHz PCM
QuickTime, ProRes 422, 24-bit 48 kHz PCM QuickTime、ProRes 422、24-bit 48 kHz PCM
Settings 設定
Hide FilmCraft FilmCraft を隠す
Hide Others ほかを隠す
Quit FilmCraft FilmCraft を終了
Rate Stretch Tool レートストレッチツール
Razor Tool カミソリツール
@@ -3621,6 +3629,7 @@
Saves this session's log and shows it in your file manager. このセッションのログを保存し、ファイルマネージャーで表示します。
Shows a report on this computer. このコンピューターのレポートを表示します。
Shows the version, community links and credits in a FilmCraft window. バージョン、コミュニティへのリンク、クレジットを FilmCraft のウィンドウに表示します。
Looks for a newer FilmCraft release and offers to update. FilmCraft の新しいリリースを確認し、アップデートを提案します。
Couldn't open your web browser ウェブブラウザーを開けませんでした
Open this address in your browser yourself: このアドレスをご自身でブラウザーで開いてください:
# View > Reset Interface Zoom (#2253).
Can't render this file because it contains an unexpected character in line 11 and column 59.
+11 -11
View File
@@ -616,7 +616,7 @@
Close All Other Projects 다른 프로젝트 모두 닫기
Close All Projects 모든 프로젝트 닫기
Close Bin 빈 닫기
Close Gap 간격 닫기
Close Gap 빈 구간 제거
Close Other Timeline Panels 다른 타임라인 패널 닫기
Close Panel 패널 닫기
Close Project 프로젝트 닫기
@@ -1098,10 +1098,10 @@
Extend Selection Right 선택 영역 오른쪽으로 확장
Extend Selection Up 선택 영역 위로 확장
Extended Dynamic Range Monitoring 확장 다이내믹 레인지 모니터링
Extract 추출
Extract (') 추출(')
Extract 구간 삭제 (간격 닫기)
Extract (') 구간 삭제 (간격 닫기) (')
Extract Audio 오디오 추출
Extract selected text 선택한 텍스트 추출
Extract selected text 선택한 텍스트 구간 삭제 (간격 닫기)
Eyedropper (pick a color from the Program monitor) 스포이드(프로그램 모니터에서 색상 선택)
Eyedropper: no picture to sample 스포이드: 샘플링할 화면 없음
Face Detection (protect skin tones) 얼굴 감지(피부 톤 보호)
@@ -1198,7 +1198,7 @@
Flare Color 플레어 색상
Flash 플래시
Flash Cue Marker Flash 큐 마커
Flatten 병합
Flatten 멀티카메라 클립 펼치기
Flicker 깜박임
Flicker Removal 깜박임 제거
Flickers 깜박임
@@ -1310,7 +1310,7 @@
Glow Width 광선 너비
Go Back 뒤로
Go Forward 앞으로
Go to Gap 간격으로 이동
Go to Gap 빈 구간으로 이동
Go to In 시작 표시로 이동
Go to In (Shift+I) 시작으로 이동(Shift+I)
Go to In Point 시작 표시(In)로 이동
@@ -1649,11 +1649,11 @@
Level 4 레벨 4
Levels 레벨
Libraries 라이브러리
Lift 들어올리기
Lift (;) 들어내기(;)
Lift selected text 선택한 텍스트 들어내기
Lift 구간 삭제 (간격 유지)
Lift (;) 구간 삭제 (간격 유지) (;)
Lift selected text 선택한 텍스트 구간 삭제 (간격 유지)
Lift Shadows 섀도 올리기
Lifted blacks, softer contrast, muted colour 들뜬 검정, 부드러운 대비, 차분한 색상
Lifted blacks, softer contrast, muted colour 검정 레벨을 높이고 대비를 부드럽게, 색은 차분하게
Ligatures 합자
Light 밝게
Light 1 조명 1
@@ -2659,7 +2659,7 @@
Saved preset “{name}” 사전 설정 “{name}” 저장됨
Saved template {path} 템플릿 저장됨: {path}
Saves the view, columns, sort, thumbnail and font size. 보기, 열, 정렬, 썸네일, 글꼴 크기를 저장합니다.
Scale 비율
Scale 배율
Scale (Degrees) 비율(도)
Scale Amount 비율 양
Scale Attribute Times 속성 시간 비율 조정
1 # Korean (ko) catalog. Columns, separated by a TAB: context <TAB> source <TAB> translation
616
617
618
619
620
621
622
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1198
1199
1200
1201
1202
1203
1204
1310
1311
1312
1313
1314
1315
1316
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
2659
2660
2661
2662
2663
2664
2665
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
asset: pl.tsv
title: Polish interface translation catalog
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT and LICENSE-APACHE
added: 2026-10-13 by lewandowskit
notes: Original English-to-Polish interface translations using clean-room NLE vocabulary (AI-assisted draft).
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -5,4 +5,9 @@ source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT and LICENSE-APACHE
added: 2026-10-10 by grib_job
notes: Original English-to-Russian menu translations using clean-room NLE vocabulary.
notes: Original clean-room English-to-Russian translation of the full 3,592-string
reference catalog, using ordinary Russian video-editing vocabulary (Premiere-style
terms: последовательность, корзина, дорожка, визуализация). No proprietary
localisation resource, string table or product interface was consulted. Drafted
with the project's agents and reviewed against the tests; native-speaker review
still welcome.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
asset: sv.tsv
title: Swedish interface translation catalog
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT and LICENSE-APACHE
added: 2026-10-14 by kler
notes: Original English-to-Swedish interface translations using clean-room NLE vocabulary.
+139 -2
View File
@@ -14,7 +14,12 @@ fn catalog_text(l: Language) -> Option<&'static str> {
Language::Fr => Some(include_str!("fr.tsv")),
Language::Id => Some(include_str!("id.tsv")),
Language::It => Some(include_str!("it.tsv")),
Language::Bs => Some(include_str!("bs.tsv")),
Language::Ko => Some(include_str!("ko.tsv")),
Language::Cs => Some(include_str!("cs.tsv")),
Language::Pl => Some(include_str!("pl.tsv")),
Language::Sv => Some(include_str!("sv.tsv")),
Language::Tr => Some(include_str!("tr.tsv")),
}
}
@@ -49,7 +54,7 @@ fn unknown_strings_pass_through_and_languages_round_trip() {
}
assert_eq!(Language::En.tr("File"), "File");
assert_eq!(Language::parse("xx"), None);
for code in ["de-DE", "fr-FR", "id-ID", "it-IT"] {
for code in ["de-DE", "fr-FR", "id-ID", "it-IT", "cs-CZ", "sv-SE"] {
assert_eq!(Language::parse(code), None, "the preference stores the bare code");
}
// language names are always shown in their own language
@@ -62,7 +67,12 @@ fn unknown_strings_pass_through_and_languages_round_trip() {
(Language::Fr, "Français"),
(Language::Id, "Bahasa Indonesia"),
(Language::It, "Italiano"),
(Language::Bs, "Bosanski"),
(Language::Ko, "한국어"),
(Language::Cs, "Čeština"),
(Language::Pl, "Polski"),
(Language::Sv, "Svenska"),
(Language::Tr, "Türkçe"),
] {
assert_eq!(l.name(), name);
}
@@ -513,6 +523,11 @@ fn language_commands_switch_and_persist_without_a_document() {
("french", Language::Fr, "fr", "Français", Some(("app.language.german", "Deutsch"))),
("indonesian", Language::Id, "id", "Bahasa Indonesia", Some(("app.language.french", "Français"))),
("italian", Language::It, "it", "Italiano", Some(("app.language.indonesian", "Bahasa Indonesia"))),
("czech", Language::Cs, "cs", "Čeština", Some(("app.language.italian", "Italiano"))),
("polish", Language::Pl, "pl", "Polski", Some(("app.language.italian", "Italiano"))),
("swedish", Language::Sv, "sv", "Svenska", Some(("app.language.italian", "Italiano"))),
("turkish", Language::Tr, "tr", "Türkçe", Some(("app.language.italian", "Italiano"))),
("bosnian", Language::Bs, "bs", "Bosanski", Some(("app.language.italian", "Italiano"))),
] {
switch_and_check(&mut app, &ctx, name, lang, code, own, earlier);
}
@@ -575,10 +590,23 @@ fn system_language_picks_italian_korean_and_the_first_known() {
for tag in ["ko", "ko-KR", "ko_KR.UTF-8"] {
assert_eq!(l(&[tag]), Language::Ko, "{tag}");
}
for tag in ["cs", "CS", "cs-CZ", "cs_CZ.UTF-8"] {
assert_eq!(l(&[tag]), Language::Cs, "{tag}");
}
for tag in ["sv", "SV", "sv-SE", "sv-FI", "sv-AX", "sv_SE.UTF-8", "sv_FI.UTF-8@euro", "sv_SE"] {
assert_eq!(l(&[tag]), Language::Sv, "{tag}");
}
assert_eq!(l(&["fi-FI", "sv-FI", "en"]), Language::Sv, "Finnish has no catalog");
for none in [&["swe"][..], &["s"], &["-sv"], &["da"], &["nb-NO"], &["nn"], &["no"]] {
assert_eq!(l(none), Language::En, "{none:?}");
}
for tag in ["tr", "TR", "tr-TR", "tr_TR.UTF-8", "tr_TR"] {
assert_eq!(l(&[tag]), Language::Tr, "{tag}");
}
for none in [&["ita"][..], &["i"], &["-it"]] {
assert_eq!(l(none), Language::En, "{none:?}");
}
assert_eq!(l(&["sv-SE", "nl", "es-MX", "ja"]), Language::Es, "the first one the interface has");
assert_eq!(l(&["da-DK", "nl", "es-MX", "ja"]), Language::Es, "the first one the interface has");
assert_eq!(l(&["rm-CH", "de-CH", "fr-CH", "en"]), Language::De);
assert_eq!(l(&["rm-CH", "fr-CH", "de-CH", "en"]), Language::Fr);
assert_eq!(l(&["en-GB", "es"]), Language::En);
@@ -707,6 +735,14 @@ fn italian_renders_with_bundled_fonts() {
assert_bundled_fonts_cover(alphabet.chars().chain(Language::It.name().chars()).chain(letters));
}
#[test]
fn czech_renders_with_bundled_fonts() {
let (entries, _) = catalog::parse_entries(include_str!("cs.tsv"));
let alphabet = "ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽáčďéěíňóřšťúůýž„“‚‘’";
let letters = entries.iter().flat_map(|(_, _, cs)| cs.chars()).filter(|ch| ch.is_alphabetic());
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Cs.name().chars()).chain(letters));
}
#[test]
fn japanese_needs_an_installed_font() {
let mut app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
@@ -730,6 +766,7 @@ fn japanese_needs_an_installed_font() {
for family in crate::theme::font_families() {
let mut stack = fonts.definitions().families.get(&family).cloned().unwrap_or_default();
stack.retain(|n| !crate::rtl::is_system_face(n));
stack.retain(|n| n != crate::cjk::KOREAN_FACE && n != "system-chinese");
if craft {
assert!(stack.last().is_some_and(|n| n.starts_with("craft:")), "{family:?}: {stack:?}");
assert!(!stack.iter().any(|n| n == JAPANESE_FONT), "{family:?}: {stack:?}");
@@ -749,3 +786,103 @@ fn japanese_needs_an_installed_font() {
}
});
}
/// Edit ▸ Language and Settings ▸ Interface Language list the same languages in the same order.
#[test]
fn language_menu_and_settings_choices_are_in_the_same_order() {
use filmcraft_engine::settings::{Kind, Row};
fn choices(rows: &[Row], out: &mut Vec<&'static str>) {
for r in rows {
match r {
Row::Field(f) if f.key == "general.interfaceLanguage" => {
if let Kind::Choice(opts) = f.kind {
out.extend(opts.iter().filter(|(code, _)| *code != "system").map(|(_, label)| *label));
}
}
Row::Group(_, inner) => choices(inner, out),
_ => {}
}
}
}
let mut settings = Vec::new();
for c in filmcraft_engine::settings::categories() {
choices(c.rows, &mut settings);
}
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
let menu: Vec<String> =
crate::menus::menu_items(&app).into_iter().filter(|it| it.id.starts_with("app.language.")).map(|it| it.label).collect();
assert!(!settings.is_empty());
assert_eq!(menu, settings);
}
#[test]
fn polish_renders_with_bundled_fonts() {
let (entries, errors) = catalog::parse_entries(include_str!("pl.tsv"));
assert!(errors.is_empty(), "{errors:?}");
// every Polish letter, even ones no entry uses yet, and every letter, curly quote, dash and
// ellipsis the entries use
let alphabet = "ĄĆĘŁŃÓŚŹŻąćęłńóśźż„”";
let letters = entries.iter().flat_map(|(_, _, pl)| pl.chars()).filter(|ch| ch.is_alphabetic() || "„”“‘’–—…".contains(*ch));
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Pl.name().chars()).chain(letters));
}
#[test]
fn swedish_renders_with_bundled_fonts() {
let (entries, _) = catalog::parse_entries(include_str!("sv.tsv"));
// every Swedish letter (é as in "idé", ü in loanwords), even ones no entry uses yet, and every
// letter, curly quote, dash and ellipsis the entries use (symbols such as ✔ come from the
// English sources)
let alphabet = "ÅÄÖÉÜåäöéü";
let letters = entries.iter().flat_map(|(_, _, sv)| sv.chars()).filter(|ch| ch.is_alphabetic() || "“”‘’–—…".contains(*ch));
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Sv.name().chars()).chain(letters));
}
#[test]
fn russian_renders_with_bundled_fonts() {
let (entries, _) = catalog::parse_entries(include_str!("ru.tsv"));
// the whole alphabet including «ё», even letters no entry uses yet, and every letter, quote,
// dash and ellipsis the entries use
let alphabet = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя«»";
let letters = entries.iter().flat_map(|(_, _, ru)| ru.chars()).filter(|ch| ch.is_alphabetic() || "«»“”‘’–—…".contains(*ch));
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Ru.name().chars()).chain(letters));
}
#[test]
fn turkish_casing_and_search_folding() {
assert_eq!(Language::Tr.to_uppercase("i"), "İ");
assert_eq!(Language::Tr.to_uppercase("ı"), "I");
assert_eq!(Language::Tr.to_lowercase("İ"), "i");
assert_eq!(Language::Tr.to_lowercase("I"), "ı");
assert_eq!(Language::Tr.to_lowercase("I\u{0307}"), "i");
assert_eq!(to_uppercase("file"), "FILE");
set_current(Language::Tr);
assert_eq!(to_uppercase("i"), "İ");
assert_eq!(to_lowercase("I"), "ı");
assert_eq!(to_lowercase("I\u{0307}"), "i");
assert!(matches_query("Gaussian Blur", "gaussian"));
assert!(matches_query("Gaussian Blur", "GAUSSIAN"));
assert!(matches_query("Inceleme", "inceleme"));
assert!(matches_query("İnceleme", "inceleme"));
assert!(matches_query("inceleme", "İnceleme"));
assert!(matches_query("Işık", "ışık"));
assert!(matches_query("ışık", "IŞIK"));
set_current(Language::En);
}
#[test]
fn turkish_renders_with_bundled_fonts() {
let (entries, _) = catalog::parse_entries(include_str!("tr.tsv"));
let alphabet = "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZabcçdefgğhıijklmnoöprsştuüvyz";
let letters = entries.iter().flat_map(|(_, _, tr)| tr.chars()).filter(|ch| ch.is_alphabetic() || "“”‘’–—…".contains(*ch));
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Tr.name().chars()).chain(letters));
}
/// Every Bosnian locale, with region, script or encoding, picks the Bosnian catalog.
#[test]
fn system_language_picks_bosnian() {
let l = locales;
for tag in ["bs", "BS", "bs-BA", "bs_BA.UTF-8", "bs-Latn-BA", "bs-Cyrl-BA", "BS-ba"] {
assert_eq!(l(&[tag]), Language::Bs, "{tag}");
}
assert_eq!(l(&["hr-HR", "bs-BA"]), Language::Bs, "Croatian has no catalog here, so the next preference wins");
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
asset: tr.tsv
title: Turkish interface translation catalog
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
license-file: LICENSE-MIT and LICENSE-APACHE
added: 2026-10-18 by MOzcelik14
notes: Original English-to-Turkish interface translations using clean-room NLE vocabulary.
+30 -8
View File
@@ -27,16 +27,23 @@ pub const UI_COMMANDS: &[UiCommand] = &[
uic!("window.floatPanel", "Undock Panel", [], None),
uic!("window.closePanel", "Close Panel", [], None),
uic!("app.language.english", "English", ["Edit", "Language"], None),
uic!("app.language.japanese", "日本語", ["Edit", "Language"], None),
uic!("app.language.spanish", "Español", ["Edit", "Language"], None),
uic!("app.language.portuguese", "Português (Brasil)", ["Edit", "Language"], None),
uic!("app.language.ukrainian", "Українська", ["Edit", "Language"], None),
uic!("app.language.chinese", "简体中文", ["Edit", "Language"], None),
uic!("app.language.german", "Deutsch", ["Edit", "Language"], None),
uic!("app.language.russian", "Русский", ["Edit", "Language"], None),
uic!("app.language.french", "Français", ["Edit", "Language"], None),
// Alphabetical by the language's own name after English; Cyrillic, then CJK names follow
// the Latin ones. Settings ▸ General ▸ Interface Language lists them the same way.
uic!("app.language.indonesian", "Bahasa Indonesia", ["Edit", "Language"], None),
uic!("app.language.bosnian", "Bosanski", ["Edit", "Language"], None),
uic!("app.language.czech", "Čeština", ["Edit", "Language"], None),
uic!("app.language.german", "Deutsch", ["Edit", "Language"], None),
uic!("app.language.spanish", "Español", ["Edit", "Language"], None),
uic!("app.language.french", "Français", ["Edit", "Language"], None),
uic!("app.language.italian", "Italiano", ["Edit", "Language"], None),
uic!("app.language.polish", "Polski", ["Edit", "Language"], None),
uic!("app.language.portuguese", "Português (Brasil)", ["Edit", "Language"], None),
uic!("app.language.swedish", "Svenska", ["Edit", "Language"], None),
uic!("app.language.turkish", "Türkçe", ["Edit", "Language"], None),
uic!("app.language.russian", "Русский", ["Edit", "Language"], None),
uic!("app.language.ukrainian", "Українська", ["Edit", "Language"], None),
uic!("app.language.japanese", "日本語", ["Edit", "Language"], None),
uic!("app.language.chinese", "简体中文", ["Edit", "Language"], None),
uic!("app.language.korean", "한국어", ["Edit", "Language"], None),
uic!("source.playback.toggle", "Source Play/Stop", [], None),
uic!("source.playback.play", "Play Source", [], None),
@@ -202,7 +209,12 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
| "app.language.french"
| "app.language.indonesian"
| "app.language.italian"
| "app.language.bosnian"
| "app.language.korean"
| "app.language.czech"
| "app.language.polish"
| "app.language.swedish"
| "app.language.turkish"
) {
// Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system
if id == "app.language.japanese" && !crate::i18n::install_japanese_font(ctx) {
@@ -227,7 +239,12 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
"app.language.french" => crate::i18n::Language::Fr,
"app.language.indonesian" => crate::i18n::Language::Id,
"app.language.italian" => crate::i18n::Language::It,
"app.language.bosnian" => crate::i18n::Language::Bs,
"app.language.korean" => crate::i18n::Language::Ko,
"app.language.czech" => crate::i18n::Language::Cs,
"app.language.polish" => crate::i18n::Language::Pl,
"app.language.swedish" => crate::i18n::Language::Sv,
"app.language.turkish" => crate::i18n::Language::Tr,
_ => crate::i18n::Language::En,
};
// The preference is updated in memory before it is written, so a failed write (read-only
@@ -852,7 +869,12 @@ pub fn menu_items(app: &FilmcraftApp) -> Vec<MenuItem> {
"app.language.french" => it.checked = Some(app.ui.language == crate::i18n::Language::Fr),
"app.language.indonesian" => it.checked = Some(app.ui.language == crate::i18n::Language::Id),
"app.language.italian" => it.checked = Some(app.ui.language == crate::i18n::Language::It),
"app.language.bosnian" => it.checked = Some(app.ui.language == crate::i18n::Language::Bs),
"app.language.korean" => it.checked = Some(app.ui.language == crate::i18n::Language::Ko),
"app.language.czech" => it.checked = Some(app.ui.language == crate::i18n::Language::Cs),
"app.language.polish" => it.checked = Some(app.ui.language == crate::i18n::Language::Pl),
"app.language.swedish" => it.checked = Some(app.ui.language == crate::i18n::Language::Sv),
"app.language.turkish" => it.checked = Some(app.ui.language == crate::i18n::Language::Tr),
_ => {}
}
if it.id.starts_with("view.") {
+32 -8
View File
@@ -32,7 +32,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
};
let mut bui = ui.new_child(egui::UiBuilder::new().max_rect(body).id_salt("fx-body"));
bui.set_clip_rect(body);
let filter = q.to_lowercase();
let filter = q.trim();
let defs = filmcraft_project::effect_defs();
// folder tree: top-level categories in Premiere's order
let tops = filmcraft_project::vtransition::EFFECT_TOP_FOLDERS;
@@ -42,19 +42,19 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
egui::ScrollArea::vertical().id_salt("fx-scroll").auto_shrink([false, false]).show(&mut bui, |ui| {
for &top in tops {
if top == "Presets" {
let any = app.session.presets.all().iter().any(|p| crate::i18n::matches_query(&p.name, &filter));
if any && folder_row(app, ui, top, 0, &filter) {
preset_action = crate::panels::presets::folder_rows(app, ui, &filter).or(preset_action.take());
let any = app.session.presets.all().iter().any(|p| crate::i18n::matches_query(&p.name, filter));
if any && folder_row(app, ui, top, 0, filter) {
preset_action = crate::panels::presets::folder_rows(app, ui, filter).or(preset_action.take());
}
continue;
}
if top == "Lumetri Presets" {
if let Some(name) = lumetri_rows(app, ui, &filter) {
if let Some(name) = lumetri_rows(app, ui, filter) {
lumetri_apply = Some(name);
}
continue;
}
let mut items: Vec<_> = defs.iter().filter(|d| d.category.first() == Some(&top)).filter(|d| crate::i18n::matches_query(d.name, &filter)).collect();
let mut items: Vec<_> = defs.iter().filter(|d| d.category.first() == Some(&top)).filter(|d| crate::i18n::matches_query(d.name, filter)).collect();
// Premiere lists effect folders and effects alphabetically (obsolete ones last)
if top == "Video Effects" || top == "Legacy" {
items.sort_by_key(|d| (d.category.get(1) == Some(&"Obsolete"), d.category.get(1).copied().unwrap_or(""), d.name.to_ascii_lowercase()));
@@ -62,7 +62,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
if !filter.is_empty() && items.is_empty() {
continue;
}
let open = folder_row(app, ui, top, 0, &filter);
let open = folder_row(app, ui, top, 0, filter);
if !open {
continue;
}
@@ -79,7 +79,7 @@ pub fn show(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
seen.push(s);
let key = format!("{top}/{s}");
if !s.is_empty() && !folder_row(app, ui, &key, 1, &filter) {
if !s.is_empty() && !folder_row(app, ui, &key, 1, filter) {
continue;
}
for d in items.iter().filter(|d| sub(d) == s) {
@@ -306,3 +306,27 @@ fn preset_grid(app: &mut FilmcraftApp, ui: &mut egui::Ui, area: Rect) -> Option<
}
apply
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effects_search_matches_turkish_casing() {
crate::i18n::set_current(crate::i18n::Language::Tr);
let ctx = egui::Context::default();
let session = filmcraft_engine::Session::default();
let mut app = FilmcraftApp::new(session);
let rect = Rect::from_min_size(pos2(0.0, 0.0), vec2(400.0, 600.0));
for query in ["IŞIK", "ışık", "Işık"] {
app.ui.effects_search = query.to_string();
app.auto.begin_frame();
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
show(&mut app, ui, rect);
});
out.textures_delta.clear();
assert!(app.auto.find("effects.item.light_leaks").is_some(), "query `{query}` through effects search caller must find `light_leaks`",);
}
crate::i18n::set_current(crate::i18n::Language::En);
}
}
@@ -17,7 +17,7 @@ pub(super) fn preset_manager(app: &mut FilmcraftApp, ctx: &egui::Context) {
let mut pick: Option<&'static str> = None;
let all = app.session.export_presets.all();
let favorite = |n: &str| app.session.export_presets.is_favorite(n);
let q = m.query.to_lowercase();
let q = m.query.trim().to_string();
egui::Window::new(tl!("Preset Manager"))
.id(egui::Id::unique("Preset Manager"))
.id(egui::Id::unique("preset-manager"))
@@ -196,16 +196,16 @@ fn browse(app: &mut FilmcraftApp, ui: &mut egui::Ui, rect: Rect) {
install = r.clicked();
});
b.add_space(6.0);
let q = st.query.to_lowercase();
let q = st.query.trim();
let shown: Vec<&LibraryEntry> = lib
.iter()
.filter(|e| {
let tt = &e.template;
(st.category.is_empty() || tt.category == st.category)
&& (crate::i18n::matches_query(&tt.name, &q)
|| crate::i18n::matches_query(&tt.category, &q)
|| crate::i18n::matches_query(&tt.description, &q)
|| tt.tags.iter().any(|x| crate::i18n::matches_query(x, &q)))
&& (crate::i18n::matches_query(&tt.name, q)
|| crate::i18n::matches_query(&tt.category, q)
|| crate::i18n::matches_query(&tt.description, q)
|| tt.tags.iter().any(|x| crate::i18n::matches_query(x, q)))
})
.collect();
let selected = st.selected.clone();
+12 -2
View File
@@ -608,8 +608,8 @@ fn command_list(app: &mut FilmcraftApp, ui: &mut egui::Ui, height: f32, t: &Toke
ui.add_space(4.0);
let rows = app.session.execute("shortcuts.list", json!({})).unwrap_or_default();
let rows = rows.as_array().cloned().unwrap_or_default();
let query = app.shortcut_editor.search.to_lowercase();
let rows: Vec<_> = rows.into_iter().filter(|row| matches_search(row, &query)).collect();
let query = app.shortcut_editor.search.trim();
let rows: Vec<_> = rows.into_iter().filter(|row| matches_search(row, query)).collect();
let w = ui.available_width();
// column header
let (hr, _) = ui.allocate_exact_size(vec2(w, 22.0), Sense::hover());
@@ -839,4 +839,14 @@ mod localization_tests {
}
crate::i18n::set_current(crate::i18n::Language::En);
}
#[test]
fn command_search_accepts_turkish_casing() {
crate::i18n::set_current(crate::i18n::Language::Tr);
let row = json!({"id": "marker.in_out", "label": "Add Range Marker to In and Out", "category": "Marker", "shortcuts": []});
for query in ["ÇIKIŞ", "çıkış", "Çıkış"] {
assert!(matches_search(&row, query), "{query}");
}
crate::i18n::set_current(crate::i18n::Language::En);
}
}
+13
View File
@@ -826,6 +826,19 @@ fn effects_are_indented_under_their_folders() {
}
}
#[test]
fn effects_search_matches_turkish_casing() {
let mut d = Driver::demo();
d.exec("prefs.set", json!({"key": "general.interfaceLanguage", "value": "tr"}));
d.ok("ui.panel.show", json!({"panel": "Effects"}));
d.frames(3);
for query in ["IŞIK", "ışık", "Işık"] {
d.ok("ui.set", json!({"effectsSearch": query}));
d.frames(2);
assert!(d.has("effects.item.light_leaks"), "query `{query}` through Effects search caller must find `light_leaks`");
}
}
/// The font of the first run of every text shape drawn this frame, with the text, position and colour.
fn painted_text(d: &Driver) -> Vec<(String, egui::Pos2, egui::FontId, egui::Color32)> {
fn walk(s: &egui::Shape, out: &mut Vec<(String, egui::Pos2, egui::FontId, egui::Color32)>) {
+95 -9
View File
@@ -25,25 +25,110 @@ Containment rules:
1. The crate does not use `lints.workspace = true`. Its own `[lints]` table copies the workspace
lints except `unsafe_code = "deny"` (not `forbid`), and adds
`clippy::undocumented_unsafe_blocks = "deny"`. Only the FFI modules (`videotoolbox`, `media_foundation::gpu` / `media_foundation::mft`,
`nvenc::ffi` / `nvenc::session` / `nvenc::device` on Windows and 64-bit Linux, and `vaapi::va` on Linux) carry `#[allow(unsafe_code)]`; the rest of the crate
(the fallback logic in `hybrid`, the decoder logic in `media_foundation`, `annexb`, `biplanar`,
the encoder logic in `nvenc`, H.264 and H.265 alike) has no `unsafe`. NVENC's H.265 support
`clippy::undocumented_unsafe_blocks = "deny"`. Only FFI modules may opt in with
`#[allow(unsafe_code)]`; the current allowances and deviations are recorded below. The fallback
logic in `hybrid`, the decoder logic in `media_foundation`, `annexb`, `biplanar`, and the encoder
logic in `nvenc` (H.264 and H.265 alike) keep `unsafe` denied. NVENC's H.265 support
added no `unsafe` module: it reuses `nvenc::ffi` and `nvenc::session` (new data declarations and
a second codec-configuration member of an existing union).
2. Every `unsafe` block has a `// SAFETY:` comment saying why it is sound.
3. The public API is safe: no `pub unsafe fn`, no raw pointers or FFI types in public signatures;
failures are `Result`s. The crate keeps the never-crash `deny(clippy::unwrap_used, …)`
attribute of every clean crate.
4. No panic crosses the FFI boundary: the VideoToolbox output callback runs under `catch_unwind`
and reports a panic as a failed frame. The Windows backend has no callbacks into Rust (it drives
a synchronous decoder MFT with `ProcessInput` / `ProcessOutput`), so nothing can unwind into
COM. NVENC loads the driver library at run time and has no callbacks into Rust either.
4. No panic crosses the FFI boundary. This applies to VideoToolbox's decoder and encoder output
callbacks, NVDEC's parser callbacks, and VA-API's error-message callback; their current unwind
guards are described below. Media Foundation drives a synchronous decoder MFT with
`ProcessInput` / `ProcessOutput` and registers no Rust callbacks. NVENC loads the driver library
at run time and registers no Rust callbacks either.
5. The crate compiles on every target. OS bindings are target-specific dependencies (the
`objc2-*` crates on macOS, the official `windows` crate on Windows: MIT OR Apache-2.0, already
in the lockfile through wgpu / winit, with only the Win32 features the backend uses); elsewhere
`register()` reports `Unavailable` and does nothing. It is not part of the wasm check (L5).
## Current implementation (source audit, 2026-10-24)
Checked against `main` at
[`552f008d2757e307194256b3e672c9c1de4fa9b2`](https://github.com/storytold/filmcraft/commit/552f008d2757e307194256b3e672c9c1de4fa9b2)
for [#2814](https://github.com/storytold/filmcraft/issues/2814). This section records source
facts, not new architecture approvals or evidence that builds, hardware tests or a complete
soundness review passed. The accepted decision, its approval date and the containment rules above
remain in effect; implementation differences are not exceptions to those rules.
### Unsafe allowances and module boundaries
[`Cargo.toml`](../../crates/platform/Cargo.toml) still denies `unsafe_code` and
`clippy::undocumented_unsafe_blocks`. The following are the actual allowance scopes:
| Target | Scope carrying `#[allow(unsafe_code)]` | Work at the boundary |
|---|---|---|
| macOS | [`videotoolbox`](../../crates/platform/src/videotoolbox.rs) | VideoToolbox decoding, CoreMedia/CoreVideo buffers and its output callback. |
| macOS | [`videotoolbox_encode`](../../crates/platform/src/videotoolbox_encode.rs) | VideoToolbox H.264/HEVC encoding and its output callback; `dlsym` resolves an optional VideoToolbox property. |
| Windows | [`media_foundation::gpu`](../../crates/platform/src/media_foundation/gpu.rs), [`media_foundation::mft`](../../crates/platform/src/media_foundation/mft.rs) | COM, Media Foundation, D3D11 device setup and surface readback. |
| Windows / 64-bit Linux | [`nvenc::ffi`](../../crates/platform/src/nvenc/ffi.rs), [`nvenc::session`](../../crates/platform/src/nvenc/session.rs), [`nvenc::device`](../../crates/platform/src/nvenc/device.rs) | NVENC declarations and calls, driver loading, D3D11 or CUDA device/context operations. |
| 64-bit Linux | [`nvdec::cuvid`](../../crates/platform/src/nvdec/cuvid.rs) | NVDEC driver calls, parser callbacks and CUDA readback, using `nvenc::device`. |
| Linux | [`vaapi::va`](../../crates/platform/src/vaapi/va.rs) | libva/libva-drm loading, displays, decoding, surface readback and logging. |
| Windows | [`cursor::cursor_screen_position`](../../crates/platform/src/cursor.rs) (function only) | `GetCursorPos`; a non-media deviation, discussed below. |
The media allowances are on module declarations in
[`lib.rs`](../../crates/platform/src/lib.rs) and the backend module files
([Media Foundation](../../crates/platform/src/media_foundation.rs),
[NVENC](../../crates/platform/src/nvenc.rs),
[NVDEC](../../crates/platform/src/nvdec.rs), [VA-API](../../crates/platform/src/vaapi.rs)).
They apply to the module's contents, rather than to individual blocks; the cursor allowance is
on the Windows function itself. There is no crate-wide `allow(unsafe_code)`.
`vaapi::ffi` and `nvdec::ffi` contain public declarations but do not carry an unsafe allowance.
### Callbacks into Rust and panic handling
The registered production callbacks are:
| Backend and entry point | Guard and outcome visible in the source |
|---|---|
| `videotoolbox::output_callback` (`extern "C-unwind"`) | `catch_unwind(AssertUnwindSafe(..))` guards decoded-picture processing. A caught panic becomes `Output::Failed`, queued through `Shared::push`. |
| `videotoolbox_encode::output_callback` (`extern "C-unwind"`) | The same guard covers compressed-packet processing; a caught panic becomes `Output::Failed`. |
| `nvdec::cuvid::{sequence, decode, display}` (`extern "C"`) | All three call the shared `callback` helper, whose processing closure runs under `catch_unwind(AssertUnwindSafe(..))`. The first error or caught panic is stored in `State::error` and returns `0`; `Session::parse` checks that error after the driver call. |
| `vaapi::va::on_error` (`extern "C"`) | `catch_unwind` surrounds message conversion and logging; a caught panic is ignored. `Display::init` registers this callback and disables libva's info callback. |
NVDEC installs its three callbacks in `Session::create_parser`; they run synchronously within
`cuvidParseVideoData`. The VideoToolbox callbacks deliver output asynchronously through their
shared queues. NVENC's function-table entries are driver functions called by Rust, not callbacks
registered with the driver. Media Foundation uses `Mft::input` / `Mft::poll` and rejects
asynchronous MFTs in `Mft::configure`.
These guards do not validate foreign pointers, prevent driver faults or prove state lifetimes
and aliasing sound. Error recording and queueing outside a guarded closure must also obey rule 4;
in particular, the VideoToolbox callbacks call `Shared::push` after `catch_unwind` returns.
An `extern "C-unwind"` signature does not relax the requirement that no panic reach foreign code.
### Known deviations and follow-up
- **Non-media scope:** `cursor::cursor_screen_position()` returns `Option<(i32, i32)>`, calling
`GetCursorPos` on Windows and returning `None` elsewhere. Its purpose is to locate file drops
when the windowing layer supplies no pointer position. It is outside the media-only decision
above, even though its Rust API is safe and read-only. Its presence is not approval to add other
OS integration. Removing/replacing it or granting a separate scope exception needs maintainer
resolution; [#2725](https://github.com/storytold/filmcraft/pull/2725) contains a proposed
window-system ADR and is still open and unmerged at this audit revision.
- **Public FFI surface:** rule 3 is not fully satisfied. `vaapi` publicly exports `ffi`, Linux
`va`, and the `Accel` trait. `vaapi::va::Session::new` takes `&[ffi::VAProfile]`, and
`Accel::decode_h264` / `decode_hevc` expose VA picture, matrix and slice parameter structures.
`nvdec` publicly exports `ffi` and `cuvid`; for example, `ffi::CUVIDPICPARAMS` exposes the raw
`pBitstreamData` and `pSliceDataOffsets` fields, and `nvdec::ffi` callback types contain raw pointers.
There is no production `pub unsafe fn`, but that alone does not meet the no-public-FFI-types
rule. [#2800](https://github.com/storytold/filmcraft/issues/2800) tracks the VA-API visibility
and driver-input validation problem: `Session::decode_into` forwards supplied parameter
structures and slice data to libva. Restoring encapsulation and checking caller-controlled
driver inputs are code follow-ups; this document does not waive rule 3 or fix #2800.
- **Separate FFI hardening:** [#2812](https://github.com/storytold/filmcraft/issues/2812) remains
open. Callback guards are not a resolution of its lifetime, COM-apartment or cleanup concerns.
For example, `media_foundation::mft::Mft` still has a `Send` implementation based on a
free-threaded assumption, `gpu::init_com` accepts `RPC_E_CHANGED_MODE`, and
`gpu::Readback::read` calls `Unmap` after its Rust closure rather than through an unwind guard.
Conversely, NVDEC's callback state now uses `Box::into_raw`; `Session::drop` reclaims it with
`Box::from_raw` after parser destruction, or leaks it if the parser cannot be destroyed.
That part of the older issue description must not be treated as an unchanged snapshot.
None of these observations changes the safety requirements.
## The fallback guarantee
Registering a hardware decoder never makes a file undecodable, and never changes a picture:
@@ -93,7 +178,8 @@ deliberate:
Everything else in this record applies unchanged: `unsafe` stays in `crates/platform`, every block
has a `// SAFETY:` comment, no panic crosses the FFI boundary, the public API is safe, and the crate
compiles everywhere (`register()` is a no-op off macOS).
compiles everywhere (the VideoToolbox backend is macOS-only; `register()` also selects the other
backends described in this record).
## Addendum (2026-10-08): NVENC HEVC Main 10 (HDR)
+1 -1
View File
@@ -45,7 +45,7 @@ contents). Percent is ready-for-real-work for that codec: depth, profiles, speed
| E-AC-3 (Dolby Digital Plus) | decode | own decoder (ATSC A/52 Annex E, #647): independent substream, 1–6 blocks, spectral extension; **no** adaptive hybrid transform, enhanced coupling or 7.1 dependent substreams (plays the 5.1 core) | none | SNR vs ffmpeg 65–91 dB (raw, MP4, MOV, Matroska, TS) | 75% | 10–20 h |
| MP3 / MP2 | decode + MP3 encode | MP3: symphonia (MPL-2.0); MP1 / MP2: own decoder (ISO/IEC 11172-3 / 13818-3, #809), also the bit rate / mode combinations ffmpeg writes by default (mono 384 kbit/s); in raw, MP4, MOV (#800), Matroska, TS, PS | **none** | MP1 / MP2: SNR vs ffmpeg 73–84 dB; MP2 in MP4 / MOV within 1e-4 of ffmpeg, also after a seek (#800) | 65% | 10–20 h (MP3 encoder) |
| Opus | — (not a Premiere import format) | own decoder, RFC 8251 range-exact | none | conformance vectors | ahead | — |
| FLAC, ALAC, Vorbis | partial | decode (symphonia); FLAC in MP4 silent (#603) | none | — | 70% | 3–6 h |
| FLAC, ALAC, Vorbis | partial | decode (symphonia), also FLAC in MP4 / MOV (#603) | none | — | 70% | 3–6 h |
| WMA | decode on Windows | none | none | — | 0% | 10–20 h |
## Summary
+5 -4
View File
@@ -1,6 +1,6 @@
# File format parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (GIF export binary transparency) · **Target:** Adobe Premiere Pro 2026 (26.5.2) and Media Encoder 2026
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-30 · **Change:** minor (AVI import #650 and VP8 WebM #1064 ship; GIF export binary transparency retained) · **Target:** Adobe Premiere Pro 2026 (26.5.2) and Media Encoder 2026
Containers, stills, audio files, project and interchange files, captions and presets. Codecs inside
the containers are in [codec-parity.md](codec-parity.md). Our import list is
@@ -39,8 +39,8 @@ only). "Tested" names the evidence.
| QuickTime MOV | read + write | yes | yes (H.264, HEVC, ProRes, DNxHR, APV, MJPEG, PCM) | ffprobe | 90% |
| MXF OP1a / OP-Atom | read + write (XDCAM, AVC-Intra, P2, AS-10 / AS-11, JPEG 2000, DNx) | yes (AVC, DNx, ProRes, MPEG-2, PCM / AES3, timecode) | OP1a (DNxHR, ProRes, H.264), OP-Atom | index-table seeking tests | 70% |
| MPEG TS / PS (AVCHD `.mts`, `.m2ts`, `.ts`, `.mpg`, `.vob`, `.mod`) | read; write MPEG-2 / Blu-ray / DVD | yes | **no** | fixtures | 65% |
| Matroska / WebM | read (limited) | yes (H.264, HEVC, VP9, AV1, ProRes, MJPEG + AAC, Opus, FLAC, MP3, Vorbis, PCM); WebM black report (#432), MKV multi-audio (#601) | no | fixtures; audio sample-aligned with the encoded signal (#790) | 75% |
| **AVI** | read + write | **no** (extension listed, no demuxer, #598) | no | — | 0% |
| Matroska / WebM | read (limited) | yes (H.264, HEVC, VP8 (#1064, fixes the black WebM of #432), VP9, AV1, ProRes, MJPEG + AAC, Opus, FLAC, MP3, Vorbis, PCM); MKV multi-audio (#601) | no | fixtures; audio sample-aligned with the encoded signal (#790) | 75% |
| AVI | read + write | yes: AVI 1.0 and OpenDML (`crates/avi`, #650); Motion JPEG, H.264, HEVC, uncompressed RGB / YUV with PCM, MP3, MP2 or AC-3; no DivX / Xvid or DV | no | `cargo test -p filmcraft-avi` (hand-built index paths, truncation, mutation); `tests/ffmpeg_oracle.rs` (MJPEG, H.264 B-frames, BGR24, YUY2, PCM, MP3 vs ffprobe) | read only |
| WMV / ASF | read; write on Windows | no | no | — | 0% |
| FLV / F4V | read + write | no | no | — | 0% |
| DCP | write (Wraptor) | no | no | — | 0% |
@@ -85,7 +85,7 @@ only). "Tested" names the evidence.
## Summary
**File formats dimension: ~60%, 140–240 h** (codecs counted separately). The beta blocker is
`.prproj`. After it, by user impact: AVI and WMV import, EXR / PSD stills, AAF / OMF
`.prproj`. After it, by user impact: WMV import, AVI export and DivX / Xvid / DV, EXR / PSD stills, AAF / OMF
validation in Avid and Pro Tools, DVD / Blu-ray / DCP / broadcast MXF delivery.
## Revision history
@@ -93,6 +93,7 @@ validation in Avid and Pro Tools, DVD / Blu-ray / DCP / broadcast MXF delivery.
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | Stills: EXIF Orientation applied to JPEG, PNG, WebP and TIFF (#1583); YCbCr TIFF and DNG not yet |
| 2026-10-12 | minor | VP8 WebM decodes (#1064, closes #432): Matroska row no longer lists the black-picture report. AVI import ships (#650, merged 2026-10-12): row changed from "no demuxer" to read via `crates/avi`; summary no longer lists AVI import as missing; the row's percentage and the dimension figure (~60%, 140-240 h) were not re-measured |
| 2026-10-10 | major | Created: format-by-format comparison with Premiere 26.5.2 from the import / export registries, crate READMEs, the installed bundle's exporter listings and open issues |
| 2026-10-21 | minor | PSD stills: 8/16-bit grayscale and RGB import of the flattened composite, merged transparency unblended from white (#2116) |
| 2026-10-26 | minor | GIF exports preserve binary transparency when requested; synthetic decode tests cover alpha threshold and animation disposal (#3396); estimates unchanged |
+33 -19
View File
@@ -1,6 +1,6 @@
# Where FilmCraft falls short of Premiere Pro
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-26 · **Change:** minor (G13: optional binary transparency in animated GIF exports) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-30 · **Change:** minor (G14 catalog counts refreshed; G13 GIF binary transparency retained) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
Every known shortfall, one entry each, ranked by what most stops an editor using FilmCraft for real
work. This is the work list: agents should prefer it over adding checklist items. Numbers are in
@@ -28,7 +28,7 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
| G12 | AI features | AI | no | 150–300 h |
| G13 | Delivery formats | file format | no | 60–110 h |
| G14 | Localization: 3 of the 12 key languages missing | localization | no | 150–230 h |
| G15 | Interchange never validated in Avid / Pro Tools; slow XML import | file format | partly | 20–40 h |
| G15 | Interchange never validated in Avid / Pro Tools | file format | partly | 20–40 h |
| G16 | Performance reports on 0.5.0 | performance | partly | 30–60 h |
| G17 | Audio device and playback reliability | stability | partly | 20–40 h |
| G18 | Effects depth: approximations and missing behaviour | feature | no | 60–110 h |
@@ -158,8 +158,8 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
- **Missing:** on-screen transform handles for Motion / Transform in the Program monitor (#639);
Effect Controls keyframe zoom bar, draggable divider, row lines (#640–#645); panel docking
rearrangement (#493); clicking empty space to deselect (#683); filmstrip thumbnails (#614);
marquee selection in the Project panel (#578); Freeform bins and middle-mouse pan (#579);
rearrangement (#493); filmstrip thumbnails (#614);
Freeform bins and middle-mouse pan (#579);
Program monitor Button Editor (+) (#431); keyframe curves for Position (#448); transition
editing controls (#577: Effect Controls has the transition's own timeline, drag to slide or resize,
drag the cut to move it with the transition; the A/B previews with Start / End sliders remain); track renaming (#654); window controls on macOS (#637); UI scale on
@@ -192,8 +192,7 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
from white.
- **Missing:** free-format MPEG audio, E-AC-3 adaptive hybrid
transform, enhanced coupling and the extra channels of 7.1 dependent substreams (played as the
5.1 core), AVI (#598; listed as an extension but no
demuxer), WMV / ASF, DV / DVCPRO / DV100, MPEG-4 Part 2, FLAC in MP4 (#603), camera RAW (R3D
5.1 core), WMV / ASF, DV / DVCPRO / DV100, MPEG-4 Part 2, camera RAW (R3D
incl. R3D NE, ARRIRAW, Sony RAW / X-OCN, Canon RAW, BRAW, ProRes RAW; #345, #383), JPEG 2000 /
JPEG XS MXF, the rest of CinemaDNG (lossy / JPEG XL DNG, MXF-wrapped, opcode lists, Raw
settings; DNG frames and folders import since #1065); stills: PSD layers (Merged Layers, Individual Layers, layers as a sequence) and PSB / CMYK / Lab / indexed / 32-bit / ZIP-compressed documents, EXR beyond scan-line RLE / ZIP / PXR24 (PIZ, B44, DWA,
@@ -213,6 +212,8 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
[file-format-parity.md](file-format-parity.md).
- **Have:** Matroska AAC / MP3 / AC-3 / E-AC-3 sample-aligned with the signal the encoder was
given (the exact `CodecDelay` is trimmed, not the 1 ms-rounded one; #790).
- **Have:** AVI / OpenDML import (#598; Motion JPEG, H.264, HEVC, uncompressed video; PCM, MP3,
MP2, AC-3 audio) and FLAC audio in MP4 / MOV (#603; the decoder gets its STREAMINFO from `dfLa`).
## G11. No plugin hosting
@@ -247,16 +248,18 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
## G14. Localization: 3 of the 12 key languages missing (Hindi, Arabic, Vietnamese)
- **Have:** Spanish, Japanese, Simplified Chinese, French (#498) and Portuguese (Brazil) (#859)
catalogs cover ~99.6–100% of the ~3,500 cataloged strings; Ukrainian ~10% (menus).
- **Have:** Spanish, Japanese, Simplified Chinese, French (#498, #804), Portuguese (Brazil) (#859,
#866), Indonesian (#867, #878), Korean (#508), Italian, German (#630), Russian, Czech, Polish,
Swedish, Turkish and Bosnian catalogs cover the whole interface (about 99–100% of the cataloged
strings); Ukrainian is menus only (~10%).
- **Missing:** Hindi, Arabic (no RTL interface layout or catalog; the interface has Arabic and Hebrew
fallback fonts and shows captions in reading order, but other names and fields keep egui's
left-to-right run order; Arabic titles reported broken, #395),
Korean (#508; needs Hangul fonts in craft-fonts),
Vietnamese; Premiere also ships Russian. Engine errors,
Vietnamese (no catalog on main). These three are the languages with no catalog at all, which
is what the heading counts. Engine errors,
CLI and MCP stay English. Apart from the German menus (#630), no language has a recorded
native-speaker review.
- **Done:** Indonesian (#867): `id.tsv` has every entry of the Spanish catalog (~100%), in Edit ▸
- **Done:** Indonesian (#867): `id.tsv` covers 3,562 of 3,610 strings (98.7%), in Edit ▸
Language, Settings ▸ General and the native macOS menu; System Language picks it for `id-*` (and
legacy `in-*`) locales. Native-speaker review welcome.
- **Done:** Italian: `it.tsv` has every entry of the Spanish catalog (~100%), in Edit ▸ Language,
@@ -265,13 +268,22 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
- **Done:** German: `de.tsv` has every entry of the Spanish catalog (~100%), beyond the menus of
#630 (panels, dialogs, effects and their parameters, settings, status messages). The menus are
native-reviewed; review of the rest welcome.
- **Done:** Russian: `ru.tsv` has every entry of the Spanish catalog (100% of the reference
catalog), translated from the English sources with Premiere-style Russian vocabulary
(последовательность, корзина, дорожка, визуализация); Edit ▸ Language, Settings ▸ General and
System Language (`ru-*`) reach it, a font test covers
the Russian alphabet including «ё». Native-speaker review welcome.
- **Done:** Turkish: `tr.tsv` has every entry of the Spanish catalog (~100%), in Edit ▸ Language,
Settings ▸ General and the native macOS menu; System Language picks it for `tr-*` locales.
Native-speaker review welcome.
- **Estimate:** 150–230 h. Doc: [localization-parity.md](localization-parity.md).
## G15. Interchange never validated in Avid / Pro Tools; slow XML import
## G15. Interchange never validated in Avid / Pro Tools
- **Missing:** AAF and OMF exports have never been opened in Media Composer or Pro Tools; FCP7 XML
/ OTIO import of a Premiere sequence took 97 s / 66 s for three media files (#461); `file.import`
merges into existing project content in headless round trips (#592).
- **Missing:** AAF and OMF exports have never been opened in Media Composer or Pro Tools; `file.import`
merges into existing project content in headless round trips unless `replace:true` is passed (#592).
- **Done:** FCP7 XML / OTIO / EDL / AAF import links media through the streaming reader instead of
reading each file whole into memory (#461).
- **Estimate:** 20–40 h (needs a human with Avid / Pro Tools, both installed on the owner's Mac).
## G16. Performance reports on 0.5.0
@@ -302,10 +314,11 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
## G18. Effects depth: approximations and missing behaviour
- **Missing:** Warp Stabilizer is 2-D (Premiere's is 3-D subspace), Morph Cut has no face tracking
(and occlusion edges are soft), Lumetri HDR-aware maths, multicam paging beyond 16 angles,
(and occlusion edges are soft), Lumetri HDR-aware maths,
redesigned 26.0 mask tools (a stored, re-editable
corner radius: rounding bakes fillets into the path, so a rounded corner cannot be re-rounded later).
- **Have:** Optical Flow time interpolation synthesises motion-compensated in-between frames (dense
- **Have:** multicam sources with more than 16 angles are paged 4 × 4 in the Multi-Camera view;
Optical Flow time interpolation synthesises motion-compensated in-between frames (dense
coarse-to-fine Lucas–Kanade, CPU; #780); Morph Cut morphs the subject along that optical flow
instead of cross-dissolving (CPU; #805); Transform shutter angle motion blur (#415); Ultra Key
Setting applied as a parameter preset (#458); rectangle and polygon masks round their corners
@@ -325,14 +338,15 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
## G20. The web app shell
- **Missing:** file access, WebCodecs decode, audio in the browser build beyond what exists;
image-sequence export downloads drop frames in Chrome (#378).
- **Missing:** file access, WebCodecs decode, audio in the browser build beyond what exists.
- **Estimate:** 40–80 h. Doc: [web.md](web.md).
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | G14: three key languages have no catalog (Hindi, Arabic, Vietnamese); Korean, Italian, German and Russian are near-complete; Ukrainian is menus only; G13 GIF transparency retained |
| 2026-10-12 | minor | Removed shipped items: AVI import (#598), FLAC in MP4 (#603), multicam paging, deselect on empty space (#683), Project marquee (#578), image-sequence downloads in Chrome (#378), slow interchange import (#461) |
| 2026-10-10 | major | Created from ROADMAP.md's "Where we are lacking" (2026-10-05); re-ranked against Premiere 26.5.2 and the 188 open issues; added `.prproj`, 10-bit / 4:2:2 camera decode, launch crashes and missing PR CI, localization, delivery formats |
| 2026-10-12 | minor | G10: MP1 (`A_MPEG/L1`) in Matroska imports (#1402) |
| 2026-10-12 | minor | G10: OpenEXR PXR24 import |
+10
View File
@@ -68,3 +68,13 @@ Frame has no default key; the Premiere preset moves Shift+E to Export Frame and
| Project panel: Next/Previous Column Field, Next/Previous Row Field (Tab, ⇧Tab, Return, ⇧Return) | The list view has no inline-editable metadata cells. |
| Text panel: Edit Segment (Return) | No inline transcript text editing (corrections go through `transcript.set`). |
| Text panel: Follow Active Monitor (⇧C), Show Source Transcript (⇧Z) | Only sequence transcripts exist; source clips have no transcript view. |
## Numpad keys
FilmCraft's shortcut engine runs on egui, which maps numpad digits to the same `Key::Num*` variants as the main keyboard row. This means **numpad digits cannot currently be bound independently** of their main-keyboard equivalents — binding `0` on the numpad binds `0` everywhere, and vice versa.
This is because egui reports numpad digits as the main-row digit keys (it does not expose distinct `Key::Numpad*` variants).
- `0` is bound to **Multi-Camera Record On/Off Toggle** in the Premiere-compatible preset; rebind it if you rely on numpad `0` for something else (Edit ▸ Keyboard Shortcuts…).
See [#2645](https://github.com/storytold/filmcraft/issues/2645) for upstream egui tracking.
+40 -9
View File
@@ -1,6 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (German covers the whole interface) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (German covers the whole interface; Czech and Polish interface catalogs; Swedish and Russian cover the whole interface; Turkish catalog; Bosnian covers the whole interface) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
Per-language status of FilmCraft's interface. Premiere 26.5.2 ships 10 interface languages (the
installed bundle's `.lproj` folders): German, English, Spanish, French, Italian, Japanese, Korean,
@@ -10,7 +10,7 @@ Portuguese (Brazil), Russian, Simplified Chinese.
Edit ▸ Language (also Settings ▸ General, `general.interfaceLanguage`) switches the interface and
persists across restarts; System Language (the default) follows the OS's or browser's preferred
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian|italian>`
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian|italian|korean|czech|polish|swedish|turkish>`
are reachable over the control channel. One catalog per language,
`crates/ui-egui/src/i18n/<code>.tsv` (format in the header of `es.tsv`); UI code wraps strings in
`tl!("…")` / `tlf!("…{name}", name)`, and registry names (effects and their parameters, settings,
@@ -34,16 +34,16 @@ right to left, but Arabic titles are reported broken (#395).
## Status
Measured 2026-10-10 by counting entries in each catalog. The denominator is the union of all
catalogs' sources (**3,498 strings**); strings not yet wrapped in `tl!` and engine messages are
Measured 2026-10-19 by counting entries in each catalog. The denominator is the union of all
catalogs' sources (**3,593 strings**); strings not yet wrapped in `tl!` and engine messages are
not in it, so real coverage is somewhat lower than shown. Estimates calibrated from #449 (Japanese,
whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
| Language | Code | UI strings | Dialogs / tooltips / help | Script support | Native review | Status | To `full` |
|---|---|---|---|---|---|---|---|
| English | en | 3,498 (100%, source) | yes; no help docs in-app | Latin | n/a | **full** | — |
| English | en | 3,593 (100%, source) | yes; no help docs in-app | Latin | n/a | **full** | — |
| Simplified Chinese | zh-cn | 3,485 (99.6%) | dialogs, Settings, effects, shortcuts, status; engine errors English | CJK font fallback; system IME via winit | not recorded | partial | 6–10 h + review |
| Spanish | es | 3,496 (99.9%) | as above | Latin | not recorded | partial | 4–8 h + review |
| Spanish | es | 3,592 (the reference catalog) | as above | Latin | not recorded | partial | 4–8 h + review |
| Hindi | hi | 0 (0%) | — | **no Devanagari shaping in the interface** | — | none | 30–50 h (shaping in UI + catalog + fonts) |
| Arabic | ar | 0 (0%) | — | **no RTL interface layout**; fallback fonts and shaping in the interface, captions in reading order, other text in egui's left-to-right run order; titles broken (#395) | — | none | 40–70 h |
| French | fr | 3,526 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial (#498) | review |
@@ -52,8 +52,14 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
| Japanese | ja | 3,487 (99.7%) | as zh-cn; system-font kanji may pick the Chinese fallback (#547) | CJK fonts, vertical title text | not recorded | partial | 4–8 h + review |
| German | de | 3,590 (same entries as Spanish) | as Spanish | Latin (bundled fonts) | menus yes (#630); the rest not recorded, welcome | partial | review |
| Korean | ko | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | installed system Hangul font (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); refused without one | not recorded; welcome | partial (#508) | review; Hangul in craft-fonts for the web |
| Russian | ru | 3,592 (100%; every Spanish entry) | dialogs, Settings, effects, shortcuts, status; engine errors English | Cyrillic (bundled fonts), «ё» | not recorded; welcome | partial | native review |
| Vietnamese | vi | 0 (0%) | — | Latin with stacked diacritics (font coverage to check) | — | none | 6–12 h + review |
| *Other shipped:* Italian | it | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial | review |
| Czech | cs | 3,617 (all Spanish source keys, plus the playback-resolution label `1/2` the two Work Area Bar commands and the seven audio-output and update strings Spanish lacks) | dialogs, Settings, effects, shortcuts, status; engine errors English | Latin (bundled fonts) | not reviewed | partial | native review |
| *Other shipped:* Polish | pl | every Spanish entry plus the native macOS menu items; counts written count-neutral | as Spanish | Latin (bundled fonts) | not recorded; AI-drafted, review welcome | partial | review |
| *Other shipped:* Swedish | sv | 3,581 (100%) | as Spanish | Latin (bundled fonts, å ä ö) | not recorded; welcome | partial | review |
| *Other shipped:* Turkish | tr | 3,590 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial | review |
| *Other shipped:* Bosnian | bs | 3,610 (every entry of the other catalogs) | as Spanish | Latin (bundled fonts) | AI-assisted, not yet native-reviewed; welcome | partial | review |
| *Other shipped:* Ukrainian | uk | 351 (10.0%) | menus only | Cyrillic (bundled) | not recorded | menus only | 5–9 h |
UI strings are catalog entries (the non-comment lines of `src/i18n/<code>.tsv`; English counts the
@@ -62,19 +68,44 @@ Spanish and German catalogs both have 3,590 entries, German every Spanish one. A
lacks shows in English; no test or build requires a translation. `cargo xtask i18n-report` (also
printed by `cargo xtask ci`) lists each language's missing strings.
Premiere also ships Italian, which we now cover like Spanish, and Russian (6–10 h).
Premiere also ships Italian, which we cover like Spanish, and Russian, which now covers the whole
interface (native review welcome).
**Localization dimension:** 9 of the 12 key languages near-complete (English, Spanish, Japanese,
Simplified Chinese, French, Portuguese (Brazil), Indonesian, Korean, German), 3 none (Hindi, Arabic,
Vietnamese); against Premiere's 10 languages we cover 9 well and Russian partly. **~35%, 150–230 h** for all twelve to `full` plus Italian and Russian, of which the script
work (RTL and complex shaping in the egui interface) is the largest and least parallel part.
Vietnamese); against Premiere's 10 languages we cover 10 at catalog level, Russian pending native
review. **~35%, 150–230 h** for the twelve key languages to `full` plus Russian review, of which the
script work (RTL and complex shaping in the egui interface) is the largest and least parallel part.
Every language needs native-speaker review (human).
## Czech catalog quality
The Czech rows were rewritten as complete phrases in batches from the English meanings,
with French/German entries and Rust call sites consulted for ambiguous labels. This is an
AI-assisted translation with a binding video-editing glossary, **not yet proofread by a native
speaker**. Coverage measures lookup keys, not linguistic approval or a complete UI walkthrough.
In particular, Bin is **Přihrádka**, Trash is **Koš**, Lift is **Zvednout**, Extract is
**Vyjmout s posunem**, and Timeline is **Časová osa**. Delete is **Odstranit** and Remove is
**Odebrat**; image- and audio-processing names such as Remove Fringe or Flicker Removal keep
**Odstranit**. The original catalog header comments were preserved; their older glossary
shorthand is superseded by these terms and the catalog rows.
The current catalog parser and formatter have no plural-variant selection, and none of the
other catalogs provide plural rows. The UI's existing singular branches are retained; other
counts use neutral wording such as “Počet klipů: {n}”, valid for 0, 1, 2–4 and larger counts
without a new runtime API.
Native review should concentrate on audio automation terms, Slip/Slide/Roll operations,
creative effect names, and the shared `Subtitle` key (caption format and graphics subtitle).
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Bosnian (bs, Latin, Ijekavian) covers the whole interface: 3,610 entries, Edit ▸ Language ▸ Bosanski, every `bs-*` locale; AI-assisted, not native-reviewed |
| 2026-10-26 | minor | Swedish (sv) covers the whole interface (all 3,581 strings); System Language picks it for `sv-*` locales |
| 2026-10-23 | minor | Missing translations never fail a test or build (they show in English); `cargo xtask i18n-report`, also run by `cargo xtask ci`, lists them per language |
| 2026-10-19 | minor | Added Czech (`cs`) interface catalog and locale detection; rewrote all catalog rows with a binding glossary; AI-assisted, not yet proofread by a native speaker |
| 2026-10-19 | major | Russian (ru) covers the whole interface: 3,592 entries (100% of the Spanish catalog), `ru.tsv` translated from the English sources with Premiere-style Russian vocabulary; a font test covers the Russian alphabet including «ё», System Language maps `ru-*`; native review welcome |
| 2026-10-13 | minor | German (de) covers the whole interface: every entry of the Spanish catalog (3,590); the tests require the core menus and the macOS app menu and reject entries without a Spanish one |
| 2026-10-11 | minor | German (de) ships the menus (#630): 371 of 3,498 strings (10.6%), native-reviewed; dimension now 2 menus-only, 5 none |
| 2026-10-10 | major | Created from ROADMAP.md's "Interface localization" section; catalog coverage measured (es 3,496, ja 3,487, zh-cn 3,485, pt-br 348, uk 351 of 3,498); the 12-language table and script-support gaps added |
+7 -6
View File
@@ -1,6 +1,6 @@
# Roadmap detail
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (M8: Fujifilm F-Log / F-Log2 / F-Log2 C colour spaces) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-30 · **Change:** minor (M8: Fujifilm F-Log / F-Log2 / F-Log2 C colour spaces; E-AC-3 #788, AVI #650, Russian #610, French #804, Indonesian #878, pt-BR #866 and German #630 no longer listed as open) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
Forward-looking plan: milestones with status, the current focus and what's next, with estimates in
Opus 5.5 agent-hours (calibration in [target-app-parity.md](target-app-parity.md#calibration)).
@@ -28,7 +28,7 @@ workflow would make FilmCraft pre-alpha.
| Workflow | Works end to end? | Evidence | Hours to pass |
|---|---|---|---|
| Ingest and organize media (import camera / phone / screen files, bins, metadata, proxies) | partial, not blocking | H.264 / HEVC / ProRes / DNx / MXF / AVCHD / MKV import with bit-exact decoders, Media Browser, bins, proxies, Link Media. Gaps: H.264 10-bit / 4:2:2 (Sony XAVC S-I, Canon XF-AVC) opens nowhere and HEVC 4:2:2 only through VideoToolbox (#626, [G3](gaps.md#g3-10-bit-and-422-camera-media-needs-hardware)); E-AC-3 (#647), AVI (#598), camera RAW | 0 (G3 is a beta item, 30–50 h) |
| Ingest and organize media (import camera / phone / screen files, bins, metadata, proxies) | partial, not blocking | H.264 / HEVC / ProRes / DNx / MXF / AVCHD / MKV import with bit-exact decoders, Media Browser, bins, proxies, Link Media. Gaps: H.264 10-bit / 4:2:2 (Sony XAVC S-I, Canon XF-AVC) opens nowhere and HEVC 4:2:2 only through VideoToolbox (#626, [G3](gaps.md#g3-10-bit-and-422-camera-media-needs-hardware)); camera RAW; E-AC-3 plays its 5.1 core (#647) and AVI imports (#598) | 0 (G3 is a beta item, 30–50 h) |
| Assemble and trim a sequence (insert / overwrite, ripple / roll / slip / slide, razor, J/K/L, multicam) | yes | Edit algebra with undo tests, Trim Monitor, Premiere keyboard preset, multicam with audio sync; UX issues (#683, #648, #668) slow it down but don't stop it | 0 |
| Colour correct and grade (Lumetri, LUTs, scopes, colour management) | yes | Lumetri complete with CPU / WGSL parity tests, all Lumetri Scopes, LUT import, HDR export signalling checked with ffprobe | 0 |
| Mix audio (levels, automation, Essential Sound, loudness) | yes on macOS | Mixer graph, automation, 53 effects, BS.1770 loudness matching ffmpeg; device reports are Windows / Linux (#605, #617) | 0 |
@@ -48,7 +48,7 @@ Beta = ready for real work ≥ ~75% and `.prproj` opens reliably ([target-app-pa
|---|---|---|---|
| B1 | PR CI on three OSs + launch-crash fixes | ⬜ | 40–80 |
| B2 | `.prproj` import (sequences, clips, transitions, markers, bins, common effect parameters) | ⬜ | 60–120 |
| B3 | Camera media: H.264 High 10 / 4:2:2, HEVC 4:2:2, E-AC-3, AVI, multi-track audio confirmed | 🟡 (multi-stream audio #361) | 50–90 |
| B3 | Camera media: H.264 High 10 / 4:2:2, HEVC 4:2:2, multi-track audio confirmed (E-AC-3 #788 and AVI #650 done) | 🟡 (multi-stream audio #361) | 50–90 |
| B4 | Fidelity harness + `cargo xtask parity` + media corpus + workflow tests | ⬜ | 90–160 |
| B5 | Hardware: zero-copy, more GPU effects, Windows vendor encoders, VideoToolbox Main 10 | 🟡 | 60–100 |
| B6 | UI fidelity top issues and Windows / Linux runtime | 🟡 | 90–160 |
@@ -69,23 +69,24 @@ Status: ✅ done · 🟡 in progress · ⬜ not started. Estimates are remaining
| M6 | Export | ✅ | Own H.264 encoder (High/Main/Baseline, B-frames, VBR/CBR/2-pass) → MP4 or QuickTime + own AAC; ProRes, DNxHR, MJPEG, PNG/TIFF/BMP sequences (Premiere-style numbering), GIF, WAV, AIFF; background jobs; M6.5: Export mode parity (frame size / rate / scaling / pixel aspect, profile / level, CBR / VBR 1- and 2-pass, keyframe distance, audio codec / rate / channels / bitrate / sample size, multiplexer, burn-in or sidecar captions, image / name / timecode overlays, video limiter, loudness normalization with true-peak limiter, metadata, ranges, estimated size, summary), 24 built-in presets + user presets with favourites and import/export (Preset Manager), export queue (reorder, cancel, retry, several sequences / ranges), Quick Export; `export.*` commands and `filmcraft-cli export --preset` | Interlaced encoding, 5.1 audio, smart render, publishing destinations | 6–8 |
| M7 | Audio | 🟡 | Mixer graph (tracks → submixes → Mix, pre/post-fader inserts and sends, latency-compensated, sample-accurate, ~6× realtime for 24 tracks × 3 effects on one core), Audio Track Mixer + Audio Clip Mixer panels, track automation (Off/Read/Latch/Touch/Write, recorded live while playing, thinned to keyframes, timeline lanes with pen editing), solo/solo-safe, channel mapping basics, peak + BS.1770 loudness meters (match ffmpeg), DSP crate with 16 clip/track effects, Audio Gain (set/adjust/normalize), Constant Power / Constant Gain / Exponential Fade, Essential Sound (types, Loudness auto-match, Repair incl. DeEss/DeReverb, Clarity, Creative, Ducking keyframes, presets; full Dialogue chain 22× realtime) | Music duration remix, ML speech enhancement, 5.1 panner and multichannel buses, voice-over record, effect editor windows (EQ curve), remaining effects (multiband, convolution reverb), clip-mixer automation recording | 6–9 |
| M8 | Colour | 🟡 | Lumetri: basic, creative + looks, RGB & hue curves, wheels, HSL secondary, vignette, section bypass; Input LUT / Look LUT (.cube 1D/3D/shaper, .3dl; tetrahedral CPU + WGSL; project LUT library; built-in camera conversions); Colour Match (Oklab tonal-range statistics, skin protection, solved in Lumetri wheels); colour management: Rec. 709 / Rec. 2100 PQ / HLG working spaces + wide gamut, Interpret Footage colour space (S-Log3, V-Log, Canon Log 2/3, LogC3/4, Apple Log, D-Log, Fujifilm F-Log / F-Log2 / F-Log2 C from published specs), metadata auto-detect, BT.2390 tone mapping, gamut mapping, HDR export signalling (VUI/colr/mdcv/clli/SEI, ffprobe-verified); Lumetri Scopes: Vectorscope YUV (75/100 % targets, skin-tone line) and HLS, Histogram, Parade (RGB/YUV/RGB-White), Waveform (RGB/Luma/YC/YC no Chroma), presets, Rec. 601/709/2100, 8-bit/float/HDR (cd/m²) scales, multi-scope grid, `scopes.read` | HDR-aware Lumetri maths, macOS EDR monitors, mastering metadata → tone-map peak, D-Log M (no published formula), HSL Secondary refine | 3–5 |
| M9 | More codecs | 🟡 | ProRes decode+encode, AAC decode+encode, HEVC Main/Main 10 decoder (bit-exact on 41 fixtures, ~225 fps 1080p), VP9 decoder (profiles 0–3, 8/10/12-bit, bit-exact on 50+ fixtures; WebM/MKV `V_VP9` and MP4 `vp09` import with key-frame-checked seeking), Matroska/WebM import (H.264/HEVC/VP9/ProRes/MJPEG + AAC/Opus/FLAC/MP3/Vorbis/PCM), Opus decoder (SILK/CELT/hybrid, 5.1/7.1 multistream; all RFC 8251 vectors range-exact; WebM/MKV/MP4), DNxHD/DNxHR decoder (all SMPTE ST 2019-1 CIDs, 8/10/12-bit, 4:2:2/4:4:4, interlaced, alpha; within IDCT precision of ffmpeg on 22 fixtures) + DNxHR LB/SQ/HQ/HQX/444 encoder and MOV `AVdh` export, AV1 decoder (Main profile, 8/10-bit, all intra/inter tools, loop filters, superres, film grain, intra BC, spatial layers; bit-exact vs libdav1d on 19 SVT fixtures + 22 libaom vectors; MP4 `av01` / WebM `V_AV1` import), MXF import (OP1a/OP-Atom; AVC with POC-ordered B pictures bit-exact, DNxHD/DNxHR, ProRes, PCM/AES3; MPEG-2 reported unsupported; index-table seeking, material package timecode), Ogg Opus/Vorbis (granule seeking, pre-skip, end trimming), image sequences, Broadcast WAV timecode | VP9 frame threading, AV1 threading/SIMD (~4 fps 1080p today), AV1 High/Professional profiles, H.264 High 10 / 4:2:2 and HEVC 4:2:2 in software (G3), E-AC-3, AVI (MPEG-2 decoder and hardware decode have landed) | 30–50 |
| M9 | More codecs | 🟡 | ProRes decode+encode, AAC decode+encode, HEVC Main/Main 10 decoder (bit-exact on 41 fixtures, ~225 fps 1080p), VP9 decoder (profiles 0–3, 8/10/12-bit, bit-exact on 50+ fixtures; WebM/MKV `V_VP9` and MP4 `vp09` import with key-frame-checked seeking), Matroska/WebM import (H.264/HEVC/VP9/ProRes/MJPEG + AAC/Opus/FLAC/MP3/Vorbis/PCM), Opus decoder (SILK/CELT/hybrid, 5.1/7.1 multistream; all RFC 8251 vectors range-exact; WebM/MKV/MP4), DNxHD/DNxHR decoder (all SMPTE ST 2019-1 CIDs, 8/10/12-bit, 4:2:2/4:4:4, interlaced, alpha; within IDCT precision of ffmpeg on 22 fixtures) + DNxHR LB/SQ/HQ/HQX/444 encoder and MOV `AVdh` export, AV1 decoder (Main profile, 8/10-bit, all intra/inter tools, loop filters, superres, film grain, intra BC, spatial layers; bit-exact vs libdav1d on 19 SVT fixtures + 22 libaom vectors; MP4 `av01` / WebM `V_AV1` import), MXF import (OP1a/OP-Atom; AVC with POC-ordered B pictures bit-exact, DNxHD/DNxHR, ProRes, PCM/AES3; MPEG-2 reported unsupported; index-table seeking, material package timecode), Ogg Opus/Vorbis (granule seeking, pre-skip, end trimming), image sequences, Broadcast WAV timecode, AC-3 and E-AC-3 decoder (`crates/ac3`; E-AC-3 independent substream 0, #788), AVI / OpenDML import (`crates/avi`; Motion JPEG, H.264, HEVC, uncompressed video; PCM, MP3, MP2, AC-3 audio; #650) | VP9 frame threading, AV1 threading/SIMD (~4 fps 1080p today), AV1 High/Professional profiles, H.264 High 10 / 4:2:2 and HEVC 4:2:2 in software (G3), E-AC-3 adaptive hybrid transform, enhanced coupling and the 7.1 dependent substreams (played as the 5.1 core) (MPEG-2 decoder and hardware decode have landed) | 30–50 |
| M10 | Graphics & captions | 🟡 | Caption tracks (Subtitle/CEA-608/708/Teletext formats, track style), SRT/WebVTT/SCC import+export (frame-exact, property-tested), caption editing (add/split/merge/trim/move, sync-locked insert/extract), Text panel Captions tab, burn-in in Program monitor and export; text engine (`crates/text`: bundled + system fonts, harfrust shaping, bidi, line breaking, paragraph layout, glyph cache; 3-line 1080p title ≈ 0.15 ms warm); graphic clips with text + shape layers (fill, 2 strokes, background, shadow, keyframable transform), Type tool with on-monitor editing, shape/pen tools, Properties/Essential Graphics editor, align/distribute; graphics templates (own `.fcgt` format, 8 original built-ins, Browse tab with engine-rendered thumbnails, export / install / apply / editable properties; never reads .mogrt), rolls/crawls with ease and pre/postroll, responsive pins and intro/outro, per-character styles, Upgrade Caption to Graphic, source graphics, Replace Fonts; MCC (608+708), EBU STL, TTML/IMSC1, DFXP (frame-exact, property-tested) | 608/708 embedding in video streams, speech-to-text | 12–18 |
| M11 | Interchange & project management | ✅ | `.fcproj` schema versions + migrations, atomic saves, Save a Copy/Revert, auto-save ring + crash-recovery journal (Preferences ▸ Auto Save, recovery prompt), FCP7 XML, FCPXML, EDL, OTIO; offline media (own slate) + Link Media (fingerprint-checked relink, folder remap, search, Align Timecode, Make Offline); proxies (ProRes Proxy/LT, H.264 ¼/½ background jobs, attach/detach/reconnect, monitor toggle, export full-res) + ingest (copy/transcode/proxies); Project Manager (collect, consolidate + transcode with handles, size estimate) | Rename media to clip names, image-sequence conversion, Media Browser-driven relink, smart (cross-drive) path tracking | 1–2 |
| M12–M16 | Multicam, web (WASM), platform, long tail | 🟡 | Multicam: audio sync (GCC-PHAT, sub-sample), Merge Clips, multi-camera source sequences, Multi-Camera view with live switching (1–9); L0–L4 crates compile to wasm32 | Web app shell (file access, WebCodecs, audio), scene detection, auto reframe, ~800 remaining commands and dialogs, performance hardening | 35–55 |
| HW | Hardware acceleration (#30) | 🟡 | Blend modes and 34 effect ids on the GPU incl. Lumetri basic / creative / vignette (#32, GPU2–GPU7); GPU export rendering (#421); decode: VideoToolbox (#33, #41), Media Foundation / D3D11 H.264 / HEVC / VP9 / AV1 (HW2, HW3), VA-API H.264 / HEVC (#358), NVDEC H.264 / HEVC (#549); encode: VideoToolbox H.264 / HEVC, NVENC H.264 (Windows, Linux) and HEVC incl. Main 10 HDR (#328, #360) | Zero-copy, remaining GPU effects, VideoToolbox Main 10, Quick Sync / AMF, VA-API VP9 / AV1 and encode | 120–200 |
| L10N | Localization | 🟡 | Spanish, Japanese, Simplified Chinese whole interface; Portuguese (Brazil), Ukrainian menus | Hindi, Arabic (RTL), French, Indonesian, German, Korean, Vietnamese, Italian, Russian; native review | 150–230 |
| L10N | Localization | 🟡 | Whole interface: Spanish, Japanese, Simplified Chinese, French, Portuguese (Brazil), Indonesian, Italian, Korean, German, Russian, Czech, Polish, Swedish, Turkish, Bosnian; Ukrainian the core menus | Hindi, Arabic (RTL), Vietnamese; native review of every language | 150–230 |
## After beta
Plugin hosting (VST3 / AU, then OpenFX; owner decision, [G11](gaps.md#g11-no-plugin-hosting)),
AI features with openly licensed models ([G12](gaps.md#g12-ai-features)), delivery formats
([G13](gaps.md#g13-delivery-formats)), localization ([G14](gaps.md#g14-localization-8-of-the-12-key-languages-missing)),
([G13](gaps.md#g13-delivery-formats)), localization ([G14](gaps.md#g14-localization-3-of-the-12-key-languages-missing-hindi-arabic-vietnamese)),
the web app shell ([G20](gaps.md#g20-the-web-app-shell)).
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | E-AC-3 (#788), AVI (#650), Russian (#610), French (#804), Indonesian (#878), pt-BR (#866) and German (#630) moved from open to done in the Alpha gate row, B3, M9 and L10N; hour estimates and statuses not re-measured |
| 2026-10-10 | minor | Alpha gate table added (craftrules core-workflow gate): six core workflows, all pass on macOS; stage stays alpha |
| 2026-10-10 | major | Created: milestone table moved from ROADMAP.md (M5 and M9 remaining work updated), HW and L10N rows, beta milestones B1–B7, Current focus from the re-measure |
+10 -9
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Premiere Pro
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (full re-measure against Premiere Pro 2026 26.5.2; replaces the scorecard and estimate that lived in ROADMAP.md) · **Target:** Adobe Premiere Pro 2026 (26.5.2, macOS)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (AVI import #650, E-AC-3 decode #647 and Russian menus #610 no longer counted as missing; percentages and hours not re-measured) · **Target:** Adobe Premiere Pro 2026 (26.5.2, macOS)
The authoritative assessment of how close FilmCraft is to Premiere Pro, and how much work is left.
[ROADMAP.md](../ROADMAP.md) summarizes it; [gaps.md](gaps.md) lists every shortfall one at a time.
@@ -23,7 +23,7 @@ The deep checklists are [file-format-parity.md](file-format-parity.md),
The previous estimate (2026-10-05) was breadth ~87%, ready ~50–60%, and "~210–325 agent-hours to
100%". The breadth figure is one point lower now because this pass counts the file formats Premiere
lists and we lack (AVI, WMV, camera RAW, EXR / DPX / PSD stills, `.prproj`, DVD / Blu-ray / DCP
lists and we lack (WMV, camera RAW, EXR / DPX / PSD stills, `.prproj`, DVD / Blu-ray / DCP
export) rather than only the formats we have. The hours went up because the old figure covered the
checklist only and left out plugins, AI, localization, `.prproj` and the depth work that 188 open
issues now document. That is new evidence, not drift: see [Calibration](#calibration).
@@ -109,7 +109,7 @@ agent-hours to close the area to Premiere, sequential.
|---|---|---|---|---|---|---|
| Editing, timeline, trimming, multicam | 20% | ~90% | ~70% | 80–140 | Edit algebra, trim modes, Trim Monitor, multicam with audio sync, Premiere default keyboard (143 commands), Source playback and range dragging (10-08). Open: ripple-delete of a gap by column (#668), Backspace on gaps (#648), clip stretching (#653), deselect on empty space (#683), new tracks by dragging media (#483), Escape cancelling drags (#580 fixed), time remapping (#510), effects on a whole track (#623) | [ui-parity.md](ui-parity.md) |
| Effects and transitions | 12% | 100% (measured) | ~60% | 80–140 | Every effect exists; Warp Stabilizer is 2-D, Auto Reframe approximate, Morph Cut morphs with optical flow but without face tracking (#805); Optical Flow time interpolation is motion compensated on the CPU (#780). 34 effect ids run on the GPU against ~82 of Premiere's ~92 marked Accelerated. No Object Mask (26.0, AI). | [hardware-parity.md](hardware-parity.md) |
| Media import and codecs | 12% | ~75% | ~60% | 150–250 | Own bit-exact decoders for H.264, HEVC, VP9, AV1, ProRes, DNx, APV, MPEG-2, AAC, AC-3, Opus. But H.264 only 8-bit 4:2:0 and HEVC no 4:2:2 in software (most 10-bit cameras, #626); no camera RAW (R3D, ARRIRAW, BRAW, Sony RAW, Canon RAW, ProRes RAW, #345, #383); no E-AC-3 (#647), AVI (#598), WMV, DV, XAVC / P2 metadata; Sony start timecode (#460); WebM black (#432) | [codec-parity.md](codec-parity.md), [file-format-parity.md](file-format-parity.md) |
| Media import and codecs | 12% | ~75% | ~60% | 150–250 | Own bit-exact decoders for H.264, HEVC, VP9, AV1, ProRes, DNx, APV, MPEG-2, AAC, AC-3, Opus. But H.264 only 8-bit 4:2:0 and HEVC no 4:2:2 in software (most 10-bit cameras, #626); no camera RAW (R3D, ARRIRAW, BRAW, Sony RAW, Canon RAW, ProRes RAW, #345, #383); E-AC-3 plays its 5.1 core only (#647), no WMV, DV, XAVC / P2 metadata; Sony start timecode (#460) | [codec-parity.md](codec-parity.md), [file-format-parity.md](file-format-parity.md) |
| Audio | 10% | ~88% | ~65% | 50–90 | Mixer graph, automation, all 53 effects, Essential Sound, 5.1, voice-over, Remix, multi-stream container audio (#361). Device and playback reports: crackle / cut-outs (#336, #410, #602, #605, #617); Enhance Speech is DSP, not a model; ALSA fallback fixed (#254) | |
| Panels and UI fidelity | 10% | ~88% | ~60% | 120–200 | All main panels. Missing: on-screen transform handles in the Program monitor (#639), Effect Controls keyframe zoom / divider / row lines (#640–#645), docking rearrangement (#493), multi-monitor windows (#466), filmstrip thumbnails (#614), marquee in Project panel (#578) | [ui-parity.md](ui-parity.md) |
| Colour | 8% | ~90% | ~70% | 40–70 | Lumetri complete incl. scopes, LUTs, colour management, HDR export. Lumetri on the GPU covers basic, creative and vignette only; HDR-aware Lumetri maths, macOS EDR display, D-Log M; a Lumetri report (#628) | |
@@ -128,7 +128,7 @@ agent-hours to close the area to Premiere, sequential.
| File formats (containers, project, interchange, captions, stills) | ~60% | 140–240 | [file-format-parity.md](file-format-parity.md) |
| Codecs (decode and encode, fidelity, camera media) | ~60% | 150–250 | [codec-parity.md](codec-parity.md) |
| Hardware acceleration (decode, encode, GPU effects, I/O devices) | ~45% | 120–200 | [hardware-parity.md](hardware-parity.md) |
| Localization (12 key languages) | ~35% of Premiere's set; 4 of 12 key languages near-complete | 150–230 | [localization-parity.md](localization-parity.md) |
| Localization (12 key languages) | ~35% of Premiere's set; 9 of 12 key languages near-complete | 150–230 | [localization-parity.md](localization-parity.md) |
| Performance (beyond hardware) | ~55% | 60–120 | [performance.md](performance.md) |
| Stability | ~45% | 60–120 | [gaps.md](gaps.md#g4-crashes-at-start-up-and-no-test-ci-on-pull-requests) |
| Platforms | ~60% | 50–90 | [gaps.md](gaps.md#g9-windows-and-linux-at-run-time) |
@@ -211,7 +211,7 @@ full-area depth (e.g. the ~20 common effects rather than all 93 and their approx
| Area | Weight | Depth | Why |
|---|---|---|---|
| Editing and trimming | 28% | 80% | Insert / overwrite, ripple / roll / slip / slide, razor, J/K/L, markers, multicam all work; gap deletion (#648, #668) and deselect (#683) are friction |
| Importing everyday footage | 14% | 65% | Phone and 8-bit camera H.264 / HEVC, ProRes, DNx, MXF open; 10-bit / 4:2:2 H.264 (Sony XAVC S-I, Canon) does not (#626); E-AC-3, AVI missing |
| Importing everyday footage | 14% | 65% | Phone and 8-bit camera H.264 / HEVC, ProRes, DNx, MXF open; 10-bit / 4:2:2 H.264 (Sony XAVC S-I, Canon) does not (#626); WMV and DV missing |
| Common effects and transitions | 9% | 75% | Lumetri, Transform, Crop, blurs, Cross Dissolve, keys exist; Warp Stabilizer is 2-D, Object Mask missing |
| Colour (Lumetri, scopes, LUTs) | 9% | 80% | Complete, parity-tested CPU / GPU; curves / wheels on CPU |
| Audio (levels, Essential Sound, loudness, ducking) | 11% | 75% | Complete; audio-device reports off macOS |
@@ -248,7 +248,7 @@ interchange and pro formats.
| Feature | Weight | Depth | Why |
|---|---|---|---|
| New project, import clips | 15% | 80% | Drag-and-drop and Import work for phone MP4 / MOV (H.264, HEVC); MKV multi-audio (#601), AVI (#598) fail |
| New project, import clips | 15% | 80% | Drag-and-drop and Import work for phone MP4 / MOV (H.264, HEVC); MKV multi-audio (#601) fails |
| Put clips on the timeline, arrange | 15% | 85% | Drop to timeline (#517), snapping, tracks; new tracks by dropping missing (#483) |
| Trim, split, delete | 15% | 80% | Razor, trim, ripple delete; Backspace on gaps (#648) and deselect (#683) confuse |
| Undo / redo | 5% | 95% | Snapshot undo for every command |
@@ -267,8 +267,8 @@ Discounts a beginner actually hits:
#687).
- **Discoverability and UI clarity ×0.90:** a dense Premiere-style interface with no guided start;
reports about deselecting, deleting gaps and transitions controls (#683, #648, #577).
- **Opening files people send them ×0.95:** phone and web video mostly opens; AVI, E-AC-3 audio
(#647), FLAC-in-MP4 (#603) and some WebM (#432) do not.
- **Opening files people send them ×0.95:** phone and web video mostly opens; FLAC-in-MP4 (#603) and
MKV multi-audio (#601) do not. (AVI #598, E-AC-3 #647 and WebM black #432 are closed.)
80.0 × 0.88 × 0.90 × 0.95 = **60.2% ≈ ~60%.**
@@ -310,7 +310,7 @@ Distance to beta: ~19 points of ready-for-real-work plus `.prproj` import, ~400
| UI fidelity top issues (Program monitor handles, Effect Controls, docking, deselect) | 60–100 |
| Hardware: zero-copy, VideoToolbox Main 10, Quick Sync / AMF encode, more GPU effects | 60–100 |
| Windows / Linux runtime fixes, audio device issues | 30–60 |
| Container gaps users hit: E-AC-3, AVI, multi-track audio, still formats | 30–50 |
| Container gaps users hit: multi-track audio, still formats | 30–50 |
| Interchange validation (AAF / OMF in Avid and Pro Tools, XML speed) | 20–40 |
| Performance: lag / CPU reports, AV1 speed | 30–60 |
| **Total** | **~400–730** |
@@ -354,6 +354,7 @@ control surfaces), and validation in Avid Media Composer / Pro Tools.
| Date | Change | Summary |
|---|---|---|
| 2026-10-12 | minor | Outdated entries corrected from merged work: AVI import (#650) and E-AC-3 decode (#647, via #788) removed from the failing lists; VP8 WebM decodes (#432, via #1064); localization "4 of 12" now 9 of 12 (localization-parity.md); scores and hours not re-measured |
| 2026-10-10 | minor | Readiness table: hours to ~95% for each audience (full ~1,450–2,600, mainstream ~500–850, essentials ~180–350); full number confirmed as an additive weighted sum (no change) |
| 2026-10-10 | minor | Added the three readiness numbers: full ready-for-real-work formula written out (~55% -> ~56%, same inputs, now computed rather than rounded down), mainstream practitioner ~55% and essentials user ~60% with written weights and discounts, and counted user evidence (31 people praising, 0 switch reports, ~64 core-path vs ~29 niche open bugs) |
| 2026-10-10 | minor | Stage checked against the new core-workflow gate (docs/roadmap.md#alpha-gate): passes, stays alpha |
+4
View File
@@ -97,3 +97,7 @@ Apple-silicon laptop CPU).
- Transcription runs synchronously inside the command (no background job or progress bar yet).
- Track items that refer to a subclip are looked up by the subclip's id, so a transcript made for
the parent media is not shown through subclip clips yet.
- Word bounds are only moved across pauses. A start that the alignment places late inside
continuous speech (no pause of 0.2 s between the two words) stays where it is, so the word
before it can hold the beginning of the next one: the audio level gives no boundary to move it
to. Words that the alignment places a whole sentence away are not moved back either.