propresenter-php/doc/api/bundle.md

5.9 KiB

Bundle Parser API

PHP module for reading, modifying, and writing ProPresenter .probundle files.

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();     // ['path' => 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: path => raw bytes
$mediaFiles = $bundle->getMediaFiles();
foreach ($mediaFiles as $path => $bytes) {
    echo "$path: " . strlen($bytes) . " bytes\n";
}

// Check if a specific media file exists
if ($bundle->hasMediaFile('/Users/me/Downloads/Media/image.png')) {
    $bytes = $bundle->getMediaFile('/Users/me/Downloads/Media/image.png');
}

// Count
$bundle->getMediaFileCount();  // 0, 1, 2, ...

Media file paths are stored as absolute paths with a leading / (matching PP7 export format).


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
$song = ProFileGenerator::generate(
    'My Presentation',
    [
        [
            'name' => 'Background',
            'color' => [0.2, 0.2, 0.2, 1.0],
            'slides' => [
                [
                    'media' => 'file:///Users/me/Downloads/Media/background.png',
                    'format' => 'png',
                    'label' => 'background.png',
                ],
            ],
        ],
    ],
    [['name' => 'normal', 'groupNames' => ['Background']]],
);

// Read the media file
$imageBytes = file_get_contents('/Users/me/Downloads/Media/background.png');

// Create the bundle
$bundle = new PresentationBundle(
    $song,
    'My Presentation.pro',
    ['/Users/me/Downloads/Media/background.png' => $imageBytes],
);

// Write to disk
ProBundleWriter::write($bundle, 'output.probundle');

Media Path Convention

Media entries use absolute paths with a leading /:

$mediaFiles = [
    '/Users/me/Downloads/Media/background.png' => $pngBytes,
    '/Users/me/Downloads/Media/intro.mp4' => $mp4Bytes,
];

This matches PP7's export format. The file:/// URL in the .pro protobuf maps to these paths (strip file:// prefix).


Writing Bundles

use ProPresenter\Parser\ProBundleWriter;

ProBundleWriter::write($bundle, 'output.probundle');

The writer:

  • Creates a standard ZIP archive (deflate compression)
  • Writes media entries first, .pro file 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
php/src/PresentationBundle.php Bundle wrapper (Song + media files)
php/src/ProBundleReader.php Reads .probundle files (with Zip64Fixer)
php/src/ProBundleWriter.php Writes .probundle files (standard ZIP)
php/src/ProFileGenerator.php Generates .pro files with media support
php/src/Zip64Fixer.php Fixes ProPresenter ZIP64 header bug
ref/TestBild.probundle Generated reference file (PP7-verified)
ref/RestBildExportFromPP.probundle PP7-exported reference file

See Also