44import os
55import shutil
66import tempfile
7- import time
8- from datetime import datetime
7+ from datetime import datetime , timedelta
98from os .path import join as join_path
109from threading import Thread
1110
1211import requests
1312from slugify import slugify
1413
14+ albums_web_path = "images/albums"
1515albums_path = "content/images/albums"
1616cover_path = "album_covers"
1717albums_url = "https://www.mainframe.io/media/album-images"
@@ -26,7 +26,14 @@ def join_folder_path(path_components: list[str]) -> str:
2626 return os .path .sep .join (slugged_path_components )
2727
2828
29- def join_web_path (path_components : list [str ]) -> str :
29+ def join_web_path (path_components : list [str ], use_slug : bool = False ) -> str :
30+ if use_slug :
31+ slugged = []
32+ for component in path_components :
33+ slugged .append (slugify (component ))
34+
35+ path_components = slugged
36+
3037 return '/' .join (path_components )
3138
3239
@@ -70,7 +77,7 @@ def create_index_md(path_components: list[str], tmp_folder_albums: str, tmp_fold
7077 f .write ('title = "' + title + '"\n ' )
7178 f .write ('template = "album/album-list.html"\n ' )
7279 f .write ('sort_by = "date"\n ' )
73- f .write ('weight = ' + str (int (time . mktime ( created_at .timetuple ()) )) + '\n ' )
80+ f .write ('weight = ' + str (int (created_at .timestamp () * 1000 )) + '\n ' )
7481
7582 f .write ('[extra]\n ' )
7683 f .write ('display_name = "' + title + '"\n ' )
@@ -84,18 +91,22 @@ def create_index_md(path_components: list[str], tmp_folder_albums: str, tmp_fold
8491 f .close ()
8592
8693
94+ def generate_image_filename (path_components : list [str ], lang : str , index : int ) -> str :
95+ filename = f"img{ index :03d} " + lang + ".md"
96+ path = join_path (join_folder_path (path_components ), filename )
97+
98+ return path
99+
100+
87101def create_image_md (path_components : list [str ], tmp_folder_albums : str , metadata : dict , created_at : datetime ,
88- lang : str ) -> None :
102+ lang : str , index : int , image_count : int ) -> None :
89103 if lang != "" :
90104 lang = "." + lang
91105
92106 base_uri = join_web_path (path_components )
93107
94- date_str = created_at .strftime ("%Y-%m-%d" )
95- time_str = created_at .strftime ("%H:%M:%S" )
96-
97- page_filename = date_str + "-" + slugify (metadata ["filename" ]) + lang + ".md"
98- image_file_path = join_path (tmp_folder_albums , join_folder_path (path_components ), page_filename )
108+ filename = generate_image_filename (path_components , lang , index )
109+ image_file_path = join_path (tmp_folder_albums , filename )
99110
100111 if os .path .exists (image_file_path ):
101112 raise FileExistsError (image_file_path )
@@ -105,56 +116,129 @@ def create_image_md(path_components: list[str], tmp_folder_albums: str, metadata
105116 image_file .write ("+++\n " )
106117 image_file .write ('title = "' + metadata ["filename" ] + '"\n ' )
107118 image_file .write ('template = "album/album-single.html"\n ' )
108- image_file .write ('sort_by = "title"\n ' )
109- image_file .write ('date = ' + date_str + "T" + time_str + '\n ' )
119+ image_file .write ('date = "' + created_at .strftime ("%Y-%m-%dT%H:%M:%S" ) + '"\n ' )
120+
121+ # add old path as alias
122+ alias = "/" .join ([albums_web_path , join_web_path (path_components , True ), slugify (metadata ["filename" ])])
123+ image_file .write ('aliases = ["/' + alias + '"]\n ' )
110124
111125 image_file .write ('[extra]\n ' )
112126 image_file .write ('filename = "' + metadata ["filename" ] + '"\n ' )
113127 image_file .write ('height = ' + str (metadata ["height" ]) + "\n " )
114128 image_file .write ('width = ' + str (metadata ["width" ]) + "\n " )
115129 image_file .write ('file_uri = "' + albums_url + "/" + base_uri + "/" + metadata ["filename" ] + '"\n ' )
116- image_file .write ('file_uri_300 = "' + albums_url + "/" + base_uri + "/.thumbs/300-" + metadata ["filename" ] + '"\n ' )
117130
118131 # Albums do not have 750 pixel thumbnail, use 1200 instead
119- image_file .write ('file_uri_750 = "' + albums_url + "/" + base_uri + "/.thumbs/1200-" + metadata ["filename" ] + '"\n ' )
120- image_file .write (
121- 'file_uri_1200 = "' + albums_url + "/" + base_uri + "/.thumbs/1200-" + metadata ["filename" ] + '"\n ' )
132+ thumbs_base = albums_url + "/" + base_uri + "/.thumbs/"
133+ image_file .write ('file_uri_300 = "' + thumbs_base + "300-" + metadata ["filename" ] + '"\n ' )
134+ image_file .write ('file_uri_750 = "' + thumbs_base + "1200-" + metadata ["filename" ] + '"\n ' )
135+ image_file .write ('file_uri_1200 = "' + thumbs_base + "1200-" + metadata ["filename" ] + '"\n ' )
136+
137+ if index > 0 :
138+ prev_filename_web = generate_image_filename (path_components , lang , index - 1 ).replace (os .path .sep , "/" )
139+ image_file .write (
140+ 'previous = "/' + albums_web_path + "/" + prev_filename_web + '"\n ' )
141+
142+ if (index + 1 ) < image_count :
143+ next_filename_web = generate_image_filename (path_components , lang , index + 1 ).replace (os .path .sep , "/" )
144+ image_file .write (
145+ 'next = "/' + albums_web_path + "/" + next_filename_web + '"\n ' )
146+
122147 image_file .write ("+++\n " )
123148 image_file .close ()
124149
125150
151+ def normalize_images (images_metadata : list [dict ]) -> list [tuple [dict , datetime ]]:
152+ images = []
153+ current_date = None
154+
155+ for image_metadata in images_metadata :
156+ if image_metadata ["exif" ]["time" ] is not None :
157+ new_date = datetime .fromtimestamp (int (image_metadata ["exif" ]["time" ]) / 1000 )
158+
159+ if current_date is None or new_date > current_date :
160+ current_date = new_date
161+
162+ if current_date is None :
163+ image_created_at = datetime .fromtimestamp (0 )
164+ else :
165+ image_created_at = current_date
166+
167+ images .append ((image_metadata , image_created_at ))
168+
169+ images .sort (key = lambda x : (x [1 ], x [0 ]["filename" ]))
170+
171+ return images
172+
173+
174+ def normalize_folders (folder_metadata : list [dict ]) -> list [tuple [dict , datetime ]]:
175+ folders = []
176+
177+ for folder in folder_metadata :
178+ if folder ["time" ] is not None :
179+ new_date = datetime .fromtimestamp (folder ["time" ] / 1000 )
180+ else :
181+ print ("'" + folder ["title" ] + "' is missing a date" )
182+ new_date = datetime .fromtimestamp (0 )
183+
184+ folders .append ((folder , new_date ))
185+
186+ folders .sort (key = lambda x : (x [1 ], x [0 ]["foldername" ]))
187+
188+ # Ensure that each album has its own unique timestamp associated with it to allow unique stubs and
189+ # deterministic list outputs in zola as it does not support sorting by more than one property
190+ unique_folders = []
191+ last_timestamp = None
192+ last_folder = None
193+ for folder , ts in folders :
194+ if last_folder is None :
195+ last_folder = folder
196+ if last_timestamp is not None and ts <= last_timestamp :
197+ new_ts = last_timestamp + timedelta (hours = 1 )
198+ print ("Adjusting '" + folder ["title" ] + "' from '" + str (ts ) + "' (cause: '"
199+ + last_folder ["title" ] + "') to '" + str (new_ts ) + "'" )
200+ ts = new_ts
201+ else :
202+ last_folder = folder
203+
204+ unique_folders .append ((folder , ts ))
205+ last_timestamp = ts
206+ folder ["time" ] = ts
207+
208+ folders .sort (key = lambda x : (x [1 ], x [0 ]["foldername" ]))
209+
210+ return unique_folders
211+
212+
126213def create_album_folder (path_components : list [str ], tmp_folder_albums : str , tmp_folder_covers : str ,
127214 metadata : dict ) -> None :
128215 path_components = path_components .copy ()
129216 path_components .append (metadata ["foldername" ])
130217
131- if metadata ['time' ] is not None :
132- album_created_at = datetime .fromtimestamp (int (metadata ["time" ]) / 1000 )
133- else :
134- print ("Album has no creation date: " , path_components )
135- album_created_at = datetime .fromtimestamp (0 )
136-
137218 joined_path = join_folder_path (path_components )
138219
139220 album_folder = join_path (tmp_folder_albums , joined_path )
140221 os .makedirs (album_folder )
141222
142- create_index_md (path_components , tmp_folder_albums , tmp_folder_covers , metadata , album_created_at , "" )
223+ create_index_md (path_components , tmp_folder_albums , tmp_folder_covers , metadata , metadata [ "time" ] , "" )
143224
144225 current_metadata = get_url_metadata (path_components )
145- for image_metadata in current_metadata ["images" ]:
146- if image_metadata ['exif' ]['time' ] is not None :
147- image_created_at = datetime .fromtimestamp (int (image_metadata ['exif' ]['time' ]) / 1000 )
148- else :
149- image_created_at = datetime .fromtimestamp (0 )
150226
151- create_image_md (path_components , tmp_folder_albums , image_metadata , image_created_at , "" )
227+ images = normalize_images (current_metadata ["images" ])
228+
229+ for i , (image_metadata , image_created_at ) in enumerate (images ):
230+ create_image_md (path_components , tmp_folder_albums , image_metadata , image_created_at , "" , i , len (images ))
152231
153232 sub_threads = []
154233
155- for sub_directory in current_metadata ["subDirs" ]:
234+ sub_folders = normalize_folders (current_metadata ["subDirs" ])
235+
236+ for (sub_folder , timestamp ) in sub_folders :
237+ # Use the normalized timestamp for the subfolder so that album_created_at
238+ # is based on the adjusted, uniqueness-enforced value in recursive calls.
239+ sub_folder ["time" ] = timestamp
156240 sub_thread = Thread (target = create_album_folder ,
157- args = (path_components , tmp_folder_albums , tmp_folder_covers , sub_directory ))
241+ args = (path_components , tmp_folder_albums , tmp_folder_covers , sub_folder ))
158242 sub_thread .start ()
159243 sub_threads .append (sub_thread )
160244
@@ -168,9 +252,12 @@ def create_album_folder(path_components: list[str], tmp_folder_albums: str, tmp_
168252tmp_ktt_ol_cover = tempfile .mkdtemp (prefix = "ktt_ol_cover" )
169253
170254threads = []
171- for directory in base_metadata ["subDirs" ]:
255+
256+ base_sub_folders = normalize_folders (base_metadata ["subDirs" ])
257+
258+ for (base_sub_folder , timestamp ) in base_sub_folders :
172259 thread = Thread (target = create_album_folder ,
173- args = ([], tmp_ktt_ol_albums , tmp_ktt_ol_cover , directory )
260+ args = ([], tmp_ktt_ol_albums , tmp_ktt_ol_cover , base_sub_folder )
174261 )
175262 thread .start ()
176263 threads .append (thread )
0 commit comments