diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ab3fadd5542bcef089a09af78c7cb3a427ea804f..7618e3128fcbcac173a42f2928e1084af4979ccb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -52,3 +52,48 @@ repos:
     hooks:
       - id: hadolint
       - id: docker-compose
+
+  - repo: https://github.com/ambv/black
+    rev: 20.8b1
+    hooks:
+      - id: black
+        args:
+          - |
+              --line-length=79
+
+  - repo: https://github.com/PyCQA/prospector
+    rev: 1.3.1
+    hooks:
+      - id: prospector
+        args:
+          - |-
+            --max-line-length=79
+          - |-
+            --with-tool=pyroma
+          - |-
+            --with-tool=bandit
+          - |-
+            --without-tool=pep257
+          - |-
+            --doc-warnings
+          - |-
+            --test-warnings
+          - |-
+            --full-pep8
+          - |-
+            --strictness=high
+          - |-
+            --no-autodetect
+        additional_dependencies:
+          - bandit
+          - pyroma
+
+  - repo: https://gitlab.com/pycqa/flake8.git
+    rev: 3.9.1
+    hooks:
+      - id: flake8
+        args:
+          - |-
+            --doctests
+        additional_dependencies:
+          - flake8-bugbear
diff --git a/daemon/Dockerfile b/daemon/Dockerfile
index 017096188f09e373247374dcae992391ecbc91d1..fd887f0861dcfd3d88712e3e30f200e0490a86cb 100644
--- a/daemon/Dockerfile
+++ b/daemon/Dockerfile
@@ -4,13 +4,13 @@ RUN apk add --update --no-cache --repository http://dl-cdn.alpinelinux.org/alpin
         gosu \
     && \
     apk add --update --no-cache \
-        curl \
+        python3 \
         transmission-daemon \
     && \
     mkdir -m 777 /run/transmission
 COPY --chown=root:root entrypoint /usr/local/sbin/docker-entrypoint
 COPY --chown=root:root settings.json /etc/transmission/
-COPY --chown=root:root kodi-scan /usr/local/bin/
+COPY --chown=root:root kodi_scan /usr/local/bin/
 VOLUME /var/lib/transmission
 WORKDIR /var/lib/transmission
 ENV HOME /var/lib/transmission
diff --git a/daemon/kodi-scan b/daemon/kodi-scan
deleted file mode 100755
index b46337aa2656d1b1c472e9582faab4923b38e40a..0000000000000000000000000000000000000000
--- a/daemon/kodi-scan
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-set -eu
-
-scan() {
-    case "${1:-}" in
-        audio) curl \
-                --data-binary \
-                '{ "jsonrpc": "2.0", "method": "AudioLibrary.Scan", "id": "transmission"}' \
-                -H 'content-type: application/json;' \
-                http://172.18.0.1:8080/jsonrpc ;;
-        video) curl \
-                --data-binary \
-                '{ "jsonrpc": "2.0", "method": "VideoLibrary.Scan", "id": "transmission"}' \
-                -H 'content-type: application/json;' \
-                http://172.18.0.1:8080/jsonrpc ;;
-        *) scan video; scan audio ;;
-    esac
-}
-
-starts_with() {
-    [ "${1##$2}" != "$1" ]
-}
-
-is_in_dir() {
-    ! starts_with "$(realpath --relative-base "$1" "$2")" '/'
-}
-
-if [ -z "${TR_TORRENT_DIR:-}" ]
-then
-    scan
-elif is_in_dir '/var/lib/transmission/Downloads/TV Shows' "$TR_TORRENT_DIR"
-then
-    scan video
-elif is_in_dir '/var/lib/transmission/Downloads/Movies' "$TR_TORRENT_DIR"
-then
-    scan video
-elif is_in_dir '/var/lib/transmission/Downloads/Music' "$TR_TORRENT_DIR"
-then
-    scan audio
-else
-    scan
-fi
diff --git a/daemon/kodi_scan b/daemon/kodi_scan
new file mode 100755
index 0000000000000000000000000000000000000000..f800c6ac5ccfc9778dc10ca0b8b65e623742f157
--- /dev/null
+++ b/daemon/kodi_scan
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+"""Trigger a Kodi library update on torrent completion.
+See:
+https://github.com/transmission/transmission/wiki/Scripts#On_Torrent_Completion
+"""
+
+import json
+import pathlib
+import os
+import sys
+import urllib
+
+
+def scan(library=None):
+    """Trigger a Kodi library scan, either just audio, video or both."""
+    kodi_json_rpc_url = "http://172.18.0.1:8080/jsonrpc"
+    headers = {"content-type": "application/json"}
+    base_request_data = {"jsonrpc": "2.0", "id": "transmission"}
+    audio_request_data = base_request_data.copy()
+    audio_request_data["method"] = "AudioLibrary.Scan"
+    video_request_data = base_request_data.copy()
+    video_request_data["method"] = "VideoLibrary.Scan"
+
+    if library == "audio":
+        req = urllib.request.Request(
+            kodi_json_rpc_url,
+            headers=headers,
+            data=json.dumps(audio_request_data),
+        )
+        urllib.request.ulropen(req)
+    elif library == "video":
+        req = urllib.request.Request(
+            kodi_json_rpc_url,
+            headers=headers,
+            data=json.dumps(video_request_data),
+        )
+        urllib.request.ulropen(req)
+    else:
+        scan("video")
+        scan("audio")
+
+
+TR_DOWNLOADS_DIR = pathlib.Path("/var/lib/transmission/Downloads")
+TR_MOVIES_DIR = TR_DOWNLOADS_DIR / "Movies"
+TR_TV_SHOWS_DIR = TR_DOWNLOADS_DIR / "TV Shows"
+TR_MUSIC_DIR = TR_DOWNLOADS_DIR / "Music"
+TR_TORRENT_DIR = os.getenv("TR_TORRENT_DIR")
+
+if __name__ == "__main__":
+    if TR_TORRENT_DIR is None:
+        scan()
+        sys.exit()
+    TR_TORRENT_DIR = pathlib.Path(TR_TORRENT_DIR).resolve()
+    if (
+        TR_MOVIES_DIR in TR_TORRENT_DIR.parents
+        or TR_TV_SHOWS_DIR in TR_TORRENT_DIR.parents
+    ):
+        scan("video")
+    elif TR_MUSIC_DIR in TR_DOWNLOADS_DIR.parents:
+        scan("audio")
+    else:
+        scan()
diff --git a/daemon/settings.json b/daemon/settings.json
index 9698f2f2580dd224b3ef56f0b5907729abf94851..ac2162b6b4be17199056b93589797d6b38721e1a 100644
--- a/daemon/settings.json
+++ b/daemon/settings.json
@@ -56,7 +56,7 @@
     "rpc-whitelist-enabled": false,
     "scrape-paused-torrents-enabled": true,
     "script-torrent-done-enabled": true,
-    "script-torrent-done-filename": "/usr/local/bin/kodi-scan",
+    "script-torrent-done-filename": "/usr/local/bin/kodi_scan",
     "seed-queue-enabled": false,
     "seed-queue-size": 10,
     "speed-limit-down": 100,