collective

Browse / github.com/storytold/filmcraft / patch

feat(ui): editable duration field in Clip Speed / Duration dialog (fixes #2191)

[Bug Report] [FilmCraft] duration field is un-changable ### Description when pressing cntrl + r to open clip/speed duration the area to enter a new duration isnt a text field and isnt changable ### Operating system and version win 11 current ### Ticket details | | | |---|---| | **App** | FilmCraft | | **Type** | Bug Report | | **Reported by** | DuckierMocha29 (Discord username: duckiermocha29) — Discord user ID `1003869176647135352` | | **Submitted** | 2026-10-16 13:15:52 UTC | | **Discord server** | ArtCraft (`1359579021108842617`) | | **Submitted from channel** | `1558917176453374102` | | **Submission ID** | `TR-RQC0KKYXVY` | <sub>Submitted

Status
open
Score
0.00
Adopters
0 other operators
Author
GabrielMatozo (imported from pull request #3242, open)
Base
45901fccd06f1c2aa1dcdaac67cc00262a85f1be
Size
+85 −11 in 2 files
Review
pending
Shared
2026-10-28
Id
765df4d56b816dfeb6d5766d

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/i18n/pt-br.tsv crates/ui-egui/src/panels/clip_dialogs.rs

diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv--- a/crates/ui-egui/src/i18n/pt-br.tsv+++ b/crates/ui-egui/src/i18n/pt-br.tsv@@ -685,6 +685,7 @@ 	Extract (')	Extrair (') 	Export Frame: use Export mode (M6)	Exportar quadro: use o modo Exportar (M6) 	Button Editor	Editor de botões+	More transport controls	Mais controles de reprodução 	Multi-Camera Record on	Gravação multicâmera ativada 	Multi-Camera Record off	Gravação multicâmera desativada 	{first} - Merged	{first} - Mesclado@@ -816,6 +817,7 @@ 	Relink others automatically	Revincular os demais automaticamente 	Search in:	Pesquisar em: 	Browse…	Procurar…+	A file dialog is already open	Uma janela de seleção de arquivos já está aberta 	Exact name matches only	Somente correspondências exatas de nome 	No candidates yet — Search a folder or Locate the file.	Ainda não há candidatos: pesquise uma pasta ou localize o arquivo. 	✔ same file	✔ mesmo arquivo@@ -1827,7 +1829,7 @@ 	HDR Graphics White (Nits)	Branco de gráficos HDR (nits) 	HLS Vectorscope + Histogram	Vetorscópio HLS + histograma 	HSL Secondary	HSL secundário-	Hard	Duro+	Hard	Rígido 	Hard Clip	Clipping duro 	Hard Light	Luz direta 	Hard Limiter	Limitador rígido@@ -1839,6 +1841,8 @@ 	Height	Altura 	Hexagon	Hexágono 	Hide Back Face	Ocultar face traseira+	Hide FilmCraft	Ocultar FilmCraft+	Hide Others	Ocultar outros 	High	Alta 	High Cutoff	Corte de agudos 	High Frequency Cut	Corte de altas frequências@@ -2355,6 +2359,9 @@ 	Set focus on the Timeline when performing Insert/Overwrite edits	Focar a linha do tempo ao fazer edições de inserção/sobrescrita 	Set to frame size	Definir para o tamanho do quadro 	Setting	Configuração++	# The native macOS application menu (Settings…).+	Settings	Preferências 	Shadow Angle	Ângulo da sombra 	Shadow Blue Balance	Balanço de azul nas sombras 	Shadow Blur	Desfoque da sombra@@ -2904,7 +2911,7 @@ 	WebVTT (.vtt)	WebVTT (.vtt) 	Title	Título 	Creator	Autor-	Copyright	Copyright+	Copyright	Direitos autorais 	Image	Imagem 	Frame Size	Tamanho do quadro 	Match Source	Igual à origem@@ -2993,6 +3000,12 @@ 	AIFF	AIFF 	AIFF 48 kHz 16-bit	AIFF 48 kHz 16 bits 	AIFF, stereo 48 kHz 16-bit PCM	AIFF, estéreo, PCM de 48 kHz e 16 bits+	FLAC	FLAC+	FLAC 48 kHz 24-bit	FLAC 48 kHz 24 bits+	FLAC, stereo 48 kHz 24-bit, lossless	FLAC, estéreo 48 kHz 24 bits, sem perdas+	FLAC (lossless)	FLAC (sem perdas)+	Compression Level	Nível de compressão+	0 fastest, 8 smallest	0 mais rápido, 8 menor tamanho 	APV	APV 	APV 422-10	APV 422-10 	APV 422-12	APV 422-12@@ -3502,6 +3515,7 @@ 	Click to select, or click in empty space and drag to marquee select. Use Shift, Alt, and Ctrl for other options.	Clique para selecionar, ou clique em um espaço vazio e arraste para selecionar com um retângulo. Use Shift, Alt e Ctrl para outras opções. 	Click to zoom in; Alt-click to zoom out.	Clique para ampliar; Alt+clique para reduzir. 	Drag: move · Alt-click: smooth/corner · Ctrl-click: delete	Arrastar: mover · Alt+clique: suave/canto · Ctrl+clique: excluir+	Drag inward to round the corners	Arraste para dentro para arredondar os cantos 	Include Alpha Channel	Incluir canal alfa 	Sequence Settings	Configurações da sequência 	Color Management	Gerenciamento de cores@@ -3565,7 +3579,16 @@ 	Composite on the GPU when available	Compor na GPU quando disponível 	Text to Speech	Texto para fala 	Edit Narration	Editar narração++	Dock Panel	Encaixar painel+	Undock Panel	Desencaixar painel+	Docking Action	Ação de encaixe+	Resize floating panel	Redimensionar painel flutuante 	Project Notes	Notas do projeto+	Get Project Notes	Obter notas do projeto+	Edit Project Notes	Editar notas do projeto+	Notes for this project: to-dos, instructions, frame numbers… Saved with the project.	Notas deste projeto: tarefas, instruções, números de quadro… Salvas com o projeto.+	Use Effect Shutter Angle	Usar o ângulo do obturador do efeito  	Appearance Settings…	Configurações de aparência… @@ -3585,3 +3608,25 @@ 	Open a sequence to search its graphics	Abra uma sequência para pesquisar nos gráficos dela 	No text graphics in this sequence. Add text with the Type tool.	Esta sequência não tem gráficos com texto. Adicione texto com a ferramenta Texto. 	No matching text.	Nenhum texto corresponde.+	Compression	Compressão+	ZIP (1 scan line)	ZIP (1 linha de varredura)+	ZIP (16 scan lines)	ZIP (16 linhas de varredura)+	Safe graphics mode: {mode}	Modo gráfico seguro: {mode}+	An earlier start failed with the default graphics. Updating the graphics driver usually fixes this.	Uma inicialização anterior falhou com os gráficos padrão. Atualizar o driver gráfico geralmente resolve isso.+	Use Default Graphics	Usar gráficos padrão+	The default graphics are used from the next start.	Os gráficos padrão serão usados a partir da próxima inicialização.+	DirectX 12 only	Somente DirectX 12+	software rendering	renderização por software+	Label Color Presets	Predefinições de cores dos rótulos+	{name} (Modified)	{name} (Modificado)+	Revert to the preset's colors and names	Restaurar as cores e os nomes da predefinição+	Import a preset file…	Importar um arquivo de predefinição…+	Export the labels to a preset file…	Exportar os rótulos para um arquivo de predefinição…+	Show the presets folder	Mostrar a pasta de predefinições+	Delete this preset	Excluir esta predefinição+	Opens in your web browser: {url}	Abre no seu navegador: {url}+	Saves this session's log and shows it in your file manager.	Salva o registro desta sessão e o mostra no gerenciador de arquivos.+	Shows a report on this computer.	Mostra um relatório sobre este computador.+	Shows the version, community links and credits in a FilmCraft window.	Mostra a versão, os links da comunidade e os créditos em uma janela do FilmCraft.+	Couldn't open your web browser	Não foi possível abrir seu navegador+	Open this address in your browser yourself:	Abra este endereço manualmente no seu navegador:diff --git a/crates/ui-egui/src/panels/clip_dialogs.rs b/crates/ui-egui/src/panels/clip_dialogs.rs--- a/crates/ui-egui/src/panels/clip_dialogs.rs+++ b/crates/ui-egui/src/panels/clip_dialogs.rs@@ -297,6 +297,41 @@ fn frames(ui: &mut egui::Ui, elems: &mut Elems, pre: &str, p: &mut Value, key: &     }); } +/// Timecode input field for duration in speed/duration dialog. Parses user-entered timecode+/// and calculates the required speed percentage from the new duration.+fn timecode_duration(ui: &mut egui::Ui, elems: &mut Elems, pre: &str, p: &mut Value, key: &str, label: &str, fps: f64, original_duration_frames: i64, original_speed: f64) {+    use filmcraft_time::{FrameRate, parse_timecode};+    ui.horizontal(|ui| {+        ui.label(label);+        let rate = FrameRate::from_f64(if fps > 0.0 { fps } else { 24.0 });+        let current_frames = p.get("speed").and_then(Value::as_f64).map(|spd| {+            let speed_ratio = spd / 100.0;+            if speed_ratio > 0.0 {+                (original_duration_frames as f64 * original_speed / speed_ratio).round() as i64+            } else {+                original_duration_frames+            }+        }).unwrap_or(original_duration_frames);+        let mut v = p.get(key).and_then(Value::as_str).unwrap_or(&filmcraft_time::format_timecode_frames(current_frames, rate, false)).to_string();+        let r = ui.add(egui::TextEdit::singleline(&mut v).desired_width(120.0));+        push(elems, format!("{pre}.{key}"), &r, label);+        if r.changed() {+            // Parse the timecode to frames+            if let Ok(frames) = parse_timecode(&v, rate, false, current_frames) {+                if frames > 0 {+                    // Calculate new speed: original_duration * original_speed / new_duration+                    let new_speed = (original_duration_frames as f64 * original_speed / frames as f64) * 100.0;+                    let clamped = new_speed.clamp(0.01, 100_000.0);+                    p["speed"] = json!(clamped);+                }+            }+            p[key] = json!(v);+        }+        // Also show the frame count for reference+        ui.label(RichText::new(format!("({current_frames} fr)")).monospace().weak());+    });+}+ /// Draw the open dialog, if any. pub fn show(app: &mut FilmcraftApp, ctx: &egui::Context) {     let Some(mut d) = app.ui.clip_dialog.clone() else { return };@@ -516,17 +551,11 @@ pub fn show(app: &mut FilmcraftApp, ctx: &egui::Context) {             }             "clip.speedDuration" => {                 number(ui, &mut elems, pre, p, "speed", tl!("Speed:"), 0.01..=100_000.0, " %");-                // Duration follows the speed: the media shown stays the same+                // Duration: editable timecode that calculates speed when changed.                 let (dur, was, fps) =                     (d.info["duration"].as_i64().unwrap_or(0), d.info["speed"].as_f64().unwrap_or(1.0), d.info["fps"].as_f64().unwrap_or(24.0));-                let now = p["speed"].as_f64().unwrap_or(100.0) / 100.0;-                if dur > 0 && now > 0.0 {-                    let rate = filmcraft_time::FrameRate::from_f64(if fps > 0.0 { fps } else { 24.0 });-                    let ticks = filmcraft_time::Tick((dur as f64 * was / now).round() as i64);-                    ui.horizontal(|ui| {-                        ui.label(tl!("Duration:"));-                        ui.label(RichText::new(filmcraft_time::format_timecode_frames(rate.frame_at(ticks), rate, false)).monospace());-                    });+                if dur > 0 {+                    timecode_duration(ui, &mut elems, pre, p, "duration", tl!("Duration:"), fps, dur, was);                 }                 check(ui, &mut elems, pre, p, "reverse", tl!("Reverse Speed"), true);                 check(ui, &mut elems, pre, p, "ripple", tl!("Ripple Edit, Shifting Trailing Clips"), true);