Add an opt-in to skip Instagram's exact resolution requirement
Some users want the assembled HDR JPEG for somewhere other than Instagram, where the exact 1080x1080/1080x1350/1080x566 match isn't relevant. A checkbox on the form now lets the whole batch skip that specific check. The SDR/HDR dimension-match check still always applies, since that's a technical requirement for the gain map to align, not an Instagram-specific rule.
This commit is contained in:
+5
-3
@@ -8,7 +8,7 @@ from flask import abort, redirect, render_template, request, send_file, url_for
|
|||||||
from . import assembler, jobs, validation
|
from . import assembler, jobs, validation
|
||||||
|
|
||||||
|
|
||||||
def _process_pair(job_dir, index, sdr_file, hdr_file):
|
def _process_pair(job_dir, index, sdr_file, hdr_file, ignore_ig_resolution=False):
|
||||||
"""Runs one SDR/HDR pair through validation + assembly. Returns an item
|
"""Runs one SDR/HDR pair through validation + assembly. Returns an item
|
||||||
dict for the job report; never raises — failures are recorded in it."""
|
dict for the job report; never raises — failures are recorded in it."""
|
||||||
name = hdr_file.filename or sdr_file.filename or f"pair {index}"
|
name = hdr_file.filename or sdr_file.filename or f"pair {index}"
|
||||||
@@ -28,7 +28,7 @@ def _process_pair(job_dir, index, sdr_file, hdr_file):
|
|||||||
sdr_file.save(sdr_path)
|
sdr_file.save(sdr_path)
|
||||||
hdr_file.save(hdr_path)
|
hdr_file.save(hdr_path)
|
||||||
|
|
||||||
ig_error = validation.instagram_resolution_error(sdr_path, hdr_path)
|
ig_error = validation.instagram_resolution_error(sdr_path, hdr_path, ignore_ig_resolution)
|
||||||
if ig_error:
|
if ig_error:
|
||||||
for p in (sdr_path, hdr_path):
|
for p in (sdr_path, hdr_path):
|
||||||
p.unlink(missing_ok=True)
|
p.unlink(missing_ok=True)
|
||||||
@@ -62,11 +62,13 @@ def register_routes(app):
|
|||||||
if len(sdr_files) != len(hdr_files):
|
if len(sdr_files) != len(hdr_files):
|
||||||
return render_template("index.html", error="Each pair needs both an SDR and an HDR file."), 400
|
return render_template("index.html", error="Each pair needs both an SDR and an HDR file."), 400
|
||||||
|
|
||||||
|
ignore_ig_resolution = request.form.get("ignore_ig_resolution") == "on"
|
||||||
|
|
||||||
job_id = uuid.uuid4().hex[:12]
|
job_id = uuid.uuid4().hex[:12]
|
||||||
job_dir = jobs.new_job_dir(job_id)
|
job_dir = jobs.new_job_dir(job_id)
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
_process_pair(job_dir, i, sdr_file, hdr_file)
|
_process_pair(job_dir, i, sdr_file, hdr_file, ignore_ig_resolution)
|
||||||
for i, (sdr_file, hdr_file) in enumerate(zip(sdr_files, hdr_files), start=1)
|
for i, (sdr_file, hdr_file) in enumerate(zip(sdr_files, hdr_files), start=1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -42,9 +42,17 @@ def probe_dimensions(path: Path) -> tuple[int, int]:
|
|||||||
raise ValueError(f"could not read image dimensions ({e})") from e
|
raise ValueError(f"could not read image dimensions ({e})") from e
|
||||||
|
|
||||||
|
|
||||||
def instagram_resolution_error(sdr_path: Path, hdr_path: Path) -> str | None:
|
def instagram_resolution_error(
|
||||||
|
sdr_path: Path, hdr_path: Path, ignore_ig_resolution: bool = False,
|
||||||
|
) -> str | None:
|
||||||
"""Returns an error message if the pair doesn't meet Instagram's feed
|
"""Returns an error message if the pair doesn't meet Instagram's feed
|
||||||
requirements, otherwise None."""
|
requirements, otherwise None.
|
||||||
|
|
||||||
|
The dimension-match check always applies: mismatched SDR/HDR
|
||||||
|
dimensions misalign the gain map regardless of what platform the
|
||||||
|
result is for. ignore_ig_resolution skips only the exact-match check
|
||||||
|
against Instagram's own feed resolutions, for results meant for
|
||||||
|
somewhere other than Instagram."""
|
||||||
try:
|
try:
|
||||||
sdr_w, sdr_h = probe_dimensions(sdr_path)
|
sdr_w, sdr_h = probe_dimensions(sdr_path)
|
||||||
hdr_w, hdr_h = probe_dimensions(hdr_path)
|
hdr_w, hdr_h = probe_dimensions(hdr_path)
|
||||||
@@ -57,7 +65,7 @@ def instagram_resolution_error(sdr_path: Path, hdr_path: Path) -> str | None:
|
|||||||
"dimensions, otherwise the gain map will misalign."
|
"dimensions, otherwise the gain map will misalign."
|
||||||
)
|
)
|
||||||
|
|
||||||
if (sdr_w, sdr_h) not in IG_RESOLUTIONS:
|
if not ignore_ig_resolution and (sdr_w, sdr_h) not in IG_RESOLUTIONS:
|
||||||
allowed = ", ".join(f"{w}x{h} ({label})" for (w, h), label in IG_RESOLUTIONS.items())
|
allowed = ", ".join(f"{w}x{h} ({label})" for (w, h), label in IG_RESOLUTIONS.items())
|
||||||
return f"Image is {sdr_w}x{sdr_h} — Instagram requires an exact match to one of: {allowed}."
|
return f"Image is {sdr_w}x{sdr_h} — Instagram requires an exact match to one of: {allowed}."
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ form { display: block; }
|
|||||||
|
|
||||||
.add-duo:hover { color: var(--text); border-color: var(--accent-dim); }
|
.add-duo:hover { color: var(--text); border-color: var(--accent-dim); }
|
||||||
|
|
||||||
|
.checkbox-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-row input { cursor: pointer; }
|
||||||
|
|
||||||
.dropzones {
|
.dropzones {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
+12
-5
@@ -40,16 +40,23 @@
|
|||||||
|
|
||||||
<button type="button" class="add-duo" id="add-duo">+ Add another pair</button>
|
<button type="button" class="add-duo" id="add-duo">+ Add another pair</button>
|
||||||
|
|
||||||
|
<label class="checkbox-row">
|
||||||
|
<input type="checkbox" name="ignore_ig_resolution">
|
||||||
|
Process anyway if the resolution doesn't match Instagram's feed sizes
|
||||||
|
</label>
|
||||||
|
|
||||||
<button class="run" type="submit">Assemble the HDR</button>
|
<button class="run" type="submit">Assemble the HDR</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="footnote">
|
<p class="footnote">
|
||||||
Each pair's files must have identical dimensions, matching exactly one of Instagram's feed
|
Each pair's files must have identical dimensions. By default that must also match one of
|
||||||
resolutions: <strong>1080x1080</strong> (1:1), <strong>1080x1350</strong> (4:5), or
|
Instagram's feed resolutions exactly: <strong>1080x1080</strong> (1:1),
|
||||||
<strong>1080x566</strong> (1.91:1). Assembling more than one pair downloads as a <code>.zip</code>.
|
<strong>1080x1350</strong> (4:5), or <strong>1080x566</strong> (1.91:1); check the box above
|
||||||
HDR can be a regular gain-map JPEG or Lightroom's 32-bit float linear HDR TIFF export; a
|
to skip that check for results meant for somewhere other than Instagram. Assembling more
|
||||||
TIFF HDR file gives a more precise result than the JPEG gain-map route.
|
than one pair downloads as a <code>.zip</code>. HDR can be a regular gain-map JPEG or
|
||||||
|
Lightroom's 32-bit float linear HDR TIFF export; a TIFF HDR file gives a more precise result
|
||||||
|
than the JPEG gain-map route.
|
||||||
</p>
|
</p>
|
||||||
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
|
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user