Extract and export worship song lyrics from ProPresenter 7
View the Project on GitHub adamswbrown/propresenterlyricexport
Understanding the ProPresenter Lyrics Export codebase.
The project supports two distribution paths from a single shared codebase:
┌────────────────────────────────────────────────┐
│ Shared Core Engine (src/) │
│ • ProPresenterClient - API wrapper │
│ • LyricsExtractor - Heuristic parsing │
│ • PptxExporter - PowerPoint generation │
│ • Services: song matching, playlist building │
└────────────────────────────────────────────────┘
↙ ↘
┌──────────────────────┐ ┌──────────────────────┐
│ CLI Path (src/cli) │ │ Electron Path │
│ • Terminal UI │ │ • electron/main/ │
│ • Commands │ │ • electron/renderer/ │
│ • Interactive mode │ │ • React components │
└──────────────────────┘ └──────────────────────┘
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ Distribution: pkg │ │ Distribution: │
│ • Standalone binary │ │ electron-builder │
│ (macOS/Windows) │ │ • .zip (macOS) │
│ │ │ • .exe (Windows) │
└──────────────────────┘ └──────────────────────┘
Key insight: Changes to src/ affect both paths. UI changes stay isolated.
Purpose: Network API wrapper for ProPresenter 7
Key methods:
connect() - Establish connectiongetPlaylists() - Fetch all playlistsgetPresentation(uuid) - Get presentation detailsgetLibraries() - List librariesFeatures:
Timeout handling:
const presentation = await client.getPresentation(uuid)
.catch(() => defaultPresentation);
Purpose: Parse presentation slides into structured lyrics
Input: Raw slides from ProPresenter API
Output: ExtractedLyrics with:
Heuristics:
Purpose: Generate PowerPoint presentations
Library: pptxgenjs 3.10.0 (LOCKED)
Features:
Styling chain:
const style = {
textColor: overrides.textColor || '#ffffff',
fontFace: overrides.fontFace || 'Arial',
fontSize: overrides.fontSize || 44,
};
Services in src/services/ handle complex business logic:
Orchestrates end-to-end export:
Uses: Progress event pattern for UI feedback
Fuzzy matching engine for song titles:
Verse reference lookup (future enhancement)
Constructs ProPresenter playlists from parsed data (Service Generator)
interface PlaylistNode {
uuid?: string; // Unique identifier
name: string; // Display name
breadcrumb: string[]; // Full path
isHeader: boolean; // Folder vs. item
children: PlaylistNode[];
}
Recursive structure allows arbitrary nesting depth.
interface ExtractedLyrics {
title: string;
sections: LyricSection[];
fullText: string;
slideCount: number;
}
interface LyricSection {
name: string; // "Verse 1", "Chorus"
lyrics: string;
slideCount: number;
}
interface Presentation {
uuid: string;
title: string;
album?: string;
artist?: string;
copyright?: string;
lyrics: string;
slideCount: number;
}
// Try feature, fall back to default if unavailable
const libraries = await client.getLibraries()
.catch(() => []);
When a feature fails, the app continues with sensible defaults.
type ProgressEvent =
| { type: 'export:start'; playlistId: string }
| { type: 'song:extracted'; songTitle: string }
| { type: 'export:complete'; outputPath: string };
// Type-safe event handling
if (event.type === 'export:complete') {
console.log(event.outputPath); // Safe to access
}
Used for real-time UI updates and logging.
const store = new Store<AppSettings>({
name: 'settings',
defaults: { ... }
});
// Auto-saved to platform-specific locations
store.set('lastPlaylistId', id);
// Main process
ipcMain.handle('export:playlist', async (event, config) => {
return await performExport(config);
});
// Renderer process
const result = await window.api.exportPlaylist(config);
Two-way communication with async/await pattern.
The LyricsExtractor uses these rules:
Example:
Slide 1: "Verse 1" → Section header
Slide 2: "Lyrics..." → Section content
Slide 3: "Chorus" → New section
Slide 4: "Lyrics..." → Section content
Problem: Versions 3.11.0+ use dynamic require() for font encoding, which pkg can’t bundle.
Solution: Locked at 3.10.0, image encoding disabled.
Impact: PPTX exports work fine; logos can’t be embedded in CLI bundles.
Code location: Lines 91-99 and 129-137 in src/pptx-exporter.ts
Never: Upgrade pptxgenjs without resolving this first.
npm start -- status
npm start -- pptx <uuid> test-output
Test both paths after core logic changes:
npm start -- [command]npm run electron:devnpm start -- status --debug # Shows raw API responses