macOS Catalina has a lot of background media processing daemons that hog idle processing power on my old MacBook Air. The result is lots of distracting fan noise as the old machine tries to cope with the load. These daemons can’t be disabled and are of marginal value to me, so the idea is to let them run only when the Photos app open and otherwise kill them off. Here’s a little script to slay your daemons:

#!/usr/bin/env python3

import os
import signal
import time
from subprocess import check_output

DEBUG = 0
CONTROL_APP = 'Photos'
COMMANDS = ['photoanalysisd', 'photolibraryd', 'mediaanalysisd']

def dprint(s):
    if DEBUG:
        print(s)

def get_command_map():
    """
    Map PIDs to commands.
    """
    res = check_output(['ps', 'axco', 'pid,command'])
    lines = res.decode("utf-8").strip().split('\n')[1:]
    pairs = [l.strip().split(' ',1) for l in lines]
    return {c:int(p) for p,c in pairs}  

def check_pid(pid):        
    """
    Check For the existence of a unix pid.
    https://stackoverflow.com/a/568285/1262591
    """
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    else:
        return True

def kill_process(pid):
    """
    Try to safely terminate process.  
    If it refuses to exit, kill it.
    """
    os.kill(pid, signal.SIGTERM)
    time.sleep(30)
    if check_pid(pid):
        os.kill(pid, signal.SIGKILL)

def main():
    cmds = get_command_map()
    if CONTROL_APP in cmds:
        dprint('control app found')
        return

    for cmd in COMMANDS:
        pid = cmds.get(cmd)
        if pid:
            dprint('killing {}'.format(cmd))
            kill_process(pid)

if __name__ == "__main__":
    main()

To get this to work, you’ll need to install a launch agent to periodically run the python script. A good place to put this is in the user LaunchAgents directory:

/Users/USER/Library/LaunchAgents/com.USER.slayer.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.USER.slayer.agent</string>

    <key>RunAtLoad</key>
    <true/>

    <key>StartInterval</key>
    <integer>60</integer>

    <key>ProgramArguments</key>
    <array>
        <string>/Library/Frameworks/Python.framework/Versions/3.8/bin/python3</string>
        <string>/path/to/slayer.py</string>
    </array>

    <key>StandardErrorPath</key>
    <string>/path/to/stderr.log</string>

    <key>StandardOutPath</key>
    <string>/path/to/stdout.log</string>
</dict>
</plist>

And finally get that launch agent going:

launchctl load /Users/USER/Library/LaunchAgents/com.USER.slayer.plist

The agent can be disabled with:

launchctl unload /Users/USER/Library/LaunchAgents/com.USER.slayer.plist