smart-media-manager 0.5.43a4__py3-none-any.whl

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.
@@ -0,0 +1,677 @@
1
+ """Machine-readable Apple Photos format rules for SMART_MEDIA_MANAGER.
2
+
3
+ This module is generated from APPLE_PHOTOS_FORMAT_RULES.md. Each rule describes:
4
+
5
+ * rule_id: stable identifier (e.g., "R-IMG-001").
6
+ * category: high-level grouping (image, raw, video, vector).
7
+ * action: deterministic handler (import, convert, rewrap, skip, etc.).
8
+ * identifiers: canonical values emitted by detection tiers.
9
+ * conditions: optional constraints (e.g., animation, size thresholds).
10
+
11
+ Helper utilities provide lookup by extension/identifiers so the CLI can
12
+ automatically choose the correct processing path.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass
18
+ from typing import Iterable, Optional
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FormatRule:
23
+ rule_id: str
24
+ category: str
25
+ action: str
26
+ extensions: tuple[str, ...]
27
+ libmagic: tuple[str, ...]
28
+ puremagic: tuple[str, ...]
29
+ pyfsig: tuple[str, ...]
30
+ binwalk: tuple[str, ...]
31
+ rawpy: tuple[str, ...]
32
+ ffprobe: tuple[str, ...]
33
+ conditions: dict[str, object]
34
+ notes: str
35
+
36
+
37
+ def _rule(
38
+ *,
39
+ rule_id: str,
40
+ category: str,
41
+ action: str,
42
+ extensions: Iterable[str],
43
+ libmagic: Iterable[str] = (),
44
+ puremagic: Iterable[str] = (),
45
+ pyfsig: Iterable[str] = (),
46
+ binwalk: Iterable[str] = (),
47
+ rawpy: Iterable[str] = (),
48
+ ffprobe: Iterable[str] = (),
49
+ conditions: Optional[dict[str, object]] = None,
50
+ notes: str = "",
51
+ ) -> FormatRule:
52
+ return FormatRule(
53
+ rule_id=rule_id,
54
+ category=category,
55
+ action=action,
56
+ extensions=tuple(sorted({ext.lower() for ext in extensions})),
57
+ libmagic=tuple(sorted({ident.lower() for ident in libmagic})),
58
+ puremagic=tuple(sorted({ident.lower() for ident in puremagic})),
59
+ pyfsig=tuple(sorted({ident.lower() for ident in pyfsig})),
60
+ binwalk=tuple(sorted({ident.lower() for ident in binwalk})),
61
+ rawpy=tuple(sorted({ident.lower() for ident in rawpy})),
62
+ ffprobe=tuple(sorted({ident.lower() for ident in ffprobe})),
63
+ conditions=conditions or {},
64
+ notes=notes,
65
+ )
66
+
67
+
68
+ FORMAT_RULES: tuple[FormatRule, ...] = (
69
+ _rule(
70
+ rule_id="R-IMG-001",
71
+ category="image",
72
+ action="import",
73
+ extensions=[".jpg", ".jpeg"],
74
+ libmagic=["image/jpeg", "jpeg image data"],
75
+ puremagic=["jpeg", "image/jpeg"],
76
+ pyfsig=["jpeg image file"],
77
+ binwalk=["jpeg image data"],
78
+ notes="Standard JPEG",
79
+ ),
80
+ _rule(
81
+ rule_id="R-IMG-002",
82
+ category="image",
83
+ action="import",
84
+ extensions=[".png"],
85
+ libmagic=["image/png"],
86
+ puremagic=["png", "image/png"],
87
+ pyfsig=["png image"],
88
+ binwalk=["png image"],
89
+ notes="Portable Network Graphics",
90
+ ),
91
+ _rule(
92
+ rule_id="R-IMG-003",
93
+ category="image",
94
+ action="import",
95
+ extensions=[".heic", ".heif"],
96
+ libmagic=["image/heic", "iso media, heif"],
97
+ puremagic=["heic", "image/heic"],
98
+ pyfsig=["iso base media (heic)"],
99
+ binwalk=["heif"],
100
+ notes="HEIF/HEIC",
101
+ ),
102
+ _rule(
103
+ rule_id="R-IMG-004",
104
+ category="image",
105
+ action="import",
106
+ extensions=[".gif"],
107
+ libmagic=["image/gif"],
108
+ puremagic=["gif", "image/gif"],
109
+ pyfsig=["gif image"],
110
+ binwalk=["gif image data"],
111
+ conditions={"animated": False},
112
+ notes="Static GIF",
113
+ ),
114
+ _rule(
115
+ rule_id="R-IMG-005",
116
+ category="image",
117
+ action="import",
118
+ extensions=[".gif"],
119
+ libmagic=["image/gif"],
120
+ puremagic=["gif", "image/gif"],
121
+ pyfsig=["gif image"],
122
+ binwalk=["gif image data"],
123
+ conditions={"animated": True, "max_size_mb": 100},
124
+ notes="Animated GIF under Apple size limit",
125
+ ),
126
+ _rule(
127
+ rule_id="R-IMG-006",
128
+ category="image",
129
+ action="convert_animation_to_hevc_mp4",
130
+ extensions=[".gif"],
131
+ libmagic=["image/gif"],
132
+ puremagic=["gif", "image/gif"],
133
+ pyfsig=["gif image"],
134
+ binwalk=["gif image data"],
135
+ conditions={"animated": True, "min_size_mb": 100},
136
+ notes="Animated GIF above Photos limit",
137
+ ),
138
+ _rule(
139
+ rule_id="R-IMG-007",
140
+ category="image",
141
+ action="import",
142
+ extensions=[".tif", ".tiff"],
143
+ libmagic=["image/tiff"],
144
+ puremagic=["tiff", "image/tiff"],
145
+ pyfsig=["tiff image"],
146
+ binwalk=["tiff image data"],
147
+ notes="Tagged Image File Format",
148
+ ),
149
+ _rule(
150
+ rule_id="R-IMG-008",
151
+ category="image",
152
+ action="import",
153
+ extensions=[".psd"],
154
+ libmagic=["application/photoshop"],
155
+ puremagic=["psd", "image/vnd.adobe.photoshop"],
156
+ pyfsig=["adobe photoshop image"],
157
+ binwalk=["photoshop image data"],
158
+ conditions={"psd_color_mode": "rgb"},
159
+ notes="Adobe Photoshop (RGB)",
160
+ ),
161
+ _rule(
162
+ rule_id="R-IMG-009",
163
+ category="image",
164
+ action="convert_to_png",
165
+ extensions=[".psd"],
166
+ libmagic=["application/photoshop"],
167
+ puremagic=["psd", "image/vnd.adobe.photoshop"],
168
+ pyfsig=["adobe photoshop image"],
169
+ binwalk=["photoshop image data"],
170
+ conditions={"psd_color_mode": "non-rgb"},
171
+ notes="PSD CMYK/multichannel",
172
+ ),
173
+ _rule(
174
+ rule_id="R-IMG-010",
175
+ category="image",
176
+ action="convert_to_png",
177
+ extensions=[".webp"],
178
+ libmagic=["image/webp"],
179
+ puremagic=["webp", "image/webp"],
180
+ pyfsig=["google webp image"],
181
+ binwalk=["webp"],
182
+ notes="WebP still",
183
+ ),
184
+ _rule(
185
+ rule_id="R-IMG-011",
186
+ category="image",
187
+ action="convert_animation_to_hevc_mp4",
188
+ extensions=[".webp"],
189
+ libmagic=["image/webp"],
190
+ puremagic=["webp", "image/webp"],
191
+ pyfsig=["google webp image"],
192
+ binwalk=["webp"],
193
+ conditions={"animated": True},
194
+ notes="Animated WebP",
195
+ ),
196
+ _rule(
197
+ rule_id="R-IMG-012",
198
+ category="image",
199
+ action="convert_to_png",
200
+ extensions=[".avif"],
201
+ libmagic=["image/avif"],
202
+ puremagic=["avif", "image/avif"],
203
+ pyfsig=["avif image"],
204
+ binwalk=["avif"],
205
+ notes="AVIF still",
206
+ ),
207
+ _rule(
208
+ rule_id="R-IMG-013",
209
+ category="image",
210
+ action="convert_to_heic_lossless",
211
+ extensions=[".jxl"],
212
+ libmagic=["image/jxl"],
213
+ puremagic=["jxl", "image/jxl"],
214
+ pyfsig=["jpeg xl image"],
215
+ binwalk=["jpeg xl"],
216
+ notes="JPEG XL",
217
+ ),
218
+ _rule(
219
+ rule_id="R-IMG-014",
220
+ category="image",
221
+ action="convert_animation_to_hevc_mp4",
222
+ extensions=[".png"],
223
+ libmagic=["image/png"],
224
+ puremagic=["png", "image/png"],
225
+ pyfsig=["png image"],
226
+ binwalk=["png image"],
227
+ conditions={"animated": True},
228
+ notes="Animated PNG (APNG)",
229
+ ),
230
+ _rule(
231
+ rule_id="R-IMG-015",
232
+ category="image",
233
+ action="import",
234
+ extensions=[".bmp"],
235
+ libmagic=["image/bmp"],
236
+ puremagic=["bmp", "image/bmp"],
237
+ pyfsig=["bmp image"],
238
+ binwalk=["pc bitmap"],
239
+ notes="Bitmap",
240
+ ),
241
+ _rule(
242
+ rule_id="R-IMG-016",
243
+ category="vector",
244
+ action="skip_vector",
245
+ extensions=[
246
+ ".svg",
247
+ ".ai",
248
+ ".eps",
249
+ ".ps",
250
+ ".pdf",
251
+ ".wmf",
252
+ ".emf",
253
+ ".drw",
254
+ ".tex",
255
+ ],
256
+ libmagic=["image/svg+xml", "application/postscript", "application/pdf"],
257
+ puremagic=["svg", "postscript", "pdf"],
258
+ pyfsig=["postscript document", "pdf document", "svg document"],
259
+ binwalk=["pdf document", "postscript"],
260
+ notes="Vector formats unsupported by Photos",
261
+ ),
262
+ # RAW rules
263
+ _rule(
264
+ rule_id="R-RAW-001",
265
+ category="raw",
266
+ action="import",
267
+ extensions=[".crw", ".cr2", ".cr3", ".crm", ".crx"],
268
+ libmagic=["image/x-canon-cr2", "application/octet-stream"],
269
+ puremagic=["cr2"],
270
+ pyfsig=["canon cr2 raw image"],
271
+ binwalk=["canon raw"],
272
+ rawpy=["canon"],
273
+ notes="Canon RAW",
274
+ ),
275
+ _rule(
276
+ rule_id="R-RAW-002",
277
+ category="raw",
278
+ action="import",
279
+ extensions=[".nef", ".nrw"],
280
+ libmagic=["image/x-nikon-nef"],
281
+ puremagic=["nef"],
282
+ pyfsig=["nikon nef raw image"],
283
+ binwalk=["nikon raw"],
284
+ rawpy=["nikon"],
285
+ notes="Nikon RAW",
286
+ ),
287
+ _rule(
288
+ rule_id="R-RAW-003",
289
+ category="raw",
290
+ action="import",
291
+ extensions=[".arw", ".srf", ".sr2"],
292
+ libmagic=["image/x-sony-arw"],
293
+ puremagic=["arw"],
294
+ pyfsig=["sony arw raw image"],
295
+ binwalk=["sony raw"],
296
+ rawpy=["sony"],
297
+ notes="Sony RAW",
298
+ ),
299
+ _rule(
300
+ rule_id="R-RAW-004",
301
+ category="raw",
302
+ action="import",
303
+ extensions=[".raf"],
304
+ libmagic=["image/x-fuji-raf"],
305
+ puremagic=["raf"],
306
+ pyfsig=["fujifilm raf raw image"],
307
+ binwalk=["fujifilm raw"],
308
+ rawpy=["fujifilm"],
309
+ notes="Fujifilm RAW",
310
+ ),
311
+ _rule(
312
+ rule_id="R-RAW-005",
313
+ category="raw",
314
+ action="import",
315
+ extensions=[".orf"],
316
+ libmagic=["image/x-olympus-orf"],
317
+ puremagic=["orf"],
318
+ pyfsig=["olympus orf raw image"],
319
+ binwalk=["olympus raw"],
320
+ rawpy=["olympus"],
321
+ notes="Olympus RAW",
322
+ ),
323
+ _rule(
324
+ rule_id="R-RAW-006",
325
+ category="raw",
326
+ action="import",
327
+ extensions=[".rw2", ".raw"],
328
+ libmagic=["image/x-panasonic-rw2"],
329
+ puremagic=["rw2"],
330
+ pyfsig=["panasonic rw2 raw image"],
331
+ binwalk=["panasonic raw"],
332
+ rawpy=["panasonic"],
333
+ notes="Panasonic RAW",
334
+ ),
335
+ _rule(
336
+ rule_id="R-RAW-007",
337
+ category="raw",
338
+ action="import",
339
+ extensions=[".pef", ".dng"],
340
+ libmagic=["image/x-pentax-pef", "image/x-adobe-dng"],
341
+ puremagic=["pef", "dng"],
342
+ pyfsig=["pentax pef raw image", "adobe dng"],
343
+ binwalk=["pentax raw", "dng image"],
344
+ rawpy=["pentax", "ricoh", "adobe"],
345
+ notes="Pentax/Adobe DNG",
346
+ ),
347
+ _rule(
348
+ rule_id="R-RAW-008",
349
+ category="raw",
350
+ action="import",
351
+ extensions=[".3fr", ".fff", ".iiq", ".cap"],
352
+ libmagic=["image/x-hasselblad-3fr"],
353
+ puremagic=["3fr", "iiq"],
354
+ pyfsig=["hasselblad raw", "phaseone raw"],
355
+ binwalk=["hasselblad raw"],
356
+ rawpy=["hasselblad", "phase one"],
357
+ notes="Medium-format RAW",
358
+ ),
359
+ _rule(
360
+ rule_id="R-RAW-009",
361
+ category="raw",
362
+ action="import",
363
+ extensions=[".x3f"],
364
+ libmagic=["image/x-sigma-x3f"],
365
+ puremagic=["x3f"],
366
+ pyfsig=["sigma x3f raw"],
367
+ binwalk=["sigma raw"],
368
+ rawpy=["sigma"],
369
+ notes="Sigma RAW",
370
+ ),
371
+ _rule(
372
+ rule_id="R-RAW-010",
373
+ category="raw",
374
+ action="import",
375
+ extensions=[".gpr"],
376
+ libmagic=["image/x-gopro-gpr"],
377
+ puremagic=["gpr"],
378
+ pyfsig=["gopro gpr raw"],
379
+ binwalk=["gopro raw"],
380
+ rawpy=["gopro"],
381
+ notes="GoPro RAW",
382
+ ),
383
+ _rule(
384
+ rule_id="R-RAW-011",
385
+ category="raw",
386
+ action="import",
387
+ extensions=[".dng"],
388
+ libmagic=["image/x-adobe-dng"],
389
+ puremagic=["dng"],
390
+ pyfsig=["adobe dng"],
391
+ binwalk=["dng image"],
392
+ rawpy=["dji"],
393
+ notes="DJI DNG",
394
+ ),
395
+ _rule(
396
+ rule_id="R-RAW-012",
397
+ category="raw",
398
+ action="skip_raw_unsupported",
399
+ extensions=[".raw", ".unknown"],
400
+ libmagic=["application/octet-stream"],
401
+ puremagic=["None"],
402
+ pyfsig=["Unknown file type"],
403
+ binwalk=["unknown"],
404
+ notes="Unknown RAW",
405
+ ),
406
+ # Video rules
407
+ _rule(
408
+ rule_id="R-VID-001a",
409
+ category="video",
410
+ action="rewrap_to_mp4",
411
+ extensions=[".m4v"],
412
+ libmagic=["video/mp4", "video/x-m4v", "iso media, mp4 base media"],
413
+ puremagic=["m4v", "video/x-m4v"],
414
+ pyfsig=["iso base media"],
415
+ binwalk=["mpeg-4 part 14"],
416
+ ffprobe=[
417
+ "video:h264",
418
+ "audio:aac",
419
+ "audio:ac3",
420
+ "audio:eac3",
421
+ "audio:alac",
422
+ "audio:pcm",
423
+ ],
424
+ notes="M4V with compatible codecs - remux to MP4",
425
+ ),
426
+ _rule(
427
+ rule_id="R-VID-001",
428
+ category="video",
429
+ action="import",
430
+ extensions=[".mp4", ".mov", ".qt"],
431
+ libmagic=["video/mp4", "iso media, mp4 base media"],
432
+ puremagic=["mp4", "video/mp4"],
433
+ pyfsig=["iso base media"],
434
+ binwalk=["mpeg-4 part 14"],
435
+ ffprobe=[
436
+ "video:h264",
437
+ "audio:aac",
438
+ "audio:ac3",
439
+ "audio:eac3",
440
+ "audio:alac",
441
+ "audio:pcm",
442
+ ],
443
+ notes="H.264 + AAC/AC-3/E-AC-3/ALAC/PCM",
444
+ ),
445
+ _rule(
446
+ rule_id="R-VID-002",
447
+ category="video",
448
+ action="import",
449
+ extensions=[".mp4", ".mov", ".hevc", ".qt"],
450
+ libmagic=["video/h265", "iso media, mp4 base media"],
451
+ puremagic=["hevc", "video/h265"],
452
+ pyfsig=["iso base media"],
453
+ binwalk=["hevc"],
454
+ ffprobe=["video:hevc", "audio:aac", "audio:ac3", "audio:eac3", "audio:alac"],
455
+ notes="HEVC + AAC/AC-3/E-AC-3/ALAC",
456
+ ),
457
+ _rule(
458
+ rule_id="R-VID-003",
459
+ category="video",
460
+ action="import",
461
+ extensions=[".mp4", ".mov", ".qt"],
462
+ libmagic=["video/mp4"],
463
+ puremagic=["mp4", "video/mp4"],
464
+ pyfsig=["iso base media"],
465
+ binwalk=["dolby vision"],
466
+ ffprobe=["video:hevc", "dolby_vision", "audio:eac3"],
467
+ notes="Dolby Vision + Atmos",
468
+ ),
469
+ _rule(
470
+ rule_id="R-VID-004",
471
+ category="video",
472
+ action="transcode_video_to_lossless_hevc",
473
+ extensions=[".mp4", ".mov", ".qt"],
474
+ libmagic=["video/mp4", "iso media, mp4 base media"],
475
+ puremagic=["mp4", "video/mp4"],
476
+ pyfsig=["iso base media"],
477
+ binwalk=["mpeg-4 part 14"],
478
+ ffprobe=["video:vp9", "video:av1", "video:mpeg2video"],
479
+ notes="Unsupported video codec inside MP4",
480
+ ),
481
+ _rule(
482
+ rule_id="R-VID-005",
483
+ category="video",
484
+ action="transcode_audio_to_aac_or_eac3",
485
+ extensions=[".mp4", ".mov", ".qt"],
486
+ libmagic=["video/mp4"],
487
+ puremagic=["mp4", "video/mp4"],
488
+ pyfsig=["iso base media"],
489
+ binwalk=["mpeg-4 part 14"],
490
+ ffprobe=["audio:opus", "audio:dts", "audio:truehd"],
491
+ notes="Unsupported audio codec inside MP4/MOV",
492
+ ),
493
+ _rule(
494
+ rule_id="R-VID-006",
495
+ category="video",
496
+ action="rewrap_to_mp4",
497
+ extensions=[".mkv"],
498
+ libmagic=["video/x-matroska"],
499
+ puremagic=["mkv", "video/x-matroska"],
500
+ pyfsig=["matroska data"],
501
+ binwalk=["matroska"],
502
+ ffprobe=["video:h264", "video:hevc"],
503
+ notes="Matroska container with compatible codecs",
504
+ ),
505
+ _rule(
506
+ rule_id="R-VID-007",
507
+ category="video",
508
+ action="transcode_to_hevc_mp4",
509
+ extensions=[".mkv", ".webm"],
510
+ libmagic=["video/x-matroska", "video/webm"],
511
+ puremagic=["webm", "video/webm"],
512
+ pyfsig=["matroska data", "webm"],
513
+ binwalk=["webm"],
514
+ ffprobe=["video:vp9", "audio:opus"],
515
+ notes="VP9/Opus containers",
516
+ ),
517
+ _rule(
518
+ rule_id="R-VID-008",
519
+ category="video",
520
+ action="transcode_to_hevc_mp4",
521
+ extensions=[".avi"],
522
+ libmagic=["video/x-msvideo"],
523
+ puremagic=["avi", "video/x-msvideo"],
524
+ pyfsig=["riff avi"],
525
+ binwalk=["avi"],
526
+ notes="AVI container",
527
+ ),
528
+ _rule(
529
+ rule_id="R-VID-009",
530
+ category="video",
531
+ action="transcode_to_hevc_mp4",
532
+ extensions=[".wmv"],
533
+ libmagic=["video/x-ms-wmv"],
534
+ puremagic=["wmv", "video/x-ms-wmv"],
535
+ pyfsig=["asf/wmv"],
536
+ binwalk=["microsoft asf"],
537
+ notes="Windows Media",
538
+ ),
539
+ _rule(
540
+ rule_id="R-VID-010",
541
+ category="video",
542
+ action="transcode_to_hevc_mp4",
543
+ extensions=[".flv"],
544
+ libmagic=["video/x-flv"],
545
+ puremagic=["flv", "video/x-flv"],
546
+ pyfsig=["flash video"],
547
+ binwalk=["flv"],
548
+ notes="Flash Video",
549
+ ),
550
+ _rule(
551
+ rule_id="R-VID-011",
552
+ category="video",
553
+ action="rewrap_or_transcode_to_mp4",
554
+ extensions=[".3gp", ".3g2"],
555
+ libmagic=["video/3gpp", "video/3gpp2"],
556
+ puremagic=["3gp", "3g2"],
557
+ pyfsig=["3gpp multimedia"],
558
+ binwalk=["3gp"],
559
+ notes="3GPP container",
560
+ ),
561
+ _rule(
562
+ rule_id="R-VID-012",
563
+ category="video",
564
+ action="skip_unknown_video",
565
+ extensions=[".unknown"],
566
+ notes="Unhandled/legacy video",
567
+ ),
568
+ )
569
+
570
+
571
+ EXTENSION_INDEX: dict[str, list[FormatRule]] = {}
572
+ for rule in FORMAT_RULES:
573
+ for ext in rule.extensions:
574
+ EXTENSION_INDEX.setdefault(ext, []).append(rule)
575
+
576
+
577
+ def find_rules_by_extension(extension: Optional[str]) -> list[FormatRule]:
578
+ if not extension:
579
+ return []
580
+ ext = extension.lower()
581
+ if not ext.startswith("."):
582
+ ext = f".{ext}"
583
+ return EXTENSION_INDEX.get(ext, [])
584
+
585
+
586
+ def match_rule(
587
+ *,
588
+ extension: Optional[str] = None,
589
+ libmagic: Optional[Iterable[str] | str] = None,
590
+ puremagic: Optional[Iterable[str] | str] = None,
591
+ pyfsig: Optional[Iterable[str] | str] = None,
592
+ binwalk: Optional[Iterable[str] | str] = None,
593
+ rawpy: Optional[Iterable[str] | str] = None,
594
+ ffprobe_streams: Optional[Iterable[str]] = None,
595
+ animated: Optional[bool] = None,
596
+ size_bytes: Optional[int] = None,
597
+ psd_color_mode: Optional[str] = None,
598
+ ) -> Optional[FormatRule]:
599
+ """Return the first rule that matches the supplied identifiers."""
600
+
601
+ candidates = list(FORMAT_RULES)
602
+ if extension:
603
+ candidates = find_rules_by_extension(extension) or candidates
604
+
605
+ def normalise_iter(value: Optional[Iterable[str] | str]) -> set[str]:
606
+ if value is None:
607
+ return set()
608
+ if isinstance(value, str):
609
+ value_lower = value.lower()
610
+ str_result = {value_lower}
611
+ if value_lower.startswith(".") and "/" not in value_lower:
612
+ stripped = value_lower.lstrip(".")
613
+ if stripped:
614
+ str_result.add(stripped)
615
+ return str_result
616
+ iter_result: set[str] = set()
617
+ for entry in value:
618
+ if entry:
619
+ lowered = entry.lower()
620
+ iter_result.add(lowered)
621
+ if lowered.startswith(".") and "/" not in lowered:
622
+ stripped = lowered.lstrip(".")
623
+ if stripped:
624
+ iter_result.add(stripped)
625
+ return iter_result
626
+
627
+ libmagic_set = normalise_iter(libmagic)
628
+ puremagic_set = normalise_iter(puremagic)
629
+ pyfsig_set = normalise_iter(pyfsig)
630
+ binwalk_set = normalise_iter(binwalk)
631
+ rawpy_set = normalise_iter(rawpy)
632
+ ffprobe_set = normalise_iter(ffprobe_streams)
633
+
634
+ for rule in candidates:
635
+ if rule.libmagic and libmagic_set and not libmagic_set & set(rule.libmagic):
636
+ continue
637
+ if rule.puremagic and puremagic_set and not puremagic_set & set(rule.puremagic):
638
+ continue
639
+ if rule.pyfsig and pyfsig_set and not pyfsig_set & set(rule.pyfsig):
640
+ pass
641
+ if rule.binwalk and binwalk_set and not binwalk_set & set(rule.binwalk):
642
+ continue
643
+ if rule.rawpy and rawpy_set and not rawpy_set & set(rule.rawpy):
644
+ continue
645
+ if rule.ffprobe and ffprobe_set and not ffprobe_set & set(rule.ffprobe):
646
+ continue
647
+
648
+ conditions = rule.conditions
649
+ if "animated" in conditions and animated is not None:
650
+ if bool(conditions["animated"]) != animated:
651
+ continue
652
+ if "max_size_mb" in conditions and size_bytes is not None:
653
+ max_size_mb = conditions["max_size_mb"]
654
+ if isinstance(max_size_mb, (int, float)) and size_bytes / (1024 * 1024) > float(max_size_mb):
655
+ continue
656
+ if "min_size_mb" in conditions and size_bytes is not None:
657
+ min_size_mb = conditions["min_size_mb"]
658
+ if isinstance(min_size_mb, (int, float)) and size_bytes / (1024 * 1024) < float(min_size_mb):
659
+ continue
660
+ if "psd_color_mode" in conditions and psd_color_mode is not None:
661
+ required_mode = conditions["psd_color_mode"]
662
+ if isinstance(required_mode, str) and required_mode == "rgb" and psd_color_mode.lower() != "rgb":
663
+ continue
664
+ if isinstance(required_mode, str) and required_mode == "non-rgb" and psd_color_mode.lower() == "rgb":
665
+ continue
666
+
667
+ return rule
668
+
669
+ return None
670
+
671
+
672
+ __all__ = [
673
+ "FormatRule",
674
+ "FORMAT_RULES",
675
+ "find_rules_by_extension",
676
+ "match_rule",
677
+ ]