diff --git a/doc/api/bundle.md b/doc/api/bundle.md index b769dee..071e9ce 100644 --- a/doc/api/bundle.md +++ b/doc/api/bundle.md @@ -15,7 +15,7 @@ $bundle = ProBundleReader::read('path/to/presentation.probundle'); // Access $bundle->getName(); // Presentation name $bundle->getSong(); // Song wrapper -$bundle->getMediaFiles(); // ['path' => bytes, ...] +$bundle->getMediaFiles(); // ['filename' => bytes, ...] // Write ProBundleWriter::write($bundle, 'output.probundle'); @@ -65,22 +65,22 @@ The `Song` object returned by `getSong()` has the same API as songs from `ProFil ## Media Files ```php -// All media files: path => raw bytes +// All media files: filename => raw bytes $mediaFiles = $bundle->getMediaFiles(); -foreach ($mediaFiles as $path => $bytes) { - echo "$path: " . strlen($bytes) . " bytes\n"; +foreach ($mediaFiles as $filename => $bytes) { + echo "$filename: " . 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'); +if ($bundle->hasMediaFile('background.png')) { + $bytes = $bundle->getMediaFile('background.png'); } // Count $bundle->getMediaFileCount(); // 0, 1, 2, ... ``` -Media file paths are stored as absolute paths with a leading `/` (matching PP7 export format). +Media files are stored as flat filenames (no directories). The writer automatically flattens any paths to `basename()`. --- @@ -93,7 +93,7 @@ use ProPresenter\Parser\PresentationBundle; use ProPresenter\Parser\ProFileGenerator; use ProPresenter\Parser\ProBundleWriter; -// Generate a song with a media slide +// Generate a song with a media slide (bundleRelative for portable bundles) $song = ProFileGenerator::generate( 'My Presentation', [ @@ -102,9 +102,10 @@ $song = ProFileGenerator::generate( 'color' => [0.2, 0.2, 0.2, 1.0], 'slides' => [ [ - 'media' => 'file:///Users/me/Downloads/Media/background.png', + 'media' => 'background.png', 'format' => 'png', 'label' => 'background.png', + 'bundleRelative' => true, ], ], ], @@ -113,13 +114,13 @@ $song = ProFileGenerator::generate( ); // Read the media file -$imageBytes = file_get_contents('/Users/me/Downloads/Media/background.png'); +$imageBytes = file_get_contents('/path/to/background.png'); -// Create the bundle +// Create the bundle (flat filenames) $bundle = new PresentationBundle( $song, 'My Presentation.pro', - ['/Users/me/Downloads/Media/background.png' => $imageBytes], + ['background.png' => $imageBytes], ); // Write to disk @@ -128,16 +129,28 @@ ProBundleWriter::write($bundle, 'output.probundle'); ### Media Path Convention -Media entries use **absolute paths with a leading `/`**: +Media entries use **flat filenames** (no directories): ```php $mediaFiles = [ - '/Users/me/Downloads/Media/background.png' => $pngBytes, - '/Users/me/Downloads/Media/intro.mp4' => $mp4Bytes, + 'background.png' => $pngBytes, + 'intro.mp4' => $mp4Bytes, ]; ``` -This matches PP7's export format. The `file:///` URL in the `.pro` protobuf maps to these paths (strip `file://` prefix). +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: + +```php +// 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'] +``` --- @@ -151,6 +164,7 @@ 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, `.pro` file last - Uses atomic write (temp file + rename) for safety diff --git a/doc/formats/pp_bundle_spec.md b/doc/formats/pp_bundle_spec.md index e1f5ede..7afbd41 100644 --- a/doc/formats/pp_bundle_spec.md +++ b/doc/formats/pp_bundle_spec.md @@ -19,9 +19,13 @@ ### Container Structure - **Archive Type:** Standard ZIP with deflate compression (default) - **ZIP64 EOCD Quirk:** ProPresenter 7 exports have the same 98-byte EOCD discrepancy as `.proplaylist` files -- **Entry Layout:** - - Media files at **absolute paths with leading `/`** (e.g., `/Users/me/Downloads/Media/image.png`) +- **Entry Layout (library output — flat, portable):** + - Media files as **flat filenames** at ZIP root (e.g., `background.png`) - Single `.pro` file at root (filename only, no directory) + - Protobuf uses `ROOT_CURRENT_RESOURCE` to resolve media relative to the bundle +- **Entry Layout (PP7 export — absolute paths):** + - Media files at **absolute paths with leading `/`** (e.g., `/Users/me/Downloads/Media/image.png`) + - Single `.pro` file at root ### Purpose A `.probundle` packages a single ProPresenter presentation (`.pro` file) together with all its referenced media assets (images, videos, audio) into a single portable archive. This enables sharing presentations between machines without losing media references. @@ -35,22 +39,28 @@ A `.probundle` packages a single ProPresenter presentation (`.pro` file) togethe ## 2. Archive Structure -### Entry Layout +### Library Output (Flat — Portable) ``` -/Users/me/Downloads/pp-test/Media/background.png <-- Media file (absolute path with leading /) +background.png <-- Media file (flat filename, no directories) +SongName.pro <-- Protobuf-encoded presentation +``` + +Media entries use **flat filenames only** (no directories, no absolute paths). The `.pro` protobuf references media via `ROOT_CURRENT_RESOURCE`, which PP7 resolves relative to the bundle. This makes bundles fully portable across machines. + +### PP7 Export (Absolute Paths) + +``` +/Users/me/Downloads/pp-test/Media/background.png <-- Absolute path with leading / SongName.pro <-- Protobuf-encoded presentation ``` +PP7's own exports use absolute filesystem paths as ZIP entry names. The reader handles both formats. + ### Entry Order - **Media files first**, then the `.pro` file last - ProPresenter does not enforce order, but this matches PP7 export behavior -### Media Entry Naming -- Media entries use **absolute filesystem paths with a leading `/`** -- Standard unzip tools strip the leading `/` with a warning: `warning: stripped absolute path spec from /Users/.../image.png` -- This is intentional and matches PP7 export behavior - ### Compression - **ProPresenter exports:** Standard deflate compression - **Writer output:** Standard deflate compression (ZipArchive defaults) @@ -62,44 +72,30 @@ SongName.pro <-- Protobuf-encoded presen ### Media URL Format -Media references in the `.pro` protobuf must use `file:///` absolute URLs with a `LocalRelativePath` in `URL.local`. +#### Bundle-Relative (Library Output — Portable) -#### URL Structure +For bundles, media references use `ROOT_CURRENT_RESOURCE` with just the filename. PP7 resolves this relative to the bundle itself. ```protobuf message URL { - string absolute_string = 1; // "file:///Users/me/Downloads/Media/image.png" - LocalRelativePath local = 4; // Root + relative path + string absolute_string = 1; // "background.png" (just the filename) + LocalRelativePath local = 4; // ROOT_CURRENT_RESOURCE + filename Platform platform = 5; // PLATFORM_MACOS } ``` -#### LocalRelativePath - -```protobuf -message LocalRelativePath { - Root root = 1; // Enum mapping to macOS directory - string path = 2; // Relative path from root directory -} +``` +URL.absolute_string = "background.png" +URL.local.root = ROOT_CURRENT_RESOURCE (12) +URL.local.path = "background.png" +URL.platform = PLATFORM_MACOS ``` -### Root Type Mappings +Both `url` and `image.file.localUrl` use the same structure. -| Root Enum | Value | macOS Directory | -|-----------|-------|-----------------| -| `ROOT_BOOT_VOLUME` | 0 | `/` (fallback) | -| `ROOT_USER_HOME` | 2 | `~/` | -| `ROOT_USER_DOWNLOADS` | 4 | `~/Downloads/` | -| `ROOT_USER_DOCUMENTS` | 5 | `~/Documents/` | -| `ROOT_USER_DESKTOP` | 6 | `~/Desktop/` | -| `ROOT_USER_MUSIC` | 7 | `~/Music/` | -| `ROOT_USER_PICTURES` | 8 | `~/Pictures/` | -| `ROOT_USER_VIDEOS` | 9 | `~/Movies/` | -| `ROOT_SHOW` | 10 | ProPresenter library directory | +#### Absolute Paths (PP7 Export / Standalone `.pro`) -### Path Construction Example - -For media at `file:///Users/thorsten/Downloads/pp-test/Media/background.png`: +PP7's own exports and standalone `.pro` files use absolute `file:///` URLs with filesystem-based root mappings: ``` URL.absolute_string = "file:///Users/thorsten/Downloads/pp-test/Media/background.png" @@ -108,6 +104,33 @@ URL.local.path = "pp-test/Media/background.png" URL.platform = PLATFORM_MACOS ``` +#### LocalRelativePath + +```protobuf +message LocalRelativePath { + Root root = 1; // Enum mapping to macOS directory or bundle context + string path = 2; // Relative path from root +} +``` + +### Root Type Mappings + +| Root Enum | Value | macOS Directory | +|-----------|-------|-----------------| +| `ROOT_UNKNOWN` | 0 | Unknown | +| `ROOT_BOOT_VOLUME` | 1 | `/` (fallback) | +| `ROOT_USER_HOME` | 2 | `~/` | +| `ROOT_USER_DOCUMENTS` | 3 | `~/Documents/` | +| `ROOT_USER_DOWNLOADS` | 4 | `~/Downloads/` | +| `ROOT_USER_MUSIC` | 5 | `~/Music/` | +| `ROOT_USER_PICTURES` | 6 | `~/Pictures/` | +| `ROOT_USER_VIDEOS` | 7 | `~/Movies/` | +| `ROOT_USER_APP_SUPPORT` | 8 | `~/Library/Application Support/` | +| `ROOT_SHARED` | 9 | `/Users/Shared/` | +| `ROOT_SHOW` | 10 | ProPresenter library directory | +| `ROOT_USER_DESKTOP` | 11 | `~/Desktop/` | +| **`ROOT_CURRENT_RESOURCE`** | **12** | **Relative to current bundle/document** | + ### Media Metadata | Field | Expected Value | Notes | @@ -133,14 +156,15 @@ The writer produces standard ZIPs without the bug. PHP's `ZipArchive` creates cl ## 5. Differences from `.proplaylist` -| Aspect | `.proplaylist` | `.probundle` | -|--------|---------------|-------------| -| **Purpose** | Playlist with multiple songs | Single presentation with media | -| **Compression** | Store only (method 0) | Deflate (default) | -| **Metadata entry** | `data` file (protobuf `rv.data.Playlist`) | None (`.pro` file IS the data) | -| **Song entries** | Multiple `.pro` files | Single `.pro` file | -| **Media paths** | Absolute minus leading `/` | Absolute **with** leading `/` | -| **ZIP64** | Always ZIP64 | Standard ZIP (PP7 exports as ZIP64) | +| Aspect | `.proplaylist` | `.probundle` (library) | `.probundle` (PP7 export) | +|--------|---------------|----------------------|--------------------------| +| **Purpose** | Playlist with multiple songs | Single presentation with media | Single presentation with media | +| **Compression** | Store only (method 0) | Deflate (default) | Deflate | +| **Metadata entry** | `data` file (protobuf `rv.data.Playlist`) | None (`.pro` file IS the data) | None | +| **Song entries** | Multiple `.pro` files | Single `.pro` file | Single `.pro` file | +| **Media paths** | Absolute minus leading `/` | **Flat filenames** | Absolute with leading `/` | +| **Media URL root** | Filesystem-based roots | `ROOT_CURRENT_RESOURCE (12)` | Filesystem-based roots | +| **ZIP64** | Always ZIP64 | Standard ZIP | ZIP64 | --- @@ -151,8 +175,8 @@ The writer produces standard ZIPs without the bug. PHP's `ZipArchive` creates cl - **Use case:** Sharing a lyrics-only presentation. ### Multiple Media Files -- **Valid.** Each media file gets its own ZIP entry at its absolute path. -- **Deduplication:** Same path stored once. +- **Valid.** Each media file gets its own ZIP entry (flat filename). +- **Deduplication:** Same filename stored once. ### Non-Image Media - **Videos** (`.mp4`, `.mov`): Same URL format, different `Metadata.format`. @@ -171,17 +195,19 @@ The writer produces standard ZIPs without the bug. PHP's `ZipArchive` creates cl - **RestBildExportFromPP.probundle:** Exported by PP7 after import, used as comparison reference ### Key Discoveries -1. **Absolute paths with leading `/`:** PP7 stores media at full absolute filesystem paths in the ZIP, including the leading `/` +1. **`ROOT_CURRENT_RESOURCE` (12) enables portable bundles:** PP7 resolves this root relative to the bundle, so media stored as flat filenames in the ZIP are found on any machine 2. **`URL.local` is required:** PP7 cannot find media without the `LocalRelativePath` in `URL.local` -3. **`file:///` prefix required:** `URL.absolute_string` must use the `file:///` protocol prefix +3. **Flat filenames work:** ZIP entries like `image.png` (no directories) with `ROOT_CURRENT_RESOURCE` in the protobuf — PP7 finds the media 4. **Lowercase format:** PP7 uses lowercase format strings (`"png"` not `"PNG"`) -5. **Standard ZIP is fine:** PP7 imports standard deflate-compressed ZIPs without issues — the ZIP64/store/permission quirks in PP7 exports are artifacts, not requirements +5. **Standard ZIP is fine:** PP7 imports standard deflate-compressed ZIPs without issues 6. **ZIP64 EOCD bug:** PP7 exports have the same 98-byte offset quirk as `.proplaylist` files +7. **PP7 exports use absolute paths:** PP7's own exports use `file:///` absolute paths — but these only work on the same machine. The library uses `ROOT_CURRENT_RESOURCE` for portability instead. ### What Didn't Work (Rejected Approaches) -- **Relative media paths:** PP7 cannot resolve `Media/image.png` — needs absolute paths +- **`file:///filename.png` with `ROOT_BOOT_VOLUME`:** PP7 cannot resolve bare filenames with filesystem roots +- **`file:///Users/.../filename.png` with flat ZIP entry:** PP7 needs the URL root to match the ZIP structure +- **`ROOT_SHOW` with bare filename:** PP7 looks in its library dir, not the bundle - **Missing `URL.local`:** PP7 shows "image not found" without `LocalRelativePath` -- **Missing `file:///`:** Plain paths like `/Users/me/image.png` are not recognized - **Uppercase format:** `"PNG"` works but doesn't match PP7's own output - **Forced store compression / 000 permissions:** Unnecessary hacks that don't affect import diff --git a/php/bin/regen-test-bundles.php b/php/bin/regen-test-bundles.php index 7894cfe..842aff1 100644 --- a/php/bin/regen-test-bundles.php +++ b/php/bin/regen-test-bundles.php @@ -17,7 +17,6 @@ $imageBytes = file_get_contents($tmpPng); unlink($tmpPng); $refDir = dirname(__DIR__, 2) . '/ref'; -$mediaAbsPath = '/Users/thorsten/AI/propresenter/ref/Media/test-background.png'; $song = ProFileGenerator::generate( 'TestBild', @@ -27,8 +26,10 @@ $song = ProFileGenerator::generate( 'color' => [0.0, 0.0, 0.0, 1.0], 'slides' => [ [ - 'media' => 'file://' . $mediaAbsPath, + 'media' => 'test-background.png', 'format' => 'png', + 'label' => 'test-background.png', + 'bundleRelative' => true, ], ], ], @@ -36,6 +37,6 @@ $song = ProFileGenerator::generate( [['name' => 'normal', 'groupNames' => ['Verse 1']]], ); -$bundle = new PresentationBundle($song, 'TestBild.pro', [$mediaAbsPath => $imageBytes]); +$bundle = new PresentationBundle($song, 'TestBild.pro', ['test-background.png' => $imageBytes]); ProBundleWriter::write($bundle, $refDir . '/TestBild.probundle'); echo "TestBild.probundle written\n"; diff --git a/php/src/PresentationBundle.php b/php/src/PresentationBundle.php index 4c6ce24..9d1dbe8 100644 --- a/php/src/PresentationBundle.php +++ b/php/src/PresentationBundle.php @@ -13,16 +13,16 @@ use Rv\Data\Presentation; * together with all its referenced media assets (images, videos, audio). * This is the Pro7 successor to the Pro6 .pro6x format. * - * Archive layout: + * Archive layout (flat — no directories): + * image.jpg ← Media files (basename only) + * video.mp4 * SongName.pro ← Protobuf-encoded presentation - * Media/image.jpg ← Referenced media files - * Media/video.mp4 */ class PresentationBundle { private Song $song; - /** @var array relative path => raw bytes */ + /** @var array filename => raw bytes */ private array $mediaFiles; private string $proFilename; @@ -74,7 +74,7 @@ class PresentationBundle /** * All media files in the bundle. * - * @return array relative path => raw bytes + * @return array filename => raw bytes */ public function getMediaFiles(): array { diff --git a/php/src/ProBundleWriter.php b/php/src/ProBundleWriter.php index d9ce66f..a3075c3 100644 --- a/php/src/ProBundleWriter.php +++ b/php/src/ProBundleWriter.php @@ -33,7 +33,7 @@ final class ProBundleWriter $isOpen = true; foreach ($bundle->getMediaFiles() as $entryName => $contents) { - self::addEntry($zip, $entryName, $contents, $filePath); + self::addEntry($zip, basename($entryName), $contents, $filePath); } $proBytes = $bundle->getPresentation()->serializeToString(); diff --git a/php/src/ProFileGenerator.php b/php/src/ProFileGenerator.php index cd579ff..d2b462d 100644 --- a/php/src/ProFileGenerator.php +++ b/php/src/ProFileGenerator.php @@ -228,6 +228,7 @@ final class ProFileGenerator $mediaName, (int) ($slideData['mediaWidth'] ?? 0), (int) ($slideData['mediaHeight'] ?? 0), + (bool) ($slideData['bundleRelative'] ?? false), ); } @@ -281,17 +282,27 @@ final class ProFileGenerator return $action; } - private static function buildMediaAction(string $absoluteUrl, string $format, ?string $name = null, int $imageWidth = 0, int $imageHeight = 0): Action + private static function buildMediaAction(string $absoluteUrl, string $format, ?string $name = null, int $imageWidth = 0, int $imageHeight = 0, bool $bundleRelative = false): Action { - $url = new URL(); - $url->setAbsoluteString($absoluteUrl); - $url->setLocal(self::buildLocalRelativePath($absoluteUrl)); - $url->setPlatform(UrlPlatform::PLATFORM_MACOS); + if ($bundleRelative) { + $filename = basename($absoluteUrl); + $url = self::buildBundleRelativeUrl($filename); + $fileLocalUrl = self::buildBundleRelativeUrl($filename); + } else { + $url = new URL(); + $url->setAbsoluteString($absoluteUrl); + $url->setLocal(self::buildLocalRelativePath($absoluteUrl)); + $url->setPlatform(UrlPlatform::PLATFORM_MACOS); + + $fileLocalUrl = new URL(); + $fileLocalUrl->setAbsoluteString($absoluteUrl); + $fileLocalUrl->setLocal(self::buildLocalRelativePath($absoluteUrl)); + $fileLocalUrl->setPlatform(UrlPlatform::PLATFORM_MACOS); + } $metadata = new Metadata(); $metadata->setFormat($format); - // Build the image type properties with drawing + file $naturalSize = new Size(); $naturalSize->setWidth($imageWidth); $naturalSize->setHeight($imageHeight); @@ -310,11 +321,6 @@ final class ProFileGenerator $drawing->setCropInsets($cropInsets); $drawing->setAlphaType(AlphaType::ALPHA_TYPE_STRAIGHT); - $fileLocalUrl = new URL(); - $fileLocalUrl->setAbsoluteString($absoluteUrl); - $fileLocalUrl->setLocal(self::buildLocalRelativePath($absoluteUrl)); - $fileLocalUrl->setPlatform(UrlPlatform::PLATFORM_MACOS); - $fileProperties = new FileProperties(); $fileProperties->setLocalUrl($fileLocalUrl); @@ -346,6 +352,20 @@ final class ProFileGenerator return $action; } + private static function buildBundleRelativeUrl(string $filename): URL + { + $local = new LocalRelativePath(); + $local->setRoot(LocalRelativePath\Root::ROOT_CURRENT_RESOURCE); + $local->setPath($filename); + + $url = new URL(); + $url->setAbsoluteString($filename); + $url->setLocal($local); + $url->setPlatform(UrlPlatform::PLATFORM_MACOS); + + return $url; + } + private static function buildSlideElement(string $name, string $text, ?Rect $bounds = null): SlideElement { $graphicsElement = new GraphicsElement(); diff --git a/php/tests/ProBundleTest.php b/php/tests/ProBundleTest.php index 4dc6752..5459eac 100644 --- a/php/tests/ProBundleTest.php +++ b/php/tests/ProBundleTest.php @@ -105,7 +105,7 @@ class ProBundleTest extends TestCase $bundle = new PresentationBundle( $song, 'Bundle Test Song.pro', - ['Media/test-background.png' => $imageBytes], + ['test-background.png' => $imageBytes], ); $bundlePath = $this->tmpDir . '/BundleTestSong.probundle'; @@ -117,7 +117,7 @@ class ProBundleTest extends TestCase $zip = new ZipArchive(); $this->assertTrue($zip->open($bundlePath) === true); $this->assertNotFalse($zip->locateName('Bundle Test Song.pro')); - $this->assertNotFalse($zip->locateName('Media/test-background.png')); + $this->assertNotFalse($zip->locateName('test-background.png')); $this->assertSame(2, $zip->numFiles); $zip->close(); @@ -126,8 +126,8 @@ class ProBundleTest extends TestCase $this->assertSame('Bundle Test Song', $readBundle->getName()); $this->assertSame('Bundle Test Song.pro', $readBundle->getProFilename()); $this->assertSame(1, $readBundle->getMediaFileCount()); - $this->assertTrue($readBundle->hasMediaFile('Media/test-background.png')); - $this->assertSame($imageBytes, $readBundle->getMediaFile('Media/test-background.png')); + $this->assertTrue($readBundle->hasMediaFile('test-background.png')); + $this->assertSame($imageBytes, $readBundle->getMediaFile('test-background.png')); $readSong = $readBundle->getSong(); $this->assertSame('Bundle Test Song', $readSong->getName()); @@ -168,8 +168,8 @@ class ProBundleTest extends TestCase $song, 'Multi Media Song.pro', [ - 'Media/slide1.png' => $image1Bytes, - 'Media/slide2.png' => $image2Bytes, + 'slide1.png' => $image1Bytes, + 'slide2.png' => $image2Bytes, ], ); @@ -179,10 +179,10 @@ class ProBundleTest extends TestCase $readBundle = ProBundleReader::read($bundlePath); $this->assertSame(2, $readBundle->getMediaFileCount()); - $this->assertTrue($readBundle->hasMediaFile('Media/slide1.png')); - $this->assertTrue($readBundle->hasMediaFile('Media/slide2.png')); - $this->assertSame($image1Bytes, $readBundle->getMediaFile('Media/slide1.png')); - $this->assertSame($image2Bytes, $readBundle->getMediaFile('Media/slide2.png')); + $this->assertTrue($readBundle->hasMediaFile('slide1.png')); + $this->assertTrue($readBundle->hasMediaFile('slide2.png')); + $this->assertSame($image1Bytes, $readBundle->getMediaFile('slide1.png')); + $this->assertSame($image2Bytes, $readBundle->getMediaFile('slide2.png')); } #[Test] @@ -238,15 +238,13 @@ class ProBundleTest extends TestCase } #[Test] - public function writeProducesStandardZipWithAbsoluteMediaPaths(): void + public function writeProducesStandardZipWithFlatMediaPaths(): void { $imagePath = $this->tmpDir . '/bg.png'; $this->createTestPngImage($imagePath, 100, 100); $imageBytes = file_get_contents($imagePath); $this->assertNotFalse($imageBytes); - $mediaAbsPath = '/Users/test/Media/bg.png'; - $song = ProFileGenerator::generate( 'ZipFormatTest', [ @@ -255,8 +253,9 @@ class ProBundleTest extends TestCase 'color' => [0, 0, 0, 1], 'slides' => [ [ - 'media' => 'file://' . $mediaAbsPath, + 'media' => 'bg.png', 'format' => 'png', + 'bundleRelative' => true, ], ], ], @@ -267,7 +266,7 @@ class ProBundleTest extends TestCase $bundle = new PresentationBundle( $song, 'ZipFormatTest.pro', - [$mediaAbsPath => $imageBytes], + ['bg.png' => $imageBytes], ); $bundlePath = $this->tmpDir . '/ZipFormatTest.probundle'; @@ -277,8 +276,8 @@ class ProBundleTest extends TestCase $this->assertTrue($zip->open($bundlePath) === true); $this->assertSame(2, $zip->numFiles); - $mediaIdx = $zip->locateName($mediaAbsPath); - $this->assertNotFalse($mediaIdx, 'Media entry should use absolute path with leading /'); + $mediaIdx = $zip->locateName('bg.png'); + $this->assertNotFalse($mediaIdx, 'Media entry should use flat filename'); $proIdx = $zip->locateName('ZipFormatTest.pro'); $this->assertNotFalse($proIdx); @@ -288,8 +287,8 @@ class ProBundleTest extends TestCase $readBundle = ProBundleReader::read($bundlePath); $this->assertSame('ZipFormatTest', $readBundle->getName()); - $this->assertTrue($readBundle->hasMediaFile($mediaAbsPath)); - $this->assertSame($imageBytes, $readBundle->getMediaFile($mediaAbsPath)); + $this->assertTrue($readBundle->hasMediaFile('bg.png')); + $this->assertSame($imageBytes, $readBundle->getMediaFile('bg.png')); } #[Test] @@ -310,7 +309,7 @@ class ProBundleTest extends TestCase $bundle = new PresentationBundle( $song, 'Wrapper Test.pro', - ['Media/bg.jpg' => 'fake-jpeg-bytes'], + ['bg.jpg' => 'fake-jpeg-bytes'], ); $this->assertSame('Wrapper Test', $bundle->getName()); @@ -318,9 +317,9 @@ class ProBundleTest extends TestCase $this->assertSame($song, $bundle->getSong()); $this->assertSame($song->getPresentation(), $bundle->getPresentation()); $this->assertSame(1, $bundle->getMediaFileCount()); - $this->assertTrue($bundle->hasMediaFile('Media/bg.jpg')); - $this->assertSame('fake-jpeg-bytes', $bundle->getMediaFile('Media/bg.jpg')); - $this->assertSame(['Media/bg.jpg' => 'fake-jpeg-bytes'], $bundle->getMediaFiles()); + $this->assertTrue($bundle->hasMediaFile('bg.jpg')); + $this->assertSame('fake-jpeg-bytes', $bundle->getMediaFile('bg.jpg')); + $this->assertSame(['bg.jpg' => 'fake-jpeg-bytes'], $bundle->getMediaFiles()); } private function createTestPngImage(string $path, int $width, int $height): void diff --git a/php/tests/ProFileGeneratorTest.php b/php/tests/ProFileGeneratorTest.php index 7671d97..ba48bd1 100644 --- a/php/tests/ProFileGeneratorTest.php +++ b/php/tests/ProFileGeneratorTest.php @@ -1133,7 +1133,7 @@ class ProFileGeneratorTest extends TestCase $bundle = new PresentationBundle( $song, 'TestBild.pro', - ['Media/test-background.png' => $fakeImageBytes], + ['test-background.png' => $fakeImageBytes], ); $path = $this->tmpDir . '/TestBild.probundle'; @@ -1145,7 +1145,7 @@ class ProFileGeneratorTest extends TestCase $this->assertSame('TestBild', $read->getName()); $this->assertSame('TestBild.pro', $read->getProFilename()); $this->assertSame(1, $read->getMediaFileCount()); - $this->assertTrue($read->hasMediaFile('Media/test-background.png')); + $this->assertTrue($read->hasMediaFile('test-background.png')); // UUID preserved through serialization $this->assertSame( diff --git a/ref/TestBild.probundle b/ref/TestBild.probundle index bfcf631..ead643b 100644 Binary files a/ref/TestBild.probundle and b/ref/TestBild.probundle differ