Programming Update: January/February 2022


I started off the year not expecting to do much programming. Compared to some months in 2021, I barely programmed, but I did end up programming much more than I expected. Let’s take a look at what I worked on in the first sixth of the year.

Python Programs

End of Year Video Games Helper

Coming off of last year in which I finally used Python to pull my last.fm data and create graphs, I decided to do the same for my End of Year Video Games blog post. I haven’t posted the code to Github, but here it is:

"""Go through Let's Play directories and sum up total time per video. Assumes directory per game."""


from datetime import time, timedelta
from pathlib import Path
from rich import print
from pymediainfo import MediaInfo


def sum_times(directory: Path) -> tuple:
    """Sum the duration of all videos in this directory.

    Return a tuple with the name of the game and the total time.

    Assumption: names of directory is name of game.
    """
    video_files = [item for item in directory.iterdir() if not item.is_dir()]
    total_length = timedelta(0, 0, 0, 0)
    for video_file in video_files:
        video_info = MediaInfo.parse(str(video_file))
        for track in video_info.tracks:
            if track.track_type == "Video":
                video_dict = track.to_data()
                video_length = video_dict['other_duration'][4]
                split_video_length = video_length.split(":")
                total_length += timedelta(hours=int(split_video_length[0]), minutes=int(split_video_length[1]),
                                          seconds=int(split_video_length[2]))
    return directory.parts[-1], total_length


if __name__ == '__main__':
    starting_directory = Path("my_videos/")
    gather_subdirectories = [item for item in starting_directory.iterdir() if item.is_dir()]
    games_and_lengths = []
    annual_total = timedelta(0, 0, 0, 0)
    for subdirectory in gather_subdirectories:
        game, length = sum_times(subdirectory)
        annual_total += length
        games_and_lengths.append((game, length))
    sorted_games_and_lengths = sorted(games_and_lengths, key=lambda game_length: game_length[1], reverse=True)
    with open("games_and_times.txt", 'w') as file:
        for position, game_tuple in enumerate(sorted_games_and_lengths):
            file.write(f"{position+1}. {game_tuple[0]} ({game_tuple[1]}): \n")
        file.write(f"Total Time for 2021: {annual_total}")

It worked very well and saved me hours of time going through each video to sum up the total play time. This is, as I have said, the best use of programming.

Extra Life Donation Tracker v7.1.0

This year was the first year in which I didn’t make an initial donation when I signed up for Extra Life. Turns out there were a few issues I’d never realized existed for someone who wanted to use my program without any donations whatsoever. I fixed those issues and cut a new release.

Prophecy Practicum (Django)

I’ve previously mentioned the project I created for my friend to help reduce the barrier on one aspect of his spiritual practice. He had asked me to work on a few quality of life issues. After I knocked those out (for a 5.0 release), I was very motivated to keep going. The 6.0 release was about beautification (mostly with CSS) to make the site look a bit more modern. I ended up using the Bulma CSS framework (apparently CSS has reached that level of modernization to where folks use frameworks instead of manually creating CSS). Of course, in the course of creating and testing the CSS changes, I found a few more places to improve the code to make it better for the users. It made me incredibly happy to make things work better for my friend and the other users.

Python Learning

As I continued to read through Data Visualization with Python and Javascript, I started to gain an understanding of CSS (which helped above when I worked on the Django project). I’m reading the book on the side as I work on other stuff so it’s slow going, but I hope to use it to visualize my last.fm data going forward.

I also started going through the Talk Python Pycharm class. I’ve been using Pycharm for a year or two now, but I’m learning so many new features that are already helping to make my programming work a lot easier – especially around refactoring.

Advent of Code

I did one tiny bit of Advent of Code in February – I implemented Advent of Code 2016 Day 5 in Go. This allowed me to learn about creating MD5 hashes in Go as well as practice functions and remind myself of the Go syntax since it’d been a couple months since I last coded in Go.

// Solution to Advent of Code Day 05 -- Do You Want to Play a Game

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"strconv"
)

// createHash returns a md5 hash of a string
func createHash(textToHash string) string {
	hash := md5.Sum([]byte(textToHash))
	return hex.EncodeToString(hash[:])
}

// validateHash returns true if this is a hash with 5 leading 0s
func validateHash(hashToCheck string) bool {
	for position, char := range hashToCheck {
		if position < 5 {
			if string(char) == "0" {
				continue
			} else {
				break
			}
		}
		if position == 5 {
			return true
		}
	}
	return false
}

// createDoorOnePassword takes in the input for this question and produces the password
func createDoorOnePassword(aocInput string) string {
	var password string
	numericalSuffix := 0
	for len(password) < 8 {
		testHash := createHash(aocInput + strconv.Itoa(numericalSuffix))
		//fmt.Println(testHash)
		if validateHash(testHash) {
			password += string([]rune(testHash)[5])
		}
		numericalSuffix++
	}
	return password
}

// createDoorTwoPassword takes in the aoc input and uses the new rules to produce the door password
func createDoorTwoPassword(aocInput string) string {
	var password [8]rune
	var locationCheck [8]bool
	numericalSuffix := 0
	for locationCheck[0] == false || locationCheck[1] == false || locationCheck[2] == false || locationCheck[3] == false || locationCheck[4] == false || locationCheck[5] == false || locationCheck[6] == false || locationCheck[7] == false {
		testHash := createHash(aocInput + strconv.Itoa(numericalSuffix))
		if validateHash(testHash) {
			probableLocation := string([]rune(testHash)[5])
			if probableLocation == "0" || probableLocation == "1" || probableLocation == "2" || probableLocation == "3" || probableLocation == "4" || probableLocation == "5" || probableLocation == "6" || probableLocation == "7" {
				location, _ := strconv.Atoi(probableLocation)
				character := []rune(testHash)[6]
				if locationCheck[location] == false {
					locationCheck[location] = true
					password[location] = character
				}

			}
		}
		numericalSuffix++
	}
	return string(password[:])
}

func main() {
	aocInput := "ugkcyxxp"
	doorOnePassword := createDoorOnePassword(aocInput)
	fmt.Printf("The password to door #1 is %s\n", doorOnePassword)
	doorTwoPassword := createDoorTwoPassword(aocInput)
	fmt.Printf("The password to door #2 is %s", doorTwoPassword)
}