diff --git a/crates/edit/src/captions.rs b/crates/edit/src/captions.rs index 343fe385..a08d0ad7 100644 --- a/crates/edit/src/captions.rs +++ b/crates/edit/src/captions.rs @@ -105,8 +105,9 @@ pub fn merge_captions(seq: &mut Sequence, ids: &[ClipId]) -> Result { if idx.windows(2).any(|w| w[1] != w[0] + 1) { return Err(EditError::Other("captions to merge must be next to each other".into())); } - let first = idx[0]; - let last = *idx.last().expect("non-empty"); + let (Some(&first), Some(&last)) = (idx.first(), idx.last()) else { + return Err(EditError::Nothing); + }; let end = tr.captions[last].end(); let text: Vec = idx.iter().map(|&i| tr.captions[i].text.trim().to_string()).filter(|t| !t.is_empty()).collect(); let keep = tr.captions[first].id; @@ -205,7 +206,7 @@ pub fn move_captions(seq: &mut Sequence, ids: &[ClipId], delta: Tick) -> Result< return Err(EditError::Nothing); } for tid in tracks { - let tr = seq.caption_track_mut(tid).expect("checked"); + let Some(tr) = seq.caption_track_mut(tid) else { continue }; for c in tr.captions.iter_mut().filter(|c| ids.contains(&c.id)) { c.start += d; } diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index c56aa532..3a84f6d3 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -15,6 +15,8 @@ //! //! Keyframes are stored in media time, so trims and splits never need to move them. +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] + pub mod captions; pub mod multicam; pub mod through; diff --git a/crates/edit/src/transcript.rs b/crates/edit/src/transcript.rs index ff7c4634..4d243996 100644 --- a/crates/edit/src/transcript.rs +++ b/crates/edit/src/transcript.rs @@ -344,7 +344,9 @@ pub fn caption_blocks(words: &[SeqWord], rules: &CaptionRules, rate: FrameRate) continue; } if fits_line { - *lines.last_mut().expect("non-empty") += 1 + len; + if let Some(l) = lines.last_mut() { + *l += 1 + len; + } } else { lines.push(len); } @@ -362,7 +364,7 @@ pub fn caption_blocks(words: &[SeqWord], rules: &CaptionRules, rate: FrameRate) for g in groups { let mut text_lines: Vec = vec![String::new()]; for w in &words[g.clone()] { - let l = text_lines.last_mut().expect("non-empty"); + let Some(l) = text_lines.last_mut() else { break }; if l.is_empty() { l.push_str(&w.text); } else if l.chars().count() + 1 + w.text.chars().count() <= max_chars { diff --git a/crates/engine/src/clip_ops.rs b/crates/engine/src/clip_ops.rs index d4f71e85..6a3f4196 100644 --- a/crates/engine/src/clip_ops.rs +++ b/crates/engine/src/clip_ops.rs @@ -1097,7 +1097,7 @@ fn subclip_plan(s: &Session, p: &Value) -> Result<(ItemId, TimeRange, String, La } else { let item = item_p(p, "item").or_else(|| subclip_source(s)).ok_or_else(|| bad("clip.makeSubclip", "open a clip in the Source monitor"))?; let (root, m, sub) = media_root(&s.project, item).ok_or_else(|| bad("clip.makeSubclip", "only media clips can be subclipped"))?; - let pi = s.project.item(item).expect("resolved"); + let pi = s.project.item(item).ok_or_else(|| EngineError::Other("no such item".into()))?; let rate = m.frame_rate(); let still = s.prefs.timeline.still_duration(rate); let is_still = matches!(m.info.kind, filmcraft_media::MediaKind::Still); @@ -1246,7 +1246,7 @@ fn audio_channels(s: &mut Session, p: &Value) -> Result { let mut out = Vec::new(); let mut maps = Vec::new(); for i in &items { - let m = s.project.item(*i).and_then(|it| it.as_media()).expect("filtered"); + let Some(m) = s.project.item(*i).and_then(|it| it.as_media()) else { continue }; let n = m.info.audio.as_ref().map_or(2, |a| a.channels) as u16; let fmt = format.unwrap_or_else(|| m.interpret.audio_channels.as_ref().map_or(AudioChannels::Stereo, |a| a.format)); let map = match &custom { @@ -1603,7 +1603,7 @@ fn extract_audio(s: &mut Session, p: &Value) -> Result { }) .ok_or_else(|| EngineError::Other("save the project first: extracted audio is written next to it".into()))?, }; - let pi = s.project.item(item).expect("resolved"); + let pi = s.project.item(item).ok_or_else(|| EngineError::Other("no such item".into()))?; let name = pi.name.clone(); let stem = std::path::Path::new(&name).file_stem().map(|x| x.to_string_lossy().into_owned()).unwrap_or_else(|| name.clone()); let src = s.media.full_res_source(&s.project, root, &*s.services).ok_or_else(|| EngineError::Other(format!("{name}: media is offline")))?; diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs index 86a2dbc6..e5cde54e 100644 --- a/crates/engine/src/commands.rs +++ b/crates/engine/src/commands.rs @@ -362,7 +362,7 @@ pub(crate) fn place_item( let frame = (seq.settings.width, seq.settings.height); let mut placements = Vec::new(); let link = if has_v && has_a { Some(p.alloc_id()) } else { None }; - if has_v { + if let Some(vdest) = vdest.filter(|_| has_v) { let mut v = p.make_track_item(item, TrackKind::Video, at, range, rate).ok_or_else(|| bad(label, "bad item"))?; v.link = link; for e in &mut v.effects { @@ -371,10 +371,9 @@ pub(crate) fn place_item( if is_media && src_size != frame { crate::settings::apply_media_scaling(&mut v, &scaling, frame, src_size); } - placements.push((vdest.expect("checked"), v)); + placements.push((vdest, v)); } - if has_a { - let adest = adest.expect("checked"); + if let Some(adest) = adest.filter(|_| has_a) { let (arange, aat) = audio_split.unwrap_or((range, at)); let mut a = p.make_track_item(item, TrackKind::Audio, aat, arange, rate).ok_or_else(|| bad(label, "bad item"))?; // Modify ▸ Audio Channels with several audio clips: one per clip, on the tracks below @@ -675,27 +674,27 @@ fn build() -> Vec { Ok(id) => ids.push(id.0), Err(e) => errors.push(format!("{path}: {e}")), }, - Ok(b) if crate::captions::detect(&path, &b).is_some() => { - let fmt = crate::captions::detect(&path, &b).expect("detected"); - match crate::captions::import(s, &path, &b, fmt, None) { - Ok(r) => reports.push(r), - Err(e) => errors.push(format!("{path}: {e}")), - } - } - Ok(b) if crate::interchange::detect(&path, &b).is_some() => { - let fmt = crate::interchange::detect(&path, &b).expect("detected"); - match crate::interchange::import(s, &path, &b, fmt) { - Ok(r) => { - sequences.extend(r["sequences"].as_array().cloned().unwrap_or_default()); - reports.push(r); + Ok(b) => { + if let Some(fmt) = crate::captions::detect(&path, &b) { + match crate::captions::import(s, &path, &b, fmt, None) { + Ok(r) => reports.push(r), + Err(e) => errors.push(format!("{path}: {e}")), + } + } else if let Some(fmt) = crate::interchange::detect(&path, &b) { + match crate::interchange::import(s, &path, &b, fmt) { + Ok(r) => { + sequences.extend(r["sequences"].as_array().cloned().unwrap_or_default()); + reports.push(r); + } + Err(e) => errors.push(format!("{path}: {e}")), + } + } else { + match import_bytes(s, &path, b.into(), bin) { + Ok(id) => ids.push(id.0), + Err(e) => errors.push(format!("{path}: {e}")), } - Err(e) => errors.push(format!("{path}: {e}")), } } - Ok(b) => match import_bytes(s, &path, b.into(), bin) { - Ok(id) => ids.push(id.0), - Err(e) => errors.push(format!("{path}: {e}")), - }, Err(e) => errors.push(format!("{path}: {e}")), } } @@ -1280,7 +1279,7 @@ fn build() -> Vec { let p = p.clone(); s.edit("Sequence Settings", |pr, _| { if let Some(n) = str_p(&p, "name") { - pr.item_mut(id).expect("seq").name = n.to_string(); + pr.item_mut(id).ok_or(EngineError::NoSequence)?.name = n.to_string(); } let q = pr.sequence_mut(id).ok_or(EngineError::NoSequence)?; if let Some(w) = u64_p(&p, "width") { @@ -2454,7 +2453,7 @@ fn nest(s: &mut Session, p: &Value) -> Result { s.edit("Nest", |pr, st| { let nid = pr.new_sequence(&name, q.settings.clone(), q.video_tracks.len(), q.audio_tracks.len(), None); { - let nq = pr.sequence_mut(nid).expect("new"); + let nq = pr.sequence_mut(nid).ok_or(EngineError::NoSequence)?; for (k, ti, it) in &items { let mut it = it.clone(); it.start -= start; @@ -2465,11 +2464,15 @@ fn nest(s: &mut Session, p: &Value) -> Result { } } let rate = q.settings.frame_rate; - let mut v = pr.make_track_item(nid, TrackKind::Video, start, TimeRange::new(Tick::ZERO, end - start), rate).expect("item"); + let mut v = pr + .make_track_item(nid, TrackKind::Video, start, TimeRange::new(Tick::ZERO, end - start), rate) + .ok_or_else(|| EngineError::Other("cannot place the nested sequence".into()))?; for e in &mut v.effects { resolve_auto_points(e, (q.settings.width, q.settings.height), (q.settings.width, q.settings.height)); } - let a = pr.make_track_item(nid, TrackKind::Audio, start, TimeRange::new(Tick::ZERO, end - start), rate).expect("item"); + let a = pr + .make_track_item(nid, TrackKind::Audio, start, TimeRange::new(Tick::ZERO, end - start), rate) + .ok_or_else(|| EngineError::Other("cannot place the nested sequence".into()))?; let link = pr.alloc_id(); let seq = pr.sequence_mut(seq_id).ok_or(EngineError::NoSequence)?; edit::delete_items(seq, &sel); diff --git a/crates/engine/src/demo.rs b/crates/engine/src/demo.rs index 5b5998ff..876f376d 100644 --- a/crates/engine/src/demo.rs +++ b/crates/engine/src/demo.rs @@ -44,6 +44,15 @@ pub fn add_generator(p: &mut Project, pool: &MediaPool, src: GeneratorSource, na /// Build the demo project. Returns (project, main sequence). pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { + build_demo(pool).unwrap_or_else(|| { + // Only reachable if the built-in items could not be placed: an empty demo, not a crash. + let mut p = Project::new("FilmCraft Demo"); + let seq = p.new_sequence("Main Edit", SequenceSettings::default(), 3, 3, None); + (p, seq) + }) +} + +fn build_demo(pool: &MediaPool) -> Option<(Project, ItemId)> { let mut p = Project::new("FilmCraft Demo"); let footage = p.add_bin("Footage", None); let audio_bin = p.add_bin("Audio", None); @@ -108,8 +117,8 @@ pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { for (ci, sin, dur) in cuts { let src_range = TimeRange::new(r.snap(Tick::from_seconds_f64(sin)), r.snap(Tick::from_seconds_f64(dur))); let link = p.alloc_id(); - let mut v = p.make_track_item(clips[ci], TrackKind::Video, t, src_range, r).expect("item"); - let mut a = p.make_track_item(clips[ci], TrackKind::Audio, t, src_range, r).expect("item"); + let mut v = p.make_track_item(clips[ci], TrackKind::Video, t, src_range, r)?; + let mut a = p.make_track_item(clips[ci], TrackKind::Audio, t, src_range, r)?; v.link = Some(link); a.link = Some(link); for e in &mut v.effects { @@ -118,14 +127,14 @@ pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { a.effect_mut("volume").map(|e| e.params.get_mut("level").map(|l| l.value = ParamValue::Float(-8.0))); let dur_t = v.duration; placed.push((v.id, a.id, t, dur_t)); - let s = p.sequence_mut(seq).expect("seq"); + let s = p.sequence_mut(seq)?; s.video_tracks[0].items.push(v); s.audio_tracks[0].items.push(a); t += dur_t; } // A lower-third-ish overlay on V2: the plasma clip scaled down in the corner with a drop shadow. let ov_range = TimeRange::new(Tick::ZERO, r.snap(Tick::from_seconds_f64(4.0))); - let mut ov = p.make_track_item(clips[4], TrackKind::Video, r.snap(Tick::from_seconds_f64(6.0)), ov_range, r).expect("item"); + let mut ov = p.make_track_item(clips[4], TrackKind::Video, r.snap(Tick::from_seconds_f64(6.0)), ov_range, r)?; for e in &mut ov.effects { resolve_auto_points(e, (1920, 1080), (1920, 1080)); } @@ -152,18 +161,18 @@ pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { lum.params.get_mut("temperature").map(|v| v.value = ParamValue::Float(18.0)); lum.params.get_mut("contrast").map(|v| v.value = ParamValue::Float(22.0)); lum.params.get_mut("vignette_amount").map(|v| v.value = ParamValue::Float(-1.4)); - let s = p.sequence_mut(seq).expect("seq"); + let s = p.sequence_mut(seq)?; if let Some(it) = s.video_tracks[0].items.get_mut(3) { it.effects.insert(0, lum); } } - let s = p.sequence_mut(seq).expect("seq"); + let s = p.sequence_mut(seq)?; s.video_tracks[1].items.push(ov); // Transitions: cross dissolve between shots 1-2, dip to black 3-4, push 5-6, fade in at start. let tr_len = r.tick_of(24); let mk = |id: u64, eff: &str, from: Option, to: Option, start: Tick| Transition { id: TransitionId(id), - effect: find_effect(eff).expect("fx").instance(), + effect: crate::presets::effect_instance(eff), start, duration: tr_len, from, @@ -181,7 +190,7 @@ pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { s.video_tracks[0].transitions = trs; s.audio_tracks[0].transitions.push(Transition { id: TransitionId(900_010), - effect: find_effect("constant_power").expect("fx").instance(), + effect: crate::presets::effect_instance("constant_power"), start: cut(1) - tr_len.mul_ratio(1, 2), duration: tr_len, from: Some(placed[0].1), @@ -207,13 +216,13 @@ pub fn demo_project(pool: &MediaPool) -> (Project, ItemId) { }); } // Music on A2 (under the whole edit), ducked -14 dB. - let mut m = p.make_track_item(music, TrackKind::Audio, Tick::ZERO, TimeRange::new(Tick::ZERO, end), r).expect("music"); + let mut m = p.make_track_item(music, TrackKind::Audio, Tick::ZERO, TimeRange::new(Tick::ZERO, end), r)?; m.effect_mut("volume").map(|e| e.params.get_mut("level").map(|l| l.value = ParamValue::Float(-14.0))); m.label = Label::Caribbean; - let s = p.sequence_mut(seq).expect("seq"); + let s = p.sequence_mut(seq)?; s.audio_tracks[1].items.push(m); for tr in s.all_tracks_mut() { tr.sort(); } - (p, seq) + Some((p, seq)) } diff --git a/crates/engine/src/graphic_templates.rs b/crates/engine/src/graphic_templates.rs index d1582beb..db00d6ee 100644 --- a/crates/engine/src/graphic_templates.rs +++ b/crates/engine/src/graphic_templates.rs @@ -157,15 +157,15 @@ pub fn thumbnail(t: &GraphicsTemplate, w: u32) -> (u32, u32, Vec) { let mut p = Project::new("thumb"); let rate = filmcraft_time::FrameRate::FPS_30; let g = p.add_item("G", Label::Rose, ItemKind::Graphic { width: t.canvas[0], height: t.canvas[1], rate }, None); - let mut it = p - .make_track_item( - g, - filmcraft_project::TrackKind::Video, - Tick::ZERO, - filmcraft_time::TimeRange::new(Tick::ZERO, t.duration.max(rate.frame_duration())), - rate, - ) - .expect("item"); + let Some(mut it) = p.make_track_item( + g, + filmcraft_project::TrackKind::Video, + Tick::ZERO, + filmcraft_time::TimeRange::new(Tick::ZERO, t.duration.max(rate.frame_duration())), + rate, + ) else { + return (w, h, vec![0; w as usize * h as usize * 4]); + }; it.effects.extend(t.layers.iter().cloned()); it.graphic = Some(Box::new(meta)); // a moment into the clip, so intro animations have settled a little diff --git a/crates/engine/src/graphics.rs b/crates/engine/src/graphics.rs index 9c9633b5..b7e57625 100644 --- a/crates/engine/src/graphics.rs +++ b/crates/engine/src/graphics.rs @@ -551,11 +551,11 @@ macro_rules! arrange_cmd { fn new_shape_cmd(s: &mut Session, p: &Value, shape: &str) -> Result { let mut q = p.clone(); q["shape"] = json!(shape); - (find_spec("graphics.newShape").run)(s, &q) + (find_spec("graphics.newShape")?.run)(s, &q) } -fn find_spec(id: &str) -> CommandSpec { - commands().into_iter().find(|c| c.id == id).expect("graphics command") +fn find_spec(id: &str) -> Result { + commands().into_iter().find(|c| c.id == id).ok_or_else(|| EngineError::UnknownCommand(id.to_string())) } /// Graphics menu commands (New Layer shapes, Align/Distribute/Arrange/Select submenus, resets). @@ -576,7 +576,7 @@ fn menu_commands() -> Vec { |s, p| { let mut p = if p.is_object() { p.clone() } else { json!({}) }; p["vertical"] = json!(true); - (find_spec("graphics.newText").run)(s, &p) + (find_spec("graphics.newText")?.run)(s, &p) }, ), spec( @@ -731,7 +731,7 @@ fn menu_commands() -> Vec { let rate = q.settings.frame_rate; let want = rate.snap_nearest(Tick::from_seconds_f64(seconds)).max(rate.frame_duration()); let tr = q.video_tracks.iter_mut().find(|tr| tr.items.iter().any(|i| i.id == clip)).ok_or(filmcraft_edit::EditError::NoItem(clip))?; - let i = tr.items.iter().position(|i| i.id == clip).expect("found"); + let i = tr.items.iter().position(|i| i.id == clip).ok_or(filmcraft_edit::EditError::NoItem(clip))?; let start = tr.items[i].start; let limit = tr.items.get(i + 1).map_or(want, |n| n.start - start); let d = want.min(limit).max(rate.frame_duration()); diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 873ba24e..2df2217d 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -8,6 +8,8 @@ //! The project is an `Arc` edited copy-on-write; undo keeps whole-project snapshots (cheap //! thanks to structural sharing of untouched items). +#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable))] + pub mod aaf_omf; pub mod autosave; pub mod captions; diff --git a/crates/engine/src/multicam.rs b/crates/engine/src/multicam.rs index a9da4709..7e0d7c0e 100644 --- a/crates/engine/src/multicam.rs +++ b/crates/engine/src/multicam.rs @@ -554,7 +554,7 @@ fn merge_clips(s: &mut Session, p: &Value) -> Result { a.link = Some(link); a_items.push(a); } - let q = pr.sequence_mut(sid).expect("new sequence"); + let q = pr.sequence_mut(sid).ok_or(EngineError::NoSequence)?; for (t, it) in q.video_tracks.iter_mut().zip(v_items) { t.items.push(it); } @@ -616,7 +616,7 @@ fn create_multicam(s: &mut Session, p: &Value) -> Result { let (mut vi, mut ai) = (0usize, 0usize); let mut placed: Vec<(TrackKind, usize, TrackItem)> = Vec::new(); let (vtracks, atracks): (Vec, Vec) = { - let q = pr.sequence(sid).expect("new sequence"); + let q = pr.sequence(sid).ok_or(EngineError::NoSequence)?; (q.video_tracks.iter().map(|t| t.id).collect(), q.audio_tracks.iter().map(|t| t.id).collect()) }; for (k, c) in clips.iter().enumerate() { @@ -640,7 +640,7 @@ fn create_multicam(s: &mut Session, p: &Value) -> Result { cameras.push(cam); } { - let q = pr.sequence_mut(sid).expect("new sequence"); + let q = pr.sequence_mut(sid).ok_or(EngineError::NoSequence)?; for (kind, idx, it) in placed { q.tracks_mut(kind)[idx].items.push(it); } @@ -892,10 +892,14 @@ fn record_start(s: &mut Session, p: &Value) -> Result { fn apply_pass(s: &mut Session, end: Tick) -> Result { let Some(seq_id) = s.mcrec.seq else { return Ok(0) }; let merging = s.history.merge_key.as_deref() == Some(s.mcrec.key.as_str()) && s.history.undo.last().is_some_and(|u| u.0 == RECORD_LABEL); - if !merging || s.mcrec.base.is_none() { - s.mcrec.base = Some(s.project.clone()); - } - let base = s.mcrec.base.clone().expect("set above"); + let base = match s.mcrec.base.clone() { + Some(b) if merging => b, + _ => { + let b = s.project.clone(); + s.mcrec.base = Some(b.clone()); + b + } + }; let (tracks, cuts, key) = (s.mcrec.tracks.clone(), s.mcrec.cuts.clone(), s.mcrec.key.clone()); let Some(source) = s.mcrec.source else { return Ok(0) }; let media = s.media.clone(); diff --git a/crates/engine/src/presets.rs b/crates/engine/src/presets.rs index ce510aa8..07de7812 100644 --- a/crates/engine/src/presets.rs +++ b/crates/engine/src/presets.rs @@ -151,8 +151,22 @@ fn kf(t_secs: f64, v: ParamValue) -> Keyframe { Keyframe::new(Tick((t_secs * TICKS_PER_SECOND as f64).round() as i64), v) } +/// A fresh instance of a built-in effect (a bare instance named `id` if the registry lacks it, +/// which the preset tests rule out). +pub(crate) fn effect_instance(id: &str) -> EffectInstance { + filmcraft_project::find_effect(id).map(|d| d.instance()).unwrap_or_else(|| EffectInstance { + effect: id.to_string(), + enabled: true, + params: Default::default(), + masks: Vec::new(), + post_fader: false, + essential: false, + layer: None, + }) +} + fn effect(id: &str, params: &[(&str, ParamValue)]) -> EffectInstance { - let mut e = filmcraft_project::find_effect(id).unwrap_or_else(|| panic!("built-in preset uses unknown effect {id}")).instance(); + let mut e = effect_instance(id); for (k, v) in params { if let Some(p) = e.params.get_mut(*k) { p.value = v.clone(); diff --git a/crates/engine/src/sequence_tools.rs b/crates/engine/src/sequence_tools.rs index 03b23e5e..d3add542 100644 --- a/crates/engine/src/sequence_tools.rs +++ b/crates/engine/src/sequence_tools.rs @@ -381,7 +381,10 @@ fn make_subsequence(s: &mut Session, p: &Value) -> Result { let offset = range.map(|r| r.start).unwrap_or_else(|| items.iter().map(|i| i.2.start).min().unwrap_or_default()); let name = match str_p(p, "name") { Some(n) => n.to_string(), - None => (1..).map(|k| format!("{seq_name}_Sub_{k:02}")).find(|n| !s.project.items.values().any(|i| &i.name == n)).expect("free name"), + None => (1..) + .map(|k| format!("{seq_name}_Sub_{k:02}")) + .find(|n| !s.project.items.values().any(|i| &i.name == n)) + .unwrap_or_else(|| format!("{seq_name}_Sub")), }; let id = s.edit("Make Subsequence", |pr, st| { let nid = pr.new_sequence(&name, q.settings.clone(), q.video_tracks.len(), q.audio_tracks.len(), None); diff --git a/crates/engine/src/shortcuts.rs b/crates/engine/src/shortcuts.rs index 301f2ea4..4165cde8 100644 --- a/crates/engine/src/shortcuts.rs +++ b/crates/engine/src/shortcuts.rs @@ -441,7 +441,9 @@ fn bindable(c: &CommandSpec) -> bool { } _ => {} } - fields.last_mut().expect("field").push(ch); + if let Some(f) = fields.last_mut() { + f.push(ch); + } } fields.iter().all(|f| f.contains('?') || f.contains('=')) } @@ -505,7 +507,7 @@ impl Shortcuts { dir: None, session_start: None, }; - s.bindings = s.builtin(DEFAULT_PRESET).expect("default preset"); + s.bindings = s.builtin(DEFAULT_PRESET).unwrap_or_default(); s } diff --git a/crates/engine/src/trim.rs b/crates/engine/src/trim.rs index b334bd80..42601d7e 100644 --- a/crates/engine/src/trim.rs +++ b/crates/engine/src/trim.rs @@ -433,7 +433,7 @@ pub fn shuttle(s: &mut Session, p: &Value) -> Result { tick(s, clock)?; } let rate = s.sequence_rate(); - let d = s.trim_play.dynamic.as_mut().expect("dynamic trim"); + let d = s.trim_play.dynamic.as_mut().ok_or_else(|| EngineError::Other("dynamic trimming is not active".into()))?; let speed = if slow { 0.25 * dir } else if d.speed * dir > 0.0 && !d.at_limit {