SiriusXM on Squeezebox Boom
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
This week I've been playing around with an old Squeezebox
Boom, and I've been reading through a
forum thread where - within the last few weeks -
people have been finding ways of playing SiriusXM channels on it.
This week I've been playing around with an old Squeezebox
Boom, and I've been reading through a
forum thread where - within the last few weeks -
people have been finding ways of playing SiriusXM channels on it.
There are a couple other areas of the Pandacap application that
are probably worth drawing attention to.
If I'm thinking about how often I use it, the ActivityPub
integration in Pandacap really isn't the most relevant part of
the application to me on a regular basis (that would be the inbox,
and its integration with DeviantArt and Bluesky). It was, however,
my inspiration for building Pandacap in the first place. There
were a few reasons I felt this was important:
Although it's not public-facing, the Inbox is perhaps the most
useful part of the Pandacap web app. As a descendant of Artwork Inbox
(Pandacap is built on EF Core + Cosmos DB in a very similar
manner), the Pandacap inbox pulls in new posts from ActivityPub,
Bluesky, DeviantArt, RSS/Atom, and Weasyl, and allows the
logged-in user (me) to view and dismiss them, kind of like an
email inbox.
Posts from users and feeds you follow are split between four
different inboxes:
The user experience here is heavily inspired by the Fur Affinity
and Weasyl inboxes: posts are shown roughly in chronological
order; image posts have thumbnails and text posts only have a
title; you have to click through to see the description / body of
the post; checkboxes are used to remove posts from your inbox; and
a "next page" button is used instead of a dynamic loading of new
content.
From a public-facing perspective, Pandacap is essentially just a
single person's art gallery (and blog, microblog, and profile, I
suppose). One of Pandacap's philosophies is that fundamentally
different kinds of content are separated, so there are three types
of public posts:
These are, not coincidentally, three of the four DeviantArt post types. The paradigm of Pandacap - the context in which it assumes you're creating and uploading your posts - is heavily based on art sharing platforms like it, some of which predate the rise of general-purpose microblogging.
( Read more... )Since Pandacap is a single-user application, I really didn't want
to write my own authentication and authorization system. The only
goal was to allow myself to log in, and no one else. So instead of
using an email/password combo, like the default Identity template,
I've limited it to just this:
This has been my hobby project for the better part of the past
year, and it's something I've been wanting to make a series of
blog posts about for a while. Pandacap is my personal Swiss Army
knife web app; it hosts my art gallery and microblog and collects
incoming posts and notifications across five different sites and
protocols.
The code for
Pandacap is open-source (AGPL v3). I don't imagine it will
be that useful to many people; trying to ask a non-Microsoft-stack
developer to make tweaks to it would be like asking me to
contribute to, well, anything in Python. (Plus, the code itself is
not very robust, and not at all scalable.) But I've made
some very deliberate decisions in the UI of this app, with an eye
towards my own psychological well-being. The context collapse of
traditional social media kept me away from it for years, and this
app (and one of its predecessors, Artwork Inbox)
is the reason I can follow artists on Bluesky and Mastodon without
giving up in frustration. I'm hoping that someday, these design
principles could be useful to other people who find themselves in
the same situation.
I recently expanded my triple-boot PC to boot four
operating systems off the same drive: DOS, Windows XP, Windows 7,
and Debian.
Generally, in the old BIOS world, you'd handle this by having
GRUB (controlled and configured by the Linux installation) as the
main bootloader, and let it boot Windows and DOS from there. But I
like to take a more unconventional method (detailed here)
where I have the newest installed version of Windows control the
main bootloader, and let Linux boot from DOS.
There were three big changes I made this time.
First, I kept Windows XP and Windows 7 both installed to separate
extended partitions. I believe they both install their bootloaders
to the DOS partition - I know Windows 7, by default, won't map it
to a drive letter because it assumes it's just a system partition
(you can change this from Disk Management).
To limit the number of consecutive menus, after installing
Windows 7, I removed FreeDOS from the boot.ini used by Windows
XP's bootloader (ntldr) and added it to Windows 7's (bootmgr). The
bootsect.dos (extracted and created by Windows XP's installer) can
be loaded through bootmgr by adding a
new entry:
bcdedit /create /d "FreeDOS 1.3" /application bootsector
bcdedit /set {new-guid} device partition=e:
bcdedit /set {new-guid} path \bootsect.dos
bcdedit /displayorder {new-guid} /addlast
Options like /displayorder or /default can be
used to customize its spot in the menu.
But most importantly, I still wanted Debian to boot from DOS so it wouldn't touch the MBR (making it easy to delete or replace from within DOS/Windows without breaking anything). But instead of installing grub-legacy in Debian to generate the menu.lst and using GRUB4DOS to boot it (which I'm sure would have worked fine), I wanted to use a method of booting Debian that would rely only on the partition GUID, and not on the drive number.
Being (loosely) based on the original Visual Basic, it's not surprising that Visual Basic .NET has features specifically targeted towards Windows Forms development. When I want to make a quick GUI app to run on my PC, I often find it easiest to build the main code in a C# or F# library, and to build a thin frontend layer in VB.NET, for two reasons: the incredibly aggressive (in a good way) auto-formatting that keeps me from being distracted by code style, and the nice set of quality-of-life helpers the language gives you for this exact use case.
( Read more... )When converting recorded ATSC 1.0 (over-the-air) broadcasts to video DVD format using devede, I've noticed the audio/video sync ends up a little off.
Re-encoding the audio seems to fix this:
ffmpeg -i input.mts -c:v copy -c:a aac -ac 2 output.mkv
This keeps the video data intact, and just re-encodes the audio (as stereo, not the original surround sound).
These sorts of broadcasts usually have an alternate audio track as well - you could use some -map arguments to pick the track(s) you want, but I didn't bother with that this time around.
After doing this, I can put the resulting file into devede :)
If that doesn't work, here's a script that will convert the closed captions to subtitles and re-encode the video. Sometimes this can be necessary - an over-the-air data stream is sometimes missing some data that automated scripts can run into problems with.
#!/bin/sh set -e for i in $*; do o="$(basename $i)" m="$o.mp4" if ! [ -f "$m" ];then chmod +r "$i" ffmpeg -i "$i" -c:a copy -c:v copy "$o" ccextractor "$o" -in=ts -out=srt ffmpeg -i "$o" -c:a aac -ac 2 -c:v libx264 -preset veryfast "$m" rm "$o" fi done
Sometimes I want to listen to a podcast on a TV, and sometimes
the easiest way to do that is by burning it to a CD (either a
CD-ROM with MP3 files, or - if it fits - a normal audio CD). Not
every CD player has the greatest seeking features, though. I've
got one whose fast-forward is more of a normal-speed-forward, and
another that actually goes forward when rewinding an MP3
at the slowest level.
I figured one way of working around these issues - which could
come into play for podcast episodes that are, like, an hour long -
would be to split the podcast into segments of five minutes each.
You'd need to make a normal audio CD (which means a limit of 75 or
80 minutes or so), but you could have gapless playback, while also
being able to use track selection to go forward and back in
chunks.
Yesterday I put together a small Windows application to help with
this. It's called Cue
Sheet Generator, and it takes in one or more audio files and
converts them to either a set of .wav files (to burn with Windows
Media Player Legacy or another app with gapless burning support)
or a .wav/.cue pair (which ImgBurn and other such apps can
handle).
The main program logic is small enough to fit into this post. I
wrote it in VB.NET (there's nothing here C# couldn't do, I'm just
tired of looking at curly brackets), and I thought it might be
helpful to annotate it.
Recently I bought a travel hub for
my laptop (up 'til now, I'd been using my work laptop's
Thunderbolt dock, but I thought it would be nice to have something
I could bring with me.) It has a USB-C plug and a small collection
of ports:
These sorts of hubs have become widespread with the rise of
laptop computers (and tablets/smartphones) with nothing but USB-C
ports. USB-C is still seen as something of a new, shiny technology
(after all, it didn't even exist ten years ago!) So out of
curiosity, and a sense of playfulness, I decided to try plugging
the hub into my Dell Dimension 4700, a computer designed almost twenty
years ago.
The HDMI port didn't do anything, of course, but everything else worked almost perfectly.
In 2004, it was the early days of PCI Express,
and the Dimension 4700 came with two PCI Express slots: a long
slot for the graphics card, and a spare one-lane slot. And since
PCI Express is, of course, still a thing, it's not hard to get
your hands on a PCIe card that gives you a couple of USB 3
slots... and then add a front panel with USB-A and USB-C
ports. (After all, USB-C at its core - if you're not using any
alternate modes - is still USB, maybe with a bit of extra
circuitry for detecting plug rotation.) My PCIe card uses a VL805
chip and provides a header for the front panel to connect to,
along with a power input - otherwise, the 4700 doesn't give enough
power over PCIe to run the card, the front panel, and a
spinning disk hard drive. (I bought this card after learning that
- but the old card found a home in a relative's PC, to add a
couple extra ports to the back.)
The USB-C connector normally provides pins for a USB 2.0 link and
two USB 3.x links, but if DisplayPort
Alternate Mode is used, one or both of the USB 3.x links can
be repurposed for a DisplayPort connection. This explains why you
see a similar port selection on so many of these hubs, and why you
can't just plug another HDMI or DisplayPort adapter into the USB-C
data port on them. I imagine the only real logic the hub has to
handle is power delivery (whether from an external source or from
the host device) - everything else can be built from just a hub
(using the USB 3 link and the USB 2 link), a card reader (attached
over USB), and a DisplayPort to HDMI adapter, all of which are
commonplace.
Thunderbolt docks work differently. There's a lot more active
circuitry in those - it lets you have a higher bandwidth
connection (and the flexibility to daisy-chain another Thunderbolt
or USB-C device), but it's really working at a higher / more
abstract level in order to make that happen. Not surprising that
the Thunderbolt dock does nothing when plugged into the 4700!
When working in .NET, it can be awkward to figure out whether two
objects are "equal". With reference types, two variables might
refer to the same object on the heap, or to two different objects
with identical properties. In the picture below, changing Snail
C's shirt would also change Snail B's, because they are the same
snail, while the otherwise identical Snail A is unchanged.
Value types are more like C structs. So giving Snail C a jacket
and hat does not affect Snail B. It wouldn't make sense to do a
"reference comparison" on a value type; the objects are always
stored in different places (setting aside explicit pointers to
objects in memory, of course - something C# also allows for).
Immutable types with built-in structural equality checks are a great way to work around this issue, but there's still the question of what to do for collection types - the standard .NET HashSet, List and Array types aren't immutable, and even ImmutableList and ImmutableSet don't consider two lists or two sets with the same items to be "equal". But there's a very easy way to handle this problem: you can use the collection types in the F# standard library (FSharp.Core), even without using F#.
( Read more... )Working in .NET, I've been in a situation where I need to implement a function in an interface that's supposed to return an IEnumerable<T>. (In some cases, I'd argue that IReadOnlyList<T> might make more sense - this way, the caller knows it's not going to be a lazily-evaluated sequence, and you can still return a .NET list, F# list, or array - but I digress).
Maybe the most obvious way to do this is by calling a function that explicitly returns an empty enumerable for you:
IEnumerable<string> SampleInterface.getAll() => Enumerable.Empty<string>();
In F#, it would be even shorter, because of the aggressive type resolution:
interface SampleInterface with member _.getAll() = Seq.empty
There are other clever ways to do the same thing, though, and it might just depend on what you think is the clearest or most readable - which might just mean keeping it consistent with the code around it. First, you can always expand out the function (because clearer isn't always shorter - I think it really does depend on the context of what's around it):
IEnumerable<string> SampleInterface.getAll() { return Enumerable.Empty<string>(); }
But there's also something clever you can do here, if you want to think of your code in a different way - where instead of resulting in "an empty list", it results in "no elements". C# lets you build iterator functions, where your code defines an IEnumerable<T> (and runs every time the resulting object is enumerated). Any function with a yield return
or a yield break
is treated in this way by the compiler. This means you can implement a function that returns "no elements" just by doing this:
IEnumerable<string> SampleInterface.getAll() { yield break; }
It's a bit different in VB.NET, where iterator functions are denoted explicitly - so the yield break isn't needed:
Public Iterator Function getAll() As IEnumerable(Of String) Implements SampleInterface.getAll End Function
That is, in a very literal sense, a function that returns no elements!
Funny thing is that there's no real equivalent to an empty iterator function in F# (not that you'd need it); the compiler won't allow a seq { }
workflow without any elements in it, and suggests you use Seq.empty
or the empty list []
instead.
The Game Boy Advance (GBA) provided backwards compatibility with Game Boy and Game Boy Color games by including a second CPU (and a clever voltage switch inside the cartridge slot), but the GBA was easily been powerful enough to emulate the GBC in software. I don't think any commercial releases ever did anything like this, but on the homebrew front, the Goomba family of emulators proved plenty useful for people with unofficial GBA flashcards (as the system can't simply switch into GBC mode in software, or anything like that), and later came in handy on the Micro and the DS.
Goomba runs on the GBA as its host platform, which means anything it needs to save, it needs to save in its own SRAM. It's designed to work with 32 KiB of save memory - within which it has to store the emulated games' SRAM and savestates (Goomba can have more than one Game Boy ROM appended to its own ROM), as well as its configuration settings. Here's a thread that explains how the saving system works. Goomba's save data is stored sequentially (with headers) starting at the beginning of SRAM; each chunk of data is either emulator config, a Game Boy savestate, or Game Boy SRAM (the latter two compressed with LZO). Meanwhile, the area at the end of GBA SRAM (56 KiB - 64 KiB) is reserved for an uncompressed working copy of the current GBC game's SRAM, if any (although I don't think Goomba Color uses this).
The goombasav code I put together is designed to extract and replace the GBC SRAM stored inside the GBA SRAM - it's for use on platforms other than the GBA, so you can transfer the save data to other emulators.
// UTF-32 is a Unicode encoding that uses exactly four bytes for each
// codepoint. Unlike UTF-8 (used widely on the Web) or UTF-16 (used in Java
// and .NET), each codepoint / character takes up the same number of bytes,
// making it much easier to do string processing (counting, substrings) based
// on character count.
// One common application is applying styling or custom behavior to Twitter
// posts based on the data provided by the API:
// https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities
( Read more... )I stopped by Free Geek Twin
Cities recently and picked up (among other things) a
Blu-ray player. It's the first time I've had one, and I was
pleasantly surprised that it was perfectly happy to play
standard audio and video files right off whatever media you put
in. It's incredibly useful with the USB port on the front, but
it can also read files off a CD or DVD if you happen to have one
(not that uncommon; I've certainly put podcasts on a CD-RW
before - a lot of car radios will play them!)
I also have a DVR that records shows from U.S. over-the-air
digital TV (ATSC 1.0) to a USB device, and the Blu-ray player
will play these raw transport stream files perfectly as well.
This gave me an idea: what if you wanted to permanently save
something you recorded off the antenna, in its original quality,
onto permanent physical media?
Of course, this is a very specific use case, one that I don't
even have a need for - it's definitely another case of me putting the
cart before the snail, if you will. But the fun part is
solving for a specific situation, and making something really
unique and cool in the process. Here were my requirements:
Obviously, it's pretty unusual to dedicate most of the space of
a video DVD to data that isn't actually part of the DVD video
content.
But the important thing here is: your hard copy has
the original media (not re-encoded in any way), in case you want
to transfer it to another format; and it can be played on
a standard consumer device still found in many households, as long
as you're only half paying attention. Because although video data
takes up most of the bandwidth of any recording, it's almost
always the audio that's conveying the most important information,
so that's what you want to focus on.
Artwork Inbox is a web app I developed for my own use a few
years ago. The idea behind Artwork Inbox is to let the user
(a.k.a. me) view a social media feed in a way that separates
the presentation of visual art (such as drawings or
photographs), from other types of posts (like journal or
status updates). Instead of simply sorting all posts by date
and time, the app splits the feed into pages of 200 items
each, and groups those items - first by type (visual or text)
and then by author/artist. The current version is built on
ASP.NET Core 7, with EF Core, Identity, and Cosmos DB.
I started working on Looping
Audio Converter back in 2015. Looping Audio Converter is
designed to handle music files with seamless loops, and maintain
those loops when converting from one format to another - usually
when extracting music from one video game, with the intention of
using it in another (this is why the default output format is the
Wii's .brstm format and ADPCM codec). It's always been a little
bit of a kludge, built from pieces that were floating around
elsewhere: almost all input and output formats the program
supports are handled by calling out to either a .NET library or a
Windows executable to convert the input to 16-bit PCM, then again
to convert that PCM data to the output format.
One important component of Looping Audio Converter is the
included FFmpeg
binary. FFmpeg is used to encode and decode certain formats
(including FLAC, Ogg Vorbis, and AAC), but it's also used for most
"effects" (like sample rate conversion and tempo and volume
adjustment).
Let's say you want to embed some videos on your website, and you
want to put them in a list so people can click through and watch
them.
The title, description, and thumbnail you give to the video are
largely subjective decisions, but the duration - in minutes and
seconds - is an objective property of the media itself, which
means you should be able to extract it if you know the video's
URL. But how exactly do you do that?
Well, the first thing you'll need to do is figure out what
exactly you're dealing with: a raw video file (or stream) that
plays in the browser's <video> tag, or a link to a page
on a site like YouTube or Vimeo that hosts embeddable content.
Technologically speaking, it's an entirely different beast. A
YouTube page gives you the code for a player, and wraps all of it
up with copy protection and a variety of other features specific
to the platform. In other words, it's not handing the end user
something to play; it's playing it for them. It's kind of like the difference
between having a record of a song, and having a band come over with their
own instruments to play it on.
That doesn't mean they can't serve the same purpose
for the end user of your site, though, and in both cases it should
be possible to programmatically determine the duration of the
media. I've written a .NET library (ISchemm.DurationFinder)
that handles this for you for a variety of common video types with
just a URL; I'll walk through how it works overall, and how it
finds the duration for each type of media that it supports.