https://pine32.be - © pine32.be 2026
Welcome! - 110 total posts. [RSS]
A Funny little cycle 2.0 [LATEST]


Search 110 posts with 46 unique tags


#1749396915


[ dev ]

I tried Ent again… I know I said that I was done with ORMs, but people were telling me that it shouldn’t be that bad, so I gave it another chance. Again with Ent, because I feel like it’s the only ORM that has a chance to work with everything I want to use it for. And up to now, it is still holding up, it hasn’t been a blocker yet for this project. Performance is fine, of course there is overhead but nothing major (unlike SQLAlchemy, which halves your performance…). It can now handle many-to-many relations with custom join tables without a problem. I was also able to change some codegen that I didn’t like (omitempty on boolean fields) using a codegen hook, and add a runtime hook for some custom row-level validation (see codeblock). The function signature of the hooks takes some getting used to, but as long as it works, it’s fine by me. I also like the bulk insert API, it is really flexible. So overall I am happily using it but I am staying watchful about any possible limitations.

func (Image) Hooks() []ent.Hook {
	return []ent.Hook{
		hook.On(
			func(next ent.Mutator) ent.Mutator {
				return hook.ImageFunc(
					func(ctx context.Context, m *gen.ImageMutation) (
						ent.Value, error) {
						_, hasRelease := m.ReleaseID()
						_, hasArtist := m.ArtistID()
						if hasRelease == hasArtist {
							if hasRelease {
								return nil, fmt.Errorf("but both were provided")
							} else {
								return nil, fmt.Errorf("neither was provided")
							}
						}
						return next.Mutate(ctx, m)
					})
			},
			ent.OpCreate|ent.OpUpdate|ent.OpUpdateOne,
		),
	}
}

#1748514616


[ music | gaming ]

Just finished Yakuza Kiwami

Cover Art
意地桜2000[Full Spec Edition]
Yuri Fukuda

#1747598386


[ dev ]

I have spent far too much time designing a custom ID type for my current project. I wanted to use it as the primary key in a SQLite database, which imposes some constraints. Specifically, the ID must fit within 63 bits, since SQLite only supports signed integers and I want to avoid negative values. (Technically, negative IDs would work, but they’re not ideal.)

You might be thinking, “Why not just use a BLOB as the primary key? That gives you much more flexibility.” And that’s a fair point, but I am intentionally avoiding that because of how SQLite handles its hidden rowid. When you use an integer as the primary key, SQLite internally aliases it to the rowid, which makes operations significantly faster. Using a BLOB would remove that performance advantage and make the database larger.

So the next step is choosing the bit layout. The first bit is unused to prevent negative values. Then I went with 43 bits for a Unix millisecond timestamp. This gives me 278 years of ranges, should be plenty. Using the default Unix epoch this will work until the year 2248. It will outlive me so that is more than enough.

The remaining 20 bits are random, which gives 1_048_576 possible values. I am using random values because I don’t want to keep track of state (as with an autoincrement), and my current system can handle collisions. It is still possible to swap approaches down the road while keeping the already generated IDs. 1_048_576 Sounds like a lot, but this gives a 1% chance of a collision occurring when only generating 146 IDs. Then again, those IDs would need to be generated within the same millisecond. I am not expecting that much volume.

Bit  | 63 (MSB) | 62 ... 20 | 19 ... 0 |        some        1JTWRZPBJ4DSE
-----|----------|-----------|----------|        example     1JTWRZSPA1NBS
Use  | Unused   | Timestamp | Random   |        IDs         1JTWRZTQSE9G6
Size | 1 bit    | 43 bits   | 20 bits  |        ->          1JTWRZVY5R7RT

The reason for using a timestamp in the leading bits is to minimize B-tree rebalances. As time advances, the generated IDs grow in sequence, allowing the B-tree to insert new entries without reorganizing older pages. By contrast, a completely random primary key (like a UUID v4) forces the B-tree to rebalance frequently, which can significantly degrade database performance.

Finally, the string representation: I chose Crockford’s Base32 (without the check digit). Just 13 characters to represent a int64. To me it’s practically perfect from a technical standpoint, and I like how the IDs look. I know aesthetics shouldn’t matter, but this is my project. So I set the rules, and I want things to look cool and and have some aesthetic appeal. Looks way better than those stupid UUIDs.

One final note, please store your IDs in a binary presentation (BLOB or integer). It hurts me every time I seed a ID stored as its string representation. It is way slower and waists storage. It mainly happens with UUIDs, most people don’t realize it actually is a binary ID and not a string. Even the spec states it but I guess people just don’t read it.

#1746469688


[ music ]

Just added some more game soundtracks to my library. I’m almost up to 5000 tracks now, but Spotify is still at 6000. I am getting closer to overtaking it, though.

I’ve also been thinking more and more about how to manage all these tracks. For genres, I think I’m going to build my own system based on musicmap.info. It provides a lot of detailed information with examples and has a great idea of using multiple levels of categorization. So tracks will have three genres: a super-genre, a normal genre, and a subgenre. Each level is more specific. There’s still a lot to read and figure out, but this is a nice foundation. Genre police soon™.

Cover Art
Full Confession
LudoWic

#1745864702


[ mb_dev ]

Of course, just when I say that my Spotify scraper hasn’t broken yet, it breaks. Luckily, it was just a small fix, but still. I also got the Navidrome embeds working, only 185 lines of code added (excluding the codegen). It only took a couple of hours. This should help for the meantime while I work on the custom music library management tool.

Cover Art
DEUTSCHLAND (Nedaj Edit)
Nedaj

#1745526310


[ mb_dev ]

Looking back, I’m really happy that I used web scraping for my Spotify embeds, seems to be quite resilient. This post has a track that doesn’t work on Spotify anymore, yet the embed still works fine. This isn’t a guarantee though, one day it will break, but not today. That’s why I am thinking about downloading all Spotify assets, so if it works now, it’ll work forever. It always feels hard to add these kinds of things while trying to stay minimalistic… maybe I should.

I am still looking for another way to integrate some kind of music embed. Maybe with Navidrome, since I’m currently using that for my ever-growing offline music library. I could share a track and then scrape that share to make an embed, that would work the same way Spotify works for now. Would work with a minimal amount of code, which aligns with the philosophy of this blog. But there are two small problems with this: having a full-length song publicly available (basically illegal distribution) and It depends on the Navidrome server and that specific share never going down. I could make the share low quality so people wont download it, but this is not a perfect fix.

This is running form a share on my Navidrome instance, it works: (KLOUD - DEFECT)

Another solution is using a custom music library management tool (which I am planning on making, soon™) that exports a music snippet to be upload in the media management of MB (automatically or manually) and a piece of html that will become the “embed”. I could already do this now manually, but I want to keep posts low-friction and this would help. It’s an almost perfect solution, no changes for MB and none of the Navidrome problems.

But a lot of code is needed for that music library management tool first. So it won’t be soon, while I could bang out the Navidrome solution in an evening or two. Maybe I should first make MB download all Spotify assets to lock them in place forever (I know it’s against the TOS but IDC). Still, it would increase the complexity of MB. It can’t be perfect, but what’s more important… I am going to download them, can’t trust Spotify with anything.

#1744920738


[ music ]

Got myself a new audio setup: the new Echo Mini from FiiO paired with the Porta Pros from Koss. A pair that looks and sounds great. I’ve been looking at DAPs (digital audio players) for a while, and this one has great audio quality with a simple, distraction-free interface. And for just 55 euros, why not! Looking forward to using it. Just pure music. Still need a nice workflow to copy things onto it, a new project maybe.

Echo mini (DAP)plus portapros (headphones)

Song: Parov Stelar - Milla’s Dream

#1744467097


[ homelab ]

I have been testing Anubis to protect my services. It is a simple proof-of-work proxy. It doesn’t have a native Traefik middleware yet, but I got it working. Should be added to the docs soon-ish (I helped with an example).

Anubis weighs the soul of your connection using a sha256 proof-of-work challenge in order to protect upstream resources from scraper bots.

It works great, but I’m still on the fence about the mascot. I don’t really like the furry look, especially compared to the style of my website. I guess I’ll fork it, but the creator doesn’t like that. But it is MIT licensed, so they can’t do anything about it…

#1743450887


[ homelab ]

It’s backup day!! Time to check your backups.

I recently got backups working on my server, only took 2 years… I am using restic wrapped in a container that I found on github. That pushed to a storage box on Hetzner via WebDAV. Restic has all the nice incremental and resumable stuff handled (written in go btw). Works great but I still have to test a restore. Some day, give me a year or two.

#1742731727


[ photography ]
sqr_dump_1_1 sqr_dump_1_2 sqr_dump_1_3 sqr_dump_1_4

sqr_dump_3, one night special. Taken by a friend of mine with a point & shoot camera straight out of 2008.

#1741968181


[ music | piano ]

I started learning piano a few weeks ago, and one thing I’ve noticed is how expensive sheet music can be, even for pieces that are in the public domain. It’s really frustrating, especially coming from the world of open source, where people happily create and share their work. This feels like everyone is just out to make money. Of course, I understand that transcribing and formatting music takes effort, but compared to some open-source projects, it doesn’t seem that demanding. It just makes me appreciate open-source projects even more.

There also doesn’t seem to be a great tool for self-hosting sheet music. SheetAble exists, but it looks abandoned and only has very basic functionality. The pdf viewer is also useless. Maybe the best solution is just plain old paper though… a piano doesn’t really move around a lot.

#1741337104


[ music | pop ]

J’sors qu’avec des geeks ou dеs dealos

Cover Art

preview of 20 sec instead of 30 sec, strange

#1740428162


[ music ]

Mood

Cover Art

#1740225625


[ dev ]

Turns out that HTTP/0.9 is a thing. No headers, no methods, no status codes, only GET. It is so simple you can easily make a request manually. Firefox still supports it apparently.

echo -e "GET /\r" | nc pine32.be 80

#1739535870


[ mb_dev ]

1 Year of MB!!

The first post was on 04/02/2024, missed the anniversary but better late than never. My goal was to make 1 post per week. I have made 70 posts this year (not including this one). So it looks like I hit my goal on average, but it was not really consistent, so that could be better. Nevertheless the goal of this blog was to make posting as easy as possible and I think it succeeded in that manner.

Development has slowed down but I still have some plans to add things like syntax highlighting for code blocks. There is nothing that I am missing at the moment, only some small nice-to-haves. I also want to keep it small, so I am keeping feature creep in check.

#1738273490


[ dev ]

Still alive, just busy. First post of the year… yay I guess.

I have been working on my fork of Navidrome that will have some audio and music analysis with the help of Essentia, the MVP is almost done. Because fuck Spotify, I am done with their bullshit. But I still want my cool data, so I am making it myself, all open source ofcourse.

Navidrome is written in Golang but most analysis libraries are written in C/C++ or Python, so gRPC was the solution because I am not writing a wrapper. My first time working with gRPC, it’s really nice once you get it all set up. But the setup can be a pain, like WTF are .pyi files. I have never seen them before, they are interface files so you have your types available (this is because Python Protobuf does some weird runtime C thing that processes the proto files or something). It has been nice to use, apart from setup. The end-to-end types are just a huge plus, you just know that they will work.

Python multiprocessing has also been a journey. Tested all types of pools but they always just deadlocked or didn’t run. In the end I just made my own worker pool with each worker having its own process. And they share a multiprocessing-safe queue for input and output. Sort of like I would do in Golang with channels instead of queues. And this dead simple approach worked first try of course, after everything I tried. But I am just happy that it works now. Still feels weird to see python use almost 100% of your CPU on all cores.

One year anniversary coming up for MB.

#1735654254


[ music | rock ]

Last post of the year, more music

Cover Art

(Sounds way better in lossless)

#1734968642


[ music | metal ]

Funny neo Japan woman

Cover Art

No lyrics caption this time, idk what they are saying

#1734385699


[ homelab ]

Finally moved my VPS from Caddy to Traefik. There are still a few kinks to work out, but it works great. I like Traefik’s configuration more; it allows my config to be inside each Docker Compose file itself, so it’s all in one spot. It also has TCP and UDP support, which I will be using in future projects. It little more complex to setup, but way more powerful.

#1733314156


[ rant | music ]

Today on Spotify is horrible for everybody. They killed major part of there API (since 27/11/2024), the following parts are deprecated and can’t be used by any new app.

And they did this without any notice. For some people (myself included) this means that a project with months of work is just scrapped in an instant. It is really time to move of Spotify but the alternatives are not that much better, but still better I guess. I will have to look into my own audio analyzer, if I even have the compute for that…

I am happy that this blog is scraping based so I am not dependent on there API and everything just works as before. But meta-tune and my upcoming scraping project are just totally bricked.

Cover Art

#1733042840


[ AOC_2024 ]

Advent of code is back! I don’t know I will have time to complete all days. Finally time to give a Scala a try. I figured that Scala is the best functional programming languages that is still useful to write real apps. I also was considering Elixir but I have already gave it a chance with the Phoenix framework, so Scala it is.

#1731920421


[ mb_dev ]

Got my first contribution and fork on this project. Happy to see his version deployed and taking on his own look. This project was made to be forked, I hope more people follow suit.

Hi Tim o/

#1731844234


[ music | pop ]

No, Billy, I haven’t done that dance since my wife died

Cover Art

#1731188466


[ dev | meta_raid ]

I am making a scraper for Spotify meta data. My testing numbers indicates that I could scrape 100% of Spotify in less then a week, something feels wrong.

INFO Stats per minute id=0 request=204 tracks=2451
INFO Stats per minute id=2 request=193 tracks=2086
INFO Stats per minute id=1 request=212 tracks=2392

#1730630725


[ dev | golang | meta_raid ]

Golangs new integrators came in handy for request pagination. I know the code is not optimal but is very readable and it is just for a proof of concept. I am try to get Spotify metadata in bulk. Hopefully I won’t get IP banned, fingers crossed.

for chunk := range slices.Chunk(allSimpleTracks, 100) {
	ids := make([]spotify.ID, len(chunk))
	for i, a := range chunk {
		ids[i] = a.ID
	}

	f, err := client.GetAudioFeatures(ctx, ids...)
	if err != nil {
		return nil, err
	}

	fullTracks := make([]*spotify.FullTrack, len(ids))
	for subChunk := range slices.Chunk(ids, 50) {
		full, err := client.GetTracks(ctx, subChunk, spotify.Limit(50))
		if err != nil {
			return nil, err
		}
		fullTracks = append(fullTracks, full...)
	}
	for i := range len(ids) {
		allTracks[i] = &FullerTrack{
			Track:    fullTracks[i],
			Features: f[i],
		}
	}
}