Friday, December 6, 2024

Download YouTube videos

These days I was transferring one of by band’s channel, and I needed to download a 1h34min long video. No web downloader worked properly.

While searching for an actual software, I found this Reddit post referring to a Python command line tool named yt-dlp. Basic usage is pretty straightforward:

yt-dlp.exe URL

My first try downloaded two files: MP4 and WEBM. The MP4 had no sound, and the WEBM was unplayable. My recently downloaded Vegas 22 didn’t seem to recognize this files. A warning message appeared, though:

WARNING: You have requested merging of multiple formats but ffmpeg is not installed.
The formats won't be merged

The repository README had an specific section labelled FFmpeg Static Auto-Builds, with a download link to Windows x64 binaries. The zip contains, among others, a bin directory with three executables. I extracted them in the same folder of the original executable – yt-dlp.exe –, and this time the download completed smoothly, with a single file with video and audio properly merged.

Excellent tool.

Monday, October 7, 2024

Goodbye my Universe

Today I finally parted ways with my long-time friend, my Ibanez Universe UV7BK green dots.

As far as I remember, I bought this guitar in 2008 amidst a craze of seven string guitars. I remember seeing an UV777BK on the TV and going nuts over that, even posting online at the old FCC forums. And by that time I joined a Dream Theater cover band, and I played solely this guitar. To this day, I still don’t know how I was able to play all that hard stuff on a seven string. Incredible young me.

The neck profile was truly great. I loved it. Pronounced shoulders, somewhat similar to the Suhr Modern Satin I had.

I remember trying to sell it 10 years ago or so, but I retreated.

After playing in a band where a seven string was demanded, and using a six string with a pitch shifter instead, I finally realized I don’t play sevens anymore. It must go. The original Ibanez wooden hardcase was wasted with mold, so I had to buy another one. Pretty bad fit, but anyway. The UV had grown mold in the neck too, but I cleaned. Maybe it will show up again in the future, who knows.

It was really hard to find someone to buy it. Professional sellers refused saying it’s hard to sell seven strings. In the end, I found someone to buy, and it posted it.

Goodbye, old friend.

Friday, July 26, 2024

Automating MSVC builds

While rewritting a few of my personal tools from Rust back to C++, I found myself willing to automate the build process. At first, I tried to invoke MSVC’s cl compiler directly, but it has proven to be absolute hell to do.

Eventually I stumbled upon the marvellous MSBuild tool, which is capable of understanding the .sln file and take all compiler and linker options from it:

msbuild foo.sln /p:Configuration=Release /p:Platform=x64

With that, I finally had the script to automate one build:

call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
set APP="vscode-font-patch"
msbuild %APP%.sln /p:Configuration=Release /p:Platform=x64
move /Y x64_Release\%APP%.exe "D:\Stuff\apps\_audio tools\"
rmdir /S /Q x64_Release
pause

Then it was time to automate the release build of all my listed projects. And I found out how awkward .bat syntax is. Loop syntax is particularly horrifying. In the end, I was able to cook a script to automate my C++ builds:

call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"

set APPS[0]="vscode-font-patch"

set "i=0"
:SymLoop
if not defined APPS[%i%] goto :EndLoop
call set APP=%%APPS[%i%]%%
cd %APP%
echo Building %APP%...
msbuild %APP%.sln /p:Configuration=Release /p:Platform=x64
move /Y x64_Release\%APP%.exe "D:\Stuff\apps\_audio tools\"
rmdir /S /Q x64_Release
cd ..

set /a "i+=1"
goto :SymLoop
:EndLoop
pause

Friday, July 12, 2024

C++20 variadics with abbreviated function template

Today, when working with my stripped C++ Win32 library – after the frustration with Rust’s long compile times in WinSafe –, I was trying to implement an overloaded variadic method, in order to get rid of initializer_list. Then I stumbled upon a great article which introduced me to the abbreviated function template feature of C++20, which I’m using.

This is an example:

int foo(std::wstring n)
{
	OutputDebugStringW(n.data());
	OutputDebugStringW(L"\n");
	return 43;
}

int foo(std::wstring n, std::convertible_to<std::wstring> auto... nn)
{
	foo(n);
	return foo(nn...);
}

The convertible_to concept is particularly interesting in narrowing the template type.

Sunday, June 9, 2024

Fixing auth error with Cargo dependencies from GitHub

When trying to use a Cargo dependency straight from GitHub, an error was popping in the console:

failed to authenticate when downloading repository
* attempted ssh-agent authentication, but no usernames succeeded: `git`

The solution was to create a ~/.cargo/config.toml file, and then paste:

[net]
git-fetch-with-cli = true

Wednesday, April 24, 2024

Goodbye Go

On this morning, I just completely wiped away the Go compiler from my computer after finishing the Rust rewritten of my very useful ID3v2 editor, then written in Go. Now I have no tools written in Go anymore.

I consider I spent way too much time banging my head against the wall writing Windigo, trying to write a decent API. Go is way too limited, its lack of expressiveness won’t allow you to write any decent API more complex than a simple CRUD. And Win32 is hard. Windigo reached a point where there was nothing else I could do to make the API better – it was so bad it actually pushed me back to C++, where all my uneasiness started.

Another upsetting fact I noticed is that Go programmers tend to be unexperienced people. Beginners with trivial questions. Very demotivating.

I think anyone talking to me at this point would label me a “Go hater”. And as such, I can no more invest my scarce time in it. I remember in the early Windigo days I was completely in love with Go, with its first lines being written in my then girlfriend’s laptop. Yes, I installed the compiler there, and I don’t remember if it’s still there.

I plan to leave Windigo repo, and all its open issues, abandoned for a while. Maybe someone forks it and keeps it going. In the future, I will archive it, and I hope I never have to write Go ever again.

And that’s a shame, because “Windigo” is one of the coolest project names I ever came up with.

Tuesday, February 27, 2024

Linux recursive file searching

Searching recursively all files with the given extension:

find . -name "*.orig"

Searching all files containing the given text (source):

grep -Rn . -e "palavra"

Huge life saver.

Monday, January 15, 2024

Zustand, devtools and immer wrapper, pt. 2

On further researching of my previous idea of creating a Zustand + Immer wrapper, I found a very interesting technique straight from Zustand docs, where all the methods are simply free functions declared outside the store, called no store actions. I was already using this to declare private store methods, but it never occurred me to use it for all methods.

Since it officially has no downsides, I applied this idea to append the actions directly to the hook itself, so we don’t need to import each free function. The problem is that the hook has a couple methods itself, plus the inherent ones to any function object. So the result is very polluted. So I decided to adopt a convention of prefixing every action with $.

But conventions are easy to break. So I implemented a reduce to prefix each method name with #. But then TypeScript could not infer the type anymore, so I had to dig and type it manually with key remapping, and finally I got this implementation:

import {create, StateCreator} from 'zustand';
import {devtools} from 'zustand/middleware';
import {immer} from 'zustand/middleware/immer';
	
export function createStore<T extends object, U extends object>(
	initialState: T,
	actions: StateCreator<
		T,
		[['zustand/devtools', never], ['zustand/immer', never]],
		[['zustand/immer', never], ['zustand/devtools', never]],
		U
	>,
) {
	const store = create(
		devtools(
			immer(() => initialState),
			{name: Math.random().toString(36).slice(2), serialize: true},
		),
	);

	type WithPrefix<T> = {
		[K in keyof T as K extends string ? `$${K}` : never]: T[K];
	};
	const actionsOrig = actions(store.setState, store.getState, store);
	const actionsPrefixed = Object.entries(actionsOrig).reduce(
		(accum, [k, v]) => ({...accum, ['$' + k]: v}),
		{} as WithPrefix<typeof actionsOrig>,
	);

	return Object.assign(store, actionsPrefixed);
}

This creator allows us to call actions directly, without the hook. They’ll be automatically prefixed with $:

const useFoo = createStore('Foo', {
	name: '',
}, (set, get) => ({
	setName(name: string): void {
		set(state => {
			state.name = name;
		});
	},
}));
	
function App() {
	const name = useFoo(s => s.name);

	return <div>
		<span>{name}</span>
		<button onClick={() => useFoo.$setName('Joe')}</button>
			Change
		</button>
	</div>;
}

That $ prefix gives me bad Vue vibes. Let’s see if I can get along with it, because I was able to develop a DX which is exactly what I was looking for.

Tuesday, January 9, 2024

Increase taskbar button width in Linux Mint 21

For my working VM, after ditching the sluggish Ubuntu 22 for the crazy good Mint Cinnamon 21, one of the customizations I wanted to make was the maximum width of the taskbar buttons. They seemed to narrow, while a lot of room was available.

After a couple minutes of searching, I found a direction in the Mint forums. The proposed solution is for Mint 20 – for Mint 21, it has a minor difference.

Edit, as root, the following file:

/usr/share/cinnamon/applets/grouped-window-list@cinnamon.org/constants.js

The value we’re after is MAX_BUTTON_WIDTH, which I increased from 150 to 210.

What caught my attention, though, is how all the layout building stuff is JavaScript.

Saturday, January 6, 2024

Installing dependencies of deb packages

A deb package can be installed on Linux by running:

sudo dpkg -i Package.deb

However, the installation may fail because dependencies are missing. In such cases, I found a tip which appears to work very well. After the failed dpkg command, run:

sudo apt-get -f install

This a shorthand for --fix-broken, and installs the dependencies and completes the aborted dpkg installation. Like magic.

Wednesday, November 29, 2023

Setting/resetting Vue reactive objects

Vue 3 has many idiosyncrasies, among them the overlapping ref and reactive constructs. One of the main differences is that reactive content cannot be replaced. One StackOverflow answer proposes using Object.assign, but it will replace all nested references, losing them all.

I ended up writing my own version of a recursive function to replace each value inside an object, so I won’t need to deal with the muddy details ever again:

/**
 * Recursively copies each field from src to dest, avoiding the loss of
 * reactivity. Used to copy values from an ordinary object to a reactive object.
 */
export function deepAssign<T extends object>(destObj: T, srcObj: T): void {
	const dest = destObj;
	const src = toRaw(srcObj);
	if (src instanceof Date) {
		throw new Error('[deepAssign] Dates must be copied manually.');
	} else if (Array.isArray(src)) {
		for (let i = 0; i < src.length; ++i) {
			if (src[i] === null) {
				(dest as any)[i] = null;
			} else if (src[i] instanceof Date) {
				(dest as any)[i] = new Date(src[i].getTime());
			} else if (Array.isArray(src[i])
					|| typeof src[i] === 'object') {
				deepAssign((dest as any)[i], src[i]);
			} else {
				(dest as any)[i] = toRaw(src[i]);
			}
		}
	} else if (typeof src === 'object') {
		for (const k in src) {
			if (src[k] === null) {
				(dest as any)[k] = null;
			} else if (src[k] instanceof Date) {
				(dest[k] as any) = new Date((src[k] as any).getTime());
			} else if (Array.isArray(src[k])
					|| typeof src[k] === 'object') {
				deepAssign(dest[k] as any, src[k] as any);
			} else {
				(dest[k] as any) = toRaw(src[k]);
			}
		}
	} else {
		throw new Error('[deepAssign] Unknown type: ' + (typeof src));
	}
}

Another problem with reactive is that, to avoid two variables pointing to the same point, we must deep clone an object when creating the object. I also wrote a function to deal with that:

/**
 * Deeply clones an object, eliminating common references. Used to create a
 * reactive object by copying from an ordinary object.
 */
export function deepClone<T>(origObj: T): T {
	const obj = toRaw(origObj);
	if (obj === undefined
			|| obj === null
			|| typeof obj === 'string'
			|| typeof obj === 'number'
			|| typeof obj === 'boolean') {
		return obj;
	} else if (Array.isArray(obj)) {
		return obj.reduce((acum, item) => [...acum, deepClone(item)], []);
	} else if (obj instanceof Date) {
		return new Date(obj.getTime()) as unknown as T;
	} else if (typeof obj === 'object') {
		return Object.entries(obj).reduce(
			(acum, [key, val]) => ({...acum, [key]: deepClone(val)}), {}) as T;
	} else {
		throw new Error('[deepClone] Tipo desconhecido: ' + (typeof obj));
	}
}

I remember from my MobX days some people saying that working with reactive variables can be tricky. Now I can clearly see why.

Monday, November 13, 2023

TypeScript function to set value by key

While researching React stores automation, I wondered whether it would be possible to have a TypeScript function to set the value of an object by the name of its property, with correctly typed parameters, of course.

Turns out, it is

export function setField<
	T extends object,
	K extends keyof T,
>(o: T, k: K, v: T[K]): void {
	o[k] = v;
}

The function above works perfectly with interfaces:

interface Person {
	name: string;
	age: number;
}

const p: Person = { name: 'Joe', age: 42 };
setField(p, 'name', 'foo');