Search This Blog

Thursday 3 October 2024

Audio over video

Or: How I learned to stop worrying and love the Fast-Fourier-Transform

 

This is the demo video of the Audio-over-STFT system. The Git repository can be accessed here. I'm currently working on a re-write in rust, until then the system is fairly limited.

At some point I'll put on a pilot exhibition to demo the tool an art installation.

Upon a real "version 1", I'll do a proper blog post describing the maths at work here, although the demo video should give a good idea of this...

Saturday 10 February 2024

Aggregated a lot of files on a sunday

Route One - automation for bulletins

Pretty endpoint page (minimalism)

After discovering the ease of the command line YouTube downloaders, i set out to create a very quick aggregator for football content which would automatically get audio files ready for Monday mornings Route One Podcast.

Note: this project is in no way endorsed or used by anyone other than me.

First we get the channel file, listing the channels we want to draw from:

with open("channels.json", "r") as f: channels = json.loads(f.read())

Then, for each channel, we download the metadata of the last 80 videos:
for channel in channels:

id = channel["url"]
os.system(f"./yt-dlp -q --progress --skip-download --write-info-json -I 0:80:1 {id} -o 'data/%(id)s'")

Then we gather the data, sorting it and trimming off any irrelevant videos. In this case we measure "relevance" by the metric of comments on the video.

data = []
for f in [file for file in os.listdir("data") if os.path.isfile(f"data/{file}")]:
    with open(f"data/{f}", "r") as raw:
        file = json.loads(raw.read())
    #os.remove(f"data/{f}")
    if "duration" in file.keys():
        video = {
            "id":file["id"],
            "title":clean(file["title"]),
            "description":clean(file["description"]),
            "uploader":file["uploader"],
            "views":file["view_count"],
            "likes":file["like_count"],
            "comments":file["comment_count"] if "comment_count" in file.keys() else 1000
        }
        data.append(video)
# sort and trim the list of videos
data.sort(key=lambda x: x["comments"]) # sets the variable we sort by
data = data[-10:]
# delete the previously gatered audio files
for f in [file for file in os.listdir("audio") if os.path.isfile(f"audio/{file}")]:
    os.remove(f"audio/{f}")

We then download the chosen files and update the website where the audio files are available for myself.

for video in data:
    os.system(f"./yt-dlp -x --audio-format mp3 {video['id']} -o 'audio/%(id)s'")
#write the index file
with open("index.html", "w") as f:
    # get the template file
    with open("template.html", "r") as t:
        template = t.read()
    f.write(template)
    for video in data[::-1]:
        path = f"audio/{video['id']}.mp3"
        f.write(f"""
            <div class="container">
                <audio controls src='{path}'></audio>
                <p class="title"><b>Title:</b> {video['title']}<br><em>@ {video['uploader']}</em></p>
                <p><b>Views:</b> {video['views']}</p>
                <p><b>Likes:</b> {video['likes']}</p>
                <p><b>Comments:</b> {video['comments']}</p>
            </div>
        """)
    f.write("</body></html>")