ProPresenter Media Cleanup Guide

How we cleaned up a ProPresenter media library, removing duplicates, old content, and fixing broken media paths after a username change.

The Problem

Our ProPresenter installation had several issues:

  • Duplicate files wasting disk space (4.6 GB of duplicates)
  • Old content like funeral slideshows and dated events no longer needed
  • Broken media paths after the Mac username changed from mediateam to worshipmedia
  • Media referenced paths like /Users/Shared/Renewed Vision Media/ that no longer existed

Part 1: Finding and Deleting Duplicate Files

We created a bash script to find files with identical content using MD5 hashes, preferring to keep “originals” over files with _copy in the name.

#!/bin/bash
# find_duplicates.sh - Find and delete duplicate files in ProPresenter Media folder

MEDIA_DIR="$HOME/Documents/ProPresenter/Media"
ONEDRIVE_DIR="$HOME/OneDrive - Your Church Name/ProPresenter_Sync/Media"

# Set to 1 to actually delete, 0 for dry run
DRY_RUN=1

# Create temp files
HASH_FILE=$(mktemp)
DUPLICATES_FILE=$(mktemp)
trap "rm -f $HASH_FILE $DUPLICATES_FILE" EXIT

echo "Scanning $MEDIA_DIR..."

# Calculate MD5 hashes for all files
find "$MEDIA_DIR" -type f ! -name ".*" -print0 | while IFS= read -r -d '' file; do
    hash=$(md5 -q "$file" 2>/dev/null)
    if [[ -n "$hash" ]]; then
        echo "$hash|$file"
    fi
done > "$HASH_FILE"

# Find duplicate hashes
cut -d'|' -f1 "$HASH_FILE" | sort | uniq -d > "$DUPLICATES_FILE"

# Process each duplicate set
while IFS= read -r dup_hash; do
    files=()
    while IFS='|' read -r hash filepath; do
        [[ "$hash" == "$dup_hash" ]] && files+=("$filepath")
    done < "$HASH_FILE"

    # Keep original (file without _copy), delete others
    keep=""
    for f in "${files[@]}"; do
        if [[ ! "$f" == *"_copy"* && ! "$f" == *" copy"* ]]; then
            keep="$f"
            break
        fi
    done
    [[ -z "$keep" ]] && keep="${files[0]}"

    echo "KEEP: $keep"
    for f in "${files[@]}"; do
        if [[ "$f" != "$keep" ]]; then
            if [[ $DRY_RUN -eq 0 ]]; then
                rm -f "$f"
                # Also delete from OneDrive sync
                relative_path="${f#$MEDIA_DIR/}"
                rm -f "$ONEDRIVE_DIR/$relative_path"
            fi
            echo "  DELETE: $f"
        fi
    done
done < "$DUPLICATES_FILE"

Results: Found 227 duplicate sets, deleted 305 files, freed 4.6 GB.

Part 2: Finding Old/One-Time Content

We searched for presentations that were unlikely to be needed again:

  • Memorial and funeral services (named after individuals)
  • Dated annual events (Christmas Pageant 2021, Confirmation 2022)
  • One-time events (Town Hall presentations, Scout ceremonies)
  • Duplicate hymns in Special folder that exist in Default library
# Find presentations with dates or person names
find ~/Documents/ProPresenter/Libraries -name "*.pro" -exec basename {} ; | 
  grep -iE "[0-9]{4}|memorial|funeral|recognition|pageant"

We created a review file listing candidates for deletion with comments explaining why each could be removed, then manually reviewed before deleting.

Part 3: Finding Associated Media for Old Presentations

ProPresenter stores imported slides in Media/Imported/{UUID}/ folders. We needed to find which media folders were ONLY used by presentations being deleted (not shared with active presentations).

#!/usr/bin/env python3
# find_unique_media.py - Find media only used by presentations marked for deletion

import os
import re
from pathlib import Path

PROPRESENTER_DIR = Path.home() / "Documents/ProPresenter"
UUID_PATTERN = re.compile(r'[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}', re.IGNORECASE)

def extract_uuids(filepath):
    """Extract all UUIDs referenced in a .pro file."""
    with open(filepath, 'rb') as f:
        content = f.read().decode('utf-8', errors='ignore')
    return set(UUID_PATTERN.findall(content))

# Get UUIDs from presentations to delete vs keep
delete_uuids = set()
keep_uuids = set()

for pro_file in delete_presentations:
    delete_uuids.update(extract_uuids(pro_file))

for pro_file in keep_presentations:
    keep_uuids.update(extract_uuids(pro_file))

# UUIDs only in delete set are safe to remove
unique_uuids = delete_uuids - keep_uuids

# Find corresponding Media/Imported folders
for uuid in unique_uuids:
    folder = PROPRESENTER_DIR / "Media/Imported" / uuid
    if folder.exists():
        print(f"Safe to delete: {folder}")

Results: Found 5 unique media folders (44.5 MB) containing memorial slideshow images that could be safely deleted.

Part 4: Fixing Broken Media Paths

After a username change from mediateam to worshipmedia, all media paths were broken. ProPresenter stores paths in two places:

  1. Playlist files (protobuf format)
  2. Workspace database (LevelDB format)

Fixing Playlist Files with Protobuf

ProPresenter 7 uses Protocol Buffers for playlist files. We used the reverse-engineered schema from greyshirtguy/ProPresenter7-Proto.

# Clone the proto definitions
git clone https://github.com/greyshirtguy/ProPresenter7-Proto.git ~/dev/ProPresenter7-Proto

# Install protobuf tools
pip3 install grpcio-tools

# Compile proto files to Python
cd ~/dev/ProPresenter7-Proto/proto
python3 -m grpc_tools.protoc -I. --python_out=. *.proto
#!/usr/bin/env python3
# fix_media_paths.py - Fix paths in ProPresenter playlist files

import sys
from pathlib import Path
from google.protobuf.message import Message

sys.path.insert(0, str(Path.home() / "dev/ProPresenter7-Proto/proto"))
from proto import propresenter_pb2

PATH_MAPPINGS = [
    ("/Users/Shared/Renewed Vision Media/",
     "/Users/worshipmedia/Documents/ProPresenter/Media/Renewed Vision Media/"),
    ("/Users/mediateam/", "/Users/worshipmedia/"),
    ("/Users/tom/", "/Users/worshipmedia/"),
]

def fix_string(s):
    for old, new in PATH_MAPPINGS:
        s = s.replace(old, new)
    return s

def fix_message(msg, path="root"):
    """Recursively fix all string fields containing paths."""
    for field in msg.DESCRIPTOR.fields:
        if field.label == 3:  # Repeated
            for i, item in enumerate(getattr(msg, field.name)):
                if field.message_type:
                    fix_message(item, f"{path}.{field.name}[{i}]")
                elif field.type == 9 and '/' in item:  # String with path
                    getattr(msg, field.name)[i] = fix_string(item)
        elif field.message_type:
            sub_msg = getattr(msg, field.name)
            if sub_msg.ByteSize() > 0:
                fix_message(sub_msg, f"{path}.{field.name}")
        elif field.type == 9:  # String
            value = getattr(msg, field.name)
            if value and '/' in value:
                setattr(msg, field.name, fix_string(value))

# Parse and fix the Media playlist
media_file = Path.home() / "Documents/ProPresenter/Playlists/Media"
doc = propresenter_pb2.PlaylistDocument()
doc.ParseFromString(media_file.read_bytes())
fix_message(doc)
media_file.write_bytes(doc.SerializeToString())

Results: Fixed 3,472 path references in the Media playlist.

Fixing the Workspace Database

ProPresenter caches media information in a LevelDB database at:

~/Library/Application Support/RenewedVision/ProPresenter/Workspaces/ProPresenter-{ID}/Database/

The simplest fix was to let ProPresenter rebuild this database:

  1. Quit ProPresenter completely
  2. Stop the helper processes:
    pkill -9 -f "ProPresenter"
    launchctl bootout gui/$(id -u)/com.renewedvision.propresenter.workspaces-helper
  3. Delete or rename the Database folder
  4. Restart ProPresenter – it rebuilds the database and rescans media

Temporary Symlinks for Legacy Paths

For presentation files (.pro) that still reference old paths, we created symlinks:

# For /Users/Shared paths
mkdir -p /Users/Shared/Documents
ln -sf ~/Documents/ProPresenter /Users/Shared/Documents/ProPresenter
ln -sf ~/Documents/ProPresenter/Media/Renewed Vision Media /Users/Shared/Renewed Vision Media

# For old username paths (requires sudo)
sudo mkdir -p /Users/mediateam/Documents
sudo ln -sf /Users/worshipmedia/Documents/ProPresenter /Users/mediateam/Documents/ProPresenter

Summary

Task Files Affected Space Freed
Duplicate removal 305 files 4.6 GB
Old presentations 24 files 785 KB
Orphaned media folders 5 folders (187 files) 44.5 MB
Path fixes 3,472 references

Total space recovered: ~4.7 GB

Tools Used

  • md5 – macOS built-in hash tool for duplicate detection
  • protobuf/grpcio-tools – For parsing ProPresenter playlist files
  • ProPresenter7-Proto – Reverse-engineered protobuf schema
  • Python 3 – Scripting for media analysis and path fixing

Tips

  1. Always run duplicate finder in dry-run mode first
  2. Back up the Playlists/Media file before modifying
  3. The ProPresenter workspace database rebuilds automatically – sometimes deleting it is the easiest fix
  4. When deleting media, also delete from your sync folder (OneDrive, Dropbox, etc.)
  5. Check both Media/Assets/ and Media/Renewed Vision Media/ for files – they may be in unexpected locations