A PHP library to read, modify, and generate ProPresenter 7 files — songs, playlists, bundles, themes, and global library files.
ProPresenter 7 stores its data in protobuf-encoded binary files (with ZIP wrappers for playlists and bundles). This library decodes those formats into idiomatic PHP objects, lets you modify them, and writes them back out — with full round-trip fidelity for global library files and verified compatibility with PP7 for songs and bundles.
- Features
- Requirements
- Installation
- Getting Started
- CLI Tools
- Documentation
- Project Structure
- Development
- Compatibility & Caveats
- Contributing
- License
- Credits
| Format | Extension | Read | Modify | Generate | Notes |
|---|---|---|---|---|---|
| Song | .pro |
✅ | ✅ | ✅ | Lyrics, groups, slides, arrangements, translations, CCLI metadata, macros, media |
| Playlist | .proplaylist |
✅ | ✅ | ✅ | ZIP64 archive, embedded songs, headers, placeholders |
| Bundle | .probundle |
✅ | ✅ | ✅ | ZIP archive containing a song + flat media assets |
| Theme | folder | ✅ | ✅ | ✅ | Theme protobuf + Assets/ directory |
| Macros | Macros |
✅ | ✅ | — | Macros + collections |
| Labels | Labels |
✅ | ✅ | — | Slide labels with optional UI colors |
| Groups | Groups |
✅ | ✅ | — | Library groups (UUID, color, hot keys) |
| ClearGroups | ClearGroups |
✅ | ✅ | — | Clear-action groups |
| CCLI | CCLI |
✅ | ✅ | — | License, copyright template |
| Messages | Messages |
✅ | ✅ | — | Lower-third / overlay messages |
| Timers | Timers |
✅ | ✅ | — | Timer definitions + clock format |
| Stage | Stage |
✅ | ✅ | — | Stage display layouts |
| Workspace | Workspace |
✅ | ✅ | — | Screens, looks, masks, audio/video inputs |
| Props | Props |
✅ | ✅ | — | Prop cues + transitions |
| TestPatterns | TestPatterns |
✅ | ✅ | — | Test pattern overrides |
| Calendar | Calendar |
✅ | ✅ | — | Scheduled events firing macros |
| KeyMappings | KeyMappings |
✅ | ✅ | — | Custom hot-key bindings |
| CommunicationDevices | JSON | ✅ | ✅ | — | MIDI / serial / OSC bindings |
- High-level wrappers — work with
Song,Group,Slide,Arrangement,PlaylistArchiveetc. instead of raw protobuf classes. - RTF text extraction —
Slide::getPlainText()returns clean text from ProPresenter's CocoaRTF, including German umlauts and Unicode. - Translation-aware — read and write multi-language slides (
hasTranslation(),getTranslation()). - ZIP64 repair — automatically fixes ProPresenter's 98-byte ZIP64 header bug on read.
- Generate from scratch — build complete
.proand.proplaylistfiles programmatically with media references. - 18 CLI tools — quickly inspect any ProPresenter file from the command line.
- 369 tests, 1,300+ assertions — covering all readers, writers, generators, and round-trip fidelity against a synthetic test corpus.
- Comprehensive docs — every API and binary format is documented in
doc/.
- PHP 8.4 or higher
google/protobuf(installed via Composer)ext-zipfor.proplaylistand.probundlefiles (bundled with most PHP distributions)
composer require bussnet/propresenter7-php-libOr clone the repository to develop locally:
git clone https://github.com/bussnet/propresenter7-php-lib.git
cd propresenter7-php-lib
composer installAll examples assume Composer's autoloader is loaded:
require 'vendor/autoload.php';use ProPresenter\Parser\ProFileReader;
$song = ProFileReader::read('path/to/Amazing Grace.pro');
echo $song->getName() . "\n"; // "Amazing Grace"
echo $song->getCcliAuthor() . "\n"; // "John Newton"
echo $song->getCcliCopyrightYear() . "\n"; // 1779
// Walk groups → slides → text
foreach ($song->getGroups() as $group) {
echo "[{$group->getName()}]\n";
foreach ($song->getSlidesForGroup($group) as $slide) {
echo " " . $slide->getPlainText() . "\n";
if ($slide->hasTranslation()) {
echo " → " . $slide->getTranslation()->getPlainText() . "\n";
}
}
}
// Resolve an arrangement to a flat list of groups (in performance order)
$arrangement = $song->getArrangements()[0];
foreach ($song->getGroupsForArrangement($arrangement) as $group) {
echo $group->getName() . " → ";
}use ProPresenter\Parser\ProFileReader;
use ProPresenter\Parser\ProFileWriter;
$song = ProFileReader::read('input.pro');
// Update CCLI metadata
$song->setName('Amazing Grace (My Chains Are Gone)');
$song->setCcliPublisher('Public Domain');
$song->setCcliCopyrightYear(2006);
// Rename a group
$song->getGroupByName('Verse 1')?->setName('Strophe 1');
// Add a label to the first slide
$song->getSlides()[0]->setLabel('Intro');
ProFileWriter::write($song, 'output.pro');use ProPresenter\Parser\ProFileGenerator;
ProFileGenerator::generateAndWrite(
'amazing-grace.pro',
'Amazing Grace',
[
[
'name' => 'Verse 1',
'color' => [0.13, 0.59, 0.95, 1.0], // RGBA floats (0..1)
'slides' => [
['text' => "Amazing grace, how sweet the sound\nThat saved a wretch like me"],
[
'text' => 'I once was lost, but now am found',
'translation' => 'Ich war verloren, doch jetzt gefunden',
],
],
],
[
'name' => 'Chorus',
'color' => [0.95, 0.27, 0.27, 1.0],
'slides' => [
['text' => 'My chains are gone, I have been set free'],
],
],
],
[
['name' => 'normal', 'groupNames' => ['Verse 1', 'Chorus', 'Verse 1', 'Chorus']],
],
[
'author' => 'John Newton',
'song_title' => 'Amazing Grace',
'copyright_year' => 1779,
],
);Every entry of a group's slides array is a slideData array. All keys are optional.
| Key | Type | Description |
|---|---|---|
text |
string |
Main slide text (multi-line allowed). |
translation |
string |
Second text element; renders original + translation side by side. |
subtitle |
string |
Smaller non-bold second run below text (ignored when translation is set). |
textBounds |
array |
Explicit placement of the plain text element (see below). |
textStyle |
array |
Explicit alignment of the plain text element (see below). |
imageOnly |
bool |
Skip the text layer entirely (image-only slide). |
media |
string |
Foreground media filename. |
background |
array |
Background media layer (a media ACTION), e.g. ['path' => 'BACKGROUND.jpg', 'bundleRelative' => true]. |
image |
array |
Image content ELEMENT appended LAST, i.e. the backmost layer of the slide, behind text (see below). |
label |
string |
Slide label text. |
clock |
array |
Live wall-clock element (see below). |
timer |
array |
Timer/countdown element bound to a ProPresenter timer (see below). |
By default the plain text element covers the historic text-safe area
(x:150, y:100, width:1620, height:880) and is centred both horizontally and
vertically. textBounds and textStyle override that per slide — useful to
place a short line (e.g. a name tag) in one of the slide's corners.
| Key | Type | Default | Description |
|---|---|---|---|
textBounds.x |
float |
150 |
Left edge in slide coordinates. |
textBounds.y |
float |
100 |
Top edge in slide coordinates. |
textBounds.width |
float |
1620 |
Box width. |
textBounds.height |
float |
880 |
Box height. |
textStyle.align |
string |
'center' |
left, center or right. |
textStyle.verticalAlign |
string |
'middle' |
top, middle or bottom. |
textStyle.color |
array |
white | [r, g, b] as 0..255 ints or 0..1 floats. |
Missing sub-keys fall back to their default, so a partial textBounds is valid.
Omitting both keys keeps the generated element byte-identical to previously
generated files. The same holds for textStyle.color: without it the RTF colour
table keeps its historic all-white entries.
// Name tag pinned to the bottom-left corner
[
'text' => 'Max Mustermann',
'subtitle' => 'Moderation',
'textBounds' => ['x' => 60, 'y' => 820, 'width' => 600, 'height' => 200],
'textStyle' => ['align' => 'left', 'verticalAlign' => 'bottom'],
]
// Amber name tag
[
'text' => 'Max Mustermann',
'textStyle' => ['color' => [255, 200, 0]],
]Slides read back from a .pro file expose getTextElementBounds(),
getTextElementAlign() and getTextElementVerticalAlign(), which resolve the
first plain text element (skipping clock, timer and image elements).
The colour is read back with Slide::getTextColor() (first plain text element)
and Slide::getTimerColor() (timer element), both returning an [r, g, b]
triple with 0..255 components, or null when the slide carries no such
element. TextElement::getTextColor() exposes the same value per element. All
three parse the second colour table entry — the one the RTF body references
via \cf2.
Unlike background — which emits a media action on the background layer — image
emits a real slide content element whose fill is the given image.
ProPresenter paints a slide's element stack front-to-back: the lowest index is
the frontmost layer, the highest index is the backmost layer. The image element
is therefore appended LAST to the slide's element array, i.e. it is the backmost
layer, so text, translation, subtitle, clock and timer elements — all emitted
before it — are painted on top of it. Combine image with imageOnly => true for an
image-only slide, or with text for text over an image.
The image is referenced bundle-relative by its bare filename (path is
reduced to basename()), so it resolves against the bytes embedded in the
.pro / .probundle archive — never by an absolute path.
| Key | Type | Default | Description |
|---|---|---|---|
path |
string |
'' |
Bare filename, referenced bundle-relative. |
format |
string |
'JPG' |
Media format, e.g. JPG, PNG. |
width |
int |
1920 |
Natural image width. |
height |
int |
1080 |
Natural image height. |
bounds |
array |
x:0, y:0, width:1920, height:1080 |
['x','y','width','height'] in slide coordinates. |
scaleBehavior |
string |
'fill' |
fill, fit or stretch. |
opacity |
float |
1.0 |
Element opacity. |
name |
string |
'' |
Name of the graphics element. |
// Uploaded info slide image with text rendered on top of it
['text' => 'Herzlich willkommen', 'image' => ['path' => 'INFO_1.jpg', 'format' => 'JPG']]
// Image-only slide (no text layer)
['imageOnly' => true, 'image' => ['path' => 'INFO_2.jpg']]Slides read back from a .pro file expose hasImageElement(),
getImageElementUrl() and getImageElementFormat() (mirroring
hasBackgroundMedia() / getBackgroundMediaUrl() / getBackgroundMediaFormat()
for the background media action).
| Key | Type | Default | Description |
|---|---|---|---|
format |
string |
'HH:mm' |
Clock format; drives Clock.Format, never written verbatim (see note below). |
military24 |
bool |
true |
24-hour time. |
text |
string |
derived from format |
Static placeholder text shown in the editor. |
bounds |
array |
x:60, y:40, width:600, height:200 |
['x','y','width','height'] in slide coordinates. |
style |
array |
— | Text styling, see below. |
| Key | Type | Default | Description |
|---|---|---|---|
timerUuid |
string |
— | UUID of the timer in the ProPresenter Timers library. Omit to leave unbound. |
timerName |
string |
'' |
Timer name (fallback lookup when the UUID is unknown). |
format |
string |
'mm:ss' |
Format string; alias formatString. Components present in the string (H/h, m, s, S) are shown (Style LONG), the rest hidden (Style NONE). Drives Timer.Format, never written verbatim (see note below). |
text |
string |
derived from format |
Static placeholder text shown in the editor. |
name |
string |
'Timer' |
Name of the graphics element. |
bounds |
array |
x:60, y:40, width:1800, height:1000 |
['x','y','width','height'] in slide coordinates. |
style |
array |
— | Text styling, see below. |
military24 |
bool |
false |
Maps to Timer.Format.is_24_hour_time. |
wallClock |
bool |
false |
Maps to Timer.Format.is_wall_clock_time. |
millisecondsUnderMinuteOnly |
bool |
false |
Maps to Timer.Format.show_milliseconds_under_minute_only. |
visibleWhen |
string |
— | Optional visibility condition bound to the same timer: hasTimeRemaining, hasExpired, isRunning or notRunning. Emitted as an additional VisibilityLink DataLink so ProPresenter hides the element once the condition no longer holds. Omit to keep the element always visible. |
| Key | Type | Default | Description |
|---|---|---|---|
fontName |
string |
'HelveticaNeue' |
Font family. |
fontSize |
int |
42 |
Font size in points. |
bold |
bool |
false |
Bold text run. |
color |
array |
white | [r, g, b] as 0..255 ints or 0..1 floats. |
align |
string |
'center' |
left, center or right. |
verticalAlign |
string |
'middle' |
top, middle or bottom. |
Omitting style keeps the default RTF template byte-identical to previously generated files.
// Big centred countdown bound to a timer from the Timers library
['timer' => [
'timerUuid' => '0E45D0AF-BCC2-4A31-BCFD-0F5A3358E225',
'timerName' => 'Gottesdienst',
'format' => 'mm:ss',
'bounds' => ['x' => 60, 'y' => 40, 'width' => 1800, 'height' => 1000],
'style' => ['fontName' => 'HelveticaNeue', 'fontSize' => 300, 'bold' => true, 'color' => [255, 255, 255]],
]]
// Countdown that disappears once it has run out
['timer' => [
'timerUuid' => '0E45D0AF-BCC2-4A31-BCFD-0F5A3358E225',
'timerName' => 'Gottesdienst',
'format' => 'mm:ss',
'visibleWhen' => 'hasTimeRemaining',
]]Format strings are never written verbatim. In real ProPresenter files
TimerText.timer_format_stringalways carries the literal token${timer}(andClockText.clock_format_stringthe literal${clock}): that field is the RTF body template, not a time pattern. Writing"mm:ss"there makes ProPresenter print the literal textmm:ss. The real format lives in the structuredTimer.Formatmessage (.rv.data.Timer.Format), whose per-componentStyleenum is only everNONE(0, hidden) orLONG(2, shown) in real files. The generator therefore emits${timer}/${clock}as the format string, derivesTimer.Formatfrom theformatkey, and keeps the element's RTF body a static placeholder.
Slides read back from a .pro file expose hasTimer(), getTimerFormat() (the raw
${timer} token), getTimerFormatMessage() (the structured Timer.Format),
getTimerName() and getTimerUuid() (mirroring hasClock() / getClockFormat()).
When visibleWhen is set, the slide additionally exposes hasTimerVisibilityCondition(), getTimerVisibilityCriterion() (returns the same string that was passed in) and getTimerVisibilityTimerUuid().
use ProPresenter\Parser\ProPlaylistReader;
$archive = ProPlaylistReader::read('Sunday Service.proplaylist');
echo $archive->getName() . "\n";
foreach ($archive->getEntries() as $entry) {
echo match ($entry->getType()) {
'header' => "── {$entry->getName()} ──\n",
'presentation' => " ♪ {$entry->getName()} (arr: " . ($entry->getArrangementName() ?? 'default') . ")\n",
'placeholder' => " · {$entry->getName()} (TBD)\n",
default => " ? {$entry->getName()}\n",
};
// Lazily parse embedded .pro files
if ($entry->getType() === 'presentation') {
$song = $archive->getEmbeddedSong($entry);
if ($song !== null) {
echo " → " . count($song->getSlides()) . " slides\n";
}
}
}use ProPresenter\Parser\ProPlaylistGenerator;
ProPlaylistGenerator::generateAndWrite(
'sunday-service.proplaylist',
'Sunday Service',
[
['type' => 'header', 'name' => 'Worship', 'color' => [0.95, 0.27, 0.27, 1.0]],
['type' => 'presentation', 'name' => 'Amazing Grace', 'path' => 'file:///Songs/amazing-grace.pro', 'arrangement' => 'normal'],
['type' => 'presentation', 'name' => 'Oceans', 'path' => 'file:///Songs/oceans.pro'],
['type' => 'header', 'name' => 'Sermon'],
['type' => 'placeholder', 'name' => 'Sermon notes'],
],
['notes' => 'Sunday morning service'],
);A .probundle is a ZIP archive containing a single .pro file plus its referenced media — perfect for sharing presentations between machines.
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProBundleWriter;
use ProPresenter\Parser\PresentationBundle;
use ProPresenter\Parser\ProFileGenerator;
// Read
$bundle = ProBundleReader::read('Christmas Slides.probundle');
echo $bundle->getName() . "\n";
echo $bundle->getMediaFileCount() . " media files\n";
foreach ($bundle->getMediaFiles() as $filename => $bytes) {
echo " $filename: " . strlen($bytes) . " bytes\n";
}
// Build a new bundle (media uses ROOT_CURRENT_RESOURCE → portable across machines)
$song = ProFileGenerator::generate(
'My Slides',
[[
'name' => 'Background',
'color' => [0.2, 0.2, 0.2, 1.0],
'slides' => [[
'media' => 'background.png',
'format' => 'png',
'label' => 'background.png',
'bundleRelative' => true,
]],
]],
[['name' => 'normal', 'groupNames' => ['Background']]],
);
$bundle = new PresentationBundle(
$song,
'My Slides.pro',
['background.png' => file_get_contents('background.png')],
);
ProBundleWriter::write($bundle, 'my-slides.probundle');ProPresenter stores its global library in extension-less protobuf files inside the user library folder. Each is exposed through a dedicated reader/writer:
use ProPresenter\Parser\MacrosFileReader;
use ProPresenter\Parser\MacrosFileWriter;
$library = MacrosFileReader::read('/path/to/Macros');
foreach ($library->getMacros() as $macro) {
echo $macro->getName() . " — " . $macro->getUuid() . "\n";
}
// Add a macro programmatically
$library->addMacro('Service Start', '00000000-0000-0000-0000-000000000001');
$library->getMacroByName('Service Start')?->setColor(['r' => 0.0, 'g' => 0.5, 'b' => 1.0]);
MacrosFileWriter::write($library, '/path/to/Macros');The same Reader::read() / Writer::write() pattern applies to every global library file. See doc/api/ for the full set.
Every supported file type ships with an inspector script in bin/:
php bin/parse-song.php path/to/song.pro
php bin/parse-playlist.php path/to/playlist.proplaylist
php bin/parse-theme.php path/to/ThemeFolder
php bin/parse-macros.php ~/Library/.../Macros
php bin/parse-labels.php ~/Library/.../Labels
php bin/parse-groups.php ~/Library/.../Groups
php bin/parse-clear-groups.php ~/Library/.../ClearGroups
php bin/parse-ccli.php ~/Library/.../CCLI
php bin/parse-messages.php ~/Library/.../Messages
php bin/parse-timers.php ~/Library/.../Timers
php bin/parse-stage.php ~/Library/.../Stage
php bin/parse-workspace.php ~/Library/.../Workspace
php bin/parse-props.php ~/Library/.../Props
php bin/parse-test-patterns.php ~/Library/.../TestPatterns
php bin/parse-calendar.php ~/Library/.../Calendar
php bin/parse-key-mappings.php ~/Library/.../KeyMappings
php bin/parse-communication-devices.php ~/Library/.../CommunicationDevicesExample output for parse-song.php:
Song: Amazing Grace
UUID: A1B2C3D4-...
CCLI Metadata:
Song Title: Amazing Grace
Author: John Newton
Copyright Year: 1779
Display: yes
Groups (3):
[1] Verse 1 (2 slides)
Slide 1: Amazing grace, how sweet the sound / That saved a wretch like me
Slide 2: I once was lost, but now am found
[2] Chorus (1 slide)
Slide 1: My chains are gone, I have been set free
...
Arrangements (1):
[1] normal: Verse 1 -> Chorus -> Verse 1 -> Chorus
Full documentation lives in doc/ — start with doc/INDEX.md.
| Topic | Document |
|---|---|
Songs (.pro) |
doc/api/song.md |
Playlists (.proplaylist) |
doc/api/playlist.md |
Bundles (.probundle) |
doc/api/bundle.md |
| Themes (folder) | doc/api/theme.md |
| Macros library | doc/api/macros.md |
| Labels library | doc/api/labels.md |
| Groups library | doc/api/groups.md |
| ClearGroups library | doc/api/clear-groups.md |
| CCLI settings | doc/api/ccli.md |
| Messages library | doc/api/messages.md |
| Timers library | doc/api/timers.md |
| Stage layouts | doc/api/stage.md |
| Workspace | doc/api/workspace.md |
| Props library | doc/api/props.md |
| TestPatterns | doc/api/test-patterns.md |
| Calendar | doc/api/calendar.md |
| KeyMappings | doc/api/key-mappings.md |
| CommunicationDevices | doc/api/communication-devices.md |
| Format | Document |
|---|---|
.pro (songs) |
doc/formats/pp_song_spec.md |
.proplaylist |
doc/formats/pp_playlist_spec.md |
.probundle |
doc/formats/pp_bundle_spec.md |
Looking for something specific? Use the keyword index: doc/keywords.md.
.
├── bin/ # 18 CLI tools (parse-*.php scripts)
├── src/ # PHP source (wrappers, readers, writers, generators)
├── generated/ # Auto-generated protobuf PHP classes (Rv\Data\…)
├── proto/ # Vendored .proto files (greyshirtguy/ProPresenter7-Proto, Proto 19beta + extras)
├── tests/ # PHPUnit test suite (369 tests)
├── doc/
│ ├── INDEX.md # Documentation entry point
│ ├── keywords.md # Keyword search index
│ ├── CONTRIBUTING.md # Documentation guidelines
│ ├── api/ # PHP API documentation
│ ├── formats/ # Binary file format specifications
│ ├── internal/ # Development notes (learnings, decisions, issues)
│ └── reference_samples/ # Reference files used by tests (real-world songs)
├── composer.json
├── phpunit.xml
├── LICENSE
└── README.md
| Class | Purpose |
|---|---|
ProPresenter\Parser\Song |
Top-level song wrapper (groups + slides + arrangements) |
ProPresenter\Parser\Group |
Song part (verse, chorus, …) |
ProPresenter\Parser\Slide |
Single slide with text, label, macro, media |
ProPresenter\Parser\TextElement |
Text element with RTF + plain-text accessors |
ProPresenter\Parser\Arrangement |
Group order for a performance |
ProPresenter\Parser\PlaylistArchive |
.proplaylist ZIP wrapper |
ProPresenter\Parser\PresentationBundle |
.probundle ZIP wrapper |
ProPresenter\Parser\ThemeBundle |
Theme folder wrapper |
ProPresenter\Parser\ProFileReader / Writer / Generator |
.pro IO |
ProPresenter\Parser\ProPlaylistReader / Writer / Generator |
.proplaylist IO |
ProPresenter\Parser\ProBundleReader / Writer |
.probundle IO |
ProPresenter\Parser\Zip64Fixer |
Repairs ProPresenter's broken ZIP64 EOCD headers |
ProPresenter\Parser\RtfExtractor |
Standalone CocoaRTF → plain-text converter |
composer install
composer testYou should see:
PHPUnit 11.5.55 by Sebastian Bergmann and contributors.
OK (369 tests, 1298 assertions)
The test suite includes:
- Unit tests — every wrapper class
- Integration tests — readers + writers round-tripping reference files
- Mass validation — parses every
.profixture indoc/reference_samples/all-songs/(tests/MassValidationTest.php) - Binary fidelity tests — verifies byte-perfect round-trips for global library files
Real ProPresenter files used by the tests live in doc/reference_samples/. They are exported from production worship environments and cover edge cases (translations, missing arrangements, ZIP64 quirks, German Unicode, embedded media).
Some test fixtures are generated procedurally:
php bin/regen-test-bundles.php- Verified against ProPresenter 7.16+ on macOS. Files generated by this library open cleanly in ProPresenter 7.
- Round-trip fidelity — global library files (
Macros,Labels,Groups, …) round-trip byte-for-byte. Songs do not: ProPresenter's protobuf schema contains undocumented fields that are dropped on re-encode. The library preserves logical content perfectly, but raw bytes will differ. See doc/internal/issues.md for the gory details. - ZIP64 quirk — ProPresenter exports
.proplaylistand.probundlefiles with a 98-byte ZIP64 header offset bug.Zip64Fixerpatches this in memory before parsing. Files written by this library use clean standard ZIPs. - RTF — slide text is stored as CocoaRTF (Windows-1252 with
\'xxhex escapes for non-ASCII).getPlainText()decodes this; the generator produces clean RTF that PP7 accepts. - macOS-centric paths — ProPresenter uses
file://URLs with absolute paths in some fields. For portable bundles, use'bundleRelative' => trueon media slides (this setsROOT_CURRENT_RESOURCEso PP7 resolves media relative to the archive).
Contributions are welcome! Please:
- Open an issue describing the change before sending a PR for anything non-trivial.
- Follow the documentation guidelines in doc/CONTRIBUTING.md.
- Add a test for any new behavior — TDD is the convention here.
- Run
composer testbefore submitting. - Keep changes focused; avoid unrelated refactors.
This project is released under the MIT License.
The bundled .proto files in proto/ are derived from greyshirtguy/ProPresenter7-Proto, Proto 19beta (dumped from ProPresenter v19 beta build 318767123) plus a few extras (calendar, keyMappings, plus three legacy analytics protos retained from the 7.16.2 set), also distributed under the MIT License.
- Renewed Vision — for ProPresenter, an excellent presentation tool.
- greyshirtguy — for reverse-engineering the ProPresenter 7 protobuf schema, without which this library would not exist.
- Google Protocol Buffers — for the underlying serialization format.
ProPresenter is a trademark of Renewed Vision, LLC. This project is not affiliated with or endorsed by Renewed Vision.