dorothea 1.9.0__tar.gz

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.
Files changed (131) hide show
  1. dorothea-1.9.0/.gitattributes +7 -0
  2. dorothea-1.9.0/.github/workflows/ci.yml +84 -0
  3. dorothea-1.9.0/.github/workflows/release.yml +45 -0
  4. dorothea-1.9.0/.gitignore +55 -0
  5. dorothea-1.9.0/.pre-commit-config.yaml +38 -0
  6. dorothea-1.9.0/CONFIG.md +124 -0
  7. dorothea-1.9.0/LICENSE.txt +21 -0
  8. dorothea-1.9.0/MIGRATION.md +86 -0
  9. dorothea-1.9.0/Makefile +63 -0
  10. dorothea-1.9.0/PKG-INFO +303 -0
  11. dorothea-1.9.0/pyproject.toml +112 -0
  12. dorothea-1.9.0/readme.md +278 -0
  13. dorothea-1.9.0/src/dorothea/__init__.py +15 -0
  14. dorothea-1.9.0/src/dorothea/__main__.py +6 -0
  15. dorothea-1.9.0/src/dorothea/builder.py +369 -0
  16. dorothea-1.9.0/src/dorothea/cache.py +145 -0
  17. dorothea-1.9.0/src/dorothea/cli.py +147 -0
  18. dorothea-1.9.0/src/dorothea/config.py +379 -0
  19. dorothea-1.9.0/src/dorothea/encoder.py +800 -0
  20. dorothea-1.9.0/src/dorothea/generator.py +245 -0
  21. dorothea-1.9.0/src/dorothea/media/__init__.py +16 -0
  22. dorothea-1.9.0/src/dorothea/media/base.py +23 -0
  23. dorothea-1.9.0/src/dorothea/media/colors.py +45 -0
  24. dorothea-1.9.0/src/dorothea/media/colors_imagemagick.py +45 -0
  25. dorothea-1.9.0/src/dorothea/media/colors_pillow.py +58 -0
  26. dorothea-1.9.0/src/dorothea/media/ffmpeg.py +70 -0
  27. dorothea-1.9.0/src/dorothea/media/image.py +165 -0
  28. dorothea-1.9.0/src/dorothea/media/markdown.py +68 -0
  29. dorothea-1.9.0/src/dorothea/media/video.py +67 -0
  30. dorothea-1.9.0/src/dorothea/scanner.py +401 -0
  31. dorothea-1.9.0/src/dorothea/template.py +79 -0
  32. dorothea-1.9.0/src/dorothea/themes/theme1/cookie.js +117 -0
  33. dorothea-1.9.0/src/dorothea/themes/theme1/global.css +669 -0
  34. dorothea-1.9.0/src/dorothea/themes/theme1/global.js +643 -0
  35. dorothea-1.9.0/src/dorothea/themes/theme1/img/camera.png +0 -0
  36. dorothea-1.9.0/src/dorothea/themes/theme1/img/camera_mask.png +0 -0
  37. dorothea-1.9.0/src/dorothea/themes/theme1/img/close.png +0 -0
  38. dorothea-1.9.0/src/dorothea/themes/theme1/img/comment.png +0 -0
  39. dorothea-1.9.0/src/dorothea/themes/theme1/img/comment_mask.png +0 -0
  40. dorothea-1.9.0/src/dorothea/themes/theme1/img/download.png +0 -0
  41. dorothea-1.9.0/src/dorothea/themes/theme1/img/download_mask.png +0 -0
  42. dorothea-1.9.0/src/dorothea/themes/theme1/img/facebook.png +0 -0
  43. dorothea-1.9.0/src/dorothea/themes/theme1/img/facebook_mask.png +0 -0
  44. dorothea-1.9.0/src/dorothea/themes/theme1/img/hackernews.png +0 -0
  45. dorothea-1.9.0/src/dorothea/themes/theme1/img/hackernews_mask.png +0 -0
  46. dorothea-1.9.0/src/dorothea/themes/theme1/img/heart.png +0 -0
  47. dorothea-1.9.0/src/dorothea/themes/theme1/img/heart_mask.png +0 -0
  48. dorothea-1.9.0/src/dorothea/themes/theme1/img/monitor.png +0 -0
  49. dorothea-1.9.0/src/dorothea/themes/theme1/img/monitor_mask.png +0 -0
  50. dorothea-1.9.0/src/dorothea/themes/theme1/img/next.png +0 -0
  51. dorothea-1.9.0/src/dorothea/themes/theme1/img/pinterest.png +0 -0
  52. dorothea-1.9.0/src/dorothea/themes/theme1/img/pinterest_mask.png +0 -0
  53. dorothea-1.9.0/src/dorothea/themes/theme1/img/prev.png +0 -0
  54. dorothea-1.9.0/src/dorothea/themes/theme1/img/reddit.png +0 -0
  55. dorothea-1.9.0/src/dorothea/themes/theme1/img/reddit_mask.png +0 -0
  56. dorothea-1.9.0/src/dorothea/themes/theme1/img/text.png +0 -0
  57. dorothea-1.9.0/src/dorothea/themes/theme1/img/text_mask.png +0 -0
  58. dorothea-1.9.0/src/dorothea/themes/theme1/img/twitter.png +0 -0
  59. dorothea-1.9.0/src/dorothea/themes/theme1/img/twitter_mask.png +0 -0
  60. dorothea-1.9.0/src/dorothea/themes/theme1/json.js +1 -0
  61. dorothea-1.9.0/src/dorothea/themes/theme1/post-template.html +11 -0
  62. dorothea-1.9.0/src/dorothea/themes/theme1/template.html +94 -0
  63. dorothea-1.9.0/src/dorothea/themes/theme2/global.css +422 -0
  64. dorothea-1.9.0/src/dorothea/themes/theme2/global.js +246 -0
  65. dorothea-1.9.0/src/dorothea/themes/theme2/img/camera.png +0 -0
  66. dorothea-1.9.0/src/dorothea/themes/theme2/img/camera_mask.png +0 -0
  67. dorothea-1.9.0/src/dorothea/themes/theme2/img/close.png +0 -0
  68. dorothea-1.9.0/src/dorothea/themes/theme2/img/comment.png +0 -0
  69. dorothea-1.9.0/src/dorothea/themes/theme2/img/comment_mask.png +0 -0
  70. dorothea-1.9.0/src/dorothea/themes/theme2/img/download.png +0 -0
  71. dorothea-1.9.0/src/dorothea/themes/theme2/img/download_mask.png +0 -0
  72. dorothea-1.9.0/src/dorothea/themes/theme2/img/facebook.png +0 -0
  73. dorothea-1.9.0/src/dorothea/themes/theme2/img/facebook_mask.png +0 -0
  74. dorothea-1.9.0/src/dorothea/themes/theme2/img/hackernews.png +0 -0
  75. dorothea-1.9.0/src/dorothea/themes/theme2/img/hackernews_mask.png +0 -0
  76. dorothea-1.9.0/src/dorothea/themes/theme2/img/heart.png +0 -0
  77. dorothea-1.9.0/src/dorothea/themes/theme2/img/heart_mask.png +0 -0
  78. dorothea-1.9.0/src/dorothea/themes/theme2/img/monitor.png +0 -0
  79. dorothea-1.9.0/src/dorothea/themes/theme2/img/monitor_mask.png +0 -0
  80. dorothea-1.9.0/src/dorothea/themes/theme2/img/next.png +0 -0
  81. dorothea-1.9.0/src/dorothea/themes/theme2/img/pinterest.png +0 -0
  82. dorothea-1.9.0/src/dorothea/themes/theme2/img/pinterest_mask.png +0 -0
  83. dorothea-1.9.0/src/dorothea/themes/theme2/img/prev.png +0 -0
  84. dorothea-1.9.0/src/dorothea/themes/theme2/img/reddit.png +0 -0
  85. dorothea-1.9.0/src/dorothea/themes/theme2/img/reddit_mask.png +0 -0
  86. dorothea-1.9.0/src/dorothea/themes/theme2/img/text.png +0 -0
  87. dorothea-1.9.0/src/dorothea/themes/theme2/img/text_mask.png +0 -0
  88. dorothea-1.9.0/src/dorothea/themes/theme2/img/twitter.png +0 -0
  89. dorothea-1.9.0/src/dorothea/themes/theme2/img/twitter_mask.png +0 -0
  90. dorothea-1.9.0/src/dorothea/themes/theme2/post-template.html +11 -0
  91. dorothea-1.9.0/src/dorothea/themes/theme2/template.html +69 -0
  92. dorothea-1.9.0/src/dorothea/themes.py +44 -0
  93. dorothea-1.9.0/src/dorothea/utils.py +54 -0
  94. dorothea-1.9.0/tests/__init__.py +0 -0
  95. dorothea-1.9.0/tests/conftest.py +90 -0
  96. dorothea-1.9.0/tests/data/test_run/01_Nature/01_Mountains/01_peak.jpg +0 -0
  97. dorothea-1.9.0/tests/data/test_run/01_Nature/01_Mountains/01_peak.txt +3 -0
  98. dorothea-1.9.0/tests/data/test_run/01_Nature/02_Oceans/01_wave.jpg +0 -0
  99. dorothea-1.9.0/tests/data/test_run/01_Nature/02_Oceans/01_wave.txt +4 -0
  100. dorothea-1.9.0/tests/data/test_run/02_Urban/01_city.jpg +0 -0
  101. dorothea-1.9.0/tests/data/test_run/02_Urban/01_city.txt +4 -0
  102. dorothea-1.9.0/tests/reference/Markdown_1.0.1/License.text +30 -0
  103. dorothea-1.9.0/tests/reference/Markdown_1.0.1/Markdown Readme.text +341 -0
  104. dorothea-1.9.0/tests/reference/Markdown_1.0.1/Markdown.pl +1450 -0
  105. dorothea-1.9.0/tests/reference/expose.sh +980 -0
  106. dorothea-1.9.0/tests/reference/oldexpose.py +1552 -0
  107. dorothea-1.9.0/tests/reference/test_parity.sh +207 -0
  108. dorothea-1.9.0/tests/test_download.py +126 -0
  109. dorothea-1.9.0/tests/test_edge_cases.py +156 -0
  110. dorothea-1.9.0/tests/test_final_parity.py +263 -0
  111. dorothea-1.9.0/tests/test_integration.py +312 -0
  112. dorothea-1.9.0/tests/test_main.py +171 -0
  113. dorothea-1.9.0/tests/test_more_coverage.py +183 -0
  114. dorothea-1.9.0/tests/test_parity.py +320 -0
  115. dorothea-1.9.0/tests/test_pure.py +93 -0
  116. dorothea-1.9.0/tests/test_video.py +387 -0
  117. dorothea-1.9.0/tests/unit/__init__.py +1 -0
  118. dorothea-1.9.0/tests/unit/media/__init__.py +1 -0
  119. dorothea-1.9.0/tests/unit/media/test_colors.py +196 -0
  120. dorothea-1.9.0/tests/unit/media/test_image.py +146 -0
  121. dorothea-1.9.0/tests/unit/media/test_markdown.py +53 -0
  122. dorothea-1.9.0/tests/unit/media/test_markdown_parity.py +87 -0
  123. dorothea-1.9.0/tests/unit/media/test_video.py +159 -0
  124. dorothea-1.9.0/tests/unit/test_cache.py +195 -0
  125. dorothea-1.9.0/tests/unit/test_config.py +232 -0
  126. dorothea-1.9.0/tests/unit/test_config_sh.py +113 -0
  127. dorothea-1.9.0/tests/unit/test_encoder_parallel.py +167 -0
  128. dorothea-1.9.0/tests/unit/test_parity_fixes.py +84 -0
  129. dorothea-1.9.0/tests/unit/test_template.py +256 -0
  130. dorothea-1.9.0/tests/unit/test_utils.py +134 -0
  131. dorothea-1.9.0/uv.lock +266 -0
@@ -0,0 +1,7 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
3
+
4
+ *.sh text
5
+
6
+ # Force unix line endings
7
+ *.sh eol=lf
@@ -0,0 +1,84 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ concurrency:
8
+ group: ${{ github.workflow }}-${{ github.ref }}
9
+ cancel-in-progress: true
10
+
11
+ jobs:
12
+ lint:
13
+ name: Lint (ruff, format, ty)
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ - uses: actions/setup-python@v7
18
+ with:
19
+ python-version: "3.14"
20
+ - uses: astral-sh/setup-uv@v7
21
+ - run: uv sync
22
+ - run: uvx prek run --all-files --show-diff-on-failure
23
+
24
+ test-full:
25
+ # Everything installed, including expose.sh's own dependencies, so the parity tests
26
+ # run. Arch ships ImageMagick 7 and ffmpeg with x264 (like the dev machine); Ubuntu's
27
+ # ImageMagick 6 picks different JPEG chroma subsampling than the one we match.
28
+ name: Tests + parity (ImageMagick, ffmpeg, perl)
29
+ runs-on: ubuntu-latest
30
+ container: archlinux:latest
31
+ steps:
32
+ - name: Install system packages
33
+ run: >-
34
+ pacman -Syu --noconfirm --needed
35
+ git make bash which file rsync perl imagemagick ffmpeg uv
36
+ - uses: actions/checkout@v7
37
+ - run: uv sync
38
+ - run: make test-cov
39
+
40
+ test-minimal:
41
+ # No ffmpeg or ImageMagick on the system: proves `uv tool install` needs nothing else
42
+ # (the bundled imageio-ffmpeg binary and Pillow are used). Parity tests skip here.
43
+ name: Tests without system packages
44
+ runs-on: ubuntu-latest
45
+ container: ghcr.io/astral-sh/uv:python3.14-bookworm-slim
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+ - name: Check the environment really is minimal
49
+ run: |
50
+ ! command -v ffmpeg
51
+ ! command -v convert
52
+ ! command -v magick
53
+ - run: uv sync
54
+ - run: uv run pytest -m "not slow" --no-cov
55
+
56
+ package:
57
+ # Build the wheel and sdist and run them the way users will (`uvx dorothea`,
58
+ # `pipx run dorothea`), outside the source tree, with no ffmpeg or ImageMagick installed.
59
+ # Catches what the test suite can't: themes missing from the package, broken entry
60
+ # points, dependencies missing from pyproject.toml.
61
+ name: Package (uvx, pipx)
62
+ runs-on: ubuntu-latest
63
+ container: ghcr.io/astral-sh/uv:python3.14-bookworm-slim
64
+ steps:
65
+ - uses: actions/checkout@v7
66
+ - run: uv build
67
+ - name: Entry points
68
+ run: |
69
+ wheel=$(ls dist/*.whl)
70
+ uvx --isolated --from "$wheel" dorothea --version
71
+ uvx --isolated --from "$wheel" expose --version
72
+ uvx pipx run --no-cache --spec "$wheel" dorothea --version
73
+ - name: Build a gallery from the wheel and from the sdist
74
+ run: |
75
+ for dist in dist/*.whl dist/*.tar.gz; do
76
+ gallery=$(mktemp -d)
77
+ cp -r tests/data/test_run/. "$gallery"
78
+ rm -rf "$gallery/_site"
79
+ (cd "$gallery" && uvx --isolated --from "$GITHUB_WORKSPACE/$dist" dorothea -d)
80
+ test -s "$gallery/_site/index.html"
81
+ test -s "$gallery/_site/global.css"
82
+ test -s "$gallery/_site/nature/mountains/peak/1024.jpg"
83
+ echo "OK: $dist"
84
+ done
@@ -0,0 +1,45 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI when a GitHub release is published (tag like v1.9.0).
4
+ # Uses PyPI trusted publishing: no API token is stored in the repository. One-time setup on
5
+ # pypi.org: add a (pending) trusted publisher for project "dorothea" with owner
6
+ # "marcolussetti", repository "dorothea", workflow "release.yml", environment "pypi".
7
+
8
+ on:
9
+ release:
10
+ types: [published]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ - uses: astral-sh/setup-uv@v7
18
+ - name: Check the tag matches the package version
19
+ env:
20
+ TAG: ${{ github.event.release.tag_name }}
21
+ run: |
22
+ version=$(uv version --short)
23
+ if [ "$TAG" != "v$version" ]; then
24
+ echo "Release tag $TAG does not match pyproject version v$version" >&2
25
+ exit 1
26
+ fi
27
+ - run: uv build
28
+ - uses: actions/upload-artifact@v7
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+
33
+ publish:
34
+ needs: build
35
+ runs-on: ubuntu-latest
36
+ environment: pypi
37
+ permissions:
38
+ id-token: write # trusted publishing
39
+ steps:
40
+ - uses: actions/download-artifact@v7
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+ - uses: astral-sh/setup-uv@v7
45
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,55 @@
1
+ # Windows image file caches
2
+ Thumbs.db
3
+ ehthumbs.db
4
+
5
+ # Folder config file
6
+ Desktop.ini
7
+
8
+ # Recycle Bin used on file shares
9
+ $RECYCLE.BIN/
10
+
11
+ # Windows Installer files
12
+ *.cab
13
+ *.msi
14
+ *.msm
15
+ *.msp
16
+
17
+ # Windows shortcuts
18
+ *.lnk
19
+
20
+ # =========================
21
+ # Operating System Files
22
+ # =========================
23
+
24
+ # OSX
25
+ # =========================
26
+
27
+ .DS_Store
28
+ .AppleDouble
29
+ .LSOverride
30
+
31
+ # Thumbnails
32
+ ._*
33
+
34
+ # Files that might appear on external disk
35
+ .Spotlight-V100
36
+ .Trashes
37
+
38
+ # Directories potentially created on remote AFP share
39
+ .AppleDB
40
+ .AppleDesktop
41
+ Network Trash Folder
42
+ Temporary Items
43
+ .apdisk
44
+
45
+ # Python
46
+ __pycache__/
47
+ *.pyc
48
+ .venv/
49
+ _site/
50
+ plans/
51
+
52
+ # Personal gallery (local test data, not part of the project)
53
+ knowneuropean-photos/
54
+ .coverage
55
+ htmlcov/
@@ -0,0 +1,38 @@
1
+ # Pre-commit configuration for dorothea
2
+ # Excludes legacy theme files and expose.sh
3
+
4
+ # Hooks that parse Python (debug-statements) must run on the project's minimum version (3.14)
5
+ default_language_version:
6
+ python: python3.14
7
+
8
+ repos:
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: v4.5.0
11
+ hooks:
12
+ - id: trailing-whitespace
13
+ exclude: ^(theme1/|theme2/|Markdown_1.0.1/|expose.sh)
14
+ - id: end-of-file-fixer
15
+ exclude: ^(theme1/|theme2/|Markdown_1.0.1/|expose.sh)
16
+ - id: check-yaml
17
+ - id: check-added-large-files
18
+ args: ['--maxkb=2000']
19
+ - id: check-merge-conflict
20
+ - id: debug-statements
21
+
22
+ - repo: https://github.com/astral-sh/ruff-pre-commit
23
+ rev: v0.16.9 # keep in sync with pyproject.toml dev group
24
+ hooks:
25
+ - id: ruff-check
26
+ args: [--fix, --exit-non-zero-on-fix]
27
+ exclude: ^(theme1/|theme2/|Markdown_1.0.1/|expose.sh)
28
+ - id: ruff-format
29
+ exclude: ^(theme1/|theme2/|Markdown_1.0.1/|expose.sh)
30
+
31
+ - repo: local
32
+ hooks:
33
+ - id: ty
34
+ name: ty check
35
+ entry: uv run ty check src
36
+ language: system
37
+ types: [python]
38
+ pass_filenames: false
@@ -0,0 +1,124 @@
1
+ # Dorothea configuration
2
+
3
+ Settings are read, in increasing order of precedence, from:
4
+
5
+ 1. built-in defaults (below, the same as expose.sh's)
6
+ 2. `_config.json` in the gallery folder, or `--config FILE` (`.json` or `.sh`). Without a
7
+ `_config.json`, expose.sh's `_config.sh` is read instead (see [MIGRATION.md](MIGRATION.md))
8
+ 3. `--set KEY=VALUE` and `-j N` on the command line
9
+ 4. draft mode (`-d`), which forces `resolution=[1024]`, `bitrate=[4]`, `video_formats=["h264"]`
10
+ and `download_button=false`
11
+
12
+ ```json
13
+ {
14
+ "site_title": "Iceland 2022",
15
+ "theme_dir": "theme2",
16
+ "resolution": [2560, 1920, 1280, 640],
17
+ "jpeg_quality": 88
18
+ }
19
+ ```
20
+
21
+ Invalid values stop the build with a message naming each problem (exit code 2); unknown keys
22
+ only print a warning, since they're usually typos.
23
+
24
+ Changing a setting that affects image or video bytes (resolution, quality, bitrates, codec
25
+ speed, per-post options...) re-encodes just the affected files on the next run. There's no need
26
+ to delete `_site`.
27
+
28
+ ## Site and theme
29
+
30
+ | Key | Default | Description |
31
+ |---|---|---|
32
+ | `site_title` | `"My Awesome Photos"` | Site name shown in every page. |
33
+ | `theme_dir` | `"theme1"` | Theme: a bundled one (`theme1`, `theme2`), a folder of that name in the gallery, or an absolute path. |
34
+ | `text_toggle` | `true` | Show a button to hide/show the text. |
35
+ | `social_button` | `true` | Show the social sharing button. |
36
+ | `disqus_shortname` | `""` | Disqus forum name for comments; empty disables them. |
37
+
38
+ ## Images
39
+
40
+ | Key | Default | Description |
41
+ |---|---|---|
42
+ | `resolution` | `[3840, 2560, 1920, 1280, 1024, 640]` | Widths to generate (heights follow the source aspect ratio). Only sizes up to the source width are made, plus the smallest one always. |
43
+ | `jpeg_quality` | `92` | JPEG quality (1–100) for generated images. |
44
+ | `autorotate` | `true` | Apply EXIF orientation. |
45
+
46
+ ## Colours
47
+
48
+ | Key | Default | Description |
49
+ |---|---|---|
50
+ | `extract_colors` | `true` | Extract a 7-colour palette from each photo/video for the theme (`color1`…`color7`, background, text). |
51
+ | `default_palette` | `["#000000", "#222222", "#444444", "#666666", "#999999", "#cccccc", "#ffffff"]` | Palette (background to foreground) used when `extract_colors` is false. |
52
+ | `backgroundcolor` | `"#000000"` | Slide background, visible before the image loads. |
53
+ | `textcolor` | `"#ffffff"` | Default text colour. |
54
+ | `override_textcolor` | `true` | Use `textcolor` for body text instead of the palette's last colour. |
55
+
56
+ ## Video
57
+
58
+ Video needs ffmpeg: a system `ffmpeg` on `PATH` is used if present, otherwise the one bundled
59
+ with Dorothea (imageio-ffmpeg).
60
+
61
+ | Key | Default | Description |
62
+ |---|---|---|
63
+ | `video_formats` | `["h264", "vp8"]` | Formats to encode, in order of preference: `h264`, `h265`, `vp9`, `vp8`, `ogv`. |
64
+ | `bitrate` | `[40, 24, 12, 7, 4, 2]` | Target bitrate in Mbit/s for each entry in `resolution` (the last value repeats if the list is shorter). |
65
+ | `bitrate_maxratio` | `2` | Max bitrate as a multiple of the target (VBR). Must be ≥ 1; 1 means constant bitrate. |
66
+ | `disable_audio` | `true` | Strip audio (otherwise it's copied as-is). |
67
+ | `h264_encodespeed` | `"veryslow"` | x264/x265 preset: `ultrafast` … `veryslow`. Slower compresses better. |
68
+ | `vp9_encodespeed` | `1` | VP9 speed, 0 (best, very slow) to 4 (fastest). |
69
+ | `ffmpeg_threads` | `0` | ffmpeg `-threads` (0 = auto). Lower it to throttle CPU use. |
70
+ | `sequence_keyword` | `"imagesequence"` | A folder whose name contains this is compiled into a video from its images. |
71
+ | `sequence_framerate` | `24` | Frame rate of compiled image sequences. |
72
+
73
+ ## Downloads
74
+
75
+ | Key | Default | Description |
76
+ |---|---|---|
77
+ | `download_button` | `false` | Offer each original in a zip with a readme. |
78
+ | `download_readme` | `"All rights reserved"` | Text of the `readme.txt` in each zip. |
79
+
80
+ ## Dorothea-only
81
+
82
+ | Key | Default | Description |
83
+ |---|---|---|
84
+ | `jobs` | `0` | Parallel workers for reading and resizing images (0 = one per CPU). Same as `-j N`. Videos always encode one at a time, since ffmpeg already uses every core. |
85
+
86
+ ## Command line
87
+
88
+ | Flag | Description |
89
+ |---|---|
90
+ | `-d`, `--draft` | Draft mode: one 1024px size, fast h264 only. |
91
+ | `-n`, `--dry-run` | List what would be built (and why) without writing anything. |
92
+ | `-c FILE`, `--config FILE` | Use this config file (`.json` or expose.sh `.sh`). |
93
+ | `-s KEY=VALUE`, `--set KEY=VALUE` | Override a setting; VALUE is JSON if it parses (`--set 'resolution=[1920,640]'`), else a string. Repeatable. |
94
+ | `-j N`, `--jobs N` | Parallel workers (see `jobs`). |
95
+ | `--convert-config` | Write `_config.json` from `_config.sh` and exit. |
96
+ | `--version` | Print the version. |
97
+
98
+ ## Per-post metadata
99
+
100
+ A text file next to a photo or video with the same name (`01 Glacier.txt` or `.md`) holds its
101
+ caption. Lines before a `---` line are `key: value` metadata; the rest is Markdown. A
102
+ `metadata.txt` in a gallery folder applies to every post in it, and a post's own metadata wins.
103
+
104
+ ```
105
+ image-options: -modulate 100,120
106
+ top: 30
107
+ left: 5
108
+ ---
109
+ Caption in *Markdown*.
110
+ ```
111
+
112
+ | Key | Description |
113
+ |---|---|
114
+ | `image-options` | Extra ImageMagick `convert` arguments for this photo (needs ImageMagick installed; otherwise ignored with a warning). Not applied to video thumbnails. |
115
+ | `video-options` | Extra ffmpeg arguments, e.g. `-ss 10 -t 5` to cut a clip. |
116
+ | `video-filters` | ffmpeg filters appended after scaling, e.g. `hflip`. |
117
+ | anything else | Available to the theme as `{{key}}`. theme1 uses `top`, `left`, `width`, `height` (percent), `polygon` and `textcolor`; theme2 uses `width` and `class`. `color1`…`color7` come from the extracted palette. |
118
+
119
+ ## Build cache
120
+
121
+ Dorothea keeps `.dorothea-cache.json` in the gallery folder. It holds extracted palettes, so
122
+ unchanged photos aren't re-analysed, and a fingerprint for every generated file, so changed
123
+ sources, settings or metadata rebuild exactly what they affect. It's safe to delete: the next
124
+ run re-analyses the photos and keeps any existing output that's newer than its source.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jack Qiao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,86 @@
1
+ # Migrating from expose.sh to Dorothea
2
+
3
+ Dorothea is a Python port of [expose.sh](https://github.com/Jack000/Expose). For the same
4
+ photos and settings it produces the same site: its test suite builds galleries with both and
5
+ checks that the file lists match, HTML/CSS/JS are byte-identical, and every image and video has
6
+ the same file size. This page covers what you need to change, and the places where Dorothea
7
+ deliberately behaves differently.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ uvx dorothea # run without installing (or: pipx run dorothea)
13
+ uv tool install dorothea # install the command (or: pipx install dorothea)
14
+ ```
15
+
16
+ No system packages are required: Pillow resizes images and an ffmpeg binary is bundled.
17
+ Optional:
18
+
19
+ - a system **ffmpeg** on `PATH` is preferred over the bundled one
20
+ - **ImageMagick** is used for colour extraction (identical palettes to expose.sh) and for
21
+ per-post `image-options`
22
+
23
+ Run it the same way as before, inside the gallery folder: `dorothea` or `dorothea -d`. The
24
+ command is also installed as `expose`, so `expose -d` keeps working.
25
+
26
+ ## Configuration
27
+
28
+ Your `_config.sh` keeps working: without a `_config.json`, Dorothea reads it. It's parsed,
29
+ never executed, so only plain assignments are supported:
30
+
31
+ ```sh
32
+ site_title="Iceland 2022" # quoted or unquoted values, comments
33
+ resolution=(2560 1920 1280 640) # arrays on one line
34
+ autorotate=false # true/false become booleans, numbers become numbers
35
+ ```
36
+
37
+ Anything else (`$(...)`, `${VAR}`, `export`, `if`) is skipped with a warning naming the line.
38
+ To switch to JSON, the recommended format:
39
+
40
+ ```sh
41
+ dorothea --convert-config # writes _config.json from _config.sh
42
+ ```
43
+
44
+ If both files exist, `_config.json` wins. Every setting is described in
45
+ [CONFIG.md](CONFIG.md).
46
+
47
+ > **Note:** expose.sh ignores `resolution`, `bitrate`, `video_formats` and `default_palette` set
48
+ > in `_config.sh` (it reassigns them after reading the file). Dorothea honours them, so a
49
+ > gallery that set them will now actually use those values.
50
+
51
+ ## New in Dorothea
52
+
53
+ - **Incremental builds that notice changes.** expose.sh skips any output that already exists,
54
+ so edits needed a manual `rm -rf _site`. Dorothea rebuilds exactly the files whose source,
55
+ settings or per-post metadata changed. State lives in `.dorothea-cache.json` in the gallery
56
+ folder, which also caches colour palettes so re-runs are fast. An existing `_site` from
57
+ expose.sh is adopted as-is, and files newer than their source aren't re-encoded.
58
+ - `-n` / `--dry-run` lists what would be built and why.
59
+ - `--set KEY=VALUE` overrides a setting for one run; `--config FILE` picks a config file.
60
+ - `-j N` resizes images in parallel (default: one worker per CPU).
61
+ - Config values are validated, with a clear error instead of a broken build.
62
+ - A failed or interrupted encode never leaves a truncated file behind; it's retried next run.
63
+ - A corrupt photo is reported and skipped instead of aborting the build.
64
+
65
+ ## Intentional differences
66
+
67
+ These are places where expose.sh has a bug or a platform quirk that Dorothea doesn't copy:
68
+
69
+ | expose.sh | Dorothea |
70
+ |---|---|
71
+ | Skips hidden folders only at the top level (`.foo/`). | Skips hidden folders at any depth (`gallery/.thumbs/`). |
72
+ | An image sequence mixing formats (JPEG + PNG) or extensions (`.JPG` + `.jpg`) silently loses frames. | Every frame is used; mixed formats are converted to lossless PNG first. |
73
+ | Download zips for image sequences fail (it `cp`s the folder). | The zip contains the compiled sequence video. |
74
+ | Unknown video extensions are checked with `file -ib`, using a path relative to the wrong directory, so effectively never. | Detected from the file extension's MIME type. |
75
+ | Leaves `ffmpeg2pass-*.log` in the current folder while encoding. | Keeps 2-pass logs in a temporary folder. |
76
+ | A non-text or non-UTF-8 caption file is skipped (`file` check). | Same, with a warning. Captions are always read and written as UTF-8. |
77
+ | Requires ImageMagick and `zip`; video needs ffmpeg and ffprobe. | Needs nothing beyond `pip`/`uv`; ffprobe isn't used. |
78
+
79
+ Caption Markdown is rendered by python-markdown and normalized to match expose.sh's
80
+ Markdown.pl output. One exception: Markdown.pl obfuscates email autolinks (`<me@x.com>`) with
81
+ random character entities, which by design can't be reproduced. Both versions display the same
82
+ address.
83
+
84
+ Resized JPEGs are made by Pillow rather than ImageMagick. File sizes match ImageMagick 7 (same
85
+ chroma subsampling rules) but the bytes aren't identical, so image files won't hash the same as
86
+ expose.sh's output; HTML, CSS and JS do.
@@ -0,0 +1,63 @@
1
+ # Dorothea Makefile
2
+ # Common development tasks using uv
3
+
4
+ .PHONY: help test test-cov test-fast clean install lint format typecheck
5
+
6
+ help:
7
+ @echo "Available commands:"
8
+ @echo " make test - Run all tests"
9
+ @echo " make test-cov - Run tests with coverage report"
10
+ @echo " make test-fast - Run tests without slow/parity tests"
11
+ @echo " make clean - Clean generated files and caches"
12
+ @echo " make install - Install dependencies with uv"
13
+ @echo " make lint - Run all pre-commit hooks (via prek): ruff, format, ty"
14
+ @echo " make typecheck - Type-check src/ with ty"
15
+ @echo " make format - Format code (via prek ruff-format)"
16
+ @echo " make install-hooks - Install prek hooks"
17
+
18
+ # Run all tests
19
+ test:
20
+ uv run pytest tests/ --no-cov
21
+
22
+ # Run tests with coverage
23
+ test-cov:
24
+ @rm -rf .coverage htmlcov
25
+ uv run pytest tests/ --cov=src/dorothea --cov-report=term --cov-report=html
26
+
27
+ # Run only fast tests (skip slow parity tests)
28
+ test-fast:
29
+ uv run pytest tests/ -m "not slow" --no-cov
30
+
31
+ # Run only parity tests
32
+ test-parity:
33
+ uv run pytest tests/ -m "slow" --no-cov
34
+
35
+ # Clean generated files
36
+ clean:
37
+ @rm -rf .coverage htmlcov .pytest_cache
38
+ @rm -rf tests/__pycache__ tests/.pytest_cache
39
+ @find tests -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
40
+ @find . -type f -name "*.pyc" -delete 2>/dev/null || true
41
+
42
+ # Install dependencies
43
+ install:
44
+ uv pip install -e ".[dev]"
45
+
46
+ # Install pre-commit hooks (via prek)
47
+ install-hooks:
48
+ prek install-hooks
49
+
50
+ # Run all pre-commit hooks on all files (via prek)
51
+ lint:
52
+ prek run --all-files
53
+
54
+ # Type-check with ty
55
+ typecheck:
56
+ uv run ty check src
57
+
58
+ # Format code only (via prek ruff-format hook)
59
+ format:
60
+ prek run ruff-format --all-files
61
+
62
+ # Run full CI checks locally
63
+ ci: clean lint test-cov