media-optimization-engine 1.4.0__tar.gz → 1.4.1__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.0/media_optimization_engine.egg-info → media_optimization_engine-1.4.1}/PKG-INFO +23 -1
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/README.md +22 -0
- media_optimization_engine-1.4.1/media_engine/adapters/flutter/README.md +63 -0
- media_optimization_engine-1.4.1/media_engine/adapters/flutter/standard_images.dart +106 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1/media_optimization_engine.egg-info}/PKG-INFO +23 -1
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_optimization_engine.egg-info/SOURCES.txt +1 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/pyproject.toml +1 -1
- media_optimization_engine-1.4.0/media_engine/adapters/flutter/README.md +0 -10
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/LICENSE +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/flutter/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/flutter/models.dart +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/marzipano/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/marzipano/manifest.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/pannellum/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/pannellum/manifest.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/threejs/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/adapters/threejs/manifest.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/admin.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/apps.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/auth.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/autoregister.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/control_plane.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/doctor.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/focal_detectors.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/health.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/image_ops.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/integration.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/locks.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/audit_media_engine.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/audit_panorama_media.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/backfill_panorama_media.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/backfill_registered_media.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/backfill_responsive_images.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/cleanup_image_derivatives.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/media_engine_doctor.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/media_engine_node_report.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/rollback_media_engine_config.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/management/commands/sync_media_engine_hub.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/manifest.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/metrics.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/migrations/0001_initial.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/migrations/0002_panorama_multires.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/migrations/0003_panorama_cube_tiles.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/migrations/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/models.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/node.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/base.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/image/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/image/standard.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/cube_tiles.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/cubemap.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/detect.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/equirectangular.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/manifest.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/panorama/tiles.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/processors/registry.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/profiles.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/queueing.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/registry.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/runtime_config.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/serializers.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/services.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/signals.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/spec/openapi.yaml +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/static/media_engine/adaptive-media.js +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/static/media_engine/media-cache-sw.js +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/storage_paths.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/system_views.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tasks.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/templatetags/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/templatetags/responsive_media.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/__init__.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_distributed_node.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_image_ops.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_panorama.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_paths.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_profiles.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_responsive_media.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/urls.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/validators.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/views.py +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_optimization_engine.egg-info/dependency_links.txt +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_optimization_engine.egg-info/entry_points.txt +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_optimization_engine.egg-info/requires.txt +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_optimization_engine.egg-info/top_level.txt +0 -0
- {media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/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.1
|
|
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,28 @@ 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
|
+
## Flutter / native clients
|
|
237
|
+
|
|
238
|
+
MOE also ships reference Dart adapters for native applications.
|
|
239
|
+
|
|
240
|
+
Standard Flutter clients should fetch the image manifest, measure the rendered widget width, multiply it by device pixel ratio, and select the first derivative at or above that physical width.
|
|
241
|
+
|
|
242
|
+
~~~dart
|
|
243
|
+
final variant = manifest.select(
|
|
244
|
+
logicalWidth: constraints.maxWidth,
|
|
245
|
+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
|
|
246
|
+
preferredFormat: 'webp',
|
|
247
|
+
);
|
|
248
|
+
~~~
|
|
249
|
+
|
|
250
|
+
Reference adapters are packaged under:
|
|
251
|
+
|
|
252
|
+
~~~text
|
|
253
|
+
media_engine/adapters/flutter/
|
|
254
|
+
~~~
|
|
255
|
+
|
|
256
|
+
This keeps optimization and derivative generation on the Django/Python backend while allowing Flutter to select and cache the most appropriate immutable URL. Panorama manifests use the same principle at tile/level granularity.
|
|
257
|
+
|
|
236
258
|
## Backfill
|
|
237
259
|
|
|
238
260
|
Historical media can be ingested with maintenance commands:
|
|
@@ -177,6 +177,28 @@ 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
|
+
## Flutter / native clients
|
|
181
|
+
|
|
182
|
+
MOE also ships reference Dart adapters for native applications.
|
|
183
|
+
|
|
184
|
+
Standard Flutter clients should fetch the image manifest, measure the rendered widget width, multiply it by device pixel ratio, and select the first derivative at or above that physical width.
|
|
185
|
+
|
|
186
|
+
~~~dart
|
|
187
|
+
final variant = manifest.select(
|
|
188
|
+
logicalWidth: constraints.maxWidth,
|
|
189
|
+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
|
|
190
|
+
preferredFormat: 'webp',
|
|
191
|
+
);
|
|
192
|
+
~~~
|
|
193
|
+
|
|
194
|
+
Reference adapters are packaged under:
|
|
195
|
+
|
|
196
|
+
~~~text
|
|
197
|
+
media_engine/adapters/flutter/
|
|
198
|
+
~~~
|
|
199
|
+
|
|
200
|
+
This keeps optimization and derivative generation on the Django/Python backend while allowing Flutter to select and cache the most appropriate immutable URL. Panorama manifests use the same principle at tile/level granularity.
|
|
201
|
+
|
|
180
202
|
## Backfill
|
|
181
203
|
|
|
182
204
|
Historical media can be ingested with maintenance commands:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Flutter adapter
|
|
2
|
+
|
|
3
|
+
Media Optimization Engineer can be consumed by Flutter through the same immutable manifest API used by other native clients.
|
|
4
|
+
|
|
5
|
+
The Python package ships Dart reference models under this directory. They are framework-neutral examples that consuming Flutter applications can copy or wrap in their own networking/cache layer.
|
|
6
|
+
|
|
7
|
+
## Standard images
|
|
8
|
+
|
|
9
|
+
\`standard_images.dart\` parses a standard MOE manifest and selects a derivative from:
|
|
10
|
+
|
|
11
|
+
~~~text
|
|
12
|
+
target physical width = rendered logical width × devicePixelRatio
|
|
13
|
+
~~~
|
|
14
|
+
|
|
15
|
+
Recommended policy:
|
|
16
|
+
|
|
17
|
+
1. fetch the asset manifest once;
|
|
18
|
+
2. cache the manifest in memory;
|
|
19
|
+
3. measure the actual widget width with \`LayoutBuilder\`;
|
|
20
|
+
4. multiply by \`MediaQuery.devicePixelRatioOf(context)\`;
|
|
21
|
+
5. select the first WebP derivative at or above that physical width;
|
|
22
|
+
6. if none is large enough, use the largest available derivative;
|
|
23
|
+
7. cache the selected immutable URL with the application's normal image cache;
|
|
24
|
+
8. fall back to the manifest fallback/original URL if needed.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
|
|
28
|
+
~~~dart
|
|
29
|
+
final targetLogicalWidth = constraints.maxWidth;
|
|
30
|
+
final dpr = MediaQuery.devicePixelRatioOf(context);
|
|
31
|
+
|
|
32
|
+
final variant = manifest.select(
|
|
33
|
+
logicalWidth: targetLogicalWidth,
|
|
34
|
+
devicePixelRatio: dpr,
|
|
35
|
+
preferredFormat: 'webp',
|
|
36
|
+
);
|
|
37
|
+
~~~
|
|
38
|
+
|
|
39
|
+
WebP is a conservative default for broad Android/iOS compatibility. Applications may prefer AVIF on platforms where their Flutter image stack reliably supports it.
|
|
40
|
+
|
|
41
|
+
## Panorama 360
|
|
42
|
+
|
|
43
|
+
\`models.dart\` parses the generic panorama manifest.
|
|
44
|
+
|
|
45
|
+
Recommended panorama policy:
|
|
46
|
+
|
|
47
|
+
- show \`preview\` immediately;
|
|
48
|
+
- select a level from viewport width × DPR;
|
|
49
|
+
- request only tiles intersecting the current field of view;
|
|
50
|
+
- keep adjacent tiles warm for smooth panning;
|
|
51
|
+
- use AVIF where supported, otherwise WebP.
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
A standard image manifest is available from the REST viewset:
|
|
56
|
+
|
|
57
|
+
~~~text
|
|
58
|
+
GET /api/v1/images/<asset-id>/manifest/?profile=<profile>
|
|
59
|
+
~~~
|
|
60
|
+
|
|
61
|
+
When MOE is mounted under another prefix in a Django project, keep the same route relative to that prefix.
|
|
62
|
+
|
|
63
|
+
The native client should never upload or process an image merely to render it. Processing stays server-side; Flutter only chooses among READY immutable derivatives.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
class MediaVariant {
|
|
2
|
+
final int width;
|
|
3
|
+
final int height;
|
|
4
|
+
final String url;
|
|
5
|
+
final int bytes;
|
|
6
|
+
final int quality;
|
|
7
|
+
|
|
8
|
+
const MediaVariant({
|
|
9
|
+
required this.width,
|
|
10
|
+
required this.height,
|
|
11
|
+
required this.url,
|
|
12
|
+
this.bytes = 0,
|
|
13
|
+
this.quality = 0,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
factory MediaVariant.fromJson(Map<String, dynamic> json) => MediaVariant(
|
|
17
|
+
width: (json['width'] as num?)?.toInt() ?? 0,
|
|
18
|
+
height: (json['height'] as num?)?.toInt() ?? 0,
|
|
19
|
+
url: (json['url'] ?? '').toString(),
|
|
20
|
+
bytes: (json['bytes'] as num?)?.toInt() ?? 0,
|
|
21
|
+
quality: (json['quality'] as num?)?.toInt() ?? 0,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class MediaManifest {
|
|
26
|
+
final String id;
|
|
27
|
+
final String status;
|
|
28
|
+
final int width;
|
|
29
|
+
final int height;
|
|
30
|
+
final String profile;
|
|
31
|
+
final String dominantColor;
|
|
32
|
+
final String blurhash;
|
|
33
|
+
final String placeholderDataUrl;
|
|
34
|
+
final String originalUrl;
|
|
35
|
+
final MediaVariant? fallback;
|
|
36
|
+
final Map<String, List<MediaVariant>> variants;
|
|
37
|
+
|
|
38
|
+
const MediaManifest({
|
|
39
|
+
required this.id,
|
|
40
|
+
required this.status,
|
|
41
|
+
required this.width,
|
|
42
|
+
required this.height,
|
|
43
|
+
required this.profile,
|
|
44
|
+
required this.variants,
|
|
45
|
+
this.dominantColor = '',
|
|
46
|
+
this.blurhash = '',
|
|
47
|
+
this.placeholderDataUrl = '',
|
|
48
|
+
this.originalUrl = '',
|
|
49
|
+
this.fallback,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
factory MediaManifest.fromJson(Map<String, dynamic> json) {
|
|
53
|
+
final rawVariants =
|
|
54
|
+
Map<String, dynamic>.from(json['variants'] as Map? ?? const {});
|
|
55
|
+
final parsed = <String, List<MediaVariant>>{};
|
|
56
|
+
for (final entry in rawVariants.entries) {
|
|
57
|
+
final items = (entry.value as List? ?? const [])
|
|
58
|
+
.whereType<Map>()
|
|
59
|
+
.map((item) => MediaVariant.fromJson(
|
|
60
|
+
Map<String, dynamic>.from(item),
|
|
61
|
+
))
|
|
62
|
+
.toList()
|
|
63
|
+
..sort((a, b) => a.width.compareTo(b.width));
|
|
64
|
+
parsed[entry.key] = items;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return MediaManifest(
|
|
68
|
+
id: (json['id'] ?? '').toString(),
|
|
69
|
+
status: (json['status'] ?? '').toString(),
|
|
70
|
+
width: (json['width'] as num?)?.toInt() ?? 0,
|
|
71
|
+
height: (json['height'] as num?)?.toInt() ?? 0,
|
|
72
|
+
profile: (json['profile'] ?? '').toString(),
|
|
73
|
+
dominantColor: (json['dominant_color'] ?? '').toString(),
|
|
74
|
+
blurhash: (json['blurhash'] ?? '').toString(),
|
|
75
|
+
placeholderDataUrl: (json['placeholder_data_url'] ?? '').toString(),
|
|
76
|
+
originalUrl: (json['original_url'] ?? '').toString(),
|
|
77
|
+
fallback: json['fallback'] is Map
|
|
78
|
+
? MediaVariant.fromJson(
|
|
79
|
+
Map<String, dynamic>.from(json['fallback'] as Map),
|
|
80
|
+
)
|
|
81
|
+
: null,
|
|
82
|
+
variants: parsed,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
MediaVariant? select({
|
|
87
|
+
required double logicalWidth,
|
|
88
|
+
required double devicePixelRatio,
|
|
89
|
+
String preferredFormat = 'webp',
|
|
90
|
+
}) {
|
|
91
|
+
final target = (logicalWidth * devicePixelRatio).ceil().clamp(1, 10000);
|
|
92
|
+
final order = <String>[preferredFormat, 'webp', 'avif'];
|
|
93
|
+
|
|
94
|
+
for (final format in order.toSet()) {
|
|
95
|
+
final candidates = variants[format];
|
|
96
|
+
if (candidates == null || candidates.isEmpty) continue;
|
|
97
|
+
|
|
98
|
+
for (final candidate in candidates) {
|
|
99
|
+
if (candidate.width >= target) return candidate;
|
|
100
|
+
}
|
|
101
|
+
return candidates.last;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return fallback;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: media-optimization-engine
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.1
|
|
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,28 @@ 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
|
+
## Flutter / native clients
|
|
237
|
+
|
|
238
|
+
MOE also ships reference Dart adapters for native applications.
|
|
239
|
+
|
|
240
|
+
Standard Flutter clients should fetch the image manifest, measure the rendered widget width, multiply it by device pixel ratio, and select the first derivative at or above that physical width.
|
|
241
|
+
|
|
242
|
+
~~~dart
|
|
243
|
+
final variant = manifest.select(
|
|
244
|
+
logicalWidth: constraints.maxWidth,
|
|
245
|
+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
|
|
246
|
+
preferredFormat: 'webp',
|
|
247
|
+
);
|
|
248
|
+
~~~
|
|
249
|
+
|
|
250
|
+
Reference adapters are packaged under:
|
|
251
|
+
|
|
252
|
+
~~~text
|
|
253
|
+
media_engine/adapters/flutter/
|
|
254
|
+
~~~
|
|
255
|
+
|
|
256
|
+
This keeps optimization and derivative generation on the Django/Python backend while allowing Flutter to select and cache the most appropriate immutable URL. Panorama manifests use the same principle at tile/level granularity.
|
|
257
|
+
|
|
236
258
|
## Backfill
|
|
237
259
|
|
|
238
260
|
Historical media can be ingested with maintenance commands:
|
|
@@ -34,6 +34,7 @@ media_engine/adapters/__init__.py
|
|
|
34
34
|
media_engine/adapters/flutter/README.md
|
|
35
35
|
media_engine/adapters/flutter/__init__.py
|
|
36
36
|
media_engine/adapters/flutter/models.dart
|
|
37
|
+
media_engine/adapters/flutter/standard_images.dart
|
|
37
38
|
media_engine/adapters/marzipano/__init__.py
|
|
38
39
|
media_engine/adapters/marzipano/manifest.py
|
|
39
40
|
media_engine/adapters/pannellum/__init__.py
|
|
@@ -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.1"
|
|
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" }]
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Flutter panorama adapter
|
|
2
|
-
|
|
3
|
-
`models.dart` parses the generic MOE panorama manifest. A Flutter viewer can select the lowest level that satisfies current viewport width and devicePixelRatio, then request only visible tiles.
|
|
4
|
-
|
|
5
|
-
Recommended policy:
|
|
6
|
-
- show `preview` immediately;
|
|
7
|
-
- select a level from viewport width x DPR;
|
|
8
|
-
- request only tiles intersecting the current field of view;
|
|
9
|
-
- keep adjacent tiles warm for smooth panning;
|
|
10
|
-
- use AVIF where the Flutter image stack supports it, otherwise WebP.
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/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
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/autoregister.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/control_plane.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/focal_detectors.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/image_ops.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/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
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/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.0 → media_optimization_engine-1.4.1}/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.0 → media_optimization_engine-1.4.1}/media_engine/profiles.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/queueing.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/registry.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/runtime_config.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/serializers.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/services.py
RENAMED
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/spec/openapi.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/storage_paths.py
RENAMED
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/system_views.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/tests/test_paths.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{media_optimization_engine-1.4.0 → media_optimization_engine-1.4.1}/media_engine/validators.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|