- Move src/, tests/, bin/, generated/, proto/, composer.json, composer.lock, phpunit.xml from php/ to repo root - Move ref/ to doc/reference_samples/ for better organization - Remove vendor/ from git tracking (now properly gitignored) - Update all test file paths (dirname adjustments and ref/ -> doc/reference_samples/) - Update all documentation paths (AGENTS.md, doc/*.md) - Remove php.bak/ directory - All 252 tests pass
6.4 KiB
Bundle Parser API
PHP module for reading, modifying, and writing ProPresenter
.probundlefiles.
Quick Reference
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProBundleWriter;
use ProPresenter\Parser\PresentationBundle;
// Read
$bundle = ProBundleReader::read('path/to/presentation.probundle');
// Access
$bundle->getName(); // Presentation name
$bundle->getSong(); // Song wrapper
$bundle->getMediaFiles(); // ['filename' => bytes, ...]
// Write
ProBundleWriter::write($bundle, 'output.probundle');
Reading Bundles
use ProPresenter\Parser\ProBundleReader;
$bundle = ProBundleReader::read('path/to/presentation.probundle');
The reader automatically applies Zip64Fixer to handle ProPresenter's broken ZIP64 headers. Works with both PP7-exported bundles and library-generated bundles.
Metadata Access
$bundle->getName(); // Presentation name (from embedded Song)
$bundle->getProFilename(); // "SongName.pro" (filename inside archive)
$bundle->getMediaFileCount(); // Number of media files
Presentation Access
// Get the Song wrapper (same API as ProFileReader)
$song = $bundle->getSong();
$song->getName();
$song->getUuid();
$song->getGroups();
$song->getSlides();
$song->getArrangements();
// Get the raw protobuf Presentation
$presentation = $bundle->getPresentation();
The Song object returned by getSong() has the same API as songs from ProFileReader::read(). See Song API for full details.
Media Files
// All media files: filename => raw bytes
$mediaFiles = $bundle->getMediaFiles();
foreach ($mediaFiles as $filename => $bytes) {
echo "$filename: " . strlen($bytes) . " bytes\n";
}
// Check if a specific media file exists
if ($bundle->hasMediaFile('background.png')) {
$bytes = $bundle->getMediaFile('background.png');
}
// Count
$bundle->getMediaFileCount(); // 0, 1, 2, ...
Media files are stored as flat filenames (no directories). The writer automatically flattens any paths to basename().
Creating Bundles
Build a PresentationBundle from a Song and media files:
use ProPresenter\Parser\PresentationBundle;
use ProPresenter\Parser\ProFileGenerator;
use ProPresenter\Parser\ProBundleWriter;
// Generate a song with a media slide (bundleRelative for portable bundles)
$song = ProFileGenerator::generate(
'My Presentation',
[
[
'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']]],
);
// Read the media file
$imageBytes = file_get_contents('/path/to/background.png');
// Create the bundle (flat filenames)
$bundle = new PresentationBundle(
$song,
'My Presentation.pro',
['background.png' => $imageBytes],
);
// Write to disk
ProBundleWriter::write($bundle, 'output.probundle');
Media Path Convention
Media entries use flat filenames (no directories):
$mediaFiles = [
'background.png' => $pngBytes,
'intro.mp4' => $mp4Bytes,
];
The writer flattens any paths to basename() automatically. The .pro protobuf uses ROOT_CURRENT_RESOURCE so PP7 resolves media relative to the bundle — no absolute paths needed.
bundleRelative Slide Option
Set 'bundleRelative' => true on media slides to use ROOT_CURRENT_RESOURCE instead of absolute filesystem paths:
// For bundles (portable — works on any machine)
['media' => 'image.png', 'format' => 'png', 'bundleRelative' => true]
// For standalone .pro files (uses absolute path with filesystem root detection)
['media' => 'file:///Users/me/Downloads/image.png', 'format' => 'png']
Writing Bundles
use ProPresenter\Parser\ProBundleWriter;
ProBundleWriter::write($bundle, 'output.probundle');
The writer:
- Creates a standard ZIP archive (deflate compression)
- Flattens media entries to
basename()— no directories in the ZIP - Writes media entries first,
.profile last - Uses atomic write (temp file + rename) for safety
Round-Trip Example
use ProPresenter\Parser\ProBundleReader;
use ProPresenter\Parser\ProBundleWriter;
// Read
$bundle = ProBundleReader::read('input.probundle');
// Inspect
echo "Name: " . $bundle->getName() . "\n";
echo "Media: " . $bundle->getMediaFileCount() . " files\n";
// Modify the presentation
$song = $bundle->getSong();
$song->setName("Modified Presentation");
// Write back
ProBundleWriter::write($bundle, 'output.probundle');
Error Handling
try {
$bundle = ProBundleReader::read('presentation.probundle');
} catch (\InvalidArgumentException $e) {
// File not found or empty path
echo "Error: " . $e->getMessage();
} catch (\RuntimeException $e) {
// Empty file, invalid ZIP, no .pro file found, or invalid protobuf
echo "Error: " . $e->getMessage();
}
Error Cases
| Condition | Exception | Message Pattern |
|---|---|---|
| File not found | InvalidArgumentException |
Bundle file not found: ... |
| Empty file | RuntimeException |
Bundle file is empty: ... |
| Invalid ZIP | RuntimeException |
Failed to open bundle archive: ... |
No .pro entry |
RuntimeException |
No .pro file found in bundle archive: ... |
| Target dir missing (write) | InvalidArgumentException |
Target directory does not exist: ... |
Key Files
| File | Purpose |
|---|---|
src/PresentationBundle.php |
Bundle wrapper (Song + media files) |
src/ProBundleReader.php |
Reads .probundle files (with Zip64Fixer) |
src/ProBundleWriter.php |
Writes .probundle files (standard ZIP) |
src/ProFileGenerator.php |
Generates .pro files with media support |
src/Zip64Fixer.php |
Fixes ProPresenter ZIP64 header bug |
doc/reference_samples/TestBild.probundle |
Generated reference file (PP7-verified) |
doc/reference_samples/RestBildExportFromPP.probundle |
PP7-exported reference file |
See Also
- Format Specification -- Binary format details
- Song API --
.profile handling (same Song object inside bundles) - Playlist API --
.proplaylistfile handling (similar ZIP pattern)