Fetch track thumbnails remotely

This commit is contained in:
thiloho
2025-10-23 22:17:26 +02:00
parent 42501f0bc5
commit e2cb8845b0
32 changed files with 5068 additions and 145 deletions

View File

@@ -3,17 +3,17 @@ import Logo from "../content/TH.svg";
import Icon from "./Icon.astro"; import Icon from "./Icon.astro";
import Button from "./Button.astro"; import Button from "./Button.astro";
const routes = ["blog", "tracks"]; const routes = ["blog", "tracks", "services"];
--- ---
<nav class="sticky top-0 z-10 max-w-none bg-white dark:bg-neutral-800"> <nav class="sticky top-0 z-10 max-w-none bg-white dark:bg-neutral-800">
<div <div
class="mx-auto flex max-w-screen-xl items-center justify-between ps-4 pe-2 text-neutral-700 dark:text-neutral-300" class="mx-auto flex max-w-screen-xl items-center justify-between gap-4 ps-4 pe-2 text-neutral-700 dark:text-neutral-300"
> >
<a href="/" title="Home"> <a href="/" title="Home">
<Logo width={42} height={42} /> <Logo width={42} height={42} />
</a> </a>
<div class="flex"> <div class="flex overflow-x-auto">
{ {
routes.map((route) => ( routes.map((route) => (
<Button href={`/${route}`}> <Button href={`/${route}`}>

View File

@@ -11,7 +11,7 @@ const { headings } = Astro.props;
<details <details
id="toc" id="toc"
class="sticky top-10 z-10 -translate-y-px border border-neutral-300 bg-white lg:top-2 dark:border-neutral-600 dark:bg-neutral-800" class="sticky top-10 z-10 -translate-y-px border border-neutral-300 bg-white xl:top-2 dark:border-neutral-600 dark:bg-neutral-800"
> >
<summary <summary
title="Table of contents" title="Table of contents"

View File

@@ -1,28 +1,46 @@
--- ---
import { Image } from "astro:assets";
interface Props { interface Props {
title: string; title: string;
artist: string; artist: string;
album: string; album: string;
youtubeLink: string; youtubeLink: string;
cover: any; index: number;
} }
const { title, artist, album, youtubeLink, cover } = Astro.props; const { title, artist, album, youtubeLink, index } = Astro.props;
const uniqueArtists = [...new Set(artist.split(",").map((a) => a.trim()))].join(
", ",
);
const videoId = youtubeLink.split("v=")[1];
const thumbnail = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
--- ---
<figure> <figure
class="relative flex flex-col border border-neutral-400 duration-300 hover:scale-105 active:scale-105 dark:border-neutral-500"
>
<span
class="absolute -start-2 -top-2 border border-neutral-400 bg-white px-2 text-lg font-bold dark:border-neutral-500 dark:bg-neutral-800"
>{index}</span
>
<a href={youtubeLink}> <a href={youtubeLink}>
<Image <img
src={cover} src={thumbnail}
alt={`Cover for the song '${title}' by artist(s) '${artist}'`} alt={`Cover for the song '${title}' by artist(s) '${artist}'`}
class="border border-neutral-400 duration-300 hover:scale-105 active:scale-105 dark:border-neutral-500" class="aspect-video w-full border-b border-neutral-400 dark:border-neutral-500"
/> />
</a> </a>
<figcaption class="flex flex-col p-4 text-center"> <figcaption
class="flex flex-1 flex-col p-6 text-center"
style="word-break: break-word;"
>
<p class="text-lg font-bold">{title}</p> <p class="text-lg font-bold">{title}</p>
<p>{artist}</p> <p class="mb-3">{uniqueArtists}</p>
<p class="text-sm">{album}</p> <p
class="mt-auto border-t border-neutral-400 pt-3 text-sm dark:border-neutral-500"
>
{album}
</p>
</figcaption> </figcaption>
</figure> </figure>

View File

@@ -13,15 +13,13 @@ const blog = defineCollection({
const tracks = defineCollection({ const tracks = defineCollection({
loader: file("./src/content/tracks.json"), loader: file("./src/content/tracks.json"),
schema: ({ image }) => schema: z.object({
z.object({ id: z.number().positive(),
id: z.number().positive(), title: z.string(),
title: z.string(), youtubeLink: z.string().url(),
youtubeLink: z.string().url(), artist: z.string(),
artist: z.string(), album: z.string(),
album: z.string(), }),
cover: image(),
}),
}); });
export const collections = { blog, tracks }; export const collections = { blog, tracks };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -245,4 +245,3 @@ public class ExampleTest extends BaseTest {
} }
} }
``` ```

View File

@@ -17,9 +17,9 @@ I am a software developer from Germany who is passionate about building high qua
## Software ## Software
- [NixOS](https://nixos.org) - [Arch Linux](https://archlinux.org)
- [GNOME](https://www.gnome.org) - [KDE Plasma](https://kde.org/plasma-desktop)
- [VSCodium](https://vscodium.com) - [Visual Studio Code](https://code.visualstudio.com)
- [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/developer) - [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/developer)
- [Tuta Mail](https://tuta.com) - [Tuta Mail](https://tuta.com)
- [Mullvad VPN](https://mullvad.net) - [Mullvad VPN](https://mullvad.net)

7
src/content/services.md Normal file
View File

@@ -0,0 +1,7 @@
This is an overview of popular services that are self-hosted and provided via my Hetzner NixOS VPS. The goal is to provide everyone with a central place where they can reliably access most of them.
## List
| Service | URL | Description |
| ------- | ----------------------------- | ---------------------------- |
| Redlib | https://redlib.thilohohlt.com | Private front-end for Reddit |

File diff suppressed because it is too large Load Diff

View File

@@ -25,12 +25,14 @@ export const GET: APIRoute = async (context) => {
<lastBuildDate>${latestModDate.toUTCString()}</lastBuildDate> <lastBuildDate>${latestModDate.toUTCString()}</lastBuildDate>
<atom:link href="${context.url.origin}/rss.xml" rel="self" type="application/rss+xml" /> <atom:link href="${context.url.origin}/rss.xml" rel="self" type="application/rss+xml" />
`, `,
items: blog.map(({ id, body, data }) => ({ items: blog
link: `/blog/${id}/`, .map(({ id, body, data }) => ({
content: sanitizeHtml(parser.render(body ?? ""), { link: `/blog/${id}/`,
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]), content: sanitizeHtml(parser.render(body ?? ""), {
}), allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
...data, }),
})), ...data,
}))
.sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()),
}); });
}; };

11
src/pages/services.astro Normal file
View File

@@ -0,0 +1,11 @@
---
import PageLayout from "../layouts/PageLayout.astro";
import { Content } from "../content/services.md";
---
<PageLayout
title="Services"
description="Self-hosted instances of popular services provided via my Hetzner NixOS VPS."
>
<Content />
</PageLayout>

View File

@@ -8,19 +8,21 @@ const tracks = await getCollection("tracks");
<PageLayout <PageLayout
title="Tracks" title="Tracks"
description="Collection of some of my favourite music tracks." description="My entire music playlist. It contains all kinds of songs."
> >
<p class="mb-8"> <p class="mb-8 text-center">
This is a collection of some of my favourite music tracks, each listed by My entire music playlist. It contains all kinds of songs. <br />
artist and song title. Click on an album cover to go straight to the song on Current total amount of songs: <strong class="text-lg"
YouTube! >{tracks.length}</strong
>
<br />
</p> </p>
<div <div
class="not-prose relative start-1/2 -ms-[min(50vw-1rem,50ch)] grid max-w-[calc(min(100vw-2rem,100ch))] grid-cols-[repeat(auto-fit,minmax(min(100%,200px),1fr))] place-content-center gap-4" class="not-prose relative start-1/2 -ms-[min(50vw-1rem,50ch)] grid max-w-[calc(min(100vw-2rem,100ch))] grid-cols-[repeat(auto-fit,minmax(min(100%,200px),1fr))] place-content-center gap-6"
> >
{ {
tracks.map(({ data: { title, artist, album, youtubeLink, cover } }) => ( tracks.map(({ data: { title, artist, album, youtubeLink } }, index) => (
<Track {title} {artist} {album} {youtubeLink} {cover} /> <Track {title} {artist} {album} {youtubeLink} index={++index} />
)) ))
} }
</div> </div>