media-optimization-engine 1.4.1__tar.gz → 1.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {media_optimization_engine-1.4.1/media_optimization_engine.egg-info → media_optimization_engine-1.4.2}/PKG-INFO +11 -1
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/README.md +10 -0
- media_optimization_engine-1.4.2/media_engine/management/commands/backfill_registered_media.py +72 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/storage_paths.py +1 -1
- media_optimization_engine-1.4.2/media_engine/tests/test_backfill_registered_media.py +51 -0
- media_optimization_engine-1.4.2/media_engine/tests/test_validators.py +30 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/validators.py +7 -1
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2/media_optimization_engine.egg-info}/PKG-INFO +11 -1
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_optimization_engine.egg-info/SOURCES.txt +2 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/pyproject.toml +1 -1
- media_optimization_engine-1.4.1/media_engine/management/commands/backfill_registered_media.py +0 -21
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/LICENSE +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/flutter/README.md +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/flutter/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/flutter/models.dart +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/flutter/standard_images.dart +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/marzipano/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/marzipano/manifest.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/pannellum/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/pannellum/manifest.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/threejs/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/adapters/threejs/manifest.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/admin.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/apps.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/auth.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/autoregister.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/control_plane.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/doctor.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/focal_detectors.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/health.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/image_ops.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/integration.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/locks.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/audit_media_engine.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/audit_panorama_media.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/backfill_panorama_media.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/backfill_responsive_images.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/cleanup_image_derivatives.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/media_engine_doctor.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/media_engine_node_report.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/rollback_media_engine_config.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/management/commands/sync_media_engine_hub.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/manifest.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/metrics.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/migrations/0001_initial.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/migrations/0002_panorama_multires.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/migrations/0003_panorama_cube_tiles.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/migrations/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/models.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/node.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/base.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/image/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/image/standard.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/cube_tiles.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/cubemap.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/detect.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/equirectangular.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/manifest.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/panorama/tiles.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/registry.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/profiles.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/queueing.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/registry.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/runtime_config.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/serializers.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/services.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/signals.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/spec/openapi.yaml +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/static/media_engine/adaptive-media.js +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/static/media_engine/media-cache-sw.js +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/system_views.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tasks.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/templatetags/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/templatetags/responsive_media.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/__init__.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_distributed_node.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_image_ops.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_panorama.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_paths.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_profiles.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_responsive_media.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/urls.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/views.py +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_optimization_engine.egg-info/dependency_links.txt +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_optimization_engine.egg-info/entry_points.txt +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_optimization_engine.egg-info/requires.txt +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_optimization_engine.egg-info/top_level.txt +0 -0
- {media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: media-optimization-engine
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Media Optimization Engineer for Django/Python: automatic model-field optimization, responsive picture/srcset rendering, AVIF/WebP derivatives, 360 media, autonomous nodes, and CDN-ready storage.
|
|
5
5
|
Author-email: Achille Kabasele <pepexykabasele@gmail.com>
|
|
6
6
|
Maintainer-email: Achille Kabasele <pepexykabasele@gmail.com>
|
|
@@ -233,6 +233,16 @@ JavaScript is not required for normal responsive images.
|
|
|
233
233
|
|
|
234
234
|
Runtime measurement is appropriate only for highly dynamic components whose rendered width cannot be described reliably with sizes, such as resizable editors, canvas-like builders, or asynchronously mounted panels.
|
|
235
235
|
|
|
236
|
+
## Docker image
|
|
237
|
+
|
|
238
|
+
The standalone service is also published as a multi-architecture GHCR image:
|
|
239
|
+
|
|
240
|
+
~~~bash
|
|
241
|
+
docker pull ghcr.io/kabasele754/media-optimization-engine:1.4.1
|
|
242
|
+
~~~
|
|
243
|
+
|
|
244
|
+
For Compose deployment, see `docs/DOCKER.md` and `docker-compose.ghcr.yml`.
|
|
245
|
+
|
|
236
246
|
## Flutter / native clients
|
|
237
247
|
|
|
238
248
|
MOE also ships reference Dart adapters for native applications.
|
|
@@ -177,6 +177,16 @@ JavaScript is not required for normal responsive images.
|
|
|
177
177
|
|
|
178
178
|
Runtime measurement is appropriate only for highly dynamic components whose rendered width cannot be described reliably with sizes, such as resizable editors, canvas-like builders, or asynchronously mounted panels.
|
|
179
179
|
|
|
180
|
+
## Docker image
|
|
181
|
+
|
|
182
|
+
The standalone service is also published as a multi-architecture GHCR image:
|
|
183
|
+
|
|
184
|
+
~~~bash
|
|
185
|
+
docker pull ghcr.io/kabasele754/media-optimization-engine:1.4.1
|
|
186
|
+
~~~
|
|
187
|
+
|
|
188
|
+
For Compose deployment, see `docs/DOCKER.md` and `docker-compose.ghcr.yml`.
|
|
189
|
+
|
|
180
190
|
## Flutter / native clients
|
|
181
191
|
|
|
182
192
|
MOE also ships reference Dart adapters for native applications.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from django.core.exceptions import ValidationError
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from media_engine.integration import ingest_model_field
|
|
4
|
+
from media_engine.registry import entries
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Command(BaseCommand):
|
|
8
|
+
help = 'Ingest image fields declared through register_model_image().'
|
|
9
|
+
|
|
10
|
+
def add_arguments(self, parser):
|
|
11
|
+
parser.add_argument('--limit', type=int)
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
'--fail-fast',
|
|
14
|
+
action='store_true',
|
|
15
|
+
help='Stop immediately on the first invalid/unreadable image.',
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def handle(self, *args, **options):
|
|
19
|
+
processed = 0
|
|
20
|
+
skipped = 0
|
|
21
|
+
failed = 0
|
|
22
|
+
failures = []
|
|
23
|
+
|
|
24
|
+
for entry in entries():
|
|
25
|
+
qs = entry.model._default_manager.all()
|
|
26
|
+
if options['limit']:
|
|
27
|
+
qs = qs[:options['limit']]
|
|
28
|
+
|
|
29
|
+
for obj in qs.iterator():
|
|
30
|
+
try:
|
|
31
|
+
asset = ingest_model_field(
|
|
32
|
+
obj,
|
|
33
|
+
field_name=entry.field_name,
|
|
34
|
+
profile=entry.profile,
|
|
35
|
+
role=entry.role,
|
|
36
|
+
)
|
|
37
|
+
if asset is None:
|
|
38
|
+
skipped += 1
|
|
39
|
+
else:
|
|
40
|
+
processed += 1
|
|
41
|
+
except ValidationError as exc:
|
|
42
|
+
failed += 1
|
|
43
|
+
message = (
|
|
44
|
+
f'{entry.model._meta.label}.{entry.field_name} '
|
|
45
|
+
f'pk={obj.pk}: {exc}'
|
|
46
|
+
)
|
|
47
|
+
failures.append(message)
|
|
48
|
+
self.stderr.write(self.style.WARNING(f'SKIP {message}'))
|
|
49
|
+
if options['fail_fast']:
|
|
50
|
+
raise
|
|
51
|
+
except (FileNotFoundError, OSError, ValueError) as exc:
|
|
52
|
+
failed += 1
|
|
53
|
+
message = (
|
|
54
|
+
f'{entry.model._meta.label}.{entry.field_name} '
|
|
55
|
+
f'pk={obj.pk}: {exc}'
|
|
56
|
+
)
|
|
57
|
+
failures.append(message)
|
|
58
|
+
self.stderr.write(self.style.WARNING(f'SKIP {message}'))
|
|
59
|
+
if options['fail_fast']:
|
|
60
|
+
raise
|
|
61
|
+
|
|
62
|
+
summary = (
|
|
63
|
+
f'Processed {processed} registered model images; '
|
|
64
|
+
f'skipped {skipped}; failures {failed}.'
|
|
65
|
+
)
|
|
66
|
+
if failed:
|
|
67
|
+
self.stdout.write(self.style.WARNING(summary))
|
|
68
|
+
self.stdout.write('Last failures:')
|
|
69
|
+
for message in failures[-20:]:
|
|
70
|
+
self.stdout.write(f' - {message}')
|
|
71
|
+
else:
|
|
72
|
+
self.stdout.write(self.style.SUCCESS(summary))
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/storage_paths.py
RENAMED
|
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def safe_extension(fmt):
|
|
5
|
-
return {'JPEG': 'jpg', 'JPG': 'jpg', 'PNG': 'png', 'WEBP': 'webp', 'AVIF': 'avif'}.get(fmt.upper(), fmt.lower())
|
|
5
|
+
return {'JPEG': 'jpg', 'JPG': 'jpg', 'MPO': 'jpg', 'PNG': 'png', 'WEBP': 'webp', 'AVIF': 'avif'}.get(fmt.upper(), fmt.lower())
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def original_storage_name(sha256_hex, extension):
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
from django.core.management import call_command
|
|
6
|
+
from django.test import SimpleTestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _Manager:
|
|
10
|
+
def __init__(self, objects):
|
|
11
|
+
self._objects = list(objects)
|
|
12
|
+
|
|
13
|
+
def all(self):
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __iter__(self):
|
|
17
|
+
return iter(self._objects)
|
|
18
|
+
|
|
19
|
+
def __getitem__(self, item):
|
|
20
|
+
if isinstance(item, slice):
|
|
21
|
+
return _Manager(self._objects[item])
|
|
22
|
+
return self._objects[item]
|
|
23
|
+
|
|
24
|
+
def iterator(self):
|
|
25
|
+
return iter(self._objects)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BackfillRegisteredMediaTests(SimpleTestCase):
|
|
29
|
+
@patch("media_engine.management.commands.backfill_registered_media.entries")
|
|
30
|
+
@patch("media_engine.management.commands.backfill_registered_media.ingest_model_field")
|
|
31
|
+
def test_invalid_image_does_not_abort_backfill(self, ingest, entries):
|
|
32
|
+
model = SimpleNamespace(
|
|
33
|
+
_default_manager=_Manager([
|
|
34
|
+
SimpleNamespace(pk=1),
|
|
35
|
+
SimpleNamespace(pk=2),
|
|
36
|
+
]),
|
|
37
|
+
_meta=SimpleNamespace(label="demo.Photo"),
|
|
38
|
+
)
|
|
39
|
+
entries.return_value = [
|
|
40
|
+
SimpleNamespace(
|
|
41
|
+
model=model,
|
|
42
|
+
field_name="image",
|
|
43
|
+
profile="default",
|
|
44
|
+
role="content",
|
|
45
|
+
)
|
|
46
|
+
]
|
|
47
|
+
ingest.side_effect = [ValidationError("Unsupported image format"), object()]
|
|
48
|
+
|
|
49
|
+
call_command("backfill_registered_media")
|
|
50
|
+
|
|
51
|
+
self.assertEqual(ingest.call_count, 2)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
5
|
+
from django.test import SimpleTestCase
|
|
6
|
+
|
|
7
|
+
from media_engine.validators import inspect_upload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MpoValidationTests(SimpleTestCase):
|
|
11
|
+
@patch("media_engine.validators.Image.open")
|
|
12
|
+
def test_mpo_is_normalized_as_jpeg(self, image_open):
|
|
13
|
+
image = MagicMock()
|
|
14
|
+
image.format = "MPO"
|
|
15
|
+
image.size = (1200, 800)
|
|
16
|
+
image.verify.return_value = None
|
|
17
|
+
image_open.return_value = image
|
|
18
|
+
|
|
19
|
+
upload = SimpleUploadedFile(
|
|
20
|
+
"portrait.jpg",
|
|
21
|
+
b"fake-mpo-container",
|
|
22
|
+
content_type="image/jpeg",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
raw, fmt, mime, width, height = inspect_upload(upload)
|
|
26
|
+
|
|
27
|
+
self.assertEqual(raw, b"fake-mpo-container")
|
|
28
|
+
self.assertEqual(fmt, "JPEG")
|
|
29
|
+
self.assertEqual(mime, "image/jpeg")
|
|
30
|
+
self.assertEqual((width, height), (1200, 800))
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/validators.py
RENAMED
|
@@ -7,9 +7,10 @@ try:
|
|
|
7
7
|
except Exception:
|
|
8
8
|
pillow_avif = None
|
|
9
9
|
|
|
10
|
-
ALLOWED_FORMATS = {'JPEG', 'PNG', 'WEBP', 'AVIF'}
|
|
10
|
+
ALLOWED_FORMATS = {'JPEG', 'PNG', 'WEBP', 'AVIF', 'MPO'}
|
|
11
11
|
MIME_BY_FORMAT = {
|
|
12
12
|
'JPEG': 'image/jpeg',
|
|
13
|
+
'MPO': 'image/jpeg',
|
|
13
14
|
'PNG': 'image/png',
|
|
14
15
|
'WEBP': 'image/webp',
|
|
15
16
|
'AVIF': 'image/avif',
|
|
@@ -31,6 +32,11 @@ def inspect_upload(uploaded_file):
|
|
|
31
32
|
fmt = (image.format or '').upper()
|
|
32
33
|
if fmt not in ALLOWED_FORMATS:
|
|
33
34
|
raise ValidationError(f'Unsupported image format: {fmt or "unknown"}')
|
|
35
|
+
# Pillow reports some JPEG containers as MPO. For optimization purposes,
|
|
36
|
+
# the primary frame is a normal JPEG-compatible raster and can safely
|
|
37
|
+
# enter the standard image pipeline.
|
|
38
|
+
if fmt == 'MPO':
|
|
39
|
+
fmt = 'JPEG'
|
|
34
40
|
width, height = image.size
|
|
35
41
|
max_pixels = getattr(settings, 'MEDIA_ENGINE_MAX_PIXELS', 50_000_000)
|
|
36
42
|
if width * height > max_pixels:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: media-optimization-engine
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Media Optimization Engineer for Django/Python: automatic model-field optimization, responsive picture/srcset rendering, AVIF/WebP derivatives, 360 media, autonomous nodes, and CDN-ready storage.
|
|
5
5
|
Author-email: Achille Kabasele <pepexykabasele@gmail.com>
|
|
6
6
|
Maintainer-email: Achille Kabasele <pepexykabasele@gmail.com>
|
|
@@ -233,6 +233,16 @@ JavaScript is not required for normal responsive images.
|
|
|
233
233
|
|
|
234
234
|
Runtime measurement is appropriate only for highly dynamic components whose rendered width cannot be described reliably with sizes, such as resizable editors, canvas-like builders, or asynchronously mounted panels.
|
|
235
235
|
|
|
236
|
+
## Docker image
|
|
237
|
+
|
|
238
|
+
The standalone service is also published as a multi-architecture GHCR image:
|
|
239
|
+
|
|
240
|
+
~~~bash
|
|
241
|
+
docker pull ghcr.io/kabasele754/media-optimization-engine:1.4.1
|
|
242
|
+
~~~
|
|
243
|
+
|
|
244
|
+
For Compose deployment, see `docs/DOCKER.md` and `docker-compose.ghcr.yml`.
|
|
245
|
+
|
|
236
246
|
## Flutter / native clients
|
|
237
247
|
|
|
238
248
|
MOE also ships reference Dart adapters for native applications.
|
|
@@ -75,12 +75,14 @@ media_engine/static/media_engine/media-cache-sw.js
|
|
|
75
75
|
media_engine/templatetags/__init__.py
|
|
76
76
|
media_engine/templatetags/responsive_media.py
|
|
77
77
|
media_engine/tests/__init__.py
|
|
78
|
+
media_engine/tests/test_backfill_registered_media.py
|
|
78
79
|
media_engine/tests/test_distributed_node.py
|
|
79
80
|
media_engine/tests/test_image_ops.py
|
|
80
81
|
media_engine/tests/test_panorama.py
|
|
81
82
|
media_engine/tests/test_paths.py
|
|
82
83
|
media_engine/tests/test_profiles.py
|
|
83
84
|
media_engine/tests/test_responsive_media.py
|
|
85
|
+
media_engine/tests/test_validators.py
|
|
84
86
|
media_optimization_engine.egg-info/PKG-INFO
|
|
85
87
|
media_optimization_engine.egg-info/SOURCES.txt
|
|
86
88
|
media_optimization_engine.egg-info/dependency_links.txt
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "media-optimization-engine"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.2"
|
|
8
8
|
description = "Media Optimization Engineer for Django/Python: automatic model-field optimization, responsive picture/srcset rendering, AVIF/WebP derivatives, 360 media, autonomous nodes, and CDN-ready storage."
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
authors = [{ name = "Achille Kabasele", email = "pepexykabasele@gmail.com" }]
|
media_optimization_engine-1.4.1/media_engine/management/commands/backfill_registered_media.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from django.core.management.base import BaseCommand
|
|
2
|
-
from media_engine.integration import ingest_model_field
|
|
3
|
-
from media_engine.registry import entries
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Command(BaseCommand):
|
|
7
|
-
help = 'Ingest image fields declared through register_model_image().'
|
|
8
|
-
|
|
9
|
-
def add_arguments(self, parser):
|
|
10
|
-
parser.add_argument('--limit', type=int)
|
|
11
|
-
|
|
12
|
-
def handle(self, *args, **options):
|
|
13
|
-
processed = 0
|
|
14
|
-
for entry in entries():
|
|
15
|
-
qs = entry.model._default_manager.all()
|
|
16
|
-
if options['limit']:
|
|
17
|
-
qs = qs[:options['limit']]
|
|
18
|
-
for obj in qs.iterator():
|
|
19
|
-
ingest_model_field(obj, field_name=entry.field_name, profile=entry.profile, role=entry.role)
|
|
20
|
-
processed += 1
|
|
21
|
-
self.stdout.write(self.style.SUCCESS(f'Processed {processed} registered model images.'))
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/autoregister.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/control_plane.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/focal_detectors.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/image_ops.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/manifest.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/processors/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/profiles.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/queueing.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/registry.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/runtime_config.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/serializers.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/services.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/spec/openapi.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/system_views.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.1 → media_optimization_engine-1.4.2}/media_engine/tests/test_paths.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|