Compare commits

...

58 Commits

Author SHA1 Message Date
a980dd8ead add: rename script 26 -> 00026 2025-06-15 00:31:54 +02:00
69023f1917 fix: protect touch 2025-05-22 17:53:05 +02:00
df05678ddc fix: sort file by name 2025-05-22 17:52:04 +02:00
bb29eb2b73 fix: do not recreate readme every time 2025-05-21 12:46:34 +02:00
c8f8698578 fix: raw_url 2025-05-06 23:16:37 +02:00
6236819e2b fix: rename general.html -> index.html 2025-05-06 23:14:58 +02:00
1a0391bafe fix: print 2025-05-06 23:13:02 +02:00
c7c2cf6409 add: loading bar on regen 2025-05-06 23:11:54 +02:00
26c60c5216 add: home page 2025-05-06 23:00:11 +02:00
6d1661131f clean: rename bulk 2025-05-06 22:54:18 +02:00
129dfe7df9 add: hashtag to album page to go directly on the picture you clicked 2025-05-06 22:42:22 +02:00
5fc12d5d33 fix: raw url 2025-05-06 22:12:53 +02:00
b895e5d85c add: albums link under pictures 2025-05-06 20:03:32 +02:00
b434d0837a add: regen parameter 2025-05-06 19:30:42 +02:00
b9f7090e96 add: center img without deform it 2025-05-06 19:24:56 +02:00
fe1f7d1485 add: thumb dimension is a global 2025-05-06 18:59:45 +02:00
2dd1fcfb0c add: bulk 2025-05-06 18:55:14 +02:00
8c2d5df0d2 fix: readme none 2025-05-04 19:17:43 +02:00
a551217592 fix: do not regen all small and thumb 2025-05-04 19:11:00 +02:00
5c773af571 copy use 0 padding 2025-05-04 18:35:56 +02:00
d0063c2cc4 core: gen thumb, small, readme for every image 2025-05-02 13:36:57 -04:00
08c0129af8 feat: use thumb on album page 2025-05-02 13:29:09 -04:00
de051f222d fix: copy only .NEF 2025-05-02 04:28:29 -04:00
ff56bac2cd clean: remove useless file 2025-05-01 14:53:44 -04:00
3f30154b36 add: album 2025-04-23 22:11:44 +02:00
b4a78a9e7d update readme 2025-04-23 22:11:27 +02:00
37808d6c48 fix: page linked list 2025-04-23 21:09:25 +02:00
70da0a2c28 add: path __str__ and __repr__ 2025-04-23 21:09:12 +02:00
bb0973917d recreate page 2025-04-23 20:49:58 +02:00
55bc1b36d1 add: docker nginx conf 2025-04-23 20:49:28 +02:00
73dfaf4622 add: copy give a name and not an hash 2025-04-20 13:26:55 +02:00
37a31494fc add: roadmap 2025-04-16 15:23:15 +02:00
7883f8ce6b recreate readme 2025-04-16 15:04:57 +02:00
24af283c38 clean: space-around 2025-04-16 14:35:42 +02:00
ca17bd2cab add: use path url 2025-04-16 14:05:48 +02:00
760bd86bf0 add: css and button to home page 2025-04-16 13:53:48 +02:00
97f05cee5e add: home page 2025-04-15 18:06:05 +02:00
d911dfe1cf fix: typo 2025-04-15 15:12:03 +02:00
9335107522 clean: remove: useless print 2025-04-15 14:56:26 +02:00
196ae57b2c core: simplify code 2025-04-15 14:41:06 +02:00
e27bfe74ea fix: render_prev and next 2025-04-15 13:00:21 +02:00
eb94330059 clean: format jinja 2025-04-15 12:42:21 +02:00
9a33ba64a2 clean: change a color 2025-04-15 12:34:06 +02:00
a3301572e1 fix: text 2025-04-15 00:34:01 +02:00
b73f374625 add: markdown in requirements.txt 2025-04-15 00:33:53 +02:00
d96eeb4bef add: page css 2025-04-14 20:08:03 +02:00
c92fc36b40 add: prev and next button template 2025-04-14 20:02:45 +02:00
a106770769 add: prev and next button 2025-04-14 20:02:05 +02:00
b5b306eb09 fix: do not remove raw if exiftool failed 2025-04-14 19:33:38 +02:00
8a123180c5 core: use class based code 2025-04-14 19:29:54 +02:00
c68f393703 add: gen html 2025-04-14 17:04:47 +02:00
637e2382b2 core: opti readme search 2025-04-14 13:41:33 +02:00
a4324c61d0 core: separate gen_pages and main 2025-04-14 13:37:15 +02:00
a5e03ae8d5 core: rename exif file exif.txt 2025-04-14 13:36:25 +02:00
97533dc6d8 opti: regen only updated folder 2025-04-14 13:24:08 +02:00
307c5c4d82 simplify bozocod 2025-04-13 22:32:49 +02:00
0b1e2479c1 add: copy tools 2025-04-13 19:17:07 +02:00
3831d57fe6 add: css 2024-12-01 14:31:35 +01:00
31 changed files with 777 additions and 1039 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
venv
*.pyc
output
test_out
test_in

View File

@ -1,20 +1,43 @@
# PhotoHUB
Just a simple python program to generate static site
## Screenshots
### Picture Page
![](./screenshots/page.png)
### Album Page
![](./screenshots/album.png)
## Installation
``` sh
python3 -m venv venv
python3 -m pip -r requirements.txt
```
## Usage
Move and rename file
```sh
python3 tools/copy.py %your raws% %site location%
```
Generate html page
``` 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)
### Mandatory
- a .png
### Format supported
```
RAW: .NEF
LARGE: .png
SMALL: jpeg
EDIT_DATA: .pp3
DESQ: .md
```
### Optionnal
- raw {*.NEF}
- readme.md
- (png file).out.pp3
### Futur update
- add gimp profile file
- unrepertoried page
- add audio file on page

View File

@ -1,9 +0,0 @@
{
"file_type_extensions": {
"raw": [".NEF"],
"large": [".png"],
"small": [".jpeg"],
"edits": [".pp3"],
"desq": [".md"]
}
}

44
config/default.conf Normal file
View 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;
#}
}

10
docker-compose.yml Normal file
View 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.

View File

@ -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;
}

View File

@ -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

View File

@ -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

10
requirements.txt Normal file
View File

@ -0,0 +1,10 @@
exif==1.6.1
imageio==2.37.0
Jinja2==3.1.6
Markdown==3.8
MarkupSafe==3.0.2
numpy==2.2.3
pillow==11.1.0
plum-py==0.8.7
progress==1.6
rawpy==0.24.0

BIN
screenshots/album.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

BIN
screenshots/page.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

32
src/bulk_album.py Normal file
View 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'))
album_template = env.get_template('bulk_album.jinja')
class BulkAlbum():
def __init__(self, name: str, albums_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(albums_path, f"{name}.html")
def add_picture(self, picture: Picture) -> None:
self._pictures.append(picture)
def _to_html(self) -> str|None:
html_rendered = album_template.render(album=self)
return html_rendered
def create(self) -> Path:
with open(self.path.get_absolute_path(), "w") as f:
f.write(self._to_html())

78
src/bulk_page.py Normal file
View File

@ -0,0 +1,78 @@
from __future__ import annotations
from jinja2 import Environment, FileSystemLoader
import markdown
import os
from path import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bulk_page import Page
from picture import Picture
env = Environment(loader=FileSystemLoader('src/templates'))
page_template = env.get_template('bulk_page.jinja')
class BulkPage():
def __init__(self, path: Path, name: str, pictures: list[Picture] = None, prev: Page|None = None, next: Page|None = None):
self.name: str = name
self._path: Path = path
self._pictures: list[Picture] = pictures or []
self._exif: Path = Path(self._path, "exif.txt")
self._readme: Path = Path(self._path, "readme.md")
self._raw: Path = Path(self._path, name + ".NEF")
self.html: Path = Path(self._path, "page.html")
self.prev: Page = prev
self.next: Page = next
if (not self._readme.exist()):
self._readme.touch()
def add_picture(self, picture: Picture) -> None:
self._pictures.append(picture)
def get_pictures(self) -> list[Picture]:
return self._pictures
def _to_html(self) -> str|None:
html_rendered = page_template.render(page=self)
return html_rendered
def create(self) -> Path:
with open(self.html.get_absolute_path(), "w") as f:
f.write(self._to_html())
def render_readme(self) -> str|None:
if not self._readme.exist():
return None
with open(self._readme.get_absolute_path(), 'r') as f:
text = f.read()
if len(text) == 0:
return None
html = markdown.markdown(text)
return html
def _gen_exif(self):
if self._raw.exist():
if os.system(f"exiftool {self._raw.get_absolute_path()} > {self._exif.get_absolute_path()} 2>/dev/null") == 0:
return 0
os.remove(self._exif.get_absolute_path())
return 1
def get_exif(self) -> Path | None:
if not self._exif.exist():
if (self._gen_exif()):
return None
return self._exif
def render_exif(self):
if not self.get_exif():
return None
with open(self._exif.get_absolute_path()) as f:
return f.read()
def get_raw(self):
return self._raw

4
src/config.py Normal file
View File

@ -0,0 +1,4 @@
CREATE_GENERAL_ALBUM: bool = True
THUMB_DIMENSION: tuple[int, int] = (200, 200)

View File

@ -1,5 +0,0 @@
LARGE = "*.png"
SMALL = "*.jpeg"
RAW = "*.nef"
EDIT = "*.pp3"
DESC = "*.md"

View File

@ -1,4 +0,0 @@
def display(executable: str):
print(f"{executable} {{Path of your pictures}} {{Path of the site}}")

View File

@ -1,103 +1,131 @@
import sys
import help
import rawpy
import imageio
import os
import shutil
import json
from PIL import Image
from progress.bar import Bar
from path import Path
from picture import Picture
from bulk_page import BulkPage
from bulk_album import BulkAlbum
import argparse
import config
from jinja2 import Environment, FileSystemLoader
import subprocess
with open('config.json', 'r') as file:
config: dict = json.load(file)
# Initialiser l'environnement avec un dossier de templates
env = Environment(loader=FileSystemLoader('src/templates'))
home_template = env.get_template('home.jinja')
template = env.get_template('index.html')
def argument_parsing():
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 get_files_recursive(directory):
file_paths = []
for root, dirs, files in os.walk(directory):
for file in files:
file_paths.append(os.path.join(root, file))
return file_paths
def scan_pages(folders: list[Path]) -> list[BulkPage]:
pages: list[BulkPage] = []
prev: BulkPage = None
with Bar("Scanning Pages...", max=len(folders)) as bar:
for folder in folders:
files: list[Path] = folder.get_files()
page: BulkPage = BulkPage(folder, folder.get_name(), prev=prev)
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
if len(page.get_pictures()) == 0:
bar.next()
continue
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[BulkPage]) -> None:
with Bar("Generating Pages...", max=len(pages)) as bar:
for page in pages:
page.create()
bar.next()
def scan_albums(pages: list[BulkPage], albums_path: Path) -> list[BulkAlbum]:
albums: dict[str, BulkAlbum] = {}
with Bar("Scanning pages...", max=len(pages)) as bar:
for page in pages:
for picture in page.get_pictures():
for album_name in picture.get_albums_name():
album: BulkAlbum | None = albums.get(album_name)
if (album is None):
album = BulkAlbum(album_name, albums_path)
albums.update({album_name: album})
picture.albums.append(album)
album.add_picture(picture)
bar.next()
return (albums.values())
def create_albums(albums: list[BulkAlbum]) -> None:
with Bar("Generating albums...", max=len(albums)) as bar:
for album in albums:
album.create()
bar.next()
def gen_bulk(bulk_path: Path):
album_path: Path = Path(bulk_path, "albums")
pages: list[BulkPage] = scan_pages(bulk_path.get_dirs())
albums: list[BulkAlbum] = scan_albums(pages, album_path)
if config.CREATE_GENERAL_ALBUM:
for album in albums:
if (album.name == "general"):
album.path = Path(bulk_path, "index.html")
Path("./src/templates/bulk_page.css").copy_to(Path(bulk_path, "bulk_page.css"))
create_pages(pages)
Path("./src/templates/bulk_album.css").copy_to(Path(bulk_path, "bulk_album.css"))
if (not album_path.exist()):
album_path.create()
create_albums(albums)
def move_files(files: list[str], input_path, output_path):
for key, type_file_extensions in config["file_type_extensions"].items():
for file in files:
for type_file_extension in type_file_extensions:
if file.endswith(type_file_extension):
path = file.replace(input_path, output_path)[:-len(type_file_extension)]
if not os.path.exists(path):
if (key not in ["raw", "large", "small"]):
continue
os.makedirs(path)
shutil.copy2(file, path)
def regen(bulk_path: Path, is_thumb: bool, is_small: bool):
pages: list[BulkPage] = scan_pages(bulk_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 nef_to_jpeg(path:str):
with rawpy.imread(path) as raw:
rgb = raw.postprocess()
imageio.imwrite(path.replace('.NEF', '.jpeg'), rgb)
def gen_home(site_path: Path) -> None:
home_path = Path(site_path, "index.html")
def png_to_jpeg(path: str):
im = Image.open(path)
rgb_im = im.convert('RGB')
rgb_im.save(path.replace('.nef', '.jpeg'))
with open(home_path.get_absolute_path(), "w") as f:
f.write(home_template.render())
def generate_missing_images(raws: list[str], large: list[str], small: list[str]):
if len(small) == 0:
if (len(large) == 0):
nef_to_jpeg(raws[0])
return
png_to_jpeg(large[0])
return
def get_exif(raws: list[str]):
if (len(raws) == 0):
return None
return str(subprocess.check_output(["exiftool", f"{raws[0]}"])).replace("\\n", "\n")
def generate_page(output_path: str):
for folder in os.listdir(output_path):
path = os.path.join(output_path, folder)
if (os.path.isdir(path)):
files: list[str] = [os.path.join(path, file) for file in os.listdir(path) if os.path.isfile(os.path.join(path, file))]
raws = [file for ext in config["file_type_extensions"]["raw"] for file in files if file.endswith(ext)]
larges = [file for ext in config["file_type_extensions"]["large"] for file in files if file.endswith(ext)]
smalls = [file for ext in config["file_type_extensions"]["small"] for file in files if file.endswith(ext)]
generate_missing_images(raws, larges, smalls)
exif = get_exif(raws)
raw = os.path.basename(raws[0]) if len(raws) > 0 else None
large = os.path.basename(larges[0]) if len(larges) > 0 else None
small = os.path.basename(smalls[0]) if len(smalls) > 0 else None
html_rendered = template.render(exif=exif, raw=raw, large=large, small=small)
with open(os.path.join(path, f"{folder}.html"), "w") as f:
f.write(html_rendered)
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)
gen_bulk(bulk_path)
gen_home(site_path)
if len(sys.argv) < 2:
help.display(sys.argv[0])
return
input_path = sys.argv[1]
output_path = "output/" if (len(sys.argv) == 2) else sys.argv[2] + "/"
files: list[str] = get_files_recursive(input_path)
move_files(files, input_path, output_path)
generate_page(output_path)
if (__name__ == "__main__"):
if __name__ == "__main__":
main()

74
src/path.py Normal file
View File

@ -0,0 +1,74 @@
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
def get_name(self):
if not self.exist():
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] = []
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)
return dirs
def get_files(self) -> list[Path]:
files: list[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
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})"

72
src/picture.py Normal file
View File

@ -0,0 +1,72 @@
from __future__ import annotations
from PIL import Image
import os
from path import Path
from typing import TYPE_CHECKING
import config
if TYPE_CHECKING:
from bulk_page import BulkPage
from bulk_album import Album
class Picture():
def __init__(self, picture_path: Path, id: int, page: BulkPage = 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._albums_file: Path = Path(picture_path.get_absolute_path()[:-4] + "_albums.txt")
self._raw: Path|None = raw
self.id: int = id
self._page: BulkPage = page
self._albums_name: list[str] = []
self.albums: list[Album] = []
if self._albums_file.exist():
with open(self._albums_file.get_absolute_path(), "r+") as f:
albums_name: list[str] = f.read()
if (len(albums_name)):
self._albums_name += albums_name.split("\n")
else:
self._albums_file.touch()
if (config.CREATE_GENERAL_ALBUM):
self._albums_name.append("general")
self._is_reperoried: bool = is_repertoried
self.get_small()
self.get_thumb()
def get_page(self):
return self._page
def get_albums_name(self):
return self._albums_name
def get_small(self):
if not self._small.exist():
self.gen_small()
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):
im = Image.open(self._large.get_absolute_path())
im.thumbnail(config.THUMB_DIMENSION)
im.save(self._thumb.get_absolute_path(), "JPEG")
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)

View 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;
}

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/bulk/bulk_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() }}#{{ picture.id }}">
<img src="{{ picture.get_thumb().get_url() }}">
</a>
{% endfor %}
</div>
</body>
</html>

View File

@ -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);
}

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/bulk/bulk_page.css">
</head>
<body>
<div class="navigation">
{% if page.prev %}
<a href="{{ page.prev.html.get_url() }}">Prev</a>
{% endif %}
{% if page.next %}
<a class="next" href="{{ page.next.html.get_url() }}">Next</a>
{% endif %}
</div>
{% if page.render_readme() %}
<div class="readme">
<details>
<summary>
README
</summary>
<div class="readme-content">
{{page.render_readme()}}
</div>
</details>
</div>
{% endif %}
<div class="picture-container">
{% for picture in page._pictures %}
<div class="picture-container-item" id="{{ picture.id}}">
<div class="picture">
<a href="{{ picture.get_small().get_url() }}">
<img src='{{ picture.get_small().get_url() }}'>
</a>
<div class="meta-picture">
<a href="{{ picture.get_large().get_url() }}">Large</a>
{% if picture._profile_file.exist() %}
<a class="profile" href="{{ picture.get_profile_file().get_url() }}">Raw therapee profile</a>
{% endif %}
</div>
</div>
<div class="albums_container">
<h1>Albums:</h1>
{% for album in picture.albums %}
<a href="{{ album.path.get_url() }}" class="album_item">{{ al }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% if page.get_raw().exist() %}
<div class="download">
<h1>Download</h1>
<a href="{{ page.get_raw().get_url() }}">RAW</a>
</div>
{% endif %}
{% if page.get_exif() %}
<div class="exif">
<details>
<summary>Exif</summary>
<pre>
{{ page.render_exif() }}
</pre>
</details>
</div>
{% endif %}
</body>
</html>

7
src/templates/home.jinja Normal file
View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<body>
<a href="/bulk/"><h1>Site WIP go to BULK</h1></a>
</body>
</html>

View File

@ -1,46 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="img1.css">
</head>
<body>
<img id="small" src="{{ small }}">\
{% if large or raw %}
<div id="download">
{% if large %}
<a id="large" href="{{ large }}">
<img src="../icons/file.png">
<p>large</p>
</a>
{% endif %}
{% if raw %}
<a id="raw" href="{{ raw }}">
<img src="../icons/file.png">
<p>raw</p>
</a>
{% endif %}
</div>
{% endif %}
{% if desc %}
<div id="description">
</div>
{% endif %}
{% if exif %}
<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>
{{exif}}
</code>
</pre>
</div>
</div>
{% endif %}
</body>
</html>

57
tools/copy.py Normal file
View File

@ -0,0 +1,57 @@
import uuid
import sys
import os
import argparse
import hashlib
import shutil
from progress.bar import Bar
def argument_parsing():
parser = argparse.ArgumentParser()
parser.add_argument("input_folder", help="the folder of your raws", type=str)
parser.add_argument("output_folder", help="the location of your website", type=str)
args = parser.parse_args()
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) 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]
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, 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
View 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())