Personal IMDB Calendar Heatmaps

In the previous post I changed the reading challenge personal folder to a submodule, so that I can use the personal repository for another project: This one. A Python script is processing the IMDB Rating export and generates calendar heatmaps similar to the GitHub contribution activity plots.

The first version used the dayplot Python library, but this doesn't allow per-cell categorical colors. So I asked Claude to change the code to pure matplotlib.

In the IMDB Ratings every entry has a Type, I chose to color them differently. But first I merged the Types like this:

TV Movie -> Movie
TV Mini Series -> TV Series
TV Short -> Short
TV Special -> TV Series

And I removed "Video" and "Video Game". Both are rare in my data. And especially for Video Game the date of rating has nothing to do with the date I actually played the game.

As a result we get these Types, and they get colors when mixed have visible differences:

"Movie": "#16a34a",       # green
"TV Episode": "#4f46e5",  # indigo
"TV Series": "#ea580c",   # orange
"Short": "#0d9488",       # teal

The code would be much shorter without any Type colors, like the green in the GitHub contribution calendar heatmaps.

The full source code is mirrored on GitHub.

A few times in the past, I rated a large number of TV episodes in a single day. The highest count was 140. Obviously, I didn’t actually watch that many episodes on that day. So the data is not perfect for this kind of plot. Still the resulting images are a good indicator how much I watched Movies or TV Episodes.

Next are some example years from my data. In the first years I used IMDB (2004 till 2010) I only rated Movies. I don't know anymore why I started to rate TV Episodes in 2010. But since then I use IMDB ratings to track what I have already watched.

My ratings plot for 2010, the year I started to rate TV Episodes:

img1

I can see in the plots the years where a lot happened and I had no time to watch Movies or Series, for example 2015:

img2

And there is the obvious year where we all spent time at home and watched Movies and Series, 2020:

img3

Overall a good way to visualize my personal Movie and TV Series consumption.

Using Git Submodules

Two months ago I wrote a blog post about a Reading Challenge.

The personal data (yaml files + IMDB export) are stored in a folder named "personal", which is a checkout of another repository. In the personal repository I kept the Forgejo Action that updates the statistics.svg. The script itself is in the reading-challenge repository.

I want to move the Forgejo Action into the reading-challenge repository, but then we need to detect changes in the personal repository. This can be handled by using git submodules.

First we need to replace the personal folder with a submodule:

# move personal folder away / important: is everything commited and pushed!
mv personal ../reading-challenge-personal
# add a submodule instead (this is my git url as example)
git submodule add ssh://git@forgejo/mfa/reading-challenge-personal.git personal
# add a config setting to always pull the submodule
git config submodule.recurse true

With the recurse setting a git pull is enough to pull the submodule too.

The previous Forgejo Action only updated the plot. But now we can actually update the movies, the mermaid file and the svg. This is the full Forgejo Action: statistics.yaml.

One important change, is that the changed files need to be commited to the personal repository and not the current one:

- name: Commit statistics.svg to personal repo
  run: |-
    cd personal
    git checkout main
    git config user.name "Automated"
    git config user.email "actions@users.noreply.github.com"
    git add *.yaml statistics.mmd statistics.svg
    git commit -m "Update statistics and watched movies" || exit 0
    git push

At first I updated the submodule reference too. But this would create an infinite loop because of the trigger I used. A change to the submodule reference for personal will trigger, and if we bump the submodule reference after we changed the files, the Action will run again.

The code to update the reference would look like this:

run: |-
  git config user.name "Automated"
  git config user.email "actions@users.noreply.github.com"
  git add personal
  git commit -m "Update personal submodule reference" || exit 0
  git push

But again, this is not added to the Forgejo Action to not create an infinite trigger loop.

Another tricky thing was that my local setup is using ssh to interact with Forgejo, but the Forgejo Action needs to use HTTPS. For this two changes where needed:

- name: Configure git credentials
  run: |-
    git config --global credential.helper store
    echo "https://oauth2:${{ secrets.PERSONAL_REPO_TOKEN }}@forgejo.tail07efb.ts.net" > ~/.git-credentials
- name: Checkout submodules with HTTPS
  run: |-
    git config --global url."https://forgejo.tail07efb.ts.net/".insteadOf "ssh://git@forgejo.tail07efb.ts.net/"
    git submodule update --init --recursive

The first adds credentials to allow reading (and writing) the personal repository by using a PERSONAL_REPO_TOKEN. This Personal Access Token was generated in the User settings and allows read/write on repositories. There is currently no more granular way to set access rights without a bot user account. The bot could be set to read both repositories and not every repository.

And the second step changes the url first to the https one, and then pulls the submodule.

Having only the data (and the generated svg) in the personal repository feels like the better solution.

Fix Umlaut copy issues in Wayland

I am running Emacs with Wayland support and I try to run everything else in native Wayland. But there are still occassionally issues with clipboard and encoding. This doesn't matter for pure ascii (as in English text), but it matters for German Umlauts (äöü). Today I was so annoyed that a German text copied from Emacs to Slack (Electron; maybe XWayland) had garbled Umlauts, that I investigated and added a snipped to my emacs config.

The source for the snipped is a comment on a Gist.

Snippet:

(when (getenv "WAYLAND_DISPLAY")
  (defun wl-copy (text)
    (let ((proc (make-process :name "wl-copy"
                              :buffer nil
                              :command '("wl-copy")
                              :connection-type 'pipe)))
      (process-send-string proc text)
      (process-send-eof proc)))
  (setq interprogram-cut-function #'wl-copy))

With this there are no issues with missing encoding, because when on Wayland wl-copy is used.