Reorganize into src/ with script/assets/templates, illustration/ at root

New layout:
  src/script/app/    Python package (was app/)
  src/assets/img/    SVGs (was static/*.svg)
  src/assets/css/    stylesheet (was static/style.css)
  src/templates/     HTML templates (was templates/)
  illustration/      README screenshot (was screenshots/), kept at
                      the repo root since it's a doc asset, not
                      something the app serves
  main.py, jobs/, VERSION, docker/  unchanged locations

main.py stays at the repo root as the entry point (matches how
Docker and local runs already invoke it) and prepends src/script to
sys.path before importing app, rather than moving into src/script/
itself and needing every entry point updated to match.

config.py's path constants are recomputed for app/config.py's new
depth (src/script/app/), with SRC_DIR and REPO_ROOT resolved
explicitly rather than a single ambiguous BASE_DIR, since "templates
and assets live under src/" and "jobs and VERSION live at the repo
root" are no longer the same directory. Flask's static_folder now
points at src/assets, which serves img/ and css/ under it at
whatever URL prefix Flask derives from the folder name (/assets, not
/static); templates already reference assets via the "static"
endpoint name through url_for, so this needed the url_for() calls'
filename argument updated with the img/ or css/ prefix, not a
hardcoded URL change.

Docker copies src/ into the image preserving this same internal
structure, so the path arithmetic in config.py resolves identically
in both a local checkout and the container, no --chdir or other
container-specific path handling needed.

Verified end to end after the move: local Flask dev server, a full
Docker rebuild, and a real HTTP conversion request through the
running container (TIFF HDR pipeline, numpy/tifffile imports
included) all succeed.
This commit is contained in:
Djeex
2026-08-01 15:26:18 +02:00
parent d67a2569f4
commit 974690132e
15 changed files with 29 additions and 23 deletions
+2 -2
View File
@@ -7,14 +7,14 @@
A small web front end for assembling Instagram-compatible HDR JPEGs (gain A small web front end for assembling Instagram-compatible HDR JPEGs (gain
map) from an SDR export and an HDR export out of Lightroom or Camera Raw. map) from an SDR export and an HDR export out of Lightroom or Camera Raw.
The gain-map assembly logic ([app/assembler.py](app/assembler.py)) is The gain-map assembly logic ([src/script/app/assembler.py](src/script/app/assembler.py)) is
adapted from [kostis-kounadis/instagram-hdr-assembler](https://github.com/kostis-kounadis/instagram-hdr-assembler), adapted from [kostis-kounadis/instagram-hdr-assembler](https://github.com/kostis-kounadis/instagram-hdr-assembler),
itself based on the reverse-engineering work of itself based on the reverse-engineering work of
[karachungen/instagram-hdr-converter](https://github.com/karachungen/instagram-hdr-converter). [karachungen/instagram-hdr-converter](https://github.com/karachungen/instagram-hdr-converter).
Original MIT license kept in [LICENSE](LICENSE). Original MIT license kept in [LICENSE](LICENSE).
![Instagram HDR Assembler screenshot](screenshots/insta-hdr-1.png) ![Instagram HDR Assembler screenshot](illustration/insta-hdr-1.png)
## How it works ## How it works
+1 -3
View File
@@ -27,9 +27,7 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
COPY main.py . COPY main.py .
COPY app/ app/ COPY src/ src/
COPY templates/ templates/
COPY static/ static/
COPY LICENSE VERSION ./ COPY LICENSE VERSION ./
COPY docker/entrypoint.sh /app/entrypoint.sh COPY docker/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

+9 -6
View File
@@ -8,18 +8,21 @@ LICENSE). Gives you a browser form instead of a terminal, and
surfaces the assembly report (including the "GainMapMin is negative" surfaces the assembly report (including the "GainMapMin is negative"
warning) on a results page. warning) on a results page.
This file is just the entry point — see app/ for the app itself: This file is just the entry point — see src/script/app/ for the app itself:
app/config.py constants src/script/app/config.py constants
app/jobs.py in-memory job registry + heartbeat/reaper src/script/app/jobs.py in-memory job registry + heartbeat/reaper
app/validation.py upload + Instagram resolution checks src/script/app/validation.py upload + Instagram resolution checks
app/assembler.py gain-map assembly pipeline src/script/app/assembler.py gain-map assembly pipeline
app/routes.py Flask routes src/script/app/routes.py Flask routes
Run directly: python3 main.py Run directly: python3 main.py
Run in Docker: see Dockerfile (gunicorn main:app) Run in Docker: see Dockerfile (gunicorn main:app)
""" """
import os import os
import sys import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent / "src" / "script"))
from app import create_app from app import create_app
from app.assembler import check_dependencies from app.assembler import check_dependencies

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -2,15 +2,15 @@
from flask import Flask from flask import Flask
from . import jobs from . import jobs
from .config import BASE_DIR, MAX_CONTENT_LENGTH, VERSION from .config import ASSETS_DIR, MAX_CONTENT_LENGTH, TEMPLATES_DIR, VERSION
from .routes import register_routes from .routes import register_routes
def create_app() -> Flask: def create_app() -> Flask:
app = Flask( app = Flask(
__name__, __name__,
template_folder=str(BASE_DIR / "templates"), template_folder=str(TEMPLATES_DIR),
static_folder=str(BASE_DIR / "static"), static_folder=str(ASSETS_DIR),
) )
app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH app.config["MAX_CONTENT_LENGTH"] = MAX_CONTENT_LENGTH
+8 -3
View File
@@ -1,9 +1,14 @@
"""Shared constants for the HDR-to-Instagram web front.""" """Shared constants for the HDR-to-Instagram web front."""
from pathlib import Path from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent # This file lives at <repo root>/src/script/app/config.py.
JOBS_DIR = BASE_DIR / "jobs" SRC_DIR = Path(__file__).resolve().parent.parent.parent
VERSION = (BASE_DIR / "VERSION").read_text().strip() REPO_ROOT = SRC_DIR.parent
TEMPLATES_DIR = SRC_DIR / "templates"
ASSETS_DIR = SRC_DIR / "assets"
JOBS_DIR = REPO_ROOT / "jobs"
VERSION = (REPO_ROOT / "VERSION").read_text().strip()
ALLOWED_SDR_EXT = {".jpg", ".jpeg"} ALLOWED_SDR_EXT = {".jpg", ".jpeg"}
ALLOWED_HDR_EXT = {".jpg", ".jpeg", ".tif", ".tiff"} ALLOWED_HDR_EXT = {".jpg", ".jpeg", ".tif", ".tiff"}
@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Instagram HDR Assembler</title> <title>Instagram HDR Assembler</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
@@ -61,11 +61,11 @@
<p class="version">Instagram HDR Assembler v{{ app_version }}</p> <p class="version">Instagram HDR Assembler v{{ app_version }}</p>
<div class="repo-links"> <div class="repo-links">
<a href="https://git.djeex.fr/Djeex/insta-hdr-converter" target="_blank" rel="noopener"> <a href="https://git.djeex.fr/Djeex/insta-hdr-converter" target="_blank" rel="noopener">
<span class="icon"><img src="{{ url_for('static', filename='gitea.svg') }}" alt=""></span> <span class="icon"><img src="{{ url_for('static', filename='img/gitea.svg') }}" alt=""></span>
Gitea Gitea
</a> </a>
<a href="https://github.com/Djeex/insta-hdr-converter" target="_blank" rel="noopener"> <a href="https://github.com/Djeex/insta-hdr-converter" target="_blank" rel="noopener">
<span class="icon"><img src="{{ url_for('static', filename='github.svg') }}" alt=""></span> <span class="icon"><img src="{{ url_for('static', filename='img/github.svg') }}" alt=""></span>
GitHub GitHub
</a> </a>
</div> </div>
@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Instagram HDR Assembler — Result</title> <title>Instagram HDR Assembler — Result</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
@@ -46,11 +46,11 @@
<p class="version">Instagram HDR Assembler v{{ app_version }}</p> <p class="version">Instagram HDR Assembler v{{ app_version }}</p>
<div class="repo-links"> <div class="repo-links">
<a href="https://git.djeex.fr/Djeex/insta-hdr-converter" target="_blank" rel="noopener"> <a href="https://git.djeex.fr/Djeex/insta-hdr-converter" target="_blank" rel="noopener">
<span class="icon"><img src="{{ url_for('static', filename='gitea.svg') }}" alt=""></span> <span class="icon"><img src="{{ url_for('static', filename='img/gitea.svg') }}" alt=""></span>
Gitea Gitea
</a> </a>
<a href="https://github.com/Djeex/insta-hdr-converter" target="_blank" rel="noopener"> <a href="https://github.com/Djeex/insta-hdr-converter" target="_blank" rel="noopener">
<span class="icon"><img src="{{ url_for('static', filename='github.svg') }}" alt=""></span> <span class="icon"><img src="{{ url_for('static', filename='img/github.svg') }}" alt=""></span>
GitHub GitHub
</a> </a>
</div> </div>