Add Nikon and Sony RAW support v0.9
This commit is contained in:
1 parent
f1704c9822
commit
6e8c30c1d4
6 files changed
+87
-37
No files matched your search
+36
-6
@@ -32,6 +32,8 @@ FORMAT_GROUPS: dict[str, tuple[str, ...]] = {
|
||||
"heif": (".heic", ".heif", ".hif"),
|
||||
"dng": (".dng",),
|
||||
"canon": (".cr2", ".cr3"),
|
||||
"nikon": (".nef", ".nrw"),
|
||||
"sony": (".arw", ".arq"),
|
||||
}
|
||||
FORMAT_LABELS = {
|
||||
"jpg": "JPG",
|
||||
@@ -40,6 +42,8 @@ FORMAT_LABELS = {
|
||||
"heif": "HEIF",
|
||||
"dng": "DNG",
|
||||
"canon": "Canon RAW",
|
||||
"nikon": "Nikon RAW",
|
||||
"sony": "Sony RAW",
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +89,7 @@ APP_SUBTITLE = (
|
||||
or f"Local folder · in-place {ENABLED_FORMAT_LABEL} metadata"
|
||||
)
|
||||
APP_URL = os.getenv("APP_URL", "http://localhost:8080")
|
||||
APP_VERSION = "0.8.3"
|
||||
APP_VERSION = "0.9.0"
|
||||
MAX_UPLOAD_MB = int(os.getenv("MAX_UPLOAD_MB", "150"))
|
||||
MAX_UPLOAD_BYTES = MAX_UPLOAD_MB * 1024 * 1024
|
||||
PREVIEW_MAX_EDGE = int(os.getenv("PREVIEW_MAX_EDGE", "2400"))
|
||||
@@ -97,9 +101,15 @@ TIFF_EXTENSIONS = {".tif", ".tiff"}
|
||||
HEIF_EXTENSIONS = {".heic", ".heif", ".hif"}
|
||||
DNG_EXTENSIONS = {".dng"}
|
||||
CANON_EXTENSIONS = {".cr2", ".cr3"}
|
||||
IPTC_EXTENSIONS = {".jpg", ".jpeg", ".tif", ".tiff", ".dng", ".cr2"}
|
||||
NIKON_EXTENSIONS = {".nef", ".nrw"}
|
||||
SONY_EXTENSIONS = {".arw", ".arq"}
|
||||
RAW_EXTENSIONS = DNG_EXTENSIONS | CANON_EXTENSIONS | NIKON_EXTENSIONS | SONY_EXTENSIONS
|
||||
IPTC_EXTENSIONS = {
|
||||
".jpg", ".jpeg", ".tif", ".tiff", ".dng", ".cr2",
|
||||
".nef", ".nrw", ".arw", ".arq",
|
||||
}
|
||||
SERVER_PREVIEW_EXTENSIONS = (
|
||||
TIFF_EXTENSIONS | HEIF_EXTENSIONS | DNG_EXTENSIONS | CANON_EXTENSIONS
|
||||
TIFF_EXTENSIONS | HEIF_EXTENSIONS | RAW_EXTENSIONS
|
||||
) & SUPPORTED_EXTENSIONS
|
||||
MEDIA_TYPES = {
|
||||
".jpg": "image/jpeg",
|
||||
@@ -113,6 +123,10 @@ MEDIA_TYPES = {
|
||||
".dng": "image/x-adobe-dng",
|
||||
".cr2": "image/x-canon-cr2",
|
||||
".cr3": "image/x-canon-cr3",
|
||||
".nef": "image/x-nikon-nef",
|
||||
".nrw": "image/x-nikon-nrw",
|
||||
".arw": "image/x-sony-arw",
|
||||
".arq": "image/x-sony-arq",
|
||||
}
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||
|
||||
@@ -487,6 +501,14 @@ def _format_name(suffix: str) -> str:
|
||||
return "CR2"
|
||||
if suffix == ".cr3":
|
||||
return "CR3"
|
||||
if suffix == ".nef":
|
||||
return "NEF"
|
||||
if suffix == ".nrw":
|
||||
return "NRW"
|
||||
if suffix == ".arw":
|
||||
return "ARW"
|
||||
if suffix == ".arq":
|
||||
return "ARQ"
|
||||
return suffix.lstrip(".").upper() or "image"
|
||||
|
||||
|
||||
@@ -528,6 +550,10 @@ def _validate_image_signature(path: Path, suffix: str) -> None:
|
||||
for offset in range(8, len(signature) - 3, 4)
|
||||
)
|
||||
)
|
||||
elif suffix in NIKON_EXTENSIONS | SONY_EXTENSIONS:
|
||||
# NEF, NRW, ARW, and ARQ are TIFF-based. ExifTool performs deeper
|
||||
# camera-format validation when the file is inspected or written.
|
||||
valid = signature.startswith((b"II*\x00", b"MM\x00*"))
|
||||
|
||||
if not valid:
|
||||
raise HTTPException(
|
||||
@@ -706,6 +732,10 @@ def _raw_preview_tags(suffix: str) -> tuple[str, ...]:
|
||||
return ("PreviewImage", "JpgFromRaw", "ThumbnailImage")
|
||||
if suffix == ".cr3":
|
||||
return ("JpgFromRaw", "PreviewImage", "ThumbnailImage")
|
||||
if suffix in NIKON_EXTENSIONS:
|
||||
return ("JpgFromRaw", "PreviewImage", "OtherImage", "ThumbnailImage")
|
||||
if suffix in SONY_EXTENSIONS:
|
||||
return ("JpgFromRaw", "PreviewImage", "ThumbnailImage")
|
||||
return ("JpgFromRaw", "PreviewImage", "ThumbnailImage")
|
||||
|
||||
|
||||
@@ -760,7 +790,7 @@ def _render_raw(path: Path, format_name: str) -> Image.Image:
|
||||
|
||||
def _preview_bytes(temp_path: Path, suffix: str, filename: str) -> bytes:
|
||||
try:
|
||||
if suffix in DNG_EXTENSIONS | CANON_EXTENSIONS:
|
||||
if suffix in RAW_EXTENSIONS:
|
||||
format_name = _format_name(suffix)
|
||||
preview = _embedded_raw_preview(temp_path, format_name, suffix)
|
||||
if preview is None:
|
||||
@@ -775,7 +805,7 @@ def _preview_bytes(temp_path: Path, suffix: str, filename: str) -> bytes:
|
||||
preview.load()
|
||||
|
||||
try:
|
||||
max_edge = RAW_PREVIEW_MAX_EDGE if suffix in DNG_EXTENSIONS | CANON_EXTENSIONS else PREVIEW_MAX_EDGE
|
||||
max_edge = RAW_PREVIEW_MAX_EDGE if suffix in RAW_EXTENSIONS else PREVIEW_MAX_EDGE
|
||||
preview.thumbnail((max_edge, max_edge), Image.Resampling.LANCZOS)
|
||||
if preview.mode in {"RGBA", "LA"} or (preview.mode == "P" and "transparency" in preview.info):
|
||||
rgba = preview.convert("RGBA")
|
||||
@@ -1024,7 +1054,7 @@ async def process_photo(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180 if suffix in DNG_EXTENSIONS | CANON_EXTENSIONS else 90,
|
||||
timeout=180 if suffix in RAW_EXTENSIONS else 90,
|
||||
check=False,
|
||||
)
|
||||
exiftool_seconds = time.perf_counter() - exiftool_started
|
||||
|
||||
+5
-1
@@ -126,11 +126,15 @@ function formatType(filename) {
|
||||
if (extension === 'dng') return 'DNG';
|
||||
if (extension === 'cr2') return 'CR2';
|
||||
if (extension === 'cr3') return 'CR3';
|
||||
if (extension === 'nef') return 'NEF';
|
||||
if (extension === 'nrw') return 'NRW';
|
||||
if (extension === 'arw') return 'ARW';
|
||||
if (extension === 'arq') return 'ARQ';
|
||||
return extension.toUpperCase();
|
||||
}
|
||||
|
||||
function requiresServerPreview(photo) {
|
||||
return ['TIFF', 'HEIF', 'DNG', 'CR2', 'CR3'].includes(photo.format);
|
||||
return ['TIFF', 'HEIF', 'DNG', 'CR2', 'CR3', 'NEF', 'NRW', 'ARW', 'ARQ'].includes(photo.format);
|
||||
}
|
||||
|
||||
function isEnabledFilename(filename) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE_NAME = 'photo-date-editor-v8-3';
|
||||
const CACHE_NAME = 'photo-date-editor-v9-0';
|
||||
const SHELL = ['/', '/styles.css', '/app.js', '/manifest.webmanifest', '/vendor/leaflet/leaflet.css', '/vendor/leaflet/leaflet.js'];
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
|
||||
Reference in new issue
Block a user