softspring / media-bundle
A media management bundle
Package info
github.com/softspring/media-bundle
Type:symfony-bundle
pkg:composer/softspring/media-bundle
Requires
- php: >=8.1
- ext-gd: *
- doctrine/doctrine-bundle: ^2.6
- doctrine/migrations: ^3.2
- doctrine/orm: ^2.14 | ^3.0
- google/cloud-storage: ^1.20
- imagine/imagine: ^1.3
- softspring/crudl-controller: ^5.6@dev
- softspring/doctrine-paginator: ^5.6@dev
- softspring/doctrine-query-filters: ^5.6@dev
- softspring/doctrine-target-entity-resolver: ^5.6@dev
- softspring/events: ^5.6@dev
- softspring/permissions-bundle: ^5.6@dev
- softspring/time-ago-bundle: ^5.6@dev
- softspring/translatable-bundle: ^5.6@dev
- symfony/config: ^5.4|^6.0|^7.0
- symfony/dependency-injection: ^5.4|^6.0|^7.0
- symfony/form: ^5.4|^6.0|^7.0
- symfony/http-kernel: ^5.4|^6.0|^7.0
- symfony/routing: ^5.4|^6.0|^7.0
- symfony/security-bundle: ^5.4|^6.0|^7.0
- symfony/translation: ^5.4|^6.0|^7.0
- symfony/twig-bundle: ^5.4|^6.0|^7.0
- symfony/validator: ^5.4|^6.0|^7.0
- twig/twig: ^2.10|^3.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.68
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.5.3
- rector/rector: ^1.0
- softspring/cms-bundle: ^5.6@dev
Suggests
- google/cloud-storage: Required to integrate media storage in GCS
- softspring/crudl-controller: Required if you want to enable admin controllers
Provides
None
Conflicts
None
Replaces
None
- 6.0.x-dev
- 5.6.x-dev
- v5.6.0
- 5.5.x-dev
- v5.5.13
- v5.5.12
- v5.5.11
- v5.5.10
- v5.5.9
- v5.5.8
- v5.5.7
- v5.5.6
- v5.5.5
- v5.5.4
- v5.5.3
- v5.5.2
- v5.5.1
- v5.5.0
- v5.5.0-rc9
- v5.5.0-rc8
- v5.5.0-rc7
- v5.5.0-rc6
- v5.5.0-rc5
- v5.5.0-rc4
- v5.5.0-rc3
- v5.5.0-rc2
- v5.5.0-rc1
- 5.4.x-dev
- v5.4.14
- v5.4.13
- v5.4.12
- v5.4.11
- v5.4.10
- v5.4.9
- v5.4.8
- v5.4.7
- v5.4.6
- v5.4.5
- v5.4.4
- v5.4.3
- v5.4.2
- v5.4.1
- v5.4.0
- v5.4.0-rc6
- v5.4.0-rc5
- v5.4.0-rc4
- v5.4.0-rc3
- v5.4.0-rc2
- v5.4.0-rc1
- v5.4.0-beta6
- v5.4.0-beta5
- v5.4.0-beta4
- v5.4.0-beta3
- v5.4.0-beta2
- v5.4.0-beta1
- v5.4.0-alpha10
- v5.4.0-alpha9
- v5.4.0-alpha8
- v5.4.0-alpha3
- v5.4.0-alpha2
- v5.4.0-alpha1
- 5.3.x-dev
- v5.3.15
- v5.3.14
- v5.3.13
- v5.3.12
- v5.3.11
- v5.3.10
- v5.3.9
- v5.3.8
- v5.3.7
- v5.3.6
- v5.3.5
- v5.3.4
- v5.3.3
- v5.3.2
- v5.3.1
- v5.3.0
- v5.3.0-rc9
- v5.3.0-rc8
- v5.3.0-rc7
- v5.3.0-rc6
- v5.3.0-rc5
- v5.3.0-rc4
- v5.3.0-rc3
- v5.3.0-rc2
- v5.3.0-rc1
- v5.3.0-beta4
- v5.3.0-beta3
- v5.3.0-beta2
- v5.3.0-beta1
- v5.3.0-alpha21
- 5.2.x-dev
- v5.2.7
- v5.2.6
- v5.2.5
- v5.2.4
- v5.2.3
- v5.2.2
- v5.2.1
- v5.2.0
- v5.2.0-rc10
- v5.2.0-rc9
- v5.2.0-rc8
- v5.2.0-rc7
- v5.2.0-rc6
- v5.2.0-rc5
- v5.2.0-rc4
- v5.2.0-rc1
- 5.1.x-dev
- v5.1.25
- v5.1.24
- v5.1.23
- v5.1.22
- v5.1.21
- v5.1.20
- v5.1.19
- v5.1.18
- v5.1.17
- v5.1.16
- v5.1.15
- v5.1.14
- v5.1.13
- v5.1.12
- v5.1.11
- v5.1.10
- v5.1.9
- v5.1.8
- v5.1.7
- v5.1.6
- v5.1.5
- v5.1.2
- v5.1.1
- v5.1.0
- v5.0.6
- v5.0.5
- v5.0.4
- v5.0.3
- v5.0.2
- v5.0.1
- v5.0.0
- v5.0.0-rc1
- v5.0.0-beta1
- v5.0.0-alpha4
- v5.0.0-alpha3
- v5.0.0-alpha2
- v5.0.0-alpha1
- dev-feature/admin-media-regeneration
- dev-feature/force-media-version-regeneration
- dev-feature/parallel-media-migrations
This package is auto-updated.
Last update: 2026-10-23 11:18:00 UTC
README
Media library, media versioning, media rendering, and admin media management for Symfony applications.
Parallel media migrations
The migration command defaults to a single process. Start with two workers and increase only after measuring CPU and memory usage, especially with animated media and AVIF (the encoders may also use multiple threads).
php bin/console sfs:media:types-migration --workers=2 --batch-size=100 php bin/console sfs:media:types-migration --workers=2 --type=background --dry-run php bin/console sfs:media:types-migration --workers=4 --type=background --force --dry-run php bin/console sfs:media:types-migration --workers=4 --type=background --force --no-debug
Workers partition media IDs deterministically. A media and all its versions are processed by one worker, in version order. IDs are paginated and Doctrine's identity map is cleared after each media. Each worker reports timings and counts; the parent returns a nonzero exit code if any worker fails. Dry runs report version changes without modifying media or storage. Existing matching versions are not regenerated; rerunning checks the remaining differences.
Use --force to rebuild generated versions even when their configuration is
unchanged, for example after updating image codecs. It preserves _original and
configured manual/uploaded versions. Normal migration cleanup of obsolete
versions still applies. Combine it with --type to limit its scope and with
--dry-run to inspect the plan without writing anything. Without --dry-run,
forced regeneration replaces generated versions and uploads their new files;
it can be expensive. Force is opt-in and is forwarded to every worker.
Distributed runners can use --worker-index=0 --worker-count=4 (indexes 0–3),
instead of --workers. Every task must use the same configuration, filters and
worker count, and a unique index. Do not run overlapping migration jobs against
the same media concurrently: partitioning is not a cross-job lock. Do not change
media type configuration during a run. No Messenger worker or PHP threading
extension is required; local workers use Symfony Process and the project's
bin/console. The worker count must fit the resources allocated to the container.
Regenerate versions from the admin
The media detail page includes Regenerate versions when the user has the media migration permission and the media has an original file and generated versions. This opens a confirmation page listing the generated version keys. Only a confirmed, CSRF-protected POST starts the synchronous regeneration. Originals and configured manual uploads are preserved; generated versions are rebuilt even when their options already match. Normal cleanup of obsolete versions still applies. On completion the user returns to the media detail page with a success/error notification. Large animations may take time: wait for the result before retrying. The standard and Sylius admin templates are supported.
What It Provides
- configurable media types for images and videos
- generated and manual media versions
- filesystem and Google Cloud Storage drivers
- Twig rendering helpers for images, pictures, videos, and video sets
- admin media library screens
- migration tools when type definitions change
Static image processing driver
Static generated images use Imagick by default. Override the default globally, per media type or per version (version > type > global):
sfs_media: imagine: driver: imagick # imagick | gd allow_upscale: false resize_tolerance: 0.02 preserve_original_encoding: true types: content: imagine_driver: gd versions: small: type: webp # inherits gd scale_width: 400 large: type: avif imagine_driver: imagick scale_width: 1200
Install ext-imagick and the ImageMagick codecs used by your sources and outputs.
On Alpine, AVIF support requires imagemagick-heic (libheif), in addition to the
PHP Imagick extension. Configuration checks the selected driver and its codecs;
there is no silent fallback to GD. Set imagine.driver: gd to keep using GD.
The resolved imagine_driver is stored in each static generated version's options,
so adding or changing the effective driver is detected by the media migration
checker, including existing versions created before this option existed. Run the
normal media migration explicitly to regenerate files; changing configuration
does not itself run a migration. Type/global driver changes do not affect versions
with an explicit override. Animated FFmpeg versions and manual uploads do not
inherit an Imagine driver. resampling-filter is passed to the resize operation;
Imagick supports configurable filters, while GD keeps its native resampling.
The three resize/encoding options follow the same global → type → version inheritance (use their names directly on a type or version). Their effective values are also persisted and checked for regeneration:
allow_upscale: falsecaps dimensions at the source size.resize_tolerance: 0.02skips proportional reductions of 2% or less. For example, a 1127px source requested at 1120px stays at 1127px. Set0for strict resizing. Tolerance does not apply when both width and height are explicitly requested.preserve_original_encoding: truekeeps an independent byte-for-byte copy when dimensions and format remain unchanged, unless flattening or an explicit output resolution is requested. Quality settings do not force recompression in this case; set this option tofalseto force encoding. Format conversions and larger reductions still encode normally.
Picture width descriptors use the actual generated width and duplicate widths within a source are removed. These options apply only to static generated images, not animated FFmpeg versions or manual uploads. Existing stored files are unchanged until a media migration/regeneration is explicitly run.
Delayed Google Cloud Storage deletion
Set sfs_media.google_cloud_storage.delayed_deletion_days to a positive integer to keep a removed object available at its existing URL until the configured delay has elapsed:
sfs_media: google_cloud_storage: bucket: media-bucket delayed_deletion_days: 90
The driver sets the object's Custom-Time to the delayed deletion date. Configure the bucket with this Google Cloud Storage lifecycle rule, without a matchesPrefix condition:
{
"rule": [
{
"action": {"type": "Delete"},
"condition": {"daysSinceCustomTime": 0}
}
]
}
For example, save it as lifecycle.json and apply it with:
gcloud storage buckets update gs://media-bucket --lifecycle-file=lifecycle.json
Without that lifecycle rule, marked objects are not deleted automatically.
This rule only affects objects with Custom-Time set. Ensure that no other process using the same bucket sets Custom-Time, or its objects will also be eligible for deletion when that time is reached.
Armonic
This package is part of Armonic.
Documentation
Configured media selections
Media fields restrict both their modal choices and submitted values to
media_types. For example, content: { picture: two_columns } exposes only
picture#two_columns, not raw image versions or other picture sets. Image,
video, picture and video-set choices follow the configured order and must exist
on the selected media. The version selector is hidden for zero or one choice.
Existing invalid selections remain visible as invalid; migrate saved module data
before expecting it to validate under a narrower allow-list. Rebuild/install the
bundle's assets when updating.
Picture display dimensions
By default, a rendered <picture> takes its <img> width and height from img.src_version. When that fallback has different dimensions from the responsive sources, use img.dimensions_version to provide the intrinsic display size without changing the fallback URL:
pictures: card: sources: - { srcset: [ { version: card, suffix: '1x' }, { version: card_retina, suffix: '2x' } ] } img: src_version: fallback dimensions_version: card
Explicit image attributes passed to the renderer still take precedence over the configured dimensions version.
Animated image versions
Generated animated versions use ffprobe to validate the source and ffmpeg to resize and encode every frame. Both binaries must be installed in the application runtime. Processing is synchronous; applications should keep duration and frame limits conservative until background generation is available.
sfs_media: ffmpeg: binary: ffmpeg probe_binary: ffprobe timeout: 300 types: animation: upload_requirements: mimeTypes: [image/avif, image/apng, image/gif] versions: small_avif: type: avif animated: true scale_width: 400 avif_quality: 82 animation: loop: 0 speed: 6 max_duration: 3 max_frames: 75 small_webp: type: webp animated: true scale_width: 400 webp_quality: 82 animation: loop: 0 max_duration: 3 max_frames: 75
Supported generated targets are AVIF, WebP and APNG. type: keep also preserves animated GIF input. Source timing and frame rate are preserved unless animation.fps is set.
Animated WebP outputs use independent full frames. This is slightly larger than lossy partial-frame animation, but avoids accumulated composition errors that appear as trails or stripes in browsers.
When at least one version uses animated: true, configuration compilation checks that the configured ffmpeg and ffprobe binaries are executable. It fails early with their configured names and installation instructions if either binary is missing. Types without animated versions do not require FFmpeg.
FFmpeg 6.1, currently shipped by Alpine 3.22, can encode animated WebP but cannot reliably demux it as an input. Use AVIF, APNG or GIF uploads with that runtime. Animated WebP can be enabled as an upload source when the deployed FFmpeg build includes its newer animated WebP demuxer.
Animation options:
fps: resample the output frame rate, from 1 to 120.loop: output loop count;0means infinite.crf: AVIF constant-quality value, from 0 to 63. When omitted, it is derived fromavif_quality.speed: AVIF encoding speed (libaom-av1cpu-used), from 0 to 8.keyframe_interval: encoder GOP size.max_duration: reject longer source animations, in seconds.max_frames: reject sources containing more frames.
The processor only handles generated versions explicitly marked with animated: true. Manual versions keep their uploaded file, and regular generated images use the configured Imagine driver.
Create from an existing original
The media detail screen offers a Create from this type dropdown. It creates a
new media from the source's _original, copying its name, description and all alt
translations. Original and generated files belong to the new media; deleting or
editing either media does not modify the other's files or translations.
Destinations must be public image types other than the source type, satisfy the original's stored dimensions, orientation, ratio, pixels, MIME type and size requirements, and have no independently uploaded versions. The downloaded file is validated again against the destination's upload requirements before creation. Generation uses the existing synchronous processing pipeline. The POST action requires create/read permissions and a source-specific CSRF token.
Package Files
Contributing
Use the standard package commands before sending changes:
composer fix
composer test
composer test-bc
Report issues and send Pull Requests
Security
Please report vulnerabilities privately. See SECURITY.md.
License
This package is free and released under the AGPL-3.0 license.