Compare commits
13 Commits
941e06e3d7
...
main
Author | SHA1 | Date | |
---|---|---|---|
779dab4f77 | |||
d872836624 | |||
df182fa7bb | |||
7d739ed777 | |||
6af8781aa2 | |||
00bc97465f | |||
74e99b96f0 | |||
cbab99cc82 | |||
001a8c692e | |||
e1f08f0312 | |||
cf9cd77bcf | |||
1563b3618a | |||
8c378492a3 |
20
doc/bozodown/block.md
Normal file
20
doc/bozodown/block.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Block
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
-----
|
||||
style
|
||||
---
|
||||
content
|
||||
-----
|
||||
```
|
||||
|
||||
| name | usage |
|
||||
| --- | --- |
|
||||
| style | Your custom css, multiline supported |
|
||||
| content | Your bozodown |
|
||||
|
||||
## Effect
|
||||
|
||||
Create a div with custom css
|
13
doc/bozodown/checkbox.md
Normal file
13
doc/bozodown/checkbox.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Checkbox
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
- [ ] your text
|
||||
or
|
||||
- [x] your text
|
||||
```
|
||||
|
||||
## Effect
|
||||
|
||||

|
BIN
doc/bozodown/checkbox.png
Normal file
BIN
doc/bozodown/checkbox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
17
doc/bozodown/code.md
Normal file
17
doc/bozodown/code.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Quote
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
\```
|
||||
your text
|
||||
\```
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text
|
||||
|
||||
to
|
||||
```
|
||||
your text
|
||||
```
|
11
doc/bozodown/image.md
Normal file
11
doc/bozodown/image.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Image
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
![url]
|
||||
```
|
||||
|
||||
## Effect
|
||||
|
||||
display the image
|
10
doc/bozodown/quote.md
Normal file
10
doc/bozodown/quote.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Quote
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
`your text`
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text -> `your text`
|
19
doc/bozodown/readme.md
Normal file
19
doc/bozodown/readme.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Bozodown
|
||||
|
||||
A markdown like, written in python, modulable
|
||||
|
||||
## Feature
|
||||
|
||||
### Text Formater
|
||||
- [title](./title.md)
|
||||
- [subtitle](./subtitle.md)
|
||||
- [subsubtitle](./subsubtitle.md)
|
||||
- [subsubsubtitle](./subsubsubtitle.md)
|
||||
- [code](./code.md)
|
||||
- [image](./image.md)
|
||||
- [block](./block.md)
|
||||
- [checkbox](./checkbox.md)
|
||||
|
||||
### Text Modifier
|
||||
- [strong](./strong.md)
|
||||
- [quote](./quote.md)
|
11
doc/bozodown/strong.md
Normal file
11
doc/bozodown/strong.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Strong
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
**your text**
|
||||
```
|
||||
|
||||
## Effect
|
||||
|
||||
your text -> **your text**
|
13
doc/bozodown/subsubsubtitle.md
Normal file
13
doc/bozodown/subsubsubtitle.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Subsubsubtitle
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
#### your text
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text
|
||||
|
||||
to
|
||||
#### your text
|
13
doc/bozodown/subsubtitle.md
Normal file
13
doc/bozodown/subsubtitle.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Subsubtitle
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
### your text
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text
|
||||
|
||||
to
|
||||
### your text
|
13
doc/bozodown/subtitle.md
Normal file
13
doc/bozodown/subtitle.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Subtitle
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
## your text
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text
|
||||
|
||||
to
|
||||
## your text
|
13
doc/bozodown/title.md
Normal file
13
doc/bozodown/title.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Title
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
# your text
|
||||
```
|
||||
|
||||
## Effect
|
||||
your text
|
||||
|
||||
to
|
||||
# your text
|
53
doc/photodown/gallery.md
Normal file
53
doc/photodown/gallery.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Block
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
+++++
|
||||
config
|
||||
---
|
||||
style
|
||||
---
|
||||
content
|
||||
+++++
|
||||
```
|
||||
|
||||
### Config
|
||||
|
||||
| type |
|
||||
| --- |
|
||||
| image-list |
|
||||
|
||||
| direction |
|
||||
| --- |
|
||||
| down |
|
||||
| up |
|
||||
| left |
|
||||
| right |
|
||||
|
||||
|
||||
|
||||
### Style
|
||||
Just css
|
||||
|
||||
|
||||
### Content
|
||||
|
||||
```
|
||||
![img1]
|
||||
description 1
|
||||
--
|
||||
![img2]
|
||||
description 2
|
||||
--
|
||||
...
|
||||
```
|
||||
|
||||
| name | usage |
|
||||
| --- | --- |
|
||||
| style | Your custom css, multiline supported |
|
||||
| content | Your bozodown |
|
||||
|
||||
## Effect
|
||||
|
||||
Create a group of image
|
16
doc/photodown/image.md
Normal file
16
doc/photodown/image.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Image
|
||||
|
||||
Permit you to use your own image without think about small, thumb, large, reference the bulk page. Always permit you to use external image with the url
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
![./bulk/path_to_image/image_large_name_without_ext]
|
||||
```
|
||||
|
||||
## Example
|
||||
On my server I got this image `bulk/01613/01613.png`, so I will used put
|
||||
```
|
||||
![./bulk/01613/01613]
|
||||
```
|
||||
And the python will display an image clickable who link to the bulk page
|
11
doc/photodown/readme.md
Normal file
11
doc/photodown/readme.md
Normal file
@ -0,0 +1,11 @@
|
||||
# PhotoDown
|
||||
|
||||
Just a simple extension of [BozoDown](../bozodown/readme.md)
|
||||
|
||||
## Add
|
||||
|
||||
- [Pictures gallery]()
|
||||
|
||||
## Modify
|
||||
|
||||
- [image](./image.md)
|
6
doc/readme.md
Normal file
6
doc/readme.md
Normal file
@ -0,0 +1,6 @@
|
||||
# PhotoHUB
|
||||
|
||||
## Tools
|
||||
|
||||
### Generate page
|
||||
To let user create his own page this project use [BozoDown](./bozodown/readme.md) with the [PhotoDown](./photodown/readme.md) extension
|
@ -1,3 +1,5 @@
|
||||
beautifulsoup4==4.13.4
|
||||
bs4==0.0.2
|
||||
exif==1.6.1
|
||||
imageio==2.37.0
|
||||
Jinja2==3.1.6
|
||||
@ -8,3 +10,5 @@ pillow==11.1.0
|
||||
plum-py==0.8.7
|
||||
progress==1.6
|
||||
rawpy==0.24.0
|
||||
soupsieve==2.7
|
||||
typing_extensions==4.14.1
|
||||
|
139
src/bozodown.py
139
src/bozodown.py
@ -1,139 +0,0 @@
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
def _photohub_render(id: str, text: str) -> str:
|
||||
if (id == "img"):
|
||||
return f"<img src='{text[2:-1]}'>"
|
||||
|
||||
def _bozodown_render(id: str, text: str) -> str:
|
||||
if (id == "list"):
|
||||
print("error: list not supported")
|
||||
return ""
|
||||
|
||||
|
||||
_specific_case_namespaces: dict[str, Callable[[str, str], str]] = {
|
||||
"bozodown": _bozodown_render,
|
||||
"photohub": _photohub_render,
|
||||
}
|
||||
|
||||
|
||||
_converters: list[dict[str, str]] = [
|
||||
{
|
||||
"from_prefix": "# ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h1>",
|
||||
"to_suffix": "</h1>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "## ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h2>",
|
||||
"to_suffix": "</h2>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "### ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h3>",
|
||||
"to_suffix": "</h3>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "#### ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h4>",
|
||||
"to_suffix": "</h4>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "`",
|
||||
"from_suffix": "`",
|
||||
"to_prefix": "<p><code>",
|
||||
"to_suffix": "</code></p>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "**",
|
||||
"from_suffix": "**",
|
||||
"to_prefix": "<strong>",
|
||||
"to_suffix": "</strong>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "- ",
|
||||
"from_suffix": "\n",
|
||||
"code": "bozodown:list",
|
||||
},
|
||||
{
|
||||
"from_prefix": "```",
|
||||
"from_suffix": "```",
|
||||
"to_prefix": "<pre><code>",
|
||||
"to_suffix": "</code></pre>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "![",
|
||||
"from_suffix": "]",
|
||||
"code": "photohub:img",
|
||||
},
|
||||
{
|
||||
"from_prefix": "- [ ] ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<li><input type='checkbox' disabled> ",
|
||||
"to_suffix": "</li>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "- [x] ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<li><input type='checkbox' checked disabled> ",
|
||||
"to_suffix": "</li>",
|
||||
},
|
||||
{
|
||||
"from_prefix": "<br>",
|
||||
"from_suffix": "",
|
||||
"to_prefix": "<br>",
|
||||
},
|
||||
]
|
||||
|
||||
_default_converter: dict[str, str] = {
|
||||
"from_prefix": "",
|
||||
"to_prefix": "<p>",
|
||||
"to_suffix": "</p>",
|
||||
}
|
||||
|
||||
def _render_element(text: str, converter: dict[str, str]) -> str:
|
||||
code: str = converter.get("code")
|
||||
if (code is not None):
|
||||
namespace, id = code.split(":")
|
||||
print(namespace, id)
|
||||
func = _specific_case_namespaces[namespace]
|
||||
return func(id, text)
|
||||
print(converter)
|
||||
start: int = len(converter["from_prefix"])
|
||||
stop: int = len(text) - len(converter.get("from_suffix", ""))
|
||||
return f"{converter['to_prefix']}{text[start:stop]}{converter['to_suffix']}"
|
||||
|
||||
def _get_first_converter(text: str) -> tuple[str, dict] | None:
|
||||
first_converter_found: dict[str, str | Callable[[str, list[str]], Any]] | None = None
|
||||
start: int | None = None
|
||||
for converter in _converters:
|
||||
matching_patern_pos: str = text.find(converter['from_prefix'])
|
||||
if (matching_patern_pos != -1):
|
||||
if (first_converter_found is None or matching_patern_pos < start):
|
||||
first_converter_found = converter
|
||||
start = matching_patern_pos
|
||||
if (first_converter_found is None):
|
||||
return text, _default_converter
|
||||
if (start != 0):
|
||||
return text[:start], _default_converter
|
||||
suffix: int = first_converter_found.get("from_suffix", "")
|
||||
prefix: int = first_converter_found['from_prefix']
|
||||
stop: int = text.find(suffix, start)
|
||||
if (stop == -1):
|
||||
print(f"error: '{prefix}' was never finished by a '{suffix}'")
|
||||
return
|
||||
stop += len(suffix)
|
||||
return text[start:stop], first_converter_found
|
||||
|
||||
def render(raw_content: str) -> str:
|
||||
content: str = ""
|
||||
to_parse: str = raw_content
|
||||
while len(to_parse) > 0:
|
||||
text, converter = _get_first_converter(to_parse)
|
||||
content += _render_element(text, converter)
|
||||
to_parse = to_parse[len(text):]
|
||||
return content
|
1
src/bozodown/__init__.py
Normal file
1
src/bozodown/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__all__ = ["Bozodown"]
|
69
src/bozodown/bozodown.py
Normal file
69
src/bozodown/bozodown.py
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from . import default_converters
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
class Bozodown():
|
||||
|
||||
def __init__(self):
|
||||
self._converters: dict[str, dict[str, str]] = default_converters.converters.copy()
|
||||
self._text_converter: dict[str, str] = default_converters.text_converter.copy()
|
||||
self._specific_case_namespaces: dict[str, Callable[[object, str, str], str]] = {
|
||||
"bozodown": self._bozodown_render,
|
||||
}
|
||||
|
||||
def render(self, to_parse: str):
|
||||
content: str = ""
|
||||
while len(to_parse) > 0:
|
||||
text, converter = self._get_first_converter(to_parse)
|
||||
content += self._render_element(text, converter)
|
||||
to_parse = to_parse[len(text):]
|
||||
return BeautifulSoup(content, 'html.parser').prettify()
|
||||
|
||||
|
||||
def _render_element(self, text: str, converter: dict[str, str]) -> str:
|
||||
code: str = converter.get("code")
|
||||
if (code is not None):
|
||||
namespace, id = code.split(":")
|
||||
func = self._specific_case_namespaces[namespace]
|
||||
return func(id, text)
|
||||
start: int = len(converter["from_prefix"])
|
||||
stop: int = len(text) - len(converter.get("from_suffix", ""))
|
||||
if (text[start:stop] == "\n" and converter is self._text_converter):
|
||||
return ""
|
||||
return f"{converter['to_prefix']}{text[start:stop]}{converter['to_suffix']}"
|
||||
|
||||
def _bozodown_render(self, id: str, text: str) -> str:
|
||||
if (id == "list"):
|
||||
print("error: list not supported")
|
||||
return ""
|
||||
if (id == "block"):
|
||||
style, to_parse = text[5:-5].split("---")
|
||||
style = style.replace("\n", "")
|
||||
content: str = self.render(to_parse)
|
||||
return f"<div style='{style}'>{content}</div>"
|
||||
|
||||
def _get_first_converter(self, text: str) -> tuple[str, dict] | None:
|
||||
first_converter_found: dict[str, str] | None = None
|
||||
start: int | None = None
|
||||
for converter in self._converters.values():
|
||||
matching_patern_pos: str = text.find(converter['from_prefix'])
|
||||
if (matching_patern_pos != -1):
|
||||
if (first_converter_found is None or matching_patern_pos < start):
|
||||
first_converter_found = converter
|
||||
start = matching_patern_pos
|
||||
if (first_converter_found is None):
|
||||
return text, self._text_converter
|
||||
if (start != 0):
|
||||
return text[:start], self._text_converter
|
||||
suffix: int = first_converter_found.get("from_suffix", "")
|
||||
prefix: int = first_converter_found['from_prefix']
|
||||
stop: int = text.find(suffix, start + len(prefix))
|
||||
if (stop == -1):
|
||||
print(f"error: '{prefix}' was never finished by a '{suffix}'")
|
||||
return
|
||||
stop += len(suffix)
|
||||
return text[start:stop], first_converter_found
|
78
src/bozodown/default_converters.py
Normal file
78
src/bozodown/default_converters.py
Normal file
@ -0,0 +1,78 @@
|
||||
converters: list[str, dict[str, str]] = {
|
||||
"title": {
|
||||
"from_prefix": "# ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h1>",
|
||||
"to_suffix": "</h1>",
|
||||
},
|
||||
"subtitle": {
|
||||
"from_prefix": "## ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h2>",
|
||||
"to_suffix": "</h2>",
|
||||
},
|
||||
"subsubtitle": {
|
||||
"from_prefix": "### ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h3>",
|
||||
"to_suffix": "</h3>",
|
||||
},
|
||||
"subsubsubtitle": {
|
||||
"from_prefix": "#### ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<h4>",
|
||||
"to_suffix": "</h4>",
|
||||
},
|
||||
"quote": {
|
||||
"from_prefix": "`",
|
||||
"from_suffix": "`",
|
||||
"to_prefix": "<p><code>",
|
||||
"to_suffix": "</code></p>",
|
||||
},
|
||||
"bold": {
|
||||
"from_prefix": "**",
|
||||
"from_suffix": "**",
|
||||
"to_prefix": "<strong>",
|
||||
"to_suffix": "</strong>",
|
||||
},
|
||||
"code": {
|
||||
"from_prefix": "```",
|
||||
"from_suffix": "```",
|
||||
"to_prefix": "<pre><code>",
|
||||
"to_suffix": "</code></pre>",
|
||||
},
|
||||
"image": {
|
||||
"from_prefix": "![",
|
||||
"from_suffix": "]",
|
||||
"to_prefix": "<img src='",
|
||||
"to_suffix": "'>",
|
||||
},
|
||||
"block": {
|
||||
"from_prefix": "-----",
|
||||
"from_suffix": "-----",
|
||||
"code": "bozodown:block",
|
||||
},
|
||||
"checkbox_clear": {
|
||||
"from_prefix": "- [ ] ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<li><input type='checkbox' disabled> ",
|
||||
"to_suffix": "</li>",
|
||||
},
|
||||
"checkbox_full": {
|
||||
"from_prefix": "- [x] ",
|
||||
"from_suffix": "\n",
|
||||
"to_prefix": "<li><input type='checkbox' checked disabled> ",
|
||||
"to_suffix": "</li>",
|
||||
},
|
||||
"newline": {
|
||||
"from_prefix": "<br>",
|
||||
"from_suffix": "",
|
||||
"to_prefix": "<br>",
|
||||
},
|
||||
}
|
||||
|
||||
text_converter: dict[str, str] = {
|
||||
"from_prefix": "",
|
||||
"to_prefix": "<p>",
|
||||
"to_suffix": "</p>",
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import markdown
|
||||
import os
|
||||
|
||||
from path import Path
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bulk_page import Page
|
||||
from picture import Picture
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
page_template = env.get_template('bulk_page.jinja')
|
||||
|
||||
class BulkPage():
|
||||
|
||||
def __init__(self, path: Path, name: str, pictures: list[Picture] = None, prev: BulkPage|None = None, next: BulkPage|None = None):
|
||||
self.name: str = name
|
||||
self._path: Path = path
|
||||
self._pictures: list[Picture] = pictures or []
|
||||
self._exif: Path = Path(self._path, "exif.txt")
|
||||
self._readme: Path = Path(self._path, "readme.md")
|
||||
self._raw: Path = Path(self._path, name + ".NEF")
|
||||
self.html: Path = Path(self._path, "page.html")
|
||||
self.prev: Page = prev
|
||||
self.next: Page = next
|
||||
if (not self._readme.exist()):
|
||||
self._readme.touch()
|
||||
|
||||
def add_picture(self, picture: Picture) -> None:
|
||||
self._pictures.append(picture)
|
||||
|
||||
def get_pictures(self) -> list[Picture]:
|
||||
return self._pictures
|
||||
|
||||
def _to_html(self) -> str|None:
|
||||
html_rendered = page_template.render(page=self)
|
||||
return html_rendered
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self.html.get_absolute_path(), "w") as f:
|
||||
f.write(self._to_html())
|
||||
|
||||
def render_readme(self) -> str|None:
|
||||
if not self._readme.exist():
|
||||
return None
|
||||
with open(self._readme.get_absolute_path(), 'r') as f:
|
||||
text = f.read()
|
||||
if len(text) == 0:
|
||||
return None
|
||||
html = markdown.markdown(text)
|
||||
return html
|
||||
|
||||
def _gen_exif(self):
|
||||
if self._raw.exist():
|
||||
if os.system(f"exiftool {self._raw.get_absolute_path()} > {self._exif.get_absolute_path()} 2>/dev/null") == 0:
|
||||
return 0
|
||||
os.remove(self._exif.get_absolute_path())
|
||||
return 1
|
||||
|
||||
def get_exif(self) -> Path | None:
|
||||
if not self._exif.exist():
|
||||
if (self._gen_exif()):
|
||||
return None
|
||||
return self._exif
|
||||
|
||||
def render_exif(self):
|
||||
if not self.get_exif():
|
||||
return None
|
||||
|
||||
with open(self._exif.get_absolute_path()) as f:
|
||||
return f.read()
|
||||
|
||||
def get_raw(self):
|
||||
return self._raw
|
@ -10,9 +10,9 @@ if TYPE_CHECKING:
|
||||
from path import Path
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
category_template = env.get_template('bulk_category.jinja')
|
||||
category_template = env.get_template('category.jinja')
|
||||
|
||||
class BulkCategory():
|
||||
class Category():
|
||||
|
||||
def __init__(self, name: str, categorys_path: Path, pictures: list[Picture] = None, is_repertoried: bool = False):
|
||||
self.name: str = name
|
35
src/collection.py
Normal file
35
src/collection.py
Normal file
@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import config
|
||||
from path import Path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from photodown import Photodown
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
page_template = env.get_template('collection.jinja')
|
||||
|
||||
class Collection:
|
||||
|
||||
def __init__(self, path: Path, markdown: Photodown):
|
||||
self._path: Path = path
|
||||
self._html: Path = Path(self._path.get_absolute_path()[:-len(config.COLLECTION_EXT)] + ".html")
|
||||
self._markdown: Photodown = markdown
|
||||
with open(path.get_absolute_path(), "r") as f:
|
||||
self._raw_content: str = f.read()
|
||||
|
||||
def _get_content(self):
|
||||
content = self._markdown.render(self._raw_content)
|
||||
return content
|
||||
|
||||
def _to_html(self) -> str:
|
||||
html: str = page_template.render(content=self._get_content())
|
||||
return html
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self._html.get_absolute_path(), "w") as f:
|
||||
f.write(self._to_html())
|
@ -3,4 +3,4 @@
|
||||
CREATE_GENERAL_CATEGORY: bool = True
|
||||
THUMB_DIMENSION: tuple[int, int] = (200, 200)
|
||||
MAX_THREADS: int = 50
|
||||
PAGE_EXT: str = ".ph"
|
||||
COLLECTION_EXT: str = ".ph"
|
67
src/main.py
67
src/main.py
@ -1,12 +1,11 @@
|
||||
import sys
|
||||
import os
|
||||
from progress.bar import Bar
|
||||
|
||||
from path import Path
|
||||
from picture import Picture
|
||||
from bulk_page import BulkPage
|
||||
from bulk_category import BulkCategory
|
||||
from page import Page
|
||||
from category import Category
|
||||
from collection import Collection
|
||||
from photodown import Photodown
|
||||
|
||||
import argparse
|
||||
import config
|
||||
@ -23,14 +22,14 @@ def argument_parsing():
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def scan_bulk_pages(folders: list[Path]) -> list[BulkPage]:
|
||||
pages: list[BulkPage] = []
|
||||
prev: BulkPage = None
|
||||
def scan_pages(folders: list[Path]) -> list[Page]:
|
||||
pages: list[Page] = []
|
||||
prev: Page = None
|
||||
with Bar("Scanning Pages...", max=len(folders)) as bar:
|
||||
for folder in folders:
|
||||
files: list[Path] = folder.get_files()
|
||||
|
||||
page: BulkPage = BulkPage(folder, folder.get_name(), prev=prev)
|
||||
|
||||
page: Page = Page(folder, folder.get_name(), prev=prev)
|
||||
|
||||
raw: Path = Path(folder, folder.get_name() + ".NEF")
|
||||
id: int = 0
|
||||
@ -53,62 +52,69 @@ def scan_bulk_pages(folders: list[Path]) -> list[BulkPage]:
|
||||
pages[0].prev = pages[-1]
|
||||
return pages
|
||||
|
||||
def create_pages(pages: list[BulkPage]) -> None:
|
||||
def create_pages(pages: list[Page]) -> None:
|
||||
with Bar("Generating Pages...", max=len(pages)) as bar:
|
||||
for page in pages:
|
||||
page.create()
|
||||
bar.next()
|
||||
|
||||
def scan_categories(pages: list[BulkPage], categories_path: Path) -> list[BulkCategory]:
|
||||
categories: dict[str, BulkCategory] = {}
|
||||
def scan_categories(pages: list[Page], categories_path: Path) -> list[Category]:
|
||||
categories: dict[str, Category] = {}
|
||||
with Bar("Scanning pages...", max=len(pages)) as bar:
|
||||
for page in pages:
|
||||
for picture in page.get_pictures():
|
||||
for category_name in picture.get_categories_name():
|
||||
category: BulkCategory | None = categories.get(category_name)
|
||||
category: Category | None = categories.get(category_name)
|
||||
if (category is None):
|
||||
category = BulkCategory(category_name, categories_path)
|
||||
category = Category(category_name, categories_path)
|
||||
categories.update({category_name: category})
|
||||
picture.categories.append(category)
|
||||
category.add_picture(picture)
|
||||
bar.next()
|
||||
return (categories.values())
|
||||
|
||||
def create_categories(categories: list[BulkCategory]) -> None:
|
||||
def create_categories(categories: list[Category]) -> None:
|
||||
with Bar("Generating categories...", max=len(categories)) as bar:
|
||||
for category in categories:
|
||||
category.create()
|
||||
bar.next()
|
||||
|
||||
def gen_bulk(bulk_path: Path):
|
||||
def gen_bulk(bulk_path: Path, markdown: Photodown):
|
||||
category_path: Path = Path(bulk_path, "categories")
|
||||
|
||||
pages: list[BulkPage] = scan_bulk_pages(bulk_path.get_dirs())
|
||||
categories: list[BulkCategory] = scan_categories(pages, category_path)
|
||||
pages: list[Page] = scan_pages(bulk_path.get_dirs())
|
||||
categories: list[Category] = scan_categories(pages, category_path)
|
||||
|
||||
bulk: list[Picture] = []
|
||||
for page in pages:
|
||||
for picture in page.get_pictures():
|
||||
bulk.append(picture)
|
||||
markdown.add_bulk(bulk)
|
||||
|
||||
if config.CREATE_GENERAL_CATEGORY:
|
||||
for category in categories:
|
||||
if (category.name == "general"):
|
||||
category.path = Path(bulk_path, "index.html")
|
||||
|
||||
Path("./src/templates/bulk_page.css").copy_to(Path(bulk_path, "bulk_page.css"))
|
||||
Path("./src/templates/page.css").copy_to(Path(bulk_path, "page.css"))
|
||||
create_pages(pages)
|
||||
|
||||
Path("./src/templates/bulk_category.css").copy_to(Path(bulk_path, "bulk_category.css"))
|
||||
Path("./src/templates/category.css").copy_to(Path(bulk_path, "category.css"))
|
||||
if (not category_path.exist()):
|
||||
category_path.create()
|
||||
create_categories(categories)
|
||||
|
||||
def scan_pages(site_path: Path):
|
||||
for path in [f for f in site_path.get_files() if f.get_name().endswith(config.PAGE_EXT)]:
|
||||
page: Page = Page(path)
|
||||
page.create()
|
||||
def scan_collections(site_path: Path, markdown: Photodown):
|
||||
for path in [f for f in site_path.get_files() if f.get_name().endswith(config.COLLECTION_EXT)]:
|
||||
collection: Collection = Collection(path, markdown)
|
||||
collection.create()
|
||||
|
||||
def gen_pages(site_path: Path):
|
||||
scan_pages(site_path)
|
||||
def gen_collections(site_path: Path, markdown: Photodown):
|
||||
scan_collections(site_path, markdown)
|
||||
Path("./src/templates/collection.css").copy_to(Path(site_path, "collection.css"))
|
||||
|
||||
def regen(bulk_path: Path, is_thumb: bool, is_small: bool):
|
||||
pages: list[BulkPage] = scan_bulk_pages(bulk_path.get_dirs())
|
||||
def regen(_path: Path, is_thumb: bool, is_small: bool):
|
||||
pages: list[Page] = scan_pages(_path.get_dirs())
|
||||
with Bar("Regenerating assets...", max=len(pages)) as bar:
|
||||
for page in pages:
|
||||
for picture in page.get_pictures():
|
||||
@ -132,8 +138,9 @@ def main():
|
||||
bulk_path: Path = Path(site_path, "bulk/")
|
||||
if args.regen is not None:
|
||||
regen(bulk_path, 't' in args.regen, 's' in args.regen)
|
||||
gen_bulk(bulk_path)
|
||||
gen_pages(site_path)
|
||||
markdown = Photodown()
|
||||
gen_bulk(bulk_path, markdown)
|
||||
gen_collections(site_path, markdown)
|
||||
gen_home(site_path)
|
||||
|
||||
|
||||
|
85
src/page.py
85
src/page.py
@ -1,28 +1,77 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import markdown
|
||||
import os
|
||||
|
||||
from path import Path
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import config
|
||||
import bozodown
|
||||
from path import Path
|
||||
if TYPE_CHECKING:
|
||||
from picture import Picture
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
page_template = env.get_template('page.jinja')
|
||||
|
||||
class Page:
|
||||
|
||||
def __init__(self, path: Path):
|
||||
class Page():
|
||||
|
||||
def __init__(self, path: Path, name: str, pictures: list[Picture] = None, prev: Page|None = None, next: Page|None = None):
|
||||
self.name: str = name
|
||||
self._path: Path = path
|
||||
self._html: Path = Path(self._path.get_absolute_path()[:-len(config.PAGE_EXT)] + ".html")
|
||||
with open(path.get_absolute_path(), "r") as f:
|
||||
self._raw_content: str = f.read()
|
||||
|
||||
def _get_content(self):
|
||||
content = bozodown.render(self._raw_content)
|
||||
return content
|
||||
self._pictures: list[Picture] = pictures or []
|
||||
self._exif: Path = Path(self._path, "exif.txt")
|
||||
self._readme: Path = Path(self._path, "readme.md")
|
||||
self._raw: Path = Path(self._path, name + ".NEF")
|
||||
self.html: Path = Path(self._path, "page.html")
|
||||
self.prev: Page = prev
|
||||
self.next: Page = next
|
||||
if (not self._readme.exist()):
|
||||
self._readme.touch()
|
||||
|
||||
def _to_html(self) -> str:
|
||||
html: str = self._get_content()
|
||||
def add_picture(self, picture: Picture) -> None:
|
||||
self._pictures.append(picture)
|
||||
|
||||
def get_pictures(self) -> list[Picture]:
|
||||
return self._pictures
|
||||
|
||||
def _to_html(self) -> str|None:
|
||||
html_rendered = page_template.render(page=self)
|
||||
return html_rendered
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self.html.get_absolute_path(), "w") as f:
|
||||
f.write(self._to_html())
|
||||
|
||||
def render_readme(self) -> str|None:
|
||||
if not self._readme.exist():
|
||||
return None
|
||||
with open(self._readme.get_absolute_path(), 'r') as f:
|
||||
text = f.read()
|
||||
if len(text) == 0:
|
||||
return None
|
||||
html = markdown.markdown(text)
|
||||
return html
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self._html.get_absolute_path(), "w") as f:
|
||||
f.write(self._to_html())
|
||||
def _gen_exif(self):
|
||||
if self._raw.exist():
|
||||
if os.system(f"exiftool {self._raw.get_absolute_path()} > {self._exif.get_absolute_path()} 2>/dev/null") == 0:
|
||||
return 0
|
||||
os.remove(self._exif.get_absolute_path())
|
||||
return 1
|
||||
|
||||
def get_exif(self) -> Path | None:
|
||||
if not self._exif.exist():
|
||||
if (self._gen_exif()):
|
||||
return None
|
||||
return self._exif
|
||||
|
||||
def render_exif(self):
|
||||
if not self.get_exif():
|
||||
return None
|
||||
|
||||
with open(self._exif.get_absolute_path()) as f:
|
||||
return f.read()
|
||||
|
||||
def get_raw(self):
|
||||
return self._raw
|
51
src/photodown.py
Normal file
51
src/photodown.py
Normal file
@ -0,0 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bozodown.bozodown import Bozodown
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from picture import Picture
|
||||
|
||||
class Photodown(Bozodown):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._specific_case_namespaces.update({"photohub": self._photohub_render})
|
||||
image_converter = self._converters.get("image")
|
||||
image_converter.update({"code": "photohub:image"})
|
||||
self._converters.update({"pictures": {"from_prefix": "+++++","from_suffix": "+++++","code": "photohub:pictures",}})
|
||||
self._bulk: list[Picture] = []
|
||||
|
||||
def add_bulk(self, bulk: list[Picture]):
|
||||
self._bulk = bulk.copy()
|
||||
|
||||
def _render_image(self, url: str) -> str:
|
||||
if (url.startswith("./")):
|
||||
for picture in self._bulk:
|
||||
if url == "." + picture.get_large().get_url()[:-4]:
|
||||
return f"""<a href={picture.get_page().html.get_url()}><img src='{picture.get_small().get_url()}'></a>"""
|
||||
return f"<img src='{url}'>"
|
||||
|
||||
def _render_pictures(self, config: dict[str, str | int], to_parse: str):
|
||||
segments: list[str] = to_parse.split("--")
|
||||
content: str = ""
|
||||
for segment in segments:
|
||||
content += f"<div class='pictures-item'>{self.render(segment)}</div>"
|
||||
return f"<div class='pictures-menu'>{content}</div>"
|
||||
|
||||
def _photohub_render(self, id: str, text: str) -> str:
|
||||
if (id == "image"):
|
||||
picture_url = f"{text[2:-1]}"
|
||||
return self._render_image(picture_url)
|
||||
if (id == "pictures"):
|
||||
config, style, to_parse = text[5:-5].split("---")
|
||||
style = style.replace("\n", "")
|
||||
config = {
|
||||
k: v for line in config.strip().splitlines()
|
||||
if ": " in line
|
||||
for k, v in [line.split(": ", 1)]
|
||||
}
|
||||
content: str = self._render_pictures(config, to_parse)
|
||||
return f"<div style='{style}'>{content}</div>"
|
||||
|
@ -10,11 +10,11 @@ from typing import TYPE_CHECKING
|
||||
import config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bulk_page import BulkPage
|
||||
from bulk_category import BulkCategory
|
||||
from page import Page
|
||||
from category import Category
|
||||
|
||||
class Picture():
|
||||
def __init__(self, picture_path: Path, id: int, page: BulkPage = None, raw: Path|None = None, is_repertoried: bool = True):
|
||||
def __init__(self, picture_path: Path, id: int, page: Page = None, raw: Path|None = None, is_repertoried: bool = True):
|
||||
self._large: Path = picture_path
|
||||
self._small: Path = Path(picture_path.get_absolute_path()[:-4] + "_small.jpg")
|
||||
self._thumb: Path = Path(picture_path.get_absolute_path()[:-4] + "_thumb.jpg")
|
||||
@ -22,9 +22,9 @@ class Picture():
|
||||
self._categories_file: Path = Path(picture_path.get_absolute_path()[:-4] + "_categories.txt")
|
||||
self._raw: Path|None = raw
|
||||
self.id: int = id
|
||||
self._page: BulkPage = page
|
||||
self._page: Page = page
|
||||
self._categories_name: list[str] = []
|
||||
self.categories: list[BulkCategory] = []
|
||||
self.categories: list[Category] = []
|
||||
if self._categories_file.exist():
|
||||
with open(self._categories_file.get_absolute_path(), "r+") as f:
|
||||
categories_name: list[str] = f.read()
|
||||
|
@ -1,87 +0,0 @@
|
||||
:root {
|
||||
--bg1: #002b36;
|
||||
--bg2: #073642;
|
||||
--content1: #586e75;
|
||||
--content2: #657b83;
|
||||
--content3: #839496;
|
||||
--content4: #93a1a1;
|
||||
--lbg1: #eee8d5;
|
||||
--lbg2: #fdf6e3;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 15% 15%;
|
||||
margin-top: 5%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.readme {
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
.picture-container {
|
||||
background-color: var(--bg2);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.picture {
|
||||
padding: 5px;
|
||||
background-color: var(--content1);
|
||||
}
|
||||
|
||||
.albums_container {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.meta-picture, .navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width:767px) {
|
||||
body {
|
||||
margin: 0% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.exif, .readme {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.download {
|
||||
width: 100%;
|
||||
background-color: var(--bg2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exif summary {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exif details {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--bg2);
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/bulk/bulk_page.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navigation">
|
||||
{% if page.prev %}
|
||||
<a href="{{ page.prev.html.get_url() }}">Prev</a>
|
||||
{% endif %}
|
||||
{% if page.next %}
|
||||
<a class="next" href="{{ page.next.html.get_url() }}">Next</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if page.render_readme() %}
|
||||
<div class="readme">
|
||||
<details>
|
||||
<summary>
|
||||
README
|
||||
</summary>
|
||||
<div class="readme-content">
|
||||
{{page.render_readme()}}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="picture-container">
|
||||
{% for picture in page._pictures %}
|
||||
<div class="picture-container-item" id="{{ picture.id}}">
|
||||
<div class="picture">
|
||||
<a href="{{ picture.get_small().get_url() }}">
|
||||
<img src='{{ picture.get_small().get_url() }}'>
|
||||
</a>
|
||||
<div class="meta-picture">
|
||||
<a href="{{ picture.get_large().get_url() }}">Large</a>
|
||||
{% if picture._profile_file.exist() %}
|
||||
<a class="profile" href="{{ picture.get_profile_file().get_url() }}">Raw therapee profile</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="categories_container">
|
||||
<h1>Categories:</h1>
|
||||
{% for category in picture.categories %}
|
||||
<a href="{{ category.path.get_url() }}" class="category_item">{{ al }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if page.get_raw().exist() %}
|
||||
<div class="download">
|
||||
<h1>Download</h1>
|
||||
<a href="{{ page.get_raw().get_url() }}">RAW</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page.get_exif() %}
|
||||
<div class="exif">
|
||||
<details>
|
||||
<summary>Exif</summary>
|
||||
<pre>
|
||||
{{ page.render_exif() }}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
|
||||
</html>
|
@ -2,7 +2,7 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/bulk/bulk_category.css">
|
||||
<link rel="stylesheet" href="/bulk/category.css">
|
||||
</head>
|
||||
|
||||
<body>
|
6
src/templates/collection.css
Normal file
6
src/templates/collection.css
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
img {
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
12
src/templates/collection.jinja
Normal file
12
src/templates/collection.jinja
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/page.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{ content }}
|
||||
</body>
|
||||
|
||||
</html>
|
0
src/templates/home.css
Normal file
0
src/templates/home.css
Normal file
@ -1,7 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<a href="/bulk/"><h1>Site WIP go to BULK</h1></a>
|
||||
<a href="/bulk/"><h1>See all pictures I take</h1></a>
|
||||
<ul>
|
||||
{% for collection in collections %}
|
||||
<li>
|
||||
<a href="{{collection.get_url() }}">{{ collection.get_name()}} </a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,87 @@
|
||||
:root {
|
||||
--bg1: #002b36;
|
||||
--bg2: #073642;
|
||||
--content1: #586e75;
|
||||
--content2: #657b83;
|
||||
--content3: #839496;
|
||||
--content4: #93a1a1;
|
||||
--lbg1: #eee8d5;
|
||||
--lbg2: #fdf6e3;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 15% 15%;
|
||||
margin-top: 5%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.readme {
|
||||
background-color: var(--bg2);
|
||||
}
|
||||
|
||||
.picture-container {
|
||||
background-color: var(--bg2);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.picture {
|
||||
padding: 5px;
|
||||
background-color: var(--content1);
|
||||
}
|
||||
|
||||
.albums_container {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.meta-picture, .navigation {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width:767px) {
|
||||
body {
|
||||
margin: 0% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.exif, .readme {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.download {
|
||||
width: 100%;
|
||||
background-color: var(--bg2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exif summary {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exif details {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--bg2);
|
||||
}
|
@ -2,11 +2,69 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/page.css">
|
||||
<link rel="stylesheet" href="/bulk/page.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>{{ category.name }}</h1>
|
||||
<div class="navigation">
|
||||
{% if page.prev %}
|
||||
<a href="{{ page.prev.html.get_url() }}">Prev</a>
|
||||
{% endif %}
|
||||
{% if page.next %}
|
||||
<a class="next" href="{{ page.next.html.get_url() }}">Next</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if page.render_readme() %}
|
||||
<div class="readme">
|
||||
<details>
|
||||
<summary>
|
||||
README
|
||||
</summary>
|
||||
<div class="readme-content">
|
||||
{{page.render_readme()}}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="picture-container">
|
||||
{% for picture in page._pictures %}
|
||||
<div class="picture-container-item" id="{{ picture.id}}">
|
||||
<div class="picture">
|
||||
<a href="{{ picture.get_small().get_url() }}">
|
||||
<img src='{{ picture.get_small().get_url() }}'>
|
||||
</a>
|
||||
<div class="meta-picture">
|
||||
<a href="{{ picture.get_large().get_url() }}">Large</a>
|
||||
{% if picture._profile_file.exist() %}
|
||||
<a class="profile" href="{{ picture.get_profile_file().get_url() }}">Raw therapee profile</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="categories_container">
|
||||
<h1>Categories:</h1>
|
||||
{% for category in picture.categories %}
|
||||
<a href="{{ category.path.get_url() }}" class="category_item">{{ al }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if page.get_raw().exist() %}
|
||||
<div class="download">
|
||||
<h1>Download</h1>
|
||||
<a href="{{ page.get_raw().get_url() }}">RAW</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page.get_exif() %}
|
||||
<div class="exif">
|
||||
<details>
|
||||
<summary>Exif</summary>
|
||||
<pre>
|
||||
{{ page.render_exif() }}
|
||||
</pre>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
</body>
|
||||
|
||||
</html>
|
Reference in New Issue
Block a user