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
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),
itself based on the reverse-engineering work of
[karachungen/instagram-hdr-converter](https://github.com/karachungen/instagram-hdr-converter).
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
+1 -3
View File
@@ -27,9 +27,7 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
COPY app/ app/
COPY templates/ templates/
COPY static/ static/
COPY src/ src/
COPY LICENSE VERSION ./
COPY docker/entrypoint.sh /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"
warning) on a results page.
This file is just the entry point — see app/ for the app itself:
app/config.py constants
app/jobs.py in-memory job registry + heartbeat/reaper
app/validation.py upload + Instagram resolution checks
app/assembler.py gain-map assembly pipeline
app/routes.py Flask routes
This file is just the entry point — see src/script/app/ for the app itself:
src/script/app/config.py constants
src/script/app/jobs.py in-memory job registry + heartbeat/reaper
src/script/app/validation.py upload + Instagram resolution checks
src/script/app/assembler.py gain-map assembly pipeline
src/script/app/routes.py Flask routes
Run directly: python3 main.py
Run in Docker: see Dockerfile (gunicorn main:app)
"""
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent / "src" / "script"))
from app import create_app
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 . 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
def create_app() -> Flask:
app = Flask(
__name__,
template_folder=str(BASE_DIR / "templates"),
static_folder=str(BASE_DIR / "static"),
template_folder=str(TEMPLATES_DIR),
static_folder=str(ASSETS_DIR),
)
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."""
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
JOBS_DIR = BASE_DIR / "jobs"
VERSION = (BASE_DIR / "VERSION").read_text().strip()
# This file lives at <repo root>/src/script/app/config.py.
SRC_DIR = Path(__file__).resolve().parent.parent.parent
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_HDR_EXT = {".jpg", ".jpeg", ".tif", ".tiff"}
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
<body>
<div class="wrap">
@@ -61,11 +61,11 @@
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
<div class="repo-links">
<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
</a>
<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
</a>
</div>
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
<body>
<div class="wrap">
@@ -46,11 +46,11 @@
<p class="version">Instagram HDR Assembler v{{ app_version }}</p>
<div class="repo-links">
<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
</a>
<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
</a>
</div>