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
mediateamtoworshipmedia - 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:
- Playlist files (protobuf format)
- 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:
- Quit ProPresenter completely
- Stop the helper processes:
pkill -9 -f "ProPresenter" launchctl bootout gui/$(id -u)/com.renewedvision.propresenter.workspaces-helper - Delete or rename the Database folder
- 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
- Always run duplicate finder in dry-run mode first
- Back up the
Playlists/Mediafile before modifying - The ProPresenter workspace database rebuilds automatically – sometimes deleting it is the easiest fix
- When deleting media, also delete from your sync folder (OneDrive, Dropbox, etc.)
- Check both
Media/Assets/andMedia/Renewed Vision Media/for files – they may be in unexpected locations


































