Compare commits
67 Commits
b5b306eb09
...
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 | |||
73dfaf4622 | |||
37a31494fc | |||
7883f8ce6b | |||
24af283c38 | |||
ca17bd2cab | |||
760bd86bf0 | |||
97f05cee5e | |||
d911dfe1cf | |||
9335107522 | |||
196ae57b2c | |||
e27bfe74ea | |||
eb94330059 | |||
9a33ba64a2 | |||
a3301572e1 | |||
b73f374625 | |||
d96eeb4bef | |||
c92fc36b40 | |||
a106770769 |
85
README.md
85
README.md
@ -1,20 +1,81 @@
|
||||
# PhotoHUB
|
||||
Just a simple python program to generate static site
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Bulk Page
|
||||

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

|
||||
|
||||
## Installation
|
||||
``` sh
|
||||
python3 -m venv venv
|
||||
python3 -m pip -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Move and rename file with
|
||||
```sh
|
||||
python3 tools/copy.py %your raws% %site location%/bulk/
|
||||
```
|
||||
Here you can edit your pictures.
|
||||
|
||||
Then
|
||||
|
||||
Generate html page with
|
||||
``` sh
|
||||
python3 src/main.py %site location%
|
||||
```
|
||||
|
||||
## File
|
||||
|
||||
- RAW: (optionnal)
|
||||
- EDIT_DATA: (optionnal)
|
||||
- LARGE: high resolution format (optionnal: can be generated by the RAW)
|
||||
- SMALL: low resolution format (optionnal: can be generated by the LARGE)
|
||||
- DESQ: description markdown file (optionnal)
|
||||
### File tree
|
||||
|
||||
### Format supported
|
||||
File before `python src/main.py test_out`
|
||||
```
|
||||
RAW: .NEF
|
||||
LARGE: .png
|
||||
SMALL: jpeg
|
||||
EDIT_DATA: .pp3
|
||||
DESQ: .md
|
||||
```
|
||||
test_out/
|
||||
└── bulk
|
||||
└── 00001
|
||||
├── 00001.NEF
|
||||
├── 00001.png
|
||||
└── 00001.png.out.pp3
|
||||
```
|
||||
### Bulk
|
||||
|
||||
#### Mandatory
|
||||
- a .png
|
||||
|
||||
#### Optionnal
|
||||
- **raw** {*.NEF}
|
||||
- **readme.md**
|
||||
- (png file)**.out.pp3**: permit viewer to download it
|
||||
|
||||
File after `python src/main.py test_out`
|
||||
```
|
||||
test_out/
|
||||
├── bulk
|
||||
│ └── 00001
|
||||
│ ├── 00001_categories.txt
|
||||
│ ├── 00001.NEF
|
||||
│ ├── 00001.png
|
||||
│ ├── 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: {}
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
@ -1,84 +0,0 @@
|
||||
body {
|
||||
background-color: #002b36;
|
||||
display: flex;
|
||||
justify-content: center; /* Centre l'image */
|
||||
align-items: center; /* Aligne verticalement */
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#small {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#download {
|
||||
background-color: lightgray;
|
||||
position: absolute;
|
||||
margin: 10px;
|
||||
right: 200px;
|
||||
top: 200px;
|
||||
}
|
||||
|
||||
#download * {
|
||||
background-color: aqua;
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#download * p {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#download img {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Courier New", monospace;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dropdown-btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Cacher le checkbox (pour qu'il ne soit pas visible) */
|
||||
.dropdown-checkbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Style du texte qui apparaît lorsque la case est cochée */
|
||||
.dropdown-text {
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Quand la case est cochée, afficher le texte */
|
||||
.dropdown-checkbox:checked + .dropdown-btn + .dropdown-text {
|
||||
display: flex;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="img1.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img id="small" src="./img1.jpeg">
|
||||
<div id="download">
|
||||
<a id="large" href="./img1.png">
|
||||
<img src="../icons/file.png">
|
||||
<p>large</p>
|
||||
</a>
|
||||
<a id="raw" href="./img1.NEF">
|
||||
<img src="../icons/file.png">
|
||||
<p>raw</p>
|
||||
</a>
|
||||
</div>
|
||||
<div id="exif">
|
||||
<!-- Checkbox cachée qui contrôle l'affichage du texte -->
|
||||
<input type="checkbox" id="dropdown" class="dropdown-checkbox">
|
||||
|
||||
<!-- Bouton -->
|
||||
<label for="dropdown" class="dropdown-btn">Display exif data</label>
|
||||
<div class="dropdown-text">
|
||||
<pre>
|
||||
<code>
|
||||
ExifTool Version Number : 12.57
|
||||
File Name : img1.NEF
|
||||
Directory : ./example/img1
|
||||
File Size : 5.7 MB
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
Before Width: | Height: | Size: 208 KiB |
@ -1,752 +0,0 @@
|
||||
[Version]
|
||||
AppVersion=5.9
|
||||
Version=349
|
||||
|
||||
[General]
|
||||
Rank=0
|
||||
ColorLabel=0
|
||||
InTrash=false
|
||||
|
||||
[Exposure]
|
||||
Auto=false
|
||||
Clip=0.02
|
||||
Compensation=0
|
||||
Brightness=0
|
||||
Contrast=0
|
||||
Saturation=0
|
||||
Black=0
|
||||
HighlightCompr=0
|
||||
HighlightComprThreshold=0
|
||||
ShadowCompr=50
|
||||
HistogramMatching=true
|
||||
CurveFromHistogramMatching=true
|
||||
ClampOOG=true
|
||||
CurveMode=FilmLike
|
||||
CurveMode2=Standard
|
||||
Curve=4;0;0;0.050000000000000003;0.026486708211773232;0.12;0.076991901310519445;0.21799999999999997;0.19406184409178534;0.35519999999999996;0.40643555161187844;0.54727999999999999;0.65396579527439191;0.81619199999999992;0.86925799088009603;1;1;
|
||||
Curve2=0;
|
||||
|
||||
[HLRecovery]
|
||||
Enabled=true
|
||||
Method=Blend
|
||||
Hlbl=0
|
||||
|
||||
[Retinex]
|
||||
Enabled=false
|
||||
Str=20
|
||||
Scal=3
|
||||
Iter=1
|
||||
Grad=1
|
||||
Grads=1
|
||||
Gam=1.3
|
||||
Slope=3
|
||||
Median=false
|
||||
Neigh=80
|
||||
Offs=0
|
||||
Vart=200
|
||||
Limd=8
|
||||
highl=4
|
||||
skal=3
|
||||
complexMethod=normal
|
||||
RetinexMethod=high
|
||||
mapMethod=none
|
||||
viewMethod=none
|
||||
Retinexcolorspace=Lab
|
||||
Gammaretinex=none
|
||||
CDCurve=0;
|
||||
MAPCurve=0;
|
||||
CDHCurve=0;
|
||||
LHCurve=0;
|
||||
Highlights=0
|
||||
HighlightTonalWidth=80
|
||||
Shadows=0
|
||||
ShadowTonalWidth=80
|
||||
Radius=40
|
||||
TransmissionCurve=1;0;0.5;0.34999999999999998;0.34999999999999998;0.59999999999999998;0.75;0.34999999999999998;0.34999999999999998;1;0.5;0.34999999999999998;0.34999999999999998;
|
||||
GainTransmissionCurve=1;0;0.10000000000000001;0.34999999999999998;0;0.25;0.25;0.34999999999999998;0.34999999999999998;0.69999999999999996;0.25;0.34999999999999998;0.34999999999999998;1;0.10000000000000001;0;0;
|
||||
|
||||
[Local Contrast]
|
||||
Enabled=false
|
||||
Radius=80
|
||||
Amount=0.20000000000000001
|
||||
Darkness=1
|
||||
Lightness=1
|
||||
|
||||
[Channel Mixer]
|
||||
Enabled=false
|
||||
Red=1000;0;0;
|
||||
Green=0;1000;0;
|
||||
Blue=0;0;1000;
|
||||
|
||||
[Black & White]
|
||||
Enabled=false
|
||||
Method=Desaturation
|
||||
Auto=false
|
||||
ComplementaryColors=true
|
||||
Setting=RGB-Rel
|
||||
Filter=None
|
||||
MixerRed=33
|
||||
MixerOrange=33
|
||||
MixerYellow=33
|
||||
MixerGreen=33
|
||||
MixerCyan=33
|
||||
MixerBlue=33
|
||||
MixerMagenta=33
|
||||
MixerPurple=33
|
||||
GammaRed=0
|
||||
GammaGreen=0
|
||||
GammaBlue=0
|
||||
Algorithm=SP
|
||||
LuminanceCurve=0;
|
||||
BeforeCurveMode=Standard
|
||||
AfterCurveMode=Standard
|
||||
BeforeCurve=0;
|
||||
AfterCurve=0;
|
||||
|
||||
[Luminance Curve]
|
||||
Enabled=false
|
||||
Brightness=0
|
||||
Contrast=0
|
||||
Chromaticity=0
|
||||
AvoidColorShift=false
|
||||
RedAndSkinTonesProtection=0
|
||||
LCredsk=true
|
||||
LCurve=0;
|
||||
aCurve=0;
|
||||
bCurve=0;
|
||||
ccCurve=0;
|
||||
chCurve=0;
|
||||
lhCurve=0;
|
||||
hhCurve=0;
|
||||
LcCurve=0;
|
||||
ClCurve=0;
|
||||
|
||||
[Sharpening]
|
||||
Enabled=false
|
||||
Contrast=20
|
||||
Method=usm
|
||||
Radius=0.5
|
||||
BlurRadius=0.20000000000000001
|
||||
Amount=200
|
||||
Threshold=20;80;2000;1200;
|
||||
OnlyEdges=false
|
||||
EdgedetectionRadius=1.8999999999999999
|
||||
EdgeTolerance=1800
|
||||
HalocontrolEnabled=false
|
||||
HalocontrolAmount=85
|
||||
DeconvRadius=0.75
|
||||
DeconvAmount=100
|
||||
DeconvDamping=0
|
||||
DeconvIterations=30
|
||||
|
||||
[Vibrance]
|
||||
Enabled=false
|
||||
Pastels=0
|
||||
Saturated=0
|
||||
PSThreshold=0;75;
|
||||
ProtectSkins=false
|
||||
AvoidColorShift=true
|
||||
PastSatTog=true
|
||||
SkinTonesCurve=0;
|
||||
|
||||
[SharpenEdge]
|
||||
Enabled=false
|
||||
Passes=2
|
||||
Strength=50
|
||||
ThreeChannels=false
|
||||
|
||||
[SharpenMicro]
|
||||
Enabled=false
|
||||
Matrix=false
|
||||
Strength=20
|
||||
Contrast=20
|
||||
Uniformity=5
|
||||
|
||||
[White Balance]
|
||||
Enabled=true
|
||||
Setting=Camera
|
||||
Temperature=5098
|
||||
Green=1.0387654687995256
|
||||
Equal=1
|
||||
TemperatureBias=0
|
||||
|
||||
[Color appearance]
|
||||
Enabled=false
|
||||
Degree=90
|
||||
AutoDegree=true
|
||||
Degreeout=90
|
||||
AutoDegreeout=true
|
||||
Surround=Average
|
||||
complex=normal
|
||||
ModelCat=16
|
||||
CatCat=clas
|
||||
Surrsrc=Average
|
||||
AdaptLum=16
|
||||
Badpixsl=0
|
||||
Model=RawT
|
||||
Illum=i50
|
||||
Algorithm=No
|
||||
J-Light=0
|
||||
Q-Bright=0
|
||||
C-Chroma=0
|
||||
S-Chroma=0
|
||||
M-Chroma=0
|
||||
J-Contrast=0
|
||||
Q-Contrast=0
|
||||
H-Hue=0
|
||||
RSTProtection=0
|
||||
AdaptScene=2000
|
||||
AutoAdapscen=true
|
||||
YbScene=18
|
||||
Autoybscen=true
|
||||
SurrSource=false
|
||||
Gamut=true
|
||||
Tempout=5003
|
||||
Autotempout=true
|
||||
Greenout=1
|
||||
Tempsc=5003
|
||||
Greensc=1
|
||||
Ybout=18
|
||||
Datacie=false
|
||||
Tonecie=false
|
||||
Presetcat02=false
|
||||
CurveMode=Lightness
|
||||
CurveMode2=Brightness
|
||||
CurveMode3=Chroma
|
||||
Curve=0;
|
||||
Curve2=0;
|
||||
Curve3=0;
|
||||
|
||||
[Impulse Denoising]
|
||||
Enabled=false
|
||||
Threshold=50
|
||||
|
||||
[Defringing]
|
||||
Enabled=false
|
||||
Radius=2
|
||||
Threshold=13
|
||||
HueCurve=1;0.16666666699999999;0;0.34999999999999998;0.34999999999999998;0.34699999999999998;0;0.34999999999999998;0.34999999999999998;0.51366742600000004;0;0.34999999999999998;0.34999999999999998;0.66894457100000004;0;0.34999999999999998;0.34999999999999998;0.82877752459999998;0.97835991;0.34999999999999998;0.34999999999999998;0.99088838270000001;0;0.34999999999999998;0.34999999999999998;
|
||||
|
||||
[Dehaze]
|
||||
Enabled=false
|
||||
Strength=50
|
||||
ShowDepthMap=false
|
||||
Depth=25
|
||||
Saturation=50
|
||||
|
||||
[Directional Pyramid Denoising]
|
||||
Enabled=false
|
||||
Enhance=false
|
||||
Median=false
|
||||
Luma=0
|
||||
Ldetail=0
|
||||
Chroma=15
|
||||
Method=Lab
|
||||
LMethod=SLI
|
||||
CMethod=MAN
|
||||
C2Method=AUTO
|
||||
SMethod=shal
|
||||
MedMethod=soft
|
||||
RGBMethod=soft
|
||||
MethodMed=none
|
||||
Redchro=0
|
||||
Bluechro=0
|
||||
Gamma=1.7
|
||||
Passes=1
|
||||
LCurve=1;0.050000000000000003;0.14999999999999999;0.34999999999999998;0.34999999999999998;0.55000000000000004;0.040000000000000001;0.34999999999999998;0.34999999999999998;
|
||||
CCCurve=1;0.050000000000000003;0.5;0.34999999999999998;0.34999999999999998;0.34999999999999998;0.050000000000000003;0.34999999999999998;0.34999999999999998;
|
||||
|
||||
[EPD]
|
||||
Enabled=false
|
||||
Strength=0.5
|
||||
Gamma=1
|
||||
EdgeStopping=1.3999999999999999
|
||||
Scale=1
|
||||
ReweightingIterates=0
|
||||
|
||||
[FattalToneMapping]
|
||||
Enabled=false
|
||||
Threshold=30
|
||||
Amount=20
|
||||
Anchor=50
|
||||
|
||||
[Shadows & Highlights]
|
||||
Enabled=false
|
||||
Highlights=0
|
||||
HighlightTonalWidth=70
|
||||
Shadows=0
|
||||
ShadowTonalWidth=30
|
||||
Radius=40
|
||||
Lab=false
|
||||
|
||||
[Crop]
|
||||
Enabled=false
|
||||
X=-1
|
||||
Y=-1
|
||||
W=3032
|
||||
H=2007
|
||||
FixedRatio=true
|
||||
Ratio=As Image
|
||||
Orientation=As Image
|
||||
Guide=Frame
|
||||
|
||||
[Coarse Transformation]
|
||||
Rotate=0
|
||||
HorizontalFlip=false
|
||||
VerticalFlip=false
|
||||
|
||||
[Common Properties for Transformations]
|
||||
Method=log
|
||||
AutoFill=true
|
||||
|
||||
[Rotation]
|
||||
Degree=0
|
||||
|
||||
[Distortion]
|
||||
Amount=0
|
||||
|
||||
[LensProfile]
|
||||
LcMode=lfauto
|
||||
LCPFile=
|
||||
UseDistortion=true
|
||||
UseVignette=true
|
||||
UseCA=false
|
||||
LFCameraMake=
|
||||
LFCameraModel=
|
||||
LFLens=
|
||||
|
||||
[Perspective]
|
||||
Method=simple
|
||||
Horizontal=0
|
||||
Vertical=0
|
||||
CameraCropFactor=0
|
||||
CameraFocalLength=0
|
||||
CameraPitch=0
|
||||
CameraRoll=0
|
||||
CameraShiftHorizontal=0
|
||||
CameraShiftVertical=0
|
||||
CameraYaw=0
|
||||
ProjectionShiftHorizontal=0
|
||||
ProjectionPitch=0
|
||||
ProjectionRotate=0
|
||||
ProjectionShiftVertical=0
|
||||
ProjectionYaw=0
|
||||
ControlLineValues=
|
||||
ControlLineTypes=
|
||||
|
||||
[Gradient]
|
||||
Enabled=false
|
||||
Degree=0
|
||||
Feather=25
|
||||
Strength=0.59999999999999998
|
||||
CenterX=0
|
||||
CenterY=0
|
||||
|
||||
[Locallab]
|
||||
Enabled=false
|
||||
Selspot=0
|
||||
|
||||
[PCVignette]
|
||||
Enabled=false
|
||||
Strength=0.59999999999999998
|
||||
Feather=50
|
||||
Roundness=50
|
||||
|
||||
[CACorrection]
|
||||
Red=0
|
||||
Blue=0
|
||||
|
||||
[Vignetting Correction]
|
||||
Amount=0
|
||||
Radius=50
|
||||
Strength=1
|
||||
CenterX=0
|
||||
CenterY=0
|
||||
|
||||
[Resize]
|
||||
Enabled=false
|
||||
Scale=1
|
||||
AppliesTo=Cropped area
|
||||
Method=Lanczos
|
||||
DataSpecified=3
|
||||
Width=900
|
||||
Height=900
|
||||
LongEdge=900
|
||||
ShortEdge=900
|
||||
AllowUpscaling=false
|
||||
|
||||
[PostDemosaicSharpening]
|
||||
Enabled=true
|
||||
Contrast=10
|
||||
AutoContrast=true
|
||||
AutoRadius=true
|
||||
DeconvRadius=0.75
|
||||
DeconvRadiusOffset=0
|
||||
DeconvIterCheck=true
|
||||
DeconvIterations=20
|
||||
|
||||
[PostResizeSharpening]
|
||||
Enabled=false
|
||||
Contrast=15
|
||||
Method=rld
|
||||
Radius=0.5
|
||||
Amount=200
|
||||
Threshold=20;80;2000;1200;
|
||||
OnlyEdges=false
|
||||
EdgedetectionRadius=1.8999999999999999
|
||||
EdgeTolerance=1800
|
||||
HalocontrolEnabled=false
|
||||
HalocontrolAmount=85
|
||||
DeconvRadius=0.45000000000000001
|
||||
DeconvAmount=100
|
||||
DeconvDamping=0
|
||||
DeconvIterations=100
|
||||
|
||||
[Color Management]
|
||||
InputProfile=(cameraICC)
|
||||
ToneCurve=false
|
||||
ApplyLookTable=true
|
||||
ApplyBaselineExposureOffset=true
|
||||
ApplyHueSatMap=true
|
||||
DCPIlluminant=0
|
||||
WorkingProfile=ProPhoto
|
||||
WorkingTRC=none
|
||||
Will=def
|
||||
Wprim=def
|
||||
WorkingTRCGamma=2.3999999999999999
|
||||
WorkingTRCSlope=12.92
|
||||
Redx=0.73470000000000002
|
||||
Redy=0.26529999999999998
|
||||
Grex=0.15959999999999999
|
||||
Grey=0.84040000000000004
|
||||
Blux=0.036600000000000001
|
||||
Bluy=0.0001
|
||||
LabGridcieALow=0.51763000000000003
|
||||
LabGridcieBLow=-0.33582000000000001
|
||||
LabGridcieAHigh=-0.75163000000000002
|
||||
LabGridcieBHigh=-0.81799999999999995
|
||||
LabGridcieGx=-0.69164000000000003
|
||||
LabGridcieGy=-0.70909
|
||||
LabGridcieWx=-0.18964
|
||||
LabGridcieWy=-0.16636000000000001
|
||||
Preser=0
|
||||
Fbw=false
|
||||
OutputProfile=RTv4_sRGB
|
||||
aIntent=Relative
|
||||
OutputProfileIntent=Relative
|
||||
OutputBPC=true
|
||||
|
||||
[Wavelet]
|
||||
Enabled=false
|
||||
Strength=100
|
||||
Balance=0
|
||||
Sigmafin=1
|
||||
Sigmaton=1
|
||||
Sigmacol=1
|
||||
Sigmadir=1
|
||||
Rangeab=20
|
||||
Protab=0
|
||||
Iter=0
|
||||
MaxLev=7
|
||||
TilesMethod=full
|
||||
complexMethod=normal
|
||||
mixMethod=mix
|
||||
sliMethod=sli
|
||||
quaMethod=cons
|
||||
DaubMethod=4_
|
||||
ChoiceLevMethod=all
|
||||
BackMethod=grey
|
||||
LevMethod=4
|
||||
DirMethod=all
|
||||
CBgreenhigh=0
|
||||
CBgreenmed=0
|
||||
CBgreenlow=0
|
||||
CBbluehigh=0
|
||||
CBbluemed=0
|
||||
CBbluelow=0
|
||||
Ballum=7
|
||||
Sigm=1
|
||||
Levden=0
|
||||
Thrden=0
|
||||
Limden=0
|
||||
Balchrom=0
|
||||
Chromfine=0
|
||||
Chromcoarse=0
|
||||
MergeL=20
|
||||
MergeC=20
|
||||
Softrad=0
|
||||
Softradend=0
|
||||
Strend=50
|
||||
Detend=0
|
||||
Thrend=0
|
||||
Expcontrast=false
|
||||
Expchroma=false
|
||||
Expedge=false
|
||||
expbl=false
|
||||
Expresid=false
|
||||
Expfinal=false
|
||||
Exptoning=false
|
||||
Expnoise=false
|
||||
Expclari=false
|
||||
LabGridALow=0
|
||||
LabGridBLow=0
|
||||
LabGridAHigh=0
|
||||
LabGridBHigh=0
|
||||
Contrast1=0
|
||||
Contrast2=0
|
||||
Contrast3=0
|
||||
Contrast4=0
|
||||
Contrast5=0
|
||||
Contrast6=0
|
||||
Contrast7=0
|
||||
Contrast8=0
|
||||
Contrast9=0
|
||||
Chroma1=0
|
||||
Chroma2=0
|
||||
Chroma3=0
|
||||
Chroma4=0
|
||||
Chroma5=0
|
||||
Chroma6=0
|
||||
Chroma7=0
|
||||
Chroma8=0
|
||||
Chroma9=0
|
||||
ContExtra=0
|
||||
HSMethod=with
|
||||
HLRange=50;75;100;98;
|
||||
SHRange=0;2;50;25;
|
||||
Edgcont=0;10;75;40;
|
||||
Level0noise=0;0;
|
||||
Level1noise=0;0;
|
||||
Level2noise=0;0;
|
||||
Level3noise=0;0;
|
||||
Leveldenoise=0;0;
|
||||
Levelsigm=1;1;
|
||||
ThresholdHighlight=4
|
||||
ThresholdShadow=5
|
||||
Edgedetect=90
|
||||
Edgedetectthr=20
|
||||
EdgedetectthrHi=0
|
||||
Edgesensi=60
|
||||
Edgeampli=10
|
||||
ThresholdChroma=5
|
||||
CHromaMethod=without
|
||||
Medgreinf=less
|
||||
Ushamethod=clari
|
||||
CHSLromaMethod=SL
|
||||
EDMethod=CU
|
||||
NPMethod=none
|
||||
BAMethod=none
|
||||
TMMethod=cont
|
||||
ChromaLink=0
|
||||
ContrastCurve=1;0;0.25;0.34999999999999998;0.34999999999999998;0.5;0.75;0.34999999999999998;0.34999999999999998;0.90000000000000002;0;0.34999999999999998;0.34999999999999998;
|
||||
blcurve=1;0;0;0;0.34999999999999998;0.5;0;0.34999999999999998;0.34999999999999998;1;0;0.34999999999999998;0.34999999999999998;
|
||||
Pastlev=0;2;30;20;
|
||||
Satlev=30;45;130;100;
|
||||
OpacityCurveRG=1;0;0.5;0.34999999999999998;0.34999999999999998;1;0.5;0.34999999999999998;0.34999999999999998;
|
||||
OpacityCurveBY=1;0;0.5;0.34999999999999998;0.34999999999999998;1;0.5;0.34999999999999998;0.34999999999999998;
|
||||
wavdenoise=1;0;1;0.34999999999999998;0.34999999999999998;0.5;1;0.34999999999999998;0.34999999999999998;1;1;0.34999999999999998;0.34999999999999998;
|
||||
wavdenoiseh=1;0;1;0.34999999999999998;0.34999999999999998;0.5;1;0.34999999999999998;0.34999999999999998;1;1;0.34999999999999998;0.34999999999999998;
|
||||
OpacityCurveW=1;0;0.34999999999999998;0.34999999999999998;0;0.34999999999999998;0.75;0.34999999999999998;0.34999999999999998;0.59999999999999998;0.75;0.34999999999999998;0.34999999999999998;1;0.34999999999999998;0;0;
|
||||
OpacityCurveWL=1;0;0.5;0.34999999999999998;0.34999999999999998;1;0.5;0.34999999999999998;0.34999999999999998;
|
||||
HHcurve=0;
|
||||
Wavguidcurve=0;
|
||||
Wavhuecurve=0;
|
||||
CHcurve=0;
|
||||
WavclCurve=0;
|
||||
Median=false
|
||||
Medianlev=false
|
||||
Linkedg=false
|
||||
CBenab=false
|
||||
Lipst=false
|
||||
Skinprotect=0
|
||||
chrwav=0
|
||||
bluwav=1
|
||||
Hueskin=-5;25;170;120;
|
||||
Edgrad=15
|
||||
Edgeffect=1
|
||||
Edgval=0
|
||||
ThrEdg=10
|
||||
AvoidColorShift=false
|
||||
Showmask=false
|
||||
Oldsh=true
|
||||
TMr=false
|
||||
Sigma=1
|
||||
Offset=1
|
||||
Lowthr=40
|
||||
ResidualcontShadow=0
|
||||
ResidualcontHighlight=0
|
||||
ThresholdResidShadow=30
|
||||
ThresholdResidHighLight=70
|
||||
Residualradius=40
|
||||
Residualchroma=0
|
||||
Residualblur=0
|
||||
Residualblurc=0
|
||||
ResidualTM=0
|
||||
ResidualEDGS=1.3999999999999999
|
||||
ResidualSCALE=1
|
||||
Residualgamma=1
|
||||
HueRangeResidual=0
|
||||
HueRange=-260;-250;-130;-140;
|
||||
Contrast=0
|
||||
|
||||
[Spot removal]
|
||||
Enabled=false
|
||||
|
||||
[Directional Pyramid Equalizer]
|
||||
Enabled=false
|
||||
Gamutlab=false
|
||||
cbdlMethod=bef
|
||||
Mult0=1
|
||||
Mult1=1
|
||||
Mult2=1
|
||||
Mult3=1
|
||||
Mult4=1
|
||||
Mult5=1
|
||||
Threshold=0.20000000000000001
|
||||
Skinprotect=0
|
||||
Hueskin=-5;25;170;120;
|
||||
|
||||
[HSV Equalizer]
|
||||
Enabled=false
|
||||
HCurve=0;
|
||||
SCurve=0;
|
||||
VCurve=0;
|
||||
|
||||
[SoftLight]
|
||||
Enabled=false
|
||||
Strength=30
|
||||
|
||||
[Film Simulation]
|
||||
Enabled=false
|
||||
ClutFilename=
|
||||
Strength=100
|
||||
|
||||
[RGB Curves]
|
||||
Enabled=false
|
||||
LumaMode=false
|
||||
rCurve=0;
|
||||
gCurve=0;
|
||||
bCurve=0;
|
||||
|
||||
[ColorToning]
|
||||
Enabled=false
|
||||
Method=LabRegions
|
||||
Lumamode=true
|
||||
Twocolor=Std
|
||||
Redlow=0
|
||||
Greenlow=0
|
||||
Bluelow=0
|
||||
Satlow=0
|
||||
Balance=0
|
||||
Sathigh=0
|
||||
Redmed=0
|
||||
Greenmed=0
|
||||
Bluemed=0
|
||||
Redhigh=0
|
||||
Greenhigh=0
|
||||
Bluehigh=0
|
||||
Autosat=true
|
||||
OpacityCurve=1;0;0.29999999999999999;0.34999999999999998;0;0.25;0.80000000000000004;0.34999999999999998;0.34999999999999998;0.69999999999999996;0.80000000000000004;0.34999999999999998;0.34999999999999998;1;0.29999999999999999;0;0;
|
||||
ColorCurve=1;0.050000000000000003;0.62;0.25;0.25;0.58499999999999996;0.11;0.25;0.25;
|
||||
SatProtectionThreshold=30
|
||||
SaturatedOpacity=80
|
||||
Strength=50
|
||||
HighlightsColorSaturation=60;80;
|
||||
ShadowsColorSaturation=80;208;
|
||||
ClCurve=3;0;0;0.34999999999999998;0.65000000000000002;1;1;
|
||||
Cl2Curve=3;0;0;0.34999999999999998;0.65000000000000002;1;1;
|
||||
LabGridALow=0
|
||||
LabGridBLow=0
|
||||
LabGridAHigh=0
|
||||
LabGridBHigh=0
|
||||
LabRegionA_1=0
|
||||
LabRegionB_1=0
|
||||
LabRegionSaturation_1=0
|
||||
LabRegionSlope_1=1
|
||||
LabRegionOffset_1=0
|
||||
LabRegionPower_1=1
|
||||
LabRegionHueMask_1=1;0.16666666699999999;1;0.34999999999999998;0.34999999999999998;0.82877752459999998;1;0.34999999999999998;0.34999999999999998;
|
||||
LabRegionChromaticityMask_1=1;0;1;0.34999999999999998;0.34999999999999998;1;1;0.34999999999999998;0.34999999999999998;
|
||||
LabRegionLightnessMask_1=1;0;1;0.34999999999999998;0.34999999999999998;1;1;0.34999999999999998;0.34999999999999998;
|
||||
LabRegionMaskBlur_1=0
|
||||
LabRegionChannel_1=-1
|
||||
LabRegionsShowMask=-1
|
||||
|
||||
[RAW]
|
||||
DarkFrame=
|
||||
DarkFrameAuto=false
|
||||
FlatFieldFile=
|
||||
FlatFieldAutoSelect=false
|
||||
FlatFieldBlurRadius=32
|
||||
FlatFieldBlurType=Area Flatfield
|
||||
FlatFieldAutoClipControl=false
|
||||
FlatFieldClipControl=0
|
||||
CA=true
|
||||
CAAvoidColourshift=true
|
||||
CAAutoIterations=2
|
||||
CARed=0
|
||||
CABlue=0
|
||||
HotPixelFilter=false
|
||||
DeadPixelFilter=false
|
||||
HotDeadPixelThresh=100
|
||||
PreExposure=1
|
||||
|
||||
[RAW Bayer]
|
||||
Method=amaze
|
||||
Border=4
|
||||
ImageNum=1
|
||||
CcSteps=0
|
||||
PreBlack0=0
|
||||
PreBlack1=0
|
||||
PreBlack2=0
|
||||
PreBlack3=0
|
||||
PreTwoGreen=true
|
||||
LineDenoise=0
|
||||
LineDenoiseDirection=3
|
||||
GreenEqThreshold=0
|
||||
DCBIterations=2
|
||||
DCBEnhance=true
|
||||
LMMSEIterations=2
|
||||
DualDemosaicAutoContrast=true
|
||||
DualDemosaicContrast=20
|
||||
PixelShiftMotionCorrectionMethod=1
|
||||
PixelShiftEperIso=0
|
||||
PixelShiftSigma=1
|
||||
PixelShiftShowMotion=false
|
||||
PixelShiftShowMotionMaskOnly=false
|
||||
pixelShiftHoleFill=true
|
||||
pixelShiftAverage=false
|
||||
pixelShiftMedian=false
|
||||
pixelShiftGreen=true
|
||||
pixelShiftBlur=true
|
||||
pixelShiftSmoothFactor=0.69999999999999996
|
||||
pixelShiftEqualBright=false
|
||||
pixelShiftEqualBrightChannel=false
|
||||
pixelShiftNonGreenCross=true
|
||||
pixelShiftDemosaicMethod=amaze
|
||||
PDAFLinesFilter=false
|
||||
|
||||
[RAW X-Trans]
|
||||
Method=3-pass (best)
|
||||
DualDemosaicAutoContrast=true
|
||||
DualDemosaicContrast=20
|
||||
Border=7
|
||||
CcSteps=0
|
||||
PreBlackRed=0
|
||||
PreBlackGreen=0
|
||||
PreBlackBlue=0
|
||||
|
||||
[MetaData]
|
||||
Mode=0
|
||||
|
||||
[Film Negative]
|
||||
Enabled=false
|
||||
RedRatio=1.3600000000000001
|
||||
GreenExponent=1.5
|
||||
BlueRatio=0.85999999999999999
|
||||
ColorSpace=1
|
||||
RefInput=0;0;0;
|
||||
RefOutput=0;0;0;
|
||||
|
||||
[RAW Preprocess WB]
|
||||
Mode=1
|
@ -1,3 +1,14 @@
|
||||
beautifulsoup4==4.13.4
|
||||
bs4==0.0.2
|
||||
exif==1.6.1
|
||||
imageio==2.37.0
|
||||
Jinja2==3.1.6
|
||||
Markdown==3.8
|
||||
MarkupSafe==3.0.2
|
||||
progress==1.6
|
||||
numpy==2.2.3
|
||||
pillow==11.1.0
|
||||
plum-py==0.8.7
|
||||
progress==1.6
|
||||
rawpy==0.24.0
|
||||
soupsieve==2.7
|
||||
typing_extensions==4.14.1
|
||||
|
BIN
screenshots/album.png
Normal file
BIN
screenshots/album.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 712 KiB |
BIN
screenshots/page.png
Normal file
BIN
screenshots/page.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
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>",
|
||||
}
|
32
src/category.py
Normal file
32
src/category.py
Normal file
@ -0,0 +1,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from picture import Picture
|
||||
|
||||
from path import Path
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
category_template = env.get_template('category.jinja')
|
||||
|
||||
class Category():
|
||||
|
||||
def __init__(self, name: str, categorys_path: Path, pictures: list[Picture] = None, is_repertoried: bool = False):
|
||||
self.name: str = name
|
||||
self._pictures: list[Picture] = pictures or []
|
||||
self._is_repertoried: bool = is_repertoried
|
||||
self.path: Path = Path(categorys_path, f"{name}.html")
|
||||
|
||||
def add_picture(self, picture: Picture) -> None:
|
||||
self._pictures.append(picture)
|
||||
|
||||
def _to_html(self) -> str|None:
|
||||
html_rendered = category_template.render(category=self)
|
||||
return html_rendered
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self.path.get_absolute_path(), "w") as f:
|
||||
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())
|
6
src/config.py
Normal file
6
src/config.py
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
|
||||
CREATE_GENERAL_CATEGORY: bool = True
|
||||
THUMB_DIMENSION: tuple[int, int] = (200, 200)
|
||||
MAX_THREADS: int = 50
|
||||
COLLECTION_EXT: str = ".ph"
|
140
src/main.py
140
src/main.py
@ -1,48 +1,148 @@
|
||||
import sys
|
||||
import os
|
||||
from progress.bar import Bar
|
||||
|
||||
from path import Path
|
||||
from picture import Picture
|
||||
from page import Page
|
||||
from category import Category
|
||||
from collection import Collection
|
||||
from photodown import Photodown
|
||||
|
||||
import argparse
|
||||
import config
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
home_template = env.get_template('home.jinja')
|
||||
|
||||
def argument_parsing():
|
||||
if (len(sys.argv) < 2):
|
||||
print("error: missing argument", file=sys.stderr)
|
||||
exit(1)
|
||||
return sys.argv[1]
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("site_path", help="the folder of your site MUST BE ALWAYS BEFORT PARAMETERS", type=str)
|
||||
parser.add_argument("-r", '--regen', help="Regenerate (t)thumb (s)small", type=str)
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def scan_pages(folders: list[Path]) -> list[Page]:
|
||||
pages: list[Page] = []
|
||||
prev: Page = None
|
||||
with Bar("scaning...", max=len(folders)) as bar:
|
||||
with Bar("Scanning Pages...", max=len(folders)) as bar:
|
||||
for folder in folders:
|
||||
files: list[Path] = folder.get_files()
|
||||
|
||||
page: Page = Page(folder, folder.get_name(), prev=prev)
|
||||
|
||||
prev: Page = Page(folder.get_absolute_path(), folder.get_name(), None, None, None)
|
||||
raw: Path = Path(folder, folder.get_name() + ".NEF")
|
||||
id: int = 0
|
||||
for file in files:
|
||||
if file.get_name().endswith(".png"):
|
||||
page.add_picture(Picture(file, id, page=page, raw=raw))
|
||||
id += 1
|
||||
|
||||
raw: Path = Path(folder.get_absolute_path(), folder.get_name() + ".NEF")
|
||||
images: list[Path] = [Picture(file, page=prev, raw=raw) for file in files if file.get_name().endswith(".png")]
|
||||
|
||||
prev.images = images
|
||||
|
||||
if len(images) == 0:
|
||||
if len(page.get_pictures()) == 0:
|
||||
bar.next()
|
||||
continue
|
||||
pages.append(prev)
|
||||
|
||||
pages.append(page)
|
||||
if prev is not None:
|
||||
prev.next = page
|
||||
prev = page
|
||||
|
||||
bar.next()
|
||||
pages[-1].next = pages[0]
|
||||
pages[0].prev = pages[-1]
|
||||
return pages
|
||||
|
||||
def create_pages(pages: list[Page]) -> None:
|
||||
with Bar("generating...", max=len(pages)) as bar:
|
||||
with Bar("Generating Pages...", max=len(pages)) as bar:
|
||||
for page in pages:
|
||||
page.create()
|
||||
bar.next()
|
||||
|
||||
def main():
|
||||
site_path: Path = Path(argument_parsing())
|
||||
pages: list[Page] = scan_pages(site_path.get_dirs())
|
||||
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: Category | None = categories.get(category_name)
|
||||
if (category is None):
|
||||
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[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, 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():
|
||||
args = argument_parsing()
|
||||
site_path: Path = Path(args.site_path)
|
||||
bulk_path: Path = Path(site_path, "bulk/")
|
||||
if args.regen is not None:
|
||||
regen(bulk_path, 't' in args.regen, 's' in args.regen)
|
||||
markdown = Photodown()
|
||||
gen_bulk(bulk_path, markdown)
|
||||
gen_collections(site_path, markdown)
|
||||
gen_home(site_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
54
src/page.py
54
src/page.py
@ -9,7 +9,6 @@ from path import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from page import Page
|
||||
from picture import Picture
|
||||
|
||||
env = Environment(loader=FileSystemLoader('src/templates'))
|
||||
@ -17,40 +16,43 @@ page_template = env.get_template('page.jinja')
|
||||
|
||||
class Page():
|
||||
|
||||
def __init__(self, path: Path, name: str, images: list[Picture], prev: Page|None, next: Page|None):
|
||||
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.images: list[Picture] = images
|
||||
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._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 _render_raw(self):
|
||||
if not self._raw.exist():
|
||||
return None
|
||||
return self._raw.get_name()
|
||||
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(exif=self._render_exif(), raw=self._render_raw(), images=self._render_images(), readme=self._render_readme())
|
||||
html_rendered = page_template.render(page=self)
|
||||
return html_rendered
|
||||
|
||||
def create(self) -> Path:
|
||||
with open(self._html.get_absolute_path(), "w") as f:
|
||||
with open(self.html.get_absolute_path(), "w") as f:
|
||||
f.write(self._to_html())
|
||||
self.created = True
|
||||
|
||||
def _render_readme(self) -> str|None:
|
||||
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 _render_images(self):
|
||||
return [image.render() for image in self.images]
|
||||
|
||||
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:
|
||||
@ -58,8 +60,18 @@ class Page():
|
||||
os.remove(self._exif.get_absolute_path())
|
||||
return 1
|
||||
|
||||
def _render_exif(self) -> Path | None:
|
||||
def get_exif(self) -> Path | None:
|
||||
if not self._exif.exist():
|
||||
if (self._gen_exif()):
|
||||
return None
|
||||
return self._exif.get_name()
|
||||
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
|
44
src/path.py
44
src/path.py
@ -1,19 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import sys
|
||||
import shutil
|
||||
class Path():
|
||||
|
||||
def __init__(self, *paths: str | Path):
|
||||
self._absolute_path: str = ""
|
||||
for path in paths:
|
||||
self._absolute_path = os.path.join(self._absolute_path, path._absolute_path if isinstance(path, Path) else path)
|
||||
self._absolute_path = os.path.abspath(self._absolute_path)
|
||||
self._name: str = os.path.basename(self._absolute_path)
|
||||
self._dirpath: str = os.path.dirname(self._absolute_path)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._name
|
||||
|
||||
def get_dirpath(self):
|
||||
return self._dirpath
|
||||
|
||||
def get_url(self):
|
||||
site_path: Path = Path(sys.argv[1])
|
||||
return self._absolute_path[len(site_path._absolute_path):]
|
||||
|
||||
def get_absolute_path(self):
|
||||
return self._absolute_path
|
||||
|
||||
@ -22,12 +31,23 @@ class Path():
|
||||
return None
|
||||
return self._name
|
||||
|
||||
def touch(self) -> None:
|
||||
if (self.exist()):
|
||||
return None
|
||||
open(self.get_absolute_path(), "w+").close()
|
||||
|
||||
def create(self) -> None:
|
||||
os.makedirs(self.get_absolute_path())
|
||||
|
||||
def exist(self) -> bool:
|
||||
return os.path.exists(self.get_absolute_path())
|
||||
|
||||
def get_dirs(self) -> 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)
|
||||
if (os.path.isdir(path.get_absolute_path())):
|
||||
dirs.append(path)
|
||||
@ -35,8 +55,24 @@ class Path():
|
||||
|
||||
def get_files(self) -> 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)
|
||||
if (os.path.isfile(path.get_absolute_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,28 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PIL import Image
|
||||
import os
|
||||
import thread_manager
|
||||
|
||||
from path import Path
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from page import Page
|
||||
from category import Category
|
||||
|
||||
class Picture():
|
||||
def __init__(self, picture_path: Path, page = None, raw: Path|None = None):
|
||||
self.large: Path = picture_path
|
||||
self.small: Path = Path(picture_path.get_dirpath(), picture_path.get_name()[:-4] + "_small.jpg")
|
||||
self.export_file: Path = Path(picture_path, ".out.pp3")
|
||||
self.raw: Path|None = raw
|
||||
self.page: Page = page
|
||||
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")
|
||||
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.id: int = id
|
||||
self._page: Page = page
|
||||
self._categories_name: list[str] = []
|
||||
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()
|
||||
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.get_small()
|
||||
self.get_thumb()
|
||||
|
||||
def render(self) -> tuple[str, str, str]:
|
||||
if not self.small.exist():
|
||||
def get_page(self):
|
||||
return self._page
|
||||
|
||||
def get_categories_name(self):
|
||||
return self._categories_name
|
||||
|
||||
def get_small(self):
|
||||
if not self._small.exist():
|
||||
self.gen_small()
|
||||
return self.large.get_name(), self.small.get_name(), self.export_file.get_name() if self.export_file.exist() else None
|
||||
return self._small
|
||||
|
||||
def get_thumb(self):
|
||||
if not self._thumb.exist():
|
||||
self.gen_thumb()
|
||||
return self._thumb
|
||||
|
||||
def get_large(self):
|
||||
return self._large
|
||||
|
||||
def get_profile_file(self):
|
||||
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):
|
||||
im = Image.open(self.large.get_absolute_path()).convert("RGB")
|
||||
im.save(self.small.get_absolute_path(), quality=95, optimize=True)
|
||||
def _(_large: Path, _small: Path):
|
||||
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))
|
||||
|
||||
|
43
src/templates/category.css
Normal file
43
src/templates/category.css
Normal file
@ -0,0 +1,43 @@
|
||||
:root {
|
||||
--bg1: #002b36;
|
||||
--bg2: #073642;
|
||||
--content1: #586e75;
|
||||
--content2: #657b83;
|
||||
--content3: #839496;
|
||||
--content4: #93a1a1;
|
||||
--lbg1: #eee8d5;
|
||||
--lbg2: #fdf6e3;
|
||||
}
|
||||
|
||||
.picture-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
background-color: var(--bg2);
|
||||
width: auto;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
background-color: var(--bg1);
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: contain
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
background-color: var(--content1);
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
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>
|
@ -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);
|
||||
}
|
@ -1,37 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../page.css">
|
||||
</head>
|
||||
<body>
|
||||
{% if readme %}
|
||||
<div class="readme">
|
||||
<div class="readme-content">
|
||||
{{readme}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="/bulk/page.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navigation">
|
||||
{% if page.prev %}
|
||||
<a href="{{ page.prev.html.get_url() }}">Prev</a>
|
||||
{% endif %}
|
||||
{% for image in images %}
|
||||
<div class="image-container">
|
||||
<div class="image-container-element">
|
||||
<img src='{{ image[1] }}'>
|
||||
<a href="{{ image[0] }}">Large</a>
|
||||
{% if image|length > 2 %}
|
||||
<a class="export" href="{{ image[2] }}">export file</a>
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
{% if raw or exif %}
|
||||
<div class="download">
|
||||
<h1>Download</h1>
|
||||
{% if raw %}
|
||||
<a href="{{ raw }}">RAW</a>
|
||||
{% endif %}
|
||||
{% if exif %}
|
||||
<a href="{{ exif }}">Exif</a>
|
||||
{% endif %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</body>
|
||||
</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>
|
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)
|
@ -14,21 +14,44 @@ def argument_parsing():
|
||||
|
||||
return args.input_folder, args.output_folder
|
||||
|
||||
def get_copy_info(path: str):
|
||||
copy_info: dict[str, dict[str, str]] = {}
|
||||
if os.path.exists(path):
|
||||
with open(path, "r") as f:
|
||||
for line in f.read().splitlines():
|
||||
hash, og_name, new_name = line.split(" | ")
|
||||
copy_info.update({hash: {"og_name": og_name, "new_name": new_name}})
|
||||
return copy_info
|
||||
|
||||
def save_copy_info(path: str, copy_info: dict[str, dict[str, str]]):
|
||||
copy_info_str: str = "\n".join([f"{hash} | {copy_info[hash]['og_name']} | {copy_info[hash]['new_name']}" for hash in copy_info.keys()])
|
||||
with open(path, "w") as f:
|
||||
f.write(copy_info_str)
|
||||
|
||||
def main():
|
||||
input_folder, output_folder = argument_parsing()
|
||||
copy_path = os.path.join(output_folder, "ph_copy.txt")
|
||||
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 = [f for f in files if os.path.isfile(f)]
|
||||
with Bar("generating...", max=len(files)) as bar:
|
||||
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:
|
||||
for file in files:
|
||||
with open(file, 'rb') as f:
|
||||
image_data = f.read()
|
||||
file_extention: str = file.split(".")[-1]
|
||||
file_name: str = hashlib.sha256(image_data).hexdigest()
|
||||
path: str = os.path.join(output_folder, file_name)
|
||||
if not os.path.exists(path):
|
||||
hash: str = hashlib.sha256(image_data).hexdigest()
|
||||
|
||||
if copy_info.get(hash) is None:
|
||||
new_name: str = f"{len(copy_info):05d}"
|
||||
path: str = os.path.join(output_folder, new_name)
|
||||
|
||||
os.makedirs(path)
|
||||
shutil.copy2(file, os.path.join(path, file_name + "." + file_extention))
|
||||
shutil.copy2(file, os.path.join(path, new_name + "." + file_extention))
|
||||
|
||||
copy_info.update({hash: {"og_name": os.path.basename(file), "new_name": new_name}})
|
||||
bar.next()
|
||||
save_copy_info(copy_path, copy_info)
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
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