···11+let xesite = ./types/package.dhall
22+33+let VOD = xesite.StreamVOD
44+55+in [ VOD::{
66+ , title = "Fixing Xesite in reader mode and RSS readers"
77+ , slug = "reader-mode-css"
88+ , description =
99+ ''
1010+ When you are using reader mode in Firefox, Safari or Google Chrome, the browser rends control of the website's design and renders its own design. This is typically done in order to prevent people's bad design decisions from making webpages unreadable and also to strip away advertisements from content. As a website publisher, I rely on the ability to control the CSS of my blog a lot. This stream covers the research/implementation process for fixing some long-standing issues with the Xesite CSS and making a fix to XeDN so that the site renders acceptably in reader mode.
1111+1212+ This stream covers the following topics:
1313+1414+ * Understanding complicated CSS rules and creating fixes for issues with them
1515+ * Using content distribution networks (CDNs) to help reduce page load time for readers
1616+ * Implementing image resizing capabilities into an existing CDN program (XeDN)
1717+ * Design with end-users in mind
1818+ ''
1919+ , date = "2022-01-21"
2020+ , cdnPath = "talks/vod/2023/01-21-reader-mode"
2121+ , tags = [ "css", "xedn", "imageProcessing", "scalability", "bugFix" ]
2222+ }
2323+ , VOD::{
2424+ , title = "Implementing the Pronouns service in Rust and Axum"
2525+ , slug = "pronouns-service"
2626+ , description =
2727+ ''
2828+ In this stream I implemented the [pronouns](https://pronouns.within.lgbt) service and deployed it to the cloud with [fly.io](https://fly.io). This was mostly writing a bunch of data files with [Dhall](https://dhall-lang.org) and then writing a simple Rust program to query that 'database' and then show results based on the results of those queries.
2929+3030+ This stream covers the following topics:
3131+3232+ * Starting a new Rust project from scratch with Nix flakes, Axum, and Maud
3333+ * API design for human and machine-paresable outputs
3434+ * DevOps deployment to the cloud via [fly.io](https://fly.io)
3535+ * Writing Terraform code for the pronouns service
3636+ * Building Docker images with Nix flakes and `pkgs.dockerTools.buildLayeredImage`
3737+ * Writing API documentation
3838+ * Writing [the writeup](https://xeiaso.net/blog/pronouns-service) on the service
3939+ ''
4040+ , date = "2022-01-07"
4141+ , cdnPath = "talks/vod/2023/01-07-pronouns"
4242+ , tags = [ "rust", "axum", "terraform", "nix", "flyio", "docker" ]
4343+ }
4444+ , VOD::{
4545+ , title = "Modernizing hlang with the nguh compiler"
4646+ , slug = "hlang-nguh-compiler"
4747+ , description =
4848+ ''
4949+ This stream was the last stream of 2022 and focused on modernizing the [hlang](https://xeiaso.net/blog/series/h) compiler. In this stream I reverse-engineered how WebAssembly modules work and wrote my own compiler for a trivial esoteric programming language named h. The existing compiler relied on legacy features of WebAssembly tools that don't work anymore.
5050+5151+ This stream covers the following topics:
5252+5353+ * Reverse-engineering the WebAssembly module format based on the specification and other reverse-engineering tools
5454+ * Adapting an existing compiler to output WebAssembly directly
5555+ * Deploying a new service to my NixOS machines in the cloud
5656+ * Building a Nix flake and custom NixOS module to build and deploy the new hlang website
5757+ * Terraform DNS config
5858+ * Writing [the writeup on the new compiler](https://xeiaso.net/blog/hlang-nguh)
5959+ ''
6060+ , date = "2022-12-31"
6161+ , cdnPath = "talks/vod/2022/12-31-nguh"
6262+ , tags =
6363+ [ "hlang"
6464+ , "go"
6565+ , "wasm"
6666+ , "philosophy"
6767+ , "devops"
6868+ , "terraform"
6969+ , "aws"
7070+ , "route53"
7171+ , "nixos"
7272+ ]
7373+ }
7474+ ]
+4
dhall/types/Config.dhall
···12121313let SeriesDescription = ./SeriesDescription.dhall
14141515+let VOD = ./StreamVOD.dhall
1616+1517let PronounSet = ./PronounSet.dhall
16181719let Prelude = ../Prelude.dhall
···3739 , contactLinks : List Link.Type
3840 , pronouns : List PronounSet.Type
3941 , characters : List Character.Type
4242+ , vods : List VOD.Type
4043 }
4144 , default =
4245 { signalboost = [] : List Person.Type
···5356 , contactLinks = [] : List Link.Type
5457 , pronouns = [] : List PronounSet.Type
5558 , characters = [] : List Character.Type
5959+ , vods = [] : List VOD.Type
5660 }
5761 }
+19
dhall/types/StreamVOD.dhall
···11+let Link = ./Link.dhall
22+33+in { Type =
44+ { title : Text
55+ , slug : Text
66+ , date : Text
77+ , description : Text
88+ , cdnPath : Text
99+ , tags : List Text
1010+ }
1111+ , default =
1212+ { title = ""
1313+ , slug = ""
1414+ , date = ""
1515+ , description = ""
1616+ , cdnPath = ""
1717+ , tags = [] : List Text
1818+ }
1919+ }
···1616pub mod blog;
1717pub mod feeds;
1818pub mod gallery;
1919+pub mod streams;
1920pub mod talks;
20212122fn weekday_to_name(w: Weekday) -> &'static str {
+94
src/handlers/streams.rs
···11+use crate::{
22+ app::{State, VOD},
33+ tmpl::{base, nag},
44+};
55+use axum::{extract::Path, Extension};
66+use chrono::prelude::*;
77+use http::StatusCode;
88+use lazy_static::lazy_static;
99+use maud::{html, Markup, Render};
1010+use prometheus::{opts, register_int_counter_vec, IntCounterVec};
1111+use serde::{Deserialize, Serialize};
1212+use std::sync::Arc;
1313+1414+lazy_static! {
1515+ static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
1616+ opts!("streams_hits", "Number of hits to stream vod pages"),
1717+ &["name"]
1818+ )
1919+ .unwrap();
2020+}
2121+2222+pub async fn list(Extension(state): Extension<Arc<State>>) -> Markup {
2323+ let state = state.clone();
2424+ let cfg = state.cfg.clone();
2525+2626+ crate::tmpl::base(
2727+ Some("Stream VODs"),
2828+ None,
2929+ html! {
3030+ h1 {"Stream VODs"}
3131+ p {
3232+ "I'm a VTuber and I stream every other weekend on "
3333+ a href="https://twitch.tv/princessxen" {"Twitch"}
3434+ " about technology, the weird art of programming, and sometimes video games. This page will contain copies of my stream recordings/VODs so that you can watch your favorite stream again. All VOD pages support picture-in-picture mode so that you can have the recordings open in the background while you do something else."
3535+ }
3636+ p {
3737+ "Please note that to save on filesize, all videos are rendered at 720p and optimized for viewing at that resolution or on most mobile phone screens. If you run into video quality issues, please contact me as I am still trying to find the correct balance between video quality and filesize. These videos have been tested and known to work on most of the browser and OS combinations that visit this site."
3838+ }
3939+ ul {
4040+ @for vod in &cfg.vods {
4141+ li {
4242+ (vod.detri())
4343+ " - "
4444+ a href={
4545+ "/vods/"
4646+ (vod.date.year())
4747+ "/"
4848+ (vod.date.month())
4949+ "/"
5050+ (vod.slug)
5151+ } {(vod.title)}
5252+ }
5353+ }
5454+ }
5555+ },
5656+ )
5757+}
5858+5959+#[derive(Serialize, Deserialize)]
6060+pub struct ShowArgs {
6161+ pub year: i32,
6262+ pub month: u32,
6363+ pub slug: String,
6464+}
6565+6666+pub async fn show(
6767+ Extension(state): Extension<Arc<State>>,
6868+ Path(args): Path<ShowArgs>,
6969+) -> (StatusCode, Markup) {
7070+ let state = state.clone();
7171+ let cfg = state.cfg.clone();
7272+7373+ let mut found: Option<&VOD> = None;
7474+7575+ for vod in &cfg.vods {
7676+ if vod.date.year() == args.year && vod.date.month() == args.month && vod.slug == args.slug {
7777+ found = Some(vod);
7878+ }
7979+ }
8080+8181+ if found.is_none() {
8282+ return (
8383+ StatusCode::NOT_FOUND,
8484+ crate::tmpl::error(html! {
8585+ "What you requested may not exist. Good luck."
8686+ }),
8787+ );
8888+ }
8989+9090+ let vod = found.unwrap();
9191+ HIT_COUNTER.with_label_values(&[&vod.slug]).inc();
9292+9393+ (StatusCode::OK, base(Some(&vod.title), None, vod.render()))
9494+}
···4141 @if let Some(vod) = &post.front_matter.vod {
4242 p {
4343 "This post was written live on "
4444- a href="https://twitch.tv/princessxen" {"Twitch"}
4444+ a href="https://twitch.tv/princessxen" {"Twitch"}
4545 ". You can check out the stream recording on "
4646- a href=(vod.twitch) {"Twitch"}
4646+ a href=(vod.twitch) {"Twitch"}
4747 " and on "
4848- a href=(vod.youtube) {"YouTube"}
4848+ a href=(vod.youtube) {"YouTube"}
4949 ". If you are reading this in the first day or so of this post being published, you will need to watch it on Twitch."
5050 }
5151 }