diff --git a/Cargo.toml b/Cargo.toml
index ae1ba4c3ac8cf2f24840df4e6352b10a86b886c1..cb16d6c36619ce59903019d79c70cabdc385651f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,13 +10,9 @@ edition = "2021"
 anyhow = "1.0"
 camino = "1.1"
 clap = { version = "4.4", features = ["derive"] }
-#ffmpeg = { package = "ffmpeg-next", version = "6.0" }
 indexmap = "1.9"
-rational = "1.4"
+rational = "1.5"
 serde = { version = "1.0.188", features = ["derive"] }
 serde_with = "3.4"
 svgwriter = "0.1"
 toml = { package = "basic-toml", version = "0.1.4" }
-
-[patch.crates-io]
-rational = { git = "https://github.com/msrd0/rational", branch = "error" }
diff --git a/src/render/filter.rs b/src/render/filter.rs
index b5a7e5cd6ea93f3e0bfcfc791704f24a44574a3d..a846c7d5353be70b09d875b35d6f59dc5438bc1d 100644
--- a/src/render/filter.rs
+++ b/src/render/filter.rs
@@ -1,5 +1,5 @@
 use crate::time::{format_time, Time};
-use std::{borrow::Cow, fmt::Write as _};
+use std::{borrow::Cow, collections::VecDeque, fmt::Write as _};
 
 pub(crate) enum Filter {
 	/// Trim audio and video alike
@@ -28,8 +28,7 @@ pub(crate) enum Filter {
 
 	/// Concatenate audio and video.
 	Concat {
-		inputs: Vec<Cow<'static, str>>,
-		n: usize,
+		inputs: VecDeque<Cow<'static, str>>,
 		output: Cow<'static, str>
 	},
 
@@ -48,14 +47,14 @@ pub(crate) enum Filter {
 		output: Cow<'static, str>
 	},
 
-	/// Fast forward. Too complex to explain. Its magic.
+	/// Fast forward. Will output before, ff, and after parts separately.
 	FastForward {
 		input: Cow<'static, str>,
 		ffinput: Cow<'static, str>,
 		start: Time,
 		duration: Time,
 		multiplier: usize,
-		output: Cow<'static, str>
+		output: [Cow<'static, str>; 3]
 	}
 }
 
@@ -152,13 +151,14 @@ impl Filter {
 				);
 			},
 
-			Self::Concat { inputs, n, output } => {
+			Self::Concat { inputs, output } => {
 				for i in inputs {
 					write!(complex, "{}{}", channel('v', i), channel('a', i));
 				}
 				writeln!(
 					complex,
-					"concat=n={n}:v=1:a=1{}{};",
+					"concat=n={}:v=1:a=1{}{};",
+					inputs.len(),
 					channel('v', output),
 					channel('a', output)
 				);
@@ -266,17 +266,23 @@ impl Filter {
 					channel('v', ffinput)
 				);
 
-				// and finally we concatenate everything back together
-				writeln!(
-					complex,
-					"{}{}{voverlay}{aff}{}{}concat=n=3:v=1:a=1{}{};",
-					vcut[0],
-					acut[0],
-					vcut[2],
-					acut[2],
-					channel('v', output),
-					channel('a', output)
-				);
+				// and finally we place everything into the output
+				for (i, o) in [
+					(&vcut[0], &output[0]),
+					(&voverlay, &output[1]),
+					(&vcut[2], &output[2])
+				] {
+					write!(complex, "{i}null{};", channel('v', o));
+				}
+				writeln!(complex);
+				for (i, o) in [
+					(&acut[0], &output[0]),
+					(&aff, &output[1]),
+					(&acut[2], &output[2])
+				] {
+					write!(complex, "{i}anull{};", channel('a', o));
+				}
+				writeln!(complex);
 			}
 		}
 
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 6accb374a6590c42662ec962c7a6961a3157a85d..ec0677cc63e2154f466d382d82508ae9fc3affb0 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -15,6 +15,7 @@ use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
 use rational::Rational;
 use std::{
 	borrow::Cow,
+	collections::VecDeque,
 	fs::{self, File},
 	io::Write as _,
 	process::{Command, Stdio}
@@ -226,7 +227,7 @@ impl<'a> Renderer<'a> {
 			include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/logo.svg"))
 		)?;
 		let logo_png = self.target.join("logo.png");
-		svg2png(&logo_svg, &logo_png, 150 * 1920 / source_res.width())?;
+		svg2png(&logo_svg, &logo_png, 150 * source_res.width() / 1920)?;
 
 		// copy fastforward then render to png
 		let fastforward_svg = self.target.join("fastforward.svg");
@@ -241,7 +242,7 @@ impl<'a> Renderer<'a> {
 		svg2png(
 			&fastforward_svg,
 			&fastforward_png,
-			128 * 1920 / source_res.width()
+			128 * source_res.width() / 1920
 		)?;
 
 		Ok(())
@@ -251,7 +252,13 @@ impl<'a> Renderer<'a> {
 		let mut output = self.target.join(format!(
 			"{}-{}p.mp4",
 			self.slug,
-			project.source.metadata.as_ref().unwrap().source_res.width()
+			project
+				.source
+				.metadata
+				.as_ref()
+				.unwrap()
+				.source_res
+				.height()
 		));
 		let mut ffmpeg = Ffmpeg::new(output.clone());
 
@@ -328,26 +335,37 @@ impl<'a> Renderer<'a> {
 		});
 		part3 = outrofade.into();
 
+		// prepare list for concat
+		let mut parts = VecDeque::new();
+		parts.push_back(part2);
+
 		// fast-forward the requested parts in reverse order
 		project.source.fast.sort();
 		for (i, (ff_st, ff_end)) in project.source.fast.iter().rev().enumerate() {
-			let recff = format!("recff{i}");
+			let recff = [
+				format!("recff{i}b").into(),
+				format!("recff{i}ff").into(),
+				format!("recff{i}a").into()
+			];
 			ffmpeg.add_filter(Filter::FastForward {
-				input: part2,
+				input: parts.pop_front().unwrap(),
 				ffinput: ff.clone().into(),
 				start: *ff_st - start,
 				duration: *ff_end - *ff_st,
 				multiplier: FF_MULTIPLIER,
-				output: recff.clone().into()
+				output: recff.clone()
 			});
-			part2 = recff.into();
+			parts.push_front(recff[2].clone());
+			parts.push_front(recff[1].clone());
+			parts.push_front(recff[0].clone());
 		}
 
 		// concatenate everything
+		parts.push_front(part1);
+		parts.push_back(part3);
 		let concat = "concat";
 		ffmpeg.add_filter(Filter::Concat {
-			inputs: vec![part1, part2, part3],
-			n: 3,
+			inputs: parts,
 			output: concat.into()
 		});