Compare commits

...

61 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
cc64d3012a work without css 2025-03-07 22:22:50 +01:00
86363d4c2f clean: update readme 2025-03-07 22:22:36 +01:00
0ee3a22b80 ignore output 2025-03-07 22:22:21 +01:00
30 changed files with 789 additions and 971 deletions

2
.gitignore vendored
View File

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

View File

@ -1,26 +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
- .nef: **(MANDATORY)**
- .pp3: (optionnal)
- .png: high resolution format (optionnal: can be generated by the .nef)
- .jpeg: low resolution format (optionnal: can be generated by the .png)
- desc.md: description markdown file (optionnal)
### Mandatory
- a .png
### File structure
```
img1/
├─ img1.nef
img2/
├─ img2.pp3
├─ img2.png
├─ img2.jpeg
├─ img2.nef
img3.nef
img4.png
img4.jpeg
img4.nef
```
### Optionnal
- raw {*.NEF}
- readme.md
- (png file).out.pp3
### Futur update
- add gimp profile file
- unrepertoried page
- add audio file on page

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

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dropdown Button</title>
<style>
/* Style du bouton */
.dropdown-btn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
font-size: 16px;
border-radius: 5px;
display: inline-block;
}
/* Cacher le radio (pour qu'il ne soit pas visible) */
.dropdown-radio {
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 le radio est sélectionné, afficher le texte */
.dropdown-radio:checked + .dropdown-btn + .dropdown-text {
display: block;
}
</style>
</head>
<body>
<!-- Radio caché qui contrôle l'affichage du texte -->
<input type="radio" id="dropdown" class="dropdown-radio" name="dropdown-group">
<!-- Bouton -->
<label for="dropdown" class="dropdown-btn">Afficher le texte</label>
<!-- Texte qui se déploie -->
<div class="dropdown-text">
Voici le texte qui se déploie quand on clique sur le bouton.
</div>
</body>
</html>

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,4 +0,0 @@
def display(executable: str):
print(f"{executable} {{Path of your pictures}} {{Path of the site}}")

View File

@ -1,14 +1,131 @@
import sys
import help
import PIL
import os
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
env = Environment(loader=FileSystemLoader('src/templates'))
home_template = env.get_template('home.jinja')
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 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 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 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():
if len(sys.argv) != 2:
help.display(sys.argv[0])
return
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 (__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

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<img src="./img1.je">
</body>
</html>

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>

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())