diff --git a/src/main.rs b/src/main.rs
index 04e5791ec5d7fa4c5d70abdd8172bd93155f0012..8df5f6523ba24385d2af2e331f34f3cb1397eda4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,43 +37,58 @@ struct Args {
 	mem_limit: String
 }
 
-#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
-#[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
-enum Resolution {
-	/// 640x360
-	nHD,
-	/// 1280x720
-	HD,
-	/// 1920x1080
-	FullHD,
-	/// 2560x1440
-	WQHD,
-	/// 3840x2160
-	UHD
-}
-
-impl Resolution {
-	fn width(self) -> usize {
-		match self {
-			Self::nHD => 640,
-			Self::HD => 1280,
-			Self::FullHD => 1920,
-			Self::WQHD => 2560,
-			Self::UHD => 3840
+macro_rules! resolutions {
+	($($res:ident: $width:literal x $height:literal at $bitrate:literal),+) => {
+		#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
+		#[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+		enum Resolution {
+			$(
+				#[doc = concat!(stringify!($width), "x", stringify!($height))]
+				$res
+			),+
 		}
-	}
 
-	fn height(self) -> usize {
-		match self {
-			Self::nHD => 360,
-			Self::HD => 720,
-			Self::FullHD => 1080,
-			Self::WQHD => 1440,
-			Self::UHD => 2160
+		const NUM_RESOLUTIONS: usize = {
+			let mut num = 0;
+			$(num += 1; stringify!($res);)+
+			num
+		};
+
+		impl Resolution {
+			fn values() -> [Self; NUM_RESOLUTIONS] {
+				[$(Self::$res),+]
+			}
+
+			fn width(self) -> usize {
+				match self {
+					$(Self::$res => $width),+
+				}
+			}
+
+			fn height(self) -> usize {
+				match self {
+					$(Self::$res => $height),+
+				}
+			}
+
+			fn bitrate(self) -> &'static str {
+				match self {
+					$(Self::$res => $bitrate),+
+				}
+			}
 		}
 	}
 }
 
+resolutions! {
+	nHD: 640 x 360 at "500k",
+	HD: 1280 x 720 at "1M",
+	FullHD: 1920 x 1080 at "2M",
+	WQHD: 2560 x 1440 at "3M",
+	// TODO qsx muss mal sagen wieviel bitrate für 4k
+	UHD: 3840 x 2160 at "4M"
+}
+
 #[derive(Deserialize, Serialize)]
 struct Project {
 	lecture: ProjectLecture,
@@ -245,6 +260,38 @@ fn main() {
 		fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap();
 	}
 
-	let video = renderer.render(&mut project).unwrap();
-	println!("\x1B[1m ==> DONE :)\x1B[0m Video: {video}");
+	// render the video
+	let mut videos = Vec::new();
+	videos.push(if !project.progress.rendered {
+		let video = renderer.render(&mut project).unwrap();
+		project.progress.rendered = true;
+
+		println!("{}", toml::to_string(&project).unwrap());
+		fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap();
+
+		video
+	} else {
+		renderer.video_mp4(&project)
+	});
+
+	// rescale the video
+	for res in Resolution::values() {
+		if res >= project.source.metadata.as_ref().unwrap().source_res {
+			continue;
+		}
+		if !project.progress.transcoded.contains(&res) {
+			videos.push(renderer.rescale(res, &project).unwrap());
+			project.progress.transcoded.insert(res);
+
+			println!("{}", toml::to_string(&project).unwrap());
+			fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes())
+				.unwrap();
+		}
+	}
+
+	println!("\x1B[1m ==> DONE :)\x1B[0m");
+	println!("     Videos:");
+	for v in &videos {
+		println!("     -> {v}");
+	}
 }
diff --git a/src/render/mod.rs b/src/render/mod.rs
index 56e63549f2cd4608ca97ccc7b163fbd6148266f9..49c62b8b0a16a6233f4be510a368386f098058b4 100644
--- a/src/render/mod.rs
+++ b/src/render/mod.rs
@@ -261,18 +261,17 @@ impl<'a> Renderer<'a> {
 		Ok(())
 	}
 
+	fn video_mp4_res(&self, res: Resolution) -> PathBuf {
+		self.target
+			.join(format!("{}-{}p.mp4", self.slug, res.height()))
+	}
+
+	pub(crate) fn video_mp4(&self, project: &Project) -> PathBuf {
+		self.video_mp4_res(project.source.metadata.as_ref().unwrap().source_res)
+	}
+
 	pub(crate) fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> {
-		let mut output = self.target.join(format!(
-			"{}-{}p.mp4",
-			self.slug,
-			project
-				.source
-				.metadata
-				.as_ref()
-				.unwrap()
-				.source_res
-				.height()
-		));
+		let output = self.video_mp4(project);
 		let mut ffmpeg = Ffmpeg::new(output.clone());
 
 		// add all of our inputs
@@ -435,4 +434,37 @@ impl<'a> Renderer<'a> {
 
 		Ok(output)
 	}
+
+	pub fn rescale(&self, res: Resolution, project: &Project) -> anyhow::Result<PathBuf> {
+		let input = self.video_mp4(project);
+		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(&output);
+
+		let status = ffmpeg.status()?;
+		if status.success() {
+			Ok(output)
+		} else {
+			bail!("ffmpeg failed with exit code {:?}", status.code())
+		}
+	}
 }