Compare commits
49 Commits
73dfaf4622
...
main
Author | SHA1 | Date | |
---|---|---|---|
779dab4f77 | |||
d872836624 | |||
df182fa7bb | |||
7d739ed777 | |||
6af8781aa2 | |||
00bc97465f | |||
74e99b96f0 | |||
cbab99cc82 | |||
001a8c692e | |||
e1f08f0312 | |||
cf9cd77bcf | |||
1563b3618a | |||
8c378492a3 | |||
941e06e3d7 | |||
6d6af200ab | |||
4b4459cfbb | |||
ea75630fbb | |||
4ece9bf2ea | |||
67a83708cf | |||
a980dd8ead | |||
69023f1917 | |||
df05678ddc | |||
bb29eb2b73 | |||
c8f8698578 | |||
6236819e2b | |||
1a0391bafe | |||
c7c2cf6409 | |||
26c60c5216 | |||
6d1661131f | |||
129dfe7df9 | |||
5fc12d5d33 | |||
b895e5d85c | |||
b434d0837a | |||
b9f7090e96 | |||
fe1f7d1485 | |||
2dd1fcfb0c | |||
8c2d5df0d2 | |||
a551217592 | |||
5c773af571 | |||
d0063c2cc4 | |||
08c0129af8 | |||
de051f222d | |||
ff56bac2cd | |||
3f30154b36 | |||
b4a78a9e7d | |||
37808d6c48 | |||
70da0a2c28 | |||
bb0973917d | |||
55bc1b36d1 |
71
README.md
71
README.md
@ -3,10 +3,10 @@ Just a simple python program to generate static site
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### Picture Page
|
### Bulk Page
|
||||||

|

|
||||||
|
|
||||||
### Album Page
|
### Bulk Album Page
|
||||||

|

|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@ -17,32 +17,65 @@ python3 -m pip -r requirements.txt
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Move and rename file
|
Move and rename file with
|
||||||
```sh
|
```sh
|
||||||
python3 tools/copy.py %your raws% %site location%
|
python3 tools/copy.py %your raws% %site location%/bulk/
|
||||||
```
|
```
|
||||||
|
Here you can edit your pictures.
|
||||||
|
|
||||||
Generate html page
|
Then
|
||||||
|
|
||||||
|
Generate html page with
|
||||||
``` sh
|
``` sh
|
||||||
python3 src/main.py %site location%
|
python3 src/main.py %site location%
|
||||||
```
|
```
|
||||||
|
|
||||||
## File
|
## File
|
||||||
|
|
||||||
### Mandatory
|
### File tree
|
||||||
|
|
||||||
|
File before `python src/main.py test_out`
|
||||||
|
```
|
||||||
|
test_out/
|
||||||
|
└── bulk
|
||||||
|
└── 00001
|
||||||
|
├── 00001.NEF
|
||||||
|
├── 00001.png
|
||||||
|
└── 00001.png.out.pp3
|
||||||
|
```
|
||||||
|
### Bulk
|
||||||
|
|
||||||
|
#### Mandatory
|
||||||
- a .png
|
- a .png
|
||||||
|
|
||||||
### Optionnal
|
#### Optionnal
|
||||||
- raw {*.NEF}
|
- **raw** {*.NEF}
|
||||||
- readme.md
|
- **readme.md**
|
||||||
- (png file).out.pp3
|
- (png file)**.out.pp3**: permit viewer to download it
|
||||||
|
|
||||||
### Futur update
|
File after `python src/main.py test_out`
|
||||||
- Use summary for exif data and readme
|
```
|
||||||
- rename export file to rawtherapee profil
|
test_out/
|
||||||
- add gimp profile file
|
├── bulk
|
||||||
- album
|
│ └── 00001
|
||||||
- unrepertoried page
|
│ ├── 00001_categories.txt
|
||||||
- fix: next and prev
|
│ ├── 00001.NEF
|
||||||
- add audio file on page
|
│ ├── 00001.png
|
||||||
- page mobile compatible
|
│ ├── 00001.png.out.pp3
|
||||||
|
│ ├── 00001_small.jpg
|
||||||
|
│ ├── 00001_thumb.jpg
|
||||||
|
│ ├── page.html
|
||||||
|
│ └── readme.md
|
||||||
|
└── index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Can be fill
|
||||||
|
|
||||||
|
#### _categories.txt
|
||||||
|
A file to organize your picture, 1 per line like
|
||||||
|
```
|
||||||
|
cats
|
||||||
|
portraits
|
||||||
|
```
|
||||||
|
#### readme.md**
|
||||||
|
A markdown file in the top of the bulk page
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"file_type_extensions": {
|
|
||||||
"raw": [".NEF"],
|
|
||||||
"large": [".png"],
|
|
||||||
"small": [".jpeg"],
|
|
||||||
"edits": [".pp3"],
|
|
||||||
"desq": [".md"]
|
|
||||||
}
|
|
||||||
}
|
|
44
config/default.conf
Normal file
44
config/default.conf
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
#access_log /var/log/nginx/host.access.log main;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
autoindex on;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
# redirect server error pages to the static page /50x.html
|
||||||
|
#
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||||
|
#
|
||||||
|
#location ~ \.php$ {
|
||||||
|
# proxy_pass http://127.0.0.1;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||||
|
#
|
||||||
|
#location ~ \.php$ {
|
||||||
|
# root html;
|
||||||
|
# fastcgi_pass 127.0.0.1:9000;
|
||||||
|
# fastcgi_index index.php;
|
||||||
|
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||||
|
# include fastcgi_params;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# deny access to .htaccess files, if Apache's document root
|
||||||
|
# concurs with nginx's one
|
||||||
|
#
|
||||||
|
#location ~ /\.ht {
|
||||||
|
# deny all;
|
||||||
|
#}
|
||||||
|
}
|
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
|
10
docker-compose.yml
Normal file
10
docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
my-pictures:
|
||||||
|
image: nginx
|
||||||
|
volumes:
|
||||||
|
- ./test_out:/usr/share/nginx/html
|
||||||
|
- ./config:/etc/nginx/conf.d/
|
||||||
|
ports:
|
||||||
|
- 8089:80
|
||||||
|
networks: {}
|
@ -1,3 +1,5 @@
|
|||||||
|
beautifulsoup4==4.13.4
|
||||||
|
bs4==0.0.2
|
||||||
exif==1.6.1
|
exif==1.6.1
|
||||||
imageio==2.37.0
|
imageio==2.37.0
|
||||||
Jinja2==3.1.6
|
Jinja2==3.1.6
|
||||||
@ -8,3 +10,5 @@ pillow==11.1.0
|
|||||||
plum-py==0.8.7
|
plum-py==0.8.7
|
||||||
progress==1.6
|
progress==1.6
|
||||||
rawpy==0.24.0
|
rawpy==0.24.0
|
||||||
|
soupsieve==2.7
|
||||||
|
typing_extensions==4.14.1
|
||||||
|
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>",
|
||||||
|
}
|
@ -10,23 +10,23 @@ if TYPE_CHECKING:
|
|||||||
from path import Path
|
from path import Path
|
||||||
|
|
||||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||||
album_template = env.get_template('album.jinja')
|
category_template = env.get_template('category.jinja')
|
||||||
|
|
||||||
class Album():
|
class Category():
|
||||||
|
|
||||||
def __init__(self, name: str, albums_path: Path, pictures: list[Picture] = None, is_repertoried: bool = False):
|
def __init__(self, name: str, categorys_path: Path, pictures: list[Picture] = None, is_repertoried: bool = False):
|
||||||
self._name: str = name
|
self.name: str = name
|
||||||
self._pictures: list[Picture] = pictures or []
|
self._pictures: list[Picture] = pictures or []
|
||||||
self._is_repertoried: bool = is_repertoried
|
self._is_repertoried: bool = is_repertoried
|
||||||
self._path: Path = Path(albums_path, f"{name}.html")
|
self.path: Path = Path(categorys_path, f"{name}.html")
|
||||||
|
|
||||||
def add_picture(self, picture: Picture) -> None:
|
def add_picture(self, picture: Picture) -> None:
|
||||||
self._pictures.append(picture)
|
self._pictures.append(picture)
|
||||||
|
|
||||||
def _to_html(self) -> str|None:
|
def _to_html(self) -> str|None:
|
||||||
html_rendered = album_template.render(album=self)
|
html_rendered = category_template.render(category=self)
|
||||||
return html_rendered
|
return html_rendered
|
||||||
|
|
||||||
def create(self) -> Path:
|
def create(self) -> Path:
|
||||||
with open(self._path.get_absolute_path(), "w") as f:
|
with open(self.path.get_absolute_path(), "w") as f:
|
||||||
f.write(self._to_html())
|
f.write(self._to_html())
|
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())
|
@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
|
||||||
CREATE_GENERAL_ALBUM: bool = True
|
CREATE_GENERAL_CATEGORY: bool = True
|
||||||
|
THUMB_DIMENSION: tuple[int, int] = (200, 200)
|
||||||
|
MAX_THREADS: int = 50
|
||||||
|
COLLECTION_EXT: str = ".ph"
|
128
src/main.py
128
src/main.py
@ -1,19 +1,26 @@
|
|||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
from progress.bar import Bar
|
from progress.bar import Bar
|
||||||
|
|
||||||
from path import Path
|
from path import Path
|
||||||
from picture import Picture
|
from picture import Picture
|
||||||
from page import Page
|
from page import Page
|
||||||
from album import Album
|
from category import Category
|
||||||
|
from collection import Collection
|
||||||
|
from photodown import Photodown
|
||||||
|
|
||||||
|
import argparse
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||||
|
home_template = env.get_template('home.jinja')
|
||||||
|
|
||||||
def argument_parsing():
|
def argument_parsing():
|
||||||
if (len(sys.argv) < 2):
|
parser = argparse.ArgumentParser()
|
||||||
print("error: missing argument", file=sys.stderr)
|
parser.add_argument("site_path", help="the folder of your site MUST BE ALWAYS BEFORT PARAMETERS", type=str)
|
||||||
exit(1)
|
parser.add_argument("-r", '--regen', help="Regenerate (t)thumb (s)small", type=str)
|
||||||
return sys.argv[1]
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
def scan_pages(folders: list[Path]) -> list[Page]:
|
def scan_pages(folders: list[Path]) -> list[Page]:
|
||||||
pages: list[Page] = []
|
pages: list[Page] = []
|
||||||
@ -25,9 +32,11 @@ def scan_pages(folders: list[Path]) -> list[Page]:
|
|||||||
page: Page = Page(folder, folder.get_name(), prev=prev)
|
page: Page = Page(folder, folder.get_name(), prev=prev)
|
||||||
|
|
||||||
raw: Path = Path(folder, folder.get_name() + ".NEF")
|
raw: Path = Path(folder, folder.get_name() + ".NEF")
|
||||||
|
id: int = 0
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.get_name().endswith(".png"):
|
if file.get_name().endswith(".png"):
|
||||||
page.add_picture(Picture(file, page=page, raw=raw))
|
page.add_picture(Picture(file, id, page=page, raw=raw))
|
||||||
|
id += 1
|
||||||
|
|
||||||
if len(page.get_pictures()) == 0:
|
if len(page.get_pictures()) == 0:
|
||||||
bar.next()
|
bar.next()
|
||||||
@ -49,40 +58,91 @@ def create_pages(pages: list[Page]) -> None:
|
|||||||
page.create()
|
page.create()
|
||||||
bar.next()
|
bar.next()
|
||||||
|
|
||||||
def scan_albums(pages: list[Page], albums_path: Path) -> list[Album]:
|
def scan_categories(pages: list[Page], categories_path: Path) -> list[Category]:
|
||||||
albums: dict[str, Album] = {}
|
categories: dict[str, Category] = {}
|
||||||
with Bar("Scanning pages...", max=len(pages)) as bar:
|
with Bar("Scanning pages...", max=len(pages)) as bar:
|
||||||
for page in pages:
|
for page in pages:
|
||||||
for picture in page.get_pictures():
|
for picture in page.get_pictures():
|
||||||
for album_name in picture.get_albums_name():
|
for category_name in picture.get_categories_name():
|
||||||
album: Album | None = albums.get(album_name)
|
category: Category | None = categories.get(category_name)
|
||||||
if (album is None):
|
if (category is None):
|
||||||
album = Album(album_name, albums_path)
|
category = Category(category_name, categories_path)
|
||||||
albums.update({album_name: album})
|
categories.update({category_name: category})
|
||||||
album.add_picture(picture)
|
picture.categories.append(category)
|
||||||
|
category.add_picture(picture)
|
||||||
bar.next()
|
bar.next()
|
||||||
return (albums.values())
|
return (categories.values())
|
||||||
|
|
||||||
def create_albums(albums: list[Album]) -> None:
|
def create_categories(categories: list[Category]) -> None:
|
||||||
with Bar("Generating albums...", max=len(albums)) as bar:
|
with Bar("Generating categories...", max=len(categories)) as bar:
|
||||||
for album in albums:
|
for category in categories:
|
||||||
album.create()
|
category.create()
|
||||||
bar.next()
|
bar.next()
|
||||||
|
|
||||||
|
def gen_bulk(bulk_path: Path, markdown: Photodown):
|
||||||
|
category_path: Path = Path(bulk_path, "categories")
|
||||||
|
|
||||||
|
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/page.css").copy_to(Path(bulk_path, "page.css"))
|
||||||
|
create_pages(pages)
|
||||||
|
|
||||||
|
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_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_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(_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():
|
||||||
|
if is_thumb:
|
||||||
|
picture.gen_thumb()
|
||||||
|
if is_small:
|
||||||
|
picture.gen_small()
|
||||||
|
bar.next()
|
||||||
|
|
||||||
|
def gen_home(site_path: Path) -> None:
|
||||||
|
home_path = Path(site_path, "index.html")
|
||||||
|
|
||||||
|
with open(home_path.get_absolute_path(), "w") as f:
|
||||||
|
f.write(home_template.render())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
site_path = Path(argument_parsing())
|
args = argument_parsing()
|
||||||
pages: list[Page] = scan_pages(site_path.get_dirs())
|
site_path: Path = Path(args.site_path)
|
||||||
shutil.copy2("./src/templates/page.css", site_path.get_absolute_path())
|
bulk_path: Path = Path(site_path, "bulk/")
|
||||||
create_pages(pages)
|
if args.regen is not None:
|
||||||
album_path: Path = Path(site_path, "albums")
|
regen(bulk_path, 't' in args.regen, 's' in args.regen)
|
||||||
if (not album_path.exist()):
|
markdown = Photodown()
|
||||||
album_path.create()
|
gen_bulk(bulk_path, markdown)
|
||||||
albums: list[Album] = scan_albums(pages, album_path)
|
gen_collections(site_path, markdown)
|
||||||
create_albums(albums)
|
gen_home(site_path)
|
||||||
if config.CREATE_GENERAL_ALBUM:
|
|
||||||
shutil.move(os.path.join(album_path.get_absolute_path(), "general.html"), os.path.join(site_path.get_absolute_path(), "index.html"))
|
|
||||||
shutil.copy2("./src/templates/album.css", site_path.get_absolute_path())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
12
src/page.py
12
src/page.py
@ -9,7 +9,6 @@ from path import Path
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from page import Page
|
|
||||||
from picture import Picture
|
from picture import Picture
|
||||||
|
|
||||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||||
@ -27,6 +26,8 @@ class Page():
|
|||||||
self.html: Path = Path(self._path, "page.html")
|
self.html: Path = Path(self._path, "page.html")
|
||||||
self.prev: Page = prev
|
self.prev: Page = prev
|
||||||
self.next: Page = next
|
self.next: Page = next
|
||||||
|
if (not self._readme.exist()):
|
||||||
|
self._readme.touch()
|
||||||
|
|
||||||
def add_picture(self, picture: Picture) -> None:
|
def add_picture(self, picture: Picture) -> None:
|
||||||
self._pictures.append(picture)
|
self._pictures.append(picture)
|
||||||
@ -47,6 +48,8 @@ class Page():
|
|||||||
return None
|
return None
|
||||||
with open(self._readme.get_absolute_path(), 'r') as f:
|
with open(self._readme.get_absolute_path(), 'r') as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
|
if len(text) == 0:
|
||||||
|
return None
|
||||||
html = markdown.markdown(text)
|
html = markdown.markdown(text)
|
||||||
return html
|
return html
|
||||||
|
|
||||||
@ -63,5 +66,12 @@ class Page():
|
|||||||
return None
|
return None
|
||||||
return self._exif
|
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):
|
def get_raw(self):
|
||||||
return self._raw
|
return self._raw
|
30
src/path.py
30
src/path.py
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
class Path():
|
class Path():
|
||||||
|
|
||||||
def __init__(self, *paths: str | Path):
|
def __init__(self, *paths: str | Path):
|
||||||
@ -31,6 +31,11 @@ class Path():
|
|||||||
return None
|
return None
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
def touch(self) -> None:
|
||||||
|
if (self.exist()):
|
||||||
|
return None
|
||||||
|
open(self.get_absolute_path(), "w+").close()
|
||||||
|
|
||||||
def create(self) -> None:
|
def create(self) -> None:
|
||||||
os.makedirs(self.get_absolute_path())
|
os.makedirs(self.get_absolute_path())
|
||||||
|
|
||||||
@ -39,7 +44,10 @@ class Path():
|
|||||||
|
|
||||||
def get_dirs(self) -> list[Path]:
|
def get_dirs(self) -> list[Path]:
|
||||||
dirs: list[Path] = []
|
dirs: list[Path] = []
|
||||||
for element in os.listdir(self.get_absolute_path()):
|
elements = os.listdir(self.get_absolute_path())
|
||||||
|
elements.sort()
|
||||||
|
elements.reverse()
|
||||||
|
for element in elements:
|
||||||
path: Path = Path(self._absolute_path, element)
|
path: Path = Path(self._absolute_path, element)
|
||||||
if (os.path.isdir(path.get_absolute_path())):
|
if (os.path.isdir(path.get_absolute_path())):
|
||||||
dirs.append(path)
|
dirs.append(path)
|
||||||
@ -47,8 +55,24 @@ class Path():
|
|||||||
|
|
||||||
def get_files(self) -> list[Path]:
|
def get_files(self) -> list[Path]:
|
||||||
files: list[Path] = []
|
files: list[Path] = []
|
||||||
for element in os.listdir(self.get_absolute_path()):
|
elements = os.listdir(self.get_absolute_path())
|
||||||
|
elements.sort()
|
||||||
|
elements.reverse()
|
||||||
|
for element in elements:
|
||||||
path: Path = Path(self._absolute_path, element)
|
path: Path = Path(self._absolute_path, element)
|
||||||
if (os.path.isfile(path.get_absolute_path())):
|
if (os.path.isfile(path.get_absolute_path())):
|
||||||
files.append(path)
|
files.append(path)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
def copy_to(self, destination: Path) -> None:
|
||||||
|
shutil.copy2(self.get_absolute_path(), destination.get_absolute_path())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._absolute_path
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Path({self._absolute_path})"
|
||||||
|
|
||||||
|
def __eq__(self, value):
|
||||||
|
if (isinstance(value, Path)):
|
||||||
|
self = Path(value.get_absolute_path())
|
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>"
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import os
|
import thread_manager
|
||||||
|
|
||||||
from path import Path
|
from path import Path
|
||||||
|
|
||||||
@ -11,39 +11,66 @@ import config
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from page import Page
|
from page import Page
|
||||||
from album import Album
|
from category import Category
|
||||||
|
|
||||||
class Picture():
|
class Picture():
|
||||||
def __init__(self, picture_path: Path, page = None, raw: Path|None = None, albums_name: list[str] = 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._large: Path = picture_path
|
||||||
self._small: Path = Path(picture_path.get_absolute_path()[:-4] + "_small.jpg")
|
self._small: Path = Path(picture_path.get_absolute_path()[:-4] + "_small.jpg")
|
||||||
self._export_file: Path = Path(picture_path.get_absolute_path() + ".out.pp3")
|
self._thumb: Path = Path(picture_path.get_absolute_path()[:-4] + "_thumb.jpg")
|
||||||
|
self._profile_file: Path = Path(picture_path.get_absolute_path() + ".out.pp3")
|
||||||
|
self._categories_file: Path = Path(picture_path.get_absolute_path()[:-4] + "_categories.txt")
|
||||||
self._raw: Path|None = raw
|
self._raw: Path|None = raw
|
||||||
|
self.id: int = id
|
||||||
self._page: Page = page
|
self._page: Page = page
|
||||||
self._albums_name: list[str] = albums_name or []
|
self._categories_name: list[str] = []
|
||||||
if (config.CREATE_GENERAL_ALBUM):
|
self.categories: list[Category] = []
|
||||||
self._albums_name.append("general")
|
if self._categories_file.exist():
|
||||||
|
with open(self._categories_file.get_absolute_path(), "r+") as f:
|
||||||
|
categories_name: list[str] = f.read()
|
||||||
|
if (len(categories_name)):
|
||||||
|
self._categories_name += categories_name.split("\n")
|
||||||
|
else:
|
||||||
|
self._categories_file.touch()
|
||||||
|
if (config.CREATE_GENERAL_CATEGORY):
|
||||||
|
self._categories_name.append("general")
|
||||||
self._is_reperoried: bool = is_repertoried
|
self._is_reperoried: bool = is_repertoried
|
||||||
|
self.get_small()
|
||||||
|
self.get_thumb()
|
||||||
|
|
||||||
def get_page(self):
|
def get_page(self):
|
||||||
return self._page
|
return self._page
|
||||||
|
|
||||||
def get_albums_name(self):
|
def get_categories_name(self):
|
||||||
return self._albums_name
|
return self._categories_name
|
||||||
|
|
||||||
def get_small(self):
|
def get_small(self):
|
||||||
if not self._small.exist():
|
if not self._small.exist():
|
||||||
self.gen_small()
|
self.gen_small()
|
||||||
return self._small
|
return self._small
|
||||||
|
|
||||||
|
def get_thumb(self):
|
||||||
|
if not self._thumb.exist():
|
||||||
|
self.gen_thumb()
|
||||||
|
return self._thumb
|
||||||
|
|
||||||
def get_large(self):
|
def get_large(self):
|
||||||
return self._large
|
return self._large
|
||||||
|
|
||||||
def get_export_file(self):
|
def get_profile_file(self):
|
||||||
return self._export_file
|
return self._profile_file
|
||||||
|
|
||||||
|
def gen_thumb(self):
|
||||||
|
def _(_large: Path, _thumb: Path):
|
||||||
|
im = Image.open(_large.get_absolute_path())
|
||||||
|
im.thumbnail(config.THUMB_DIMENSION)
|
||||||
|
im.save(_thumb.get_absolute_path(), "JPEG")
|
||||||
|
thread_manager.add_to_queu(_, (self._large, self._thumb))
|
||||||
|
|
||||||
def gen_small(self):
|
def gen_small(self):
|
||||||
im = Image.open(self._large.get_absolute_path()).convert("RGB")
|
def _(_large: Path, _small: Path):
|
||||||
im.save(self._small.get_absolute_path(), quality=95, optimize=True)
|
im = Image.open(_large.get_absolute_path()).convert("RGB")
|
||||||
|
im.save(_small.get_absolute_path(), quality=95, optimize=True)
|
||||||
|
thread_manager.add_to_queu(_, (self._large, self._small))
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="album.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<h1>{{ album._name }}</h1>
|
|
||||||
<div class="picture-container">
|
|
||||||
{% for picture in album._pictures %}
|
|
||||||
<a href="{{ picture.get_page().html.get_url() }}">
|
|
||||||
<img src="{{ picture.get_small().get_url() }}">
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -28,14 +28,13 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
object-fit: contain
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
justify-content: center;
|
||||||
background-color: var(--content1);
|
background-color: var(--content1);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 10px;
|
margin: 10px;
|
19
src/templates/category.jinja
Normal file
19
src/templates/category.jinja
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/bulk/category.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>{{ category.name }}</h1>
|
||||||
|
<div class="picture-container">
|
||||||
|
{% for picture in category._pictures %}
|
||||||
|
<a href="{{ picture.get_page().html.get_url() }}#{{ picture.id }}">
|
||||||
|
<img src="{{ picture.get_thumb().get_url() }}">
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
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
13
src/templates/home.jinja
Normal file
13
src/templates/home.jinja
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<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>
|
@ -10,57 +10,47 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 50%;
|
|
||||||
background-color: var(--bg1);
|
background-color: var(--bg1);
|
||||||
margin: auto;
|
display: flex;
|
||||||
margin-top: 10px;
|
flex-direction: column;
|
||||||
margin-bottom: 50px;
|
align-items: center;
|
||||||
color: lightgray;
|
margin: 15% 15%;
|
||||||
}
|
margin-top: 5%;
|
||||||
|
height: auto;
|
||||||
body * {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picture-container {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picture-container-element {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.readme {
|
.readme {
|
||||||
border: 1px solid;
|
|
||||||
background-color: var(--bg2);
|
background-color: var(--bg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.readme-content {
|
.picture-container {
|
||||||
margin: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.export,
|
|
||||||
.next {
|
|
||||||
margin-top: 0px;
|
|
||||||
display: block;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download {
|
|
||||||
margin-top: 10px;
|
|
||||||
background-color: var(--bg2);
|
background-color: var(--bg2);
|
||||||
text-align: center;
|
padding: 10px;
|
||||||
padding-bottom: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation,
|
.picture {
|
||||||
.meta-picture {
|
padding: 5px;
|
||||||
|
background-color: var(--content1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.albums_container {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-picture, .navigation {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@ -68,3 +58,30 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.5rem;
|
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,47 +2,67 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="/page.css">
|
<link rel="stylesheet" href="/bulk/page.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="navigation">
|
<div class="navigation">
|
||||||
{% if page.prev %}
|
{% if page.prev %}
|
||||||
<a href="{{ page.next.html.get_url() }}">Prev</a>
|
<a href="{{ page.prev.html.get_url() }}">Prev</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if page.next %}
|
{% if page.next %}
|
||||||
<a class="next" href="{{ page.next.html.get_url() }}">Next</a>
|
<a class="next" href="{{ page.next.html.get_url() }}">Next</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if page._readme.exist() %}
|
{% if page.render_readme() %}
|
||||||
<div class="readme">
|
<div class="readme">
|
||||||
|
<details>
|
||||||
|
<summary>
|
||||||
|
README
|
||||||
|
</summary>
|
||||||
<div class="readme-content">
|
<div class="readme-content">
|
||||||
{{page.render_readme()}}
|
{{page.render_readme()}}
|
||||||
</div>
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for picture in page._pictures %}
|
|
||||||
<div class="picture-container">
|
<div class="picture-container">
|
||||||
<div class="picture-container-element">
|
{% 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() }}'>
|
<img src='{{ picture.get_small().get_url() }}'>
|
||||||
|
</a>
|
||||||
<div class="meta-picture">
|
<div class="meta-picture">
|
||||||
<a href="{{ picture.get_large().get_url() }}">Large</a>
|
<a href="{{ picture.get_large().get_url() }}">Large</a>
|
||||||
{% if picture._export_file.exist() %}
|
{% if picture._profile_file.exist() %}
|
||||||
<a class="export" href="{{ picture.get_export_file().get_url() }}">export file</a>
|
<a class="profile" href="{{ picture.get_profile_file().get_url() }}">Raw therapee profile</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if page.get_raw().exist() or page.get_exif() %}
|
</div>
|
||||||
|
{% if page.get_raw().exist() %}
|
||||||
<div class="download">
|
<div class="download">
|
||||||
<h1>Download</h1>
|
<h1>Download</h1>
|
||||||
{% if page.get_raw().exist() %}
|
<a href="{{ page.get_raw().get_url() }}">RAW</a>
|
||||||
<a href="{{ page.get_raw() }}">RAW</a>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if page.get_exif() %}
|
{% if page.get_exif() %}
|
||||||
<a href="{{ page.get_exif().get_url() }}">Exif</a>
|
<div class="exif">
|
||||||
{% endif %}
|
<details>
|
||||||
|
<summary>Exif</summary>
|
||||||
|
<pre>
|
||||||
|
{{ page.render_exif() }}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
|
8
src/thread_manager.py
Normal file
8
src/thread_manager.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
import config
|
||||||
|
|
||||||
|
_pool = concurrent.futures.ThreadPoolExecutor(max_workers=config.MAX_THREADS)
|
||||||
|
|
||||||
|
def add_to_queu(func: callable, args: tuple):
|
||||||
|
global _pool
|
||||||
|
_pool.submit(func, *args)
|
@ -33,7 +33,8 @@ def main():
|
|||||||
copy_path = os.path.join(output_folder, "ph_copy.txt")
|
copy_path = os.path.join(output_folder, "ph_copy.txt")
|
||||||
copy_info: dict[str, str] = get_copy_info(copy_path)
|
copy_info: dict[str, str] = get_copy_info(copy_path)
|
||||||
files: list[str] = [os.path.join(input_folder, f) for f in os.listdir(input_folder)]
|
files: list[str] = [os.path.join(input_folder, f) for f in os.listdir(input_folder)]
|
||||||
files = [f for f in files if os.path.isfile(f)]
|
files = [f for f in files if os.path.isfile(f) and f.endswith(".NEF")]
|
||||||
|
files.sort()
|
||||||
with Bar("copying...", max=len(files)) as bar:
|
with Bar("copying...", max=len(files)) as bar:
|
||||||
for file in files:
|
for file in files:
|
||||||
with open(file, 'rb') as f:
|
with open(file, 'rb') as f:
|
||||||
@ -42,7 +43,7 @@ def main():
|
|||||||
hash: str = hashlib.sha256(image_data).hexdigest()
|
hash: str = hashlib.sha256(image_data).hexdigest()
|
||||||
|
|
||||||
if copy_info.get(hash) is None:
|
if copy_info.get(hash) is None:
|
||||||
new_name: str = str(len(copy_info))
|
new_name: str = f"{len(copy_info):05d}"
|
||||||
path: str = os.path.join(output_folder, new_name)
|
path: str = os.path.join(output_folder, new_name)
|
||||||
|
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
17
tools/rename.py
Normal file
17
tools/rename.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from progress.bar import Bar
|
||||||
|
|
||||||
|
def main():
|
||||||
|
folders: list[str] = os.listdir(sys.argv[1])
|
||||||
|
with Bar("copying...", max=len(folders)) as bar:
|
||||||
|
for folder in folders:
|
||||||
|
if (len(folder) != 5):
|
||||||
|
if (folder.isdigit()):
|
||||||
|
new_name: str = f"{int(folder):05d}"
|
||||||
|
shutil.move(os.path.join(sys.argv[1], folder), os.path.join(sys.argv[1], new_name))
|
||||||
|
bar.next()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
exit(main())
|
Reference in New Issue
Block a user