diff --git a/src/main.rs b/src/main.rs
index b9783194e63551a11e4e85e6c1b37b7edfdfd0d9..dcf9acd48786fc637e441412fe2bfbd3cb737c33 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,5 @@
 #![allow(clippy::manual_range_contains)]
-#![warn(rust_2018_idioms)]
+#![warn(clippy::unreadable_literal, rust_2018_idioms)]
 #![forbid(elided_lifetimes_in_paths, unsafe_code)]
 
 mod iotro;
@@ -281,7 +281,9 @@ fn main() {
 
 	// render the video
 	let mut videos = Vec::new();
-	videos.push(if !project.progress.rendered {
+	videos.push(if project.progress.rendered {
+		renderer.video_mp4(&project)
+	} else {
 		let video = renderer.render(&mut project).unwrap();
 		project.progress.rendered = true;
 
@@ -289,8 +291,6 @@ fn main() {
 		fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap();
 
 		video
-	} else {
-		renderer.video_mp4(&project)
 	});
 
 	// rescale the video
diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs
index f1e4a2ad3f88c71e0c11c17d76449f6696895a6a..2e2d174597b7c17e19b2d6e1de3acceb2d42c361 100644
--- a/src/render/ffmpeg.rs
+++ b/src/render/ffmpeg.rs
@@ -227,12 +227,13 @@ impl Ffmpeg {
 			FfmpegFilter::Filters { filters, output } => {
 				let mut complex = String::new();
 				for filter in filters {
-					filter.append_to_complex_filter(&mut complex, &mut self.filter_idx);
+					filter
+						.append_to_complex_filter(&mut complex, &mut self.filter_idx)?;
 				}
 				if vaapi {
-					write!(complex, "{}format=nv12,hwupload[v]", channel('v', &output));
+					write!(complex, "{}format=nv12,hwupload[v]", channel('v', &output))?;
 				} else {
-					write!(complex, "{}null[v]", channel('v', &output));
+					write!(complex, "{}null[v]", channel('v', &output))?;
 				}
 				cmd.arg("-filter_complex").arg(complex);
 				cmd.arg("-map").arg("[v]");
diff --git a/src/render/filter.rs b/src/render/filter.rs
index 4f0f84d22b24b9b3b223023003458bcbb2a78af8..dd82ecbaace735594b19614e6b01e9c0bb0a6ab2 100644
--- a/src/render/filter.rs
+++ b/src/render/filter.rs
@@ -1,15 +1,11 @@
-use crate::time::{format_time, Time};
-use std::{borrow::Cow, collections::VecDeque, fmt::Write as _};
+use crate::time::Time;
+use std::{
+	borrow::Cow,
+	collections::VecDeque,
+	fmt::{self, Write as _}
+};
 
 pub(crate) enum Filter {
-	/// Trim audio and video alike
-	Trim {
-		input: Cow<'static, str>,
-		start: Option<Time>,
-		duration: Option<Time>,
-		output: Cow<'static, str>
-	},
-
 	/// Apply an alpha channel on the video. No audio.
 	Alpha {
 		input: Cow<'static, str>,
@@ -57,63 +53,12 @@ pub(crate) enum Filter {
 }
 
 impl Filter {
-	pub(crate) fn is_video_filter(&self) -> bool {
-		matches!(
-			self,
-			Self::Trim { .. }
-				| Self::Alpha { .. }
-				| Self::Concat { .. }
-				| Self::Fade { .. }
-				| Self::Overlay { .. }
-		)
-	}
-
-	pub(crate) fn is_audio_filter(&self) -> bool {
-		matches!(
-			self,
-			Self::Trim { .. }
-				| Self::Concat { .. }
-				| Self::Fade { .. }
-				| Self::GenerateSilence { .. }
-		)
-	}
-
 	pub(crate) fn append_to_complex_filter(
 		&self,
 		complex: &mut String,
 		filter_idx: &mut usize
-	) {
+	) -> fmt::Result {
 		match self {
-			Self::Trim {
-				input,
-				start,
-				duration,
-				output
-			} => {
-				let mut args = String::new();
-				if let Some(start) = start {
-					write!(args, "start={start}");
-				}
-				if let Some(duration) = duration {
-					if !args.is_empty() {
-						args += ":";
-					}
-					write!(args, "duration={duration}");
-				}
-				writeln!(
-					complex,
-					"{}trim={args},setpts=PTS-STARTPTS{};",
-					channel('v', input),
-					channel('v', output)
-				);
-				writeln!(
-					complex,
-					"{}atrim={args},asetpts=PTS-STARTPTS{};",
-					channel('a', input),
-					channel('a', output)
-				);
-			},
-
 			Self::Alpha {
 				input,
 				alpha,
@@ -124,7 +69,7 @@ impl Filter {
 					"{}format=yuva444p,colorchannelmixer=aa={alpha}{};",
 					channel('v', input),
 					channel('v', output)
-				);
+				)?;
 			},
 
 			Self::Overlay {
@@ -140,18 +85,18 @@ impl Filter {
 					channel('v', video_input),
 					channel('v', overlay_input),
 					channel('v', output)
-				);
+				)?;
 				writeln!(
 					complex,
 					"{}anull{};",
 					channel('a', video_input),
 					channel('a', output)
-				);
+				)?;
 			},
 
 			Self::Concat { inputs, output } => {
 				for i in inputs {
-					write!(complex, "{}{}", channel('v', i), channel('a', i));
+					write!(complex, "{}{}", channel('v', i), channel('a', i))?;
 				}
 				writeln!(
 					complex,
@@ -159,7 +104,7 @@ impl Filter {
 					inputs.len(),
 					channel('v', output),
 					channel('a', output)
-				);
+				)?;
 			},
 
 			Self::Fade {
@@ -175,13 +120,13 @@ impl Filter {
 					"{}fade={args}{};",
 					channel('v', input),
 					channel('v', output)
-				);
+				)?;
 				writeln!(
 					complex,
 					"{}afade=t={args}{};",
 					channel('a', input),
 					channel('a', output)
-				);
+				)?;
 			},
 
 			Self::GenerateSilence { video, output } => {
@@ -190,12 +135,12 @@ impl Filter {
 					"{}null{};",
 					channel('v', video),
 					channel('v', output)
-				);
+				)?;
 				writeln!(
 					complex,
 					"aevalsrc=0:s=48000,pan=stereo|c0=c0|c1=c0{};",
 					channel('a', output)
-				);
+				)?;
 			},
 
 			Self::FastForward {
@@ -209,29 +154,29 @@ impl Filter {
 					complex,
 					"{}setpts=PTS/{multiplier}{vff};",
 					channel('v', input)
-				);
+				)?;
 				writeln!(
 					complex,
 					"{}atempo={multiplier}{};",
 					channel('a', input),
 					channel('a', output)
-				);
+				)?;
 				writeln!(
 					complex,
 					"{vff}{}overlay=x=main_w/2-overlay_w/2:y=main_h/2-overlay_h/2{};",
 					channel('v', ffinput),
 					channel('v', output)
-				);
+				)?;
 			}
 		}
 
 		// add a newline after every filter to ease debugging
-		writeln!(complex);
+		writeln!(complex)
 	}
 }
 
 pub(super) fn channel(channel: char, id: &str) -> String {
-	if id.chars().any(|ch| !ch.is_digit(10)) {
+	if id.chars().any(|ch| !ch.is_ascii_digit()) {
 		format!("[{channel}_{id}]")
 	} else {
 		format!("[{id}:{channel}]")
@@ -242,11 +187,3 @@ fn next_tmp(filter_idx: &mut usize) -> String {
 	*filter_idx += 1;
 	format!("[tmp{filter_idx}]")
 }
-
-fn next_tmp_3(filter_idx: &mut usize) -> [String; 3] {
-	[
-		next_tmp(filter_idx),
-		next_tmp(filter_idx),
-		next_tmp(filter_idx)
-	]
-}
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 2aa7398e0e2a289be9b789df93c5ef63378f4509..8d755edb8e6dc58b45c04574d3953736d8f84449 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -1,5 +1,3 @@
-#![allow(warnings)]
-
 pub mod ffmpeg;
 mod filter;
 
@@ -12,7 +10,6 @@ use crate::{
 };
 use anyhow::{bail, Context};
 use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
-use rational::Rational;
 use std::{
 	borrow::Cow,
 	collections::VecDeque,
@@ -189,7 +186,7 @@ impl<'a> Renderer<'a> {
 		let recording_txt = self.target.join("recording.txt");
 		let mut file = File::create(&recording_txt)?;
 		for filename in &project.source.files {
-			writeln!(file, "file '{}'", self.directory.join(filename).to_string());
+			writeln!(file, "file '{}'", self.directory.join(filename))?;
 		}
 		drop(file);
 
diff --git a/src/time.rs b/src/time.rs
index 888451683e7ff8a54572eeaa83bde85452b8e400..d4d9a3dc8e1c6e1e65fbf8ff22d3cb3b072d94fb 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -147,7 +147,7 @@ pub fn parse_time(s: &str) -> anyhow::Result<Time> {
 		}
 		// the 4th split is subseconds, converting to micros
 		if i == 3 {
-			micros = digits.parse::<u32>()? * 1000000 / 60;
+			micros = digits.parse::<u32>()? * 1_000_000 / 60;
 			continue;
 		}
 		// add to seconds and potentially micros
@@ -230,7 +230,7 @@ mod tests {
 	fn test_time_subsecs() {
 		test_time_parse_only("1:02:03:30", Time {
 			seconds: 3723,
-			micros: 500000
+			micros: 500_000
 		});
 	}
 
@@ -238,7 +238,7 @@ mod tests {
 	fn test_time_micros() {
 		test_time("1:02:03.5", Time {
 			seconds: 3723,
-			micros: 500000
+			micros: 500_000
 		});
 	}
 }