feat(bundle): use ROOT_CURRENT_RESOURCE for portable flat media bundles
BREAKING: Bundle media entries are now flat filenames (no directories). ProBundleWriter flattens all media paths to basename() automatically. ProFileGenerator supports bundleRelative flag for ROOT_CURRENT_RESOURCE URLs, enabling bundles that work on any machine without absolute paths.
This commit is contained in:
parent
95a2b6984e
commit
8dbcc1bafc
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<string, string> relative path => raw bytes */
|
||||
/** @var array<string, string> filename => raw bytes */
|
||||
private array $mediaFiles;
|
||||
|
||||
private string $proFilename;
|
||||
|
|
@ -74,7 +74,7 @@ class PresentationBundle
|
|||
/**
|
||||
* All media files in the bundle.
|
||||
*
|
||||
* @return array<string, string> relative path => raw bytes
|
||||
* @return array<string, string> filename => raw bytes
|
||||
*/
|
||||
public function getMediaFiles(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue