Code Project: VLC Portable Playlist to Text Dump

It’s kind of funny how one post can lead to another sometimes.  This one is pretty basic but it also just shows a bit how useful I find knowing my way around computer systems to be.  Yesterday I posted about my little annual music playlists.  And as part of that, I wanted to actually post the playlist. I am pretty sure there is a fairly universal “playlist file type” out there and being open source, I had assumed that VLC on my phone stored the playlists somewhere in playlist files.

That assumption was wrong, it uses a .db file.  A little portable database.  There is an option to dump this file to the root of the phone, presumably for backup purposes, but it’s also useful to just browse it like I am doing here.  The file itself can be opened and browsed with SQL Lite’s DB manager.  It’s standard databases inside for tracks and artists and playlists.

Fortunately, I have had some experience dealing with database queries, so I set about building what was needed tog et the data I wanted.  Pull the Playlist I want, in this case “2023 Best” but I could change that to do any available Playlist.  This gives the tracks by id, but the tracks themselves are stored in a separate table for media.  So that needs joined in.  The media table stores track names, but not artist names, so an additional join is needed to get the artist names.  This complicated things a bit because both the playlist table and artist table have a column “name” so more clarity needed to be added.

The result was this little query that dumps out a basic table of Artist and Song title.

SELECT Artist.name, Media.title 
FROM Playlist
Inner Join playlistmediarelation ON playlist_id=id_playlist
Inner Join Media ON id_media=media_id
Inner Join Artist ON media.artist_id=Artist.id_artist
WHERE Playlist.name = '2023 Best'
ORDER BY Artist.name

Now, I could have done some cute clever trick now to merge the two into a new column and add in a ” – ” between but it was easier to drop it all into a notepad file and do a fine/replace on the weird space character that it stick in between the Artist and track title.

The added bonus here is I can easily use this query again anytime I want to dump a Playlist to text.

Code Project: Automated List From Reddit Comments

This is one of those quick and kind of dirty projects I’ve been meaning to do for a while. Basically, I wanted a script that would scrape all of the top level comments from a Reddit post and push them out to a list. Most commonly, to use on /r/AskReddit style threads like, well, for this example, “What is a song from the 90s that young people should listen to.”

Basically, threads that ask for useful opinions on list. Sometimes it’s lists of websites or something. Often it’s music. The script here is made for music but could be adjusted for any thread. Here is the script, I’ll touch on it a bit in more detail after.

## Create an APP for Secrets here:
## https://www.reddit.com/prefs/apps

import praw

## Thread to scrape goes here, replace the one below
url = "https://www.reddit.com/r/Music/comments/10c4ki0/name_one_90s_song_kids_born_after_2000_should_add/"

## Fill in API Information here
reddit = praw.Reddit(
    client_id="",
    client_secret= "",
    user_agent= "script by u/", # Your Username, not really required though
    redirect_uri= "http://localhost:8080",
)


submission = reddit.submission(url=url)
submission.comments.replace_more(limit=0)
submission.comment_limit = 1

for x in submission.comments:
    with open("output.txt", mode="a", encoding="UTF-8") as file:
        if "-" in x.body:
            file.write(str(x.body)+"\n")
            # print(x.body)

The script uses praw, Python Reddit API Wrapper. A Library made for use in Python and the Reddit API. It requires free keys which can be gotten here: https://www.reddit.com/prefs/apps. Just create an app, the Client ID is a jumble of letters under the name, the secret is labeled. User Agent can be whatever really, but it’s meant to be informative.

The thread URL also needs filled in.

The script then pulls the thread data and pulls the top level comments.

I’m interested in text file lists mostly, though for the sake of music based lists, if I used Spotify, I might combine it with the Spotify Playlist maker from my 100 Days of Python course. Like I said before though, this script is made for pulling music suggestions, with this but of code:

        if "-" in x.body:
            file.write(str(x.body)+"\n")
            # print(x.body)

It’s simple, but if the comment contains a dash, as in “Taylor Swift – Shake it Off” or “ACDC – Back in Black”, it writes it to the file. Otherwise it discards it. There is a chance it means discarding some submissions, but this isn’t precision work so I’m OK with that to filter out the chaff. If I were looking for URLs or something, I might look for “http” in the comment. I could also eliminate the “if” statement and just have it write all the comments to a file.

Advent of Code 2022, I’m Done

Well, I made it farther than my last “in real time attempt” in 2020 by 3 starts. I may check in one the puzzles each day, but my experience is, they only get more complex as time goes on, so I doubt I’ll be completing any more of them. Each day is starting to take a lot more time to solve out, the solutions are getting a lot more finicky to produce. We’ve also reached the point where the puzzle inputs also feel ridiculously obtuse. Like the Day 15 puzzle, where every number was in the millions, basically, for the only purpose of making everything slow without some sort of magic reduction math. Though skimming through other’s solutions, there didn’t seem to really BE any “magic reduction” option there. \

Which is fine. It’s not supposed to be easy. I don’t expect it to be easy.

But I have long ago accepted that things I’m doing for relaxation or enjoyment, should at least be relaxing and enjoyable. And These puzzles have reached a point where the amount of enjoyment and relaxation I get from them is no longer worthwhile.

So I’m choosing to end this year’s journey here.

Maybe I’ll go back and finish them some day, but more at my own leisure. I mean, I had started doing the old 2015 puzzles in the week leading up to this year’s event. I was never doing this in any attempt to get on the leader boards or anything anyway, hell I didn’t even start most day’s puzzles until the day was half over or later.

For what it’s worth, i did make a strong attempt on Day 15 but I just could not get it to output the correct answer, and I’m not real sure why. I couldn’t even get the sample input to work out, I was always one off. It’s possible, and likely, I was counting the space where the beacon existed, but my actual input data was off by a little over 1 million, and there are not 1 million beacons on the board. Plus it was 1 million under, where my sample input solution was 1 over.

I’m not even attempting today’s, for Day 16. I can see the logic needed, but the nuance to accomplish it will just take me too long to code out and like I said above, enjoyment and relaxation is the point. I don’t need to add hours of stress to my day.

Advent of Code 2022, Day 14

Man, I really enjoyed today’s puzzle. Like, a lot. I think because it kind of felt like a game level, and probably also because it’s fluid dynamics and I am totally into Physics and Engineering shit.

For the “Plot” you enter into a cave and discover a cavern with sand falling from the ceiling. The sand accumulates in a pile and “flows” around based on some simple left then right rules. This problem consisted of a few separate but connected steps.

Step one, create an empty “cave”. This was simple enough, especially now that I remember how stupid lists are. Last time I needed to make a grid, I was appending a list and it turns out that Python doesn’t actually copy lists unless you explicitly ask it to. Which is frankly, “Fucking Stupid”. But whatever, list.copy() works too.

Step 2, draw the rocks from the input file. Each line consists of a start note, then a series of connected dots to the end point of a line of rocks.

Step 3, was to pour the sand. Which involves dropping a “chunk” of sand, down until it hits the floor, then flowing it left or right to fill an area. Once the sand starts falling off, then display the count of the total chunks. If I were more clever about my code, I could build a sweet little ASCII animation of each step, but I probably won’t anytime soon because well, I have other things I need to do too.

Part 2 modifies this, by adding a floor, instead of counting the amount of sand until it fills, and falls into the abyss below, now you count until it fills then fills all the way back to the top. This actually screwed me up a bit.

The coordinates given are all large, like, in the 500 range. In order to make my rock formation manageable, I had cut these down by the min max values so the cave was not much wider than the rock formation. The problem is, now I need to accumulate a pile across the floor, so I need the width. Like, a LOT of width. So I had to modify my code all over to bring the width back to my cave matrix.

The code works for Part 1 and Part 2 at once. Basically, it finishes Part 1, like normal, display the output count, and, just for fun, an ASCII image of the filled rocks, then, it just, starts a fresh, slightly modified loop. For the modified loop, the break for “falling off” is removed. Instead, it checks to see if it can move, and if it can’t, before placing the sand block, it verifies if it moved at all by comparing it’s position to the start position. If it hasn’t moved, it breaks the loop, prints the filled screen, and the sand count total.

import math

with open("Day14Input.txt") as file:
    data = file.read()

lines = data.split('\n')

def draw_cave(lx, ly):
    grid = []
    line = []
    floor = []
    for i in range(0,lx*2):
        line.append(".")
    for j in range(0,ly+2):
        grid.append(line.copy())
    for i in range(0, lx * 2):
        floor.append("#")
    grid.append(floor)
    return grid

def draw_rocks(rocks,cave):
    for rockline in rocks:
        for i in range(len(rockline)-1):
            startx = int(rockline[i][0])
            starty = int(rockline[i][1])
            endx = int(rockline[i+1][0])
            endy = int(rockline[i+1][1])
            if starty == endy:
                xrange = sorted([startx, endx])
                for horiz in range(xrange[0],xrange[1]+1):
                    cave[starty][horiz] = "#"
            if startx == endx:
                yrange = sorted([starty, endy])
                for vert in range(yrange[0], yrange[1]+1):
                    cave[vert][startx] = "#"
    return cave

def show_cave():
    for i in cave:
        print(" ".join(i))

smallest_x = 100000
smallest_y = 100000
largest_x = -1
largest_y = -1
rocks = []
for line in lines:
    sets = line.split(" -> ")
    r = []
    for n in sets:
        nsplit = n.split(",")
        if int(nsplit[0]) < smallest_x:
            smallest_x = int(nsplit[0])
        if int(nsplit[1]) < smallest_y:
            smallest_y = int(nsplit[1])
        if int(nsplit[0]) > largest_x:
            largest_x = int(nsplit[0])
        if int(nsplit[1]) > largest_y:
            largest_y = int(nsplit[1])
        r.append(n.split(","))
    rocks.append(r)

# print(f"{smallest_x} {largest_x} | {smallest_y} {largest_y}")
# print(rocks)

cave = draw_cave(largest_x,largest_y)
# show_cave()
rocky_cave = draw_rocks(rocks,cave)

sand_start = 500
rocky_cave[0][sand_start] = "+"

captured = True
sand_count = 0
while captured:
    sand_pos = [0,sand_start]

    sand_drop = True
    while sand_drop:
        if sand_pos[0] > len(rocky_cave)-3:
            captured = False
            sand_drop = False
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]] == ".":
            sand_pos[0] += 1
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]-1] == ".":
                sand_pos[0] += 1
                sand_pos[1] -= 1
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]+1] == ".":
                sand_pos[0] += 1
                sand_pos[1] += 1
        else:
            sand_count+=1
            rocky_cave[sand_pos[0]][sand_pos[1]] = "O"
            sand_drop = False

    # show_cave()

print(sand_count)
show_cave()
# Part 1 = 728

#### RESUME FOR PART 2 #####
captured = True
while captured:
    sand_pos = [0,sand_start]

    sand_drop = True
    while sand_drop:
        if sand_pos[0] > len(rocky_cave)-1:
            captured = False
            sand_drop = False
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]] == ".":
            sand_pos[0] += 1
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]-1] == ".":
                sand_pos[0] += 1
                sand_pos[1] -= 1
        elif rocky_cave[sand_pos[0]+1][sand_pos[1]+1] == ".":
                sand_pos[0] += 1
                sand_pos[1] += 1
        else:
            if sand_pos == [0,sand_start]:
                captured = False
            else:
                rocky_cave[sand_pos[0]][sand_pos[1]] = "O"
            sand_count+=1
            sand_drop = False

print(sand_count)

show_cave()
# Part 2 = 27623

Advent of Code 2022, Day 13

Have I ever mentioned before how much I hate Recursion?

So, I feel like I mentioned on a previous Advent of Code post, half the problem of these puzzles is figuring out how to massage the input. Which I started to work with until I found the eval() function, which I don’t recall using before. But it basically will take something like a string that looks like a list, say, “[2,[4,5,6]]” and make it into a list of lists. Which solved the first problem I had.

Then there was the checks itself. The puzzle was a series of lists full of lists and integers, that needed to be sorted based on some criteria. It basically amounts to a series of if checks, if the data is a list or an int and what to do, if the lengths are the same or different, if one value is larger than the other. Except it can be lists in lists, hence the need for recursion. I felt like I was really close, but then scrapped that for a new approach. Then I got stuck again, then I realized I needed to loop through the internal list. Everything eventually lined up for part 1 nicely.

On to Part 2. Which basically amounts to, using the algorithm on the entire list, instead of just pairs.

I was actually a bit disappointed because I was sure I had come up with a clever way to solve this without the sorting algorithm. I left it in the code, but it’s not used. Here it is anyway.

## Not Used But This really feels like it should have worked.
def decoder(decode_list):
    new_list = []
    for each in decode_list:
        each = each.replace("[]", "0")
        each = each.replace("[", "")
        each = each.replace("]", "")
        each = each.replace(",", "")
        new_list.append(int(each[0]))
    new_list.sort()
    print(new_list)
    print((new_list.index(6)+1)*(new_list.index(2)+1))

The idea was, that I would flatten each list out to an integer, then sort the data that way. The target values needed were simply, 2 and 6. The initial sort put 2 and 6 at basically positions 2 and 6. because 2 is less than 200 when sorting. So it dawned on me, that 2 and 6, would always be the “first” at the “start of the 2s” and “start of the 6s”. So the I just added the first digit to the list, then sorted that, and took the first 2 and first 6. And it still didn’t work.

Figures it wouldn’t be that simple.

While contemplating using nested for loops, I decided to check some other solutions, and found something I had not used before, “cmp_to_key”. After reading up on it, it seems it can be used with the sort function to generate sorts based on a key value returned from a function.

So I modified my sort unction to use +1, -1, and 0 instead of True and False.

Aaaaand, it still didn’t sort, it wasn’t sorting. I had forgotten that I needed to use eval() on each entry, to turn it into Lists instead of Strings. My sort wasn’t working because it doesn’t work on Strings. So I corrected that and bam! Working.

from functools import cmp_to_key

with open("Day13Input.txt") as file:
    data = file.read()

split_data = data.split("\n\n")
decode_data = []
for line in data.replace("\n\n", "\n").split("\n"):
    decode_data.append(eval(line))

def compare_lists(left,right):
    # print(left)
    # print(right)

    if isinstance(left, int) and isinstance(right, int):
        # print(f"{left} {right}")
        if left < right:
            return 1
        elif left > right:
            return -1
        return 0

    if isinstance(left, list) and isinstance(right, list):
        for value in range(min(len(left), len(right))):
            check_value = (compare_lists(left[value], right[value]))
            # print(f"{left[value]} {right[value]}")
            # print(check_value)
            if check_value != 0:
                return check_value

        if len(left) < len(right):
            return 1
        elif len(left) > len(right):
            return -1
    if isinstance(left, int) and isinstance(right, list):
        return compare_lists([left], right)
    if isinstance(left, list) and isinstance(right, int):
        return compare_lists(left, [right])

    return 0

## Not Used But This really feels like it should have worked.
def decoder(decode_list):
    new_list = []
    for each in decode_list:
        each = each.replace("[]", "0")
        each = each.replace("[", "")
        each = each.replace("]", "")
        each = each.replace(",", "")
        new_list.append(int(each[0]))
    new_list.sort()
    print(new_list)
    print((new_list.index(6)+1)*(new_list.index(2)+1))

index_sum = 0
for i in range(len(split_data)-1):
    set1 = split_data[i].split("\n")[0]
    set2 = split_data[i].split("\n")[1]

    match = compare_lists(eval(set1), eval(set2))
    # print(match)

    if match == 1:
        index_sum += (i+1)


print(index_sum)
# 6373 Too high
# 5997 Too low
# 6187

decode_data.append([[2]])
decode_data.append([[6]])
#decoder(decode_data)
decode_data.sort(key=cmp_to_key(compare_lists), reverse=True)
# for each in decode_data:
#     print(each)
print((decode_data.index([[2]])+1)*(decode_data.index([[6]])+1))

# 23520