diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs
index 63657cd05d1f996f1082c126a4ff98d5be9a229e..614ad1930fb608054edbea254797519e7c8e9dd4 100644
--- a/src/render/ffmpeg.rs
+++ b/src/render/ffmpeg.rs
@@ -1,7 +1,8 @@
 use super::{cmd, filter::Filter};
 use crate::{
 	render::filter::channel,
-	time::{format_time, Time}
+	time::{format_time, Time},
+	Resolution
 };
 use anyhow::bail;
 use camino::Utf8PathBuf as PathBuf;
@@ -55,6 +56,7 @@ pub(crate) struct FfmpegOutput {
 	pub(crate) duration: Option<Time>,
 	pub(crate) time_base: Option<Rational>,
 	pub(crate) fps_mode_vfr: bool,
+	pub(crate) faststart: bool,
 	pub(crate) path: PathBuf
 }
 
@@ -65,10 +67,16 @@ impl FfmpegOutput {
 			duration: None,
 			time_base: None,
 			fps_mode_vfr: false,
+			faststart: false,
 			path
 		}
 	}
 
+	pub(crate) fn enable_faststart(mut self) -> Self {
+		self.faststart = true;
+		self
+	}
+
 	fn append_to_cmd(self, cmd: &mut Command) {
 		if let Some(fps) = self.fps {
 			cmd.arg("-r").arg(fps.to_string());
@@ -82,16 +90,27 @@ impl FfmpegOutput {
 		if self.fps_mode_vfr {
 			cmd.arg("-fps_mode").arg("vfr");
 		}
-		cmd.arg("-movflags").arg("+faststart");
+		if self.faststart {
+			cmd.arg("-movflags").arg("+faststart");
+		}
 		cmd.arg(self.path);
 	}
 }
 
+enum FfmpegFilter {
+	None,
+	Filters {
+		filters: Vec<Filter>,
+		output: Cow<'static, str>
+	},
+	Loudnorm,
+	Rescale(Resolution)
+}
+
 pub(crate) struct Ffmpeg {
 	inputs: Vec<FfmpegInput>,
-	filters: Vec<Filter>,
-	filters_output: Cow<'static, str>,
-	loudnorm: bool,
+	filter: FfmpegFilter,
+	video_bitrate: Option<&'static str>,
 	output: FfmpegOutput,
 
 	filter_idx: usize
@@ -101,9 +120,8 @@ impl Ffmpeg {
 	pub fn new(output: FfmpegOutput) -> Self {
 		Self {
 			inputs: Vec::new(),
-			filters: Vec::new(),
-			filters_output: "0".into(),
-			loudnorm: false,
+			filter: FfmpegFilter::None,
+			video_bitrate: None,
 			output,
 
 			filter_idx: 0
@@ -116,22 +134,56 @@ impl Ffmpeg {
 	}
 
 	pub fn add_filter(&mut self, filter: Filter) -> &mut Self {
-		assert!(!self.loudnorm);
-		self.filters.push(filter);
+		match &mut self.filter {
+			FfmpegFilter::None => {
+				self.filter = FfmpegFilter::Filters {
+					filters: vec![filter],
+					output: "0".into()
+				}
+			},
+			FfmpegFilter::Filters { filters, .. } => filters.push(filter),
+			_ => panic!("An incompatible type of filter has been set before")
+		}
 		self
 	}
 
 	pub fn set_filter_output<T: Into<Cow<'static, str>>>(
 		&mut self,
-		output: T
+		filter_output: T
 	) -> &mut Self {
-		self.filters_output = output.into();
+		match &mut self.filter {
+			FfmpegFilter::None => {
+				self.filter = FfmpegFilter::Filters {
+					filters: vec![],
+					output: filter_output.into()
+				}
+			},
+			FfmpegFilter::Filters { output, .. } => *output = filter_output.into(),
+			_ => panic!("An incompatible type of filter has been set before")
+		}
 		self
 	}
 
 	pub fn enable_loudnorm(&mut self) -> &mut Self {
-		assert!(self.filters.is_empty());
-		self.loudnorm = true;
+		match &mut self.filter {
+			FfmpegFilter::None => self.filter = FfmpegFilter::Loudnorm,
+			FfmpegFilter::Loudnorm => {},
+			_ => panic!("An incompatible type of filter has been set before")
+		}
+		self
+	}
+
+	pub fn rescale_video(&mut self, res: Resolution) -> &mut Self {
+		match &mut self.filter {
+			FfmpegFilter::None => self.filter = FfmpegFilter::Rescale(res),
+			FfmpegFilter::Loudnorm => {},
+			_ => panic!("An incompatible type of filter has been set before")
+		}
+		self
+	}
+
+	pub fn set_video_bitrate(&mut self, bitrate: &'static str) -> &mut Self {
+		self.video_bitrate = Some(bitrate);
 		self
 	}
 
@@ -140,14 +192,25 @@ impl Ffmpeg {
 		cmd.arg("ffmpeg").arg("-hide_banner").arg("-y");
 
 		// determine whether the video need to be re-encoded
-		let venc = !self.filters.is_empty();
-		let aenc = !self.filters.is_empty() || self.loudnorm;
+		// vdec is only true if the video should be decoded on hardware
+		let (vdec, venc, aenc) = match &self.filter {
+			FfmpegFilter::None => (false, false, false),
+			FfmpegFilter::Filters { .. } => (false, true, true),
+			FfmpegFilter::Loudnorm => (false, false, true),
+			FfmpegFilter::Rescale(_) => (true, true, false)
+		};
 
 		// initialise a vaapi device if one exists
 		let vaapi_device: PathBuf = "/dev/dri/renderD128".into();
-		let vaapi = venc && vaapi_device.exists();
-		if vaapi {
-			cmd.arg("-vaapi_device").arg(&vaapi_device);
+		let vaapi = vaapi_device.exists();
+		if vaapi && venc {
+			if vdec {
+				cmd.arg("-hwaccel").arg("vaapi");
+				cmd.arg("-hwaccel_device").arg(vaapi_device);
+				cmd.arg("-hwaccel_output_format").arg("vaapi");
+			} else {
+				cmd.arg("-vaapi_device").arg(&vaapi_device);
+			}
 		}
 
 		// append all the inputs
@@ -159,30 +222,36 @@ impl Ffmpeg {
 		cmd.arg("-async").arg("1");
 
 		// apply filters
-		match (self.loudnorm, self.filters) {
-			(true, f) if f.is_empty() => {
-				cmd.arg("-af").arg("pan=mono|c0=FR,loudnorm,pan=stereo|c0=c0|c1=c0,aformat=sample_rates=48000");
-			},
-			(true, _) => panic!("Filters and loudnorm at the same time is not supported"),
-
-			(false, f) if f.is_empty() => {},
-			(false, f) => {
+		match self.filter {
+			FfmpegFilter::None => {},
+			FfmpegFilter::Filters { filters, output } => {
 				let mut complex = String::new();
-				for filter in f {
+				for filter in filters {
 					filter.append_to_complex_filter(&mut complex, &mut self.filter_idx);
 				}
 				if vaapi {
-					write!(
-						complex,
-						"{}format=nv12,hwupload[v]",
-						channel('v', &self.filters_output)
-					);
+					write!(complex, "{}format=nv12,hwupload[v]", channel('v', &output));
 				} else {
-					write!(complex, "{}null[v]", channel('v', &self.filters_output));
+					write!(complex, "{}null[v]", channel('v', &output));
 				}
 				cmd.arg("-filter_complex").arg(complex);
 				cmd.arg("-map").arg("[v]");
-				cmd.arg("-map").arg(channel('a', &self.filters_output));
+				cmd.arg("-map").arg(channel('a', &output));
+			},
+			FfmpegFilter::Loudnorm => {
+				cmd.arg("-af").arg(concat!(
+					"pan=mono|c0=FR,",
+					"loudnorm=dual_mono=true:print_format=summary,",
+					"pan=stereo|c0=c0|c1=c0,",
+					"aformat=sample_rates=48000"
+				));
+			},
+			FfmpegFilter::Rescale(res) => {
+				cmd.arg("-vf").arg(if vaapi {
+					format!("scale_vaapi=w={}:h={}", res.width(), res.height())
+				} else {
+					format!("scale=w={}:h={}", res.width(), res.height())
+				});
 			}
 		}
 
@@ -190,14 +259,21 @@ impl Ffmpeg {
 		const QUALITY: &str = "24";
 		if vaapi {
 			cmd.arg("-c:v").arg("h264_vaapi");
-			cmd.arg("-rc_mode").arg("CQP");
-			cmd.arg("-global_quality").arg(QUALITY);
+			if self.video_bitrate.is_none() {
+				cmd.arg("-rc_mode").arg("CQP");
+				cmd.arg("-global_quality").arg(QUALITY);
+			}
 		} else if venc {
 			cmd.arg("-c:v").arg("libx264");
-			cmd.arg("-crf").arg(QUALITY);
+			if self.video_bitrate.is_none() {
+				cmd.arg("-crf").arg(QUALITY);
+			}
 		} else {
 			cmd.arg("-c:v").arg("copy");
 		}
+		if venc && self.video_bitrate.is_some() {
+			cmd.arg("-b:v").arg(self.video_bitrate.unwrap());
+		}
 		if aenc {
 			cmd.arg("-c:a").arg("aac");
 			cmd.arg("-b:a").arg("128000");
diff --git a/src/render/mod.rs b/src/render/mod.rs
index a4fafece156c9e39c53264351d23ad868e4d64e4..3b9257c9d4ede39df92cbffdac808aea016c2ab7 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -128,6 +128,7 @@ fn svg2mp4(
 		duration: Some(duration),
 		time_base: Some(meta.source_tbn),
 		fps_mode_vfr: true,
+		faststart: false,
 		path: mp4
 	});
 	ffmpeg.add_input(FfmpegInput {
@@ -452,32 +453,12 @@ impl<'a> Renderer<'a> {
 		let output = self.video_mp4_res(res);
 		println!("\x1B[1m ==> Rescaling to {}p\x1B[0m", res.height());
 
-		let mut ffmpeg = cmd();
-		ffmpeg.arg("ffmpeg").arg("-hide_banner");
-		// TODO do we just always want hwaccel?
-		ffmpeg
-			.arg("-hwaccel")
-			.arg("vaapi")
-			.arg("-hwaccel_device")
-			.arg("/dev/dri/renderD128")
-			.arg("-hwaccel_output_format")
-			.arg("vaapi");
-		ffmpeg.arg("-i").arg(input);
-		ffmpeg.arg("-vf").arg(format!(
-			"scale_vaapi=w={}:h={}",
-			res.width(),
-			res.height()
-		));
-		ffmpeg.arg("-c:a").arg("copy").arg("-c:v").arg("h264_vaapi");
-		ffmpeg.arg("-b:v").arg(res.bitrate());
-		ffmpeg.arg("-movflags").arg("+faststart");
-		ffmpeg.arg(&output);
-
-		let status = ffmpeg.status()?;
-		if status.success() {
-			Ok(output)
-		} else {
-			bail!("ffmpeg failed with exit code {:?}", status.code())
-		}
+		let mut ffmpeg =
+			Ffmpeg::new(FfmpegOutput::new(output.clone()).enable_faststart());
+		ffmpeg.add_input(FfmpegInput::new(input));
+		ffmpeg.rescale_video(res);
+		ffmpeg.set_video_bitrate(res.bitrate());
+		ffmpeg.run()?;
+		Ok(output)
 	}
 }