import os, csv from datetime import datetime from pathlib import Path from ffmpeg import FFmpeg, Progress from lib import GalleryTable TABLE_PATH = Path(__file__, "..", "gallery-items.csv").resolve() MEDIA_FOLDER = Path(__file__, "..", "gallery", "media").resolve() THUMBNAIL_FOLDER = Path(__file__, "..", "gallery", "thumbs").resolve() THUMBNAIL_SUFFIX = "-thumb.webp" THUMBNAIL_MAX_FRAMES = 0 # No limit. Feel free to adjust this if you're hosting long animations on there or something. FILE_BLACKLIST = [ # Dot files (e.g. .gitkeep, .gitignore, .DS_Store) are ignored in general. # Windows does not do us the courtesy of having all hidden files start with a period, # so I'm filtering some of the common ones out "by hand" here. # It's possible that I'm missing a couple. # If your gallery-items.csv winds up with entries for any files that you # don't want in your gallery, please feel free to expand this list by hand. "Thumbs.db", # Windows Thumbnail Cache "Desktop.ini" # Windows Folder Settings ] def generate_and_register_thumbnail(file_path, thumbnail_path): try: generate_thumbnail(file_path, thumbnail_path) table[file_name]["Thumbnail"] = thumbnail_path.name print("\n✅ Generated thumbnail successfully: {}".format(thumbnail_path.name)) except Exception as error: print("\n⚠️ Error while generating thumbnail for {}:".format(file_path.name)) print("{error_name}: {error}".format(error_name=type(error).__name__, error=error)) print("Continuing on...") def generate_thumbnail(file_path, thumbnail_path): ffmpeg = ( FFmpeg() .option("n") # Do not overwrite existing files (we already checked for them earlier, so this is just to be sure) .input(file_path) # Input file .output( thumbnail_path, # Output file vf="scale=300:300:force_original_aspect_ratio=increase", # Resize smallest side to 300px while preserving aspect ratio vcodec="webp", # Webp files are TINY and load super fast. Incredible file format. lossless=0, quality=90, # Basically undetectable in a thumbnail, even for animations. compression_level=6, # Highest compression level, takes a little longer to process loop=0 # Infinite loop (e.g. for thumbnails of animated gifs) ) ) @ffmpeg.on("start") # Log ffmpeg arguments for thumbnail generation at the start def on_start(arguments: list[str]): print("Running FFmpeg with arguments:\n", arguments, "\n") @ffmpeg.on("progress") # Log thumbnail generation progress as it happens def on_progress(progress: Progress): print("Progress:", progress) if THUMBNAIL_MAX_FRAMES > 0 and progress.frame > THUMBNAIL_MAX_FRAMES: ffmpeg.terminate() ffmpeg.execute() # Go!!! def prompt_confirmation(prompt_message): answer = input(prompt_message + "\n") return answer.lower() in ["y", "yes"] # Accept "y", "Y", "yes", "Yes" etc. def print_horizontal_separator(): print("--------------") def is_file_blacklisted(file_name): if file_name.startswith("."): return True # Hidden files on Linux and Mac elif file_name in FILE_BLACKLIST: return True # Specifically blacklisted else: return False # Fine if __name__ == "__main__": # Only do this when the script is run directly print("Generating thumbnails...") # Load existing table, if one does exists: if TABLE_PATH.is_file(): table = GalleryTable.parse_table(TABLE_PATH) else: table = {} # Generate thumbnails and table information # for all images discovered in the folder: for file_name in os.listdir(MEDIA_FOLDER): if is_file_blacklisted(file_name): continue print("Checking \"{}\"...".format(file_name)) file_path = Path(MEDIA_FOLDER, file_name).resolve() if file_name in table.keys(): # Mark the existing table entry as valid: table[file_name]["present"] = True else: # Make a new table entry for images that don't have one yet: table[file_name] = { "File": file_name, "Thumbnail": "", "Title": file_path.stem, "Description": "".format(file_name), "Tags (with commas in-between)": "Example Tag 1, Example Tag 2", "present": True } # Check whether a thumbnail already exists, # or has to be generated - there are a lot of # potential edge cases to address here: if not table[file_name]["Thumbnail"]: # There's no thumbnail for this image listed in the table yet... thumbnail_path = Path(THUMBNAIL_FOLDER, file_name + THUMBNAIL_SUFFIX).resolve() if thumbnail_path.is_file(): # ...but a thumbnail with the default name already exists: table[file_name]["Thumbnail"] = thumbnail_path.name # Write the existing thumbnail into the table print("✅ Thumbnail already exists (will be added to {}).".format(TABLE_PATH)) else: # ...and no thumbnail with the default name already exists: print("Thumbnail does not exist, generating...") generate_and_register_thumbnail(file_path, thumbnail_path) else: # There's a thumbnail for this image listed in the table already... thumbnail_path = Path(THUMBNAIL_FOLDER, table[file_name]["Thumbnail"]).resolve() if thumbnail_path.is_file(): # ...and it does actually exist inside the THUMBNAIL_FOLDER: print("✅ Thumbnail already exists.") else: # ...but it doesn't actually exist inside the THUMBNAIL_FOLDER: print("⚠️ Thumbnail {} is listed in {}, but was not found.".format(thumbnail_path, TABLE_PATH)) if prompt_confirmation("Generate a thumbnail with that name? [Y/n]"): print("Generating thumbnail...") generate_and_register_thumbnail(file_path, thumbnail_path) else: print("Ignoring missing thumbnail {}.".format(thumbnail_path)) print_horizontal_separator() # Identify images that were deleted from the folder, # but not from the table, and offer to remove them: orphaned_entries = {name: entry for name, entry in table.items() if not entry.get("present")} if orphaned_entries: print("Found {} gallery entries missing their source images:".format(len(orphaned_entries))) for name, entry in orphaned_entries.items(): print(name) if prompt_confirmation("Remove these orphaned entries from the gallery? [yes/no]"): print("Removing {} orphaned entries from gallery...".format(len(orphaned_entries))) for name, entry in orphaned_entries.items(): table.pop(name) else: print("Ignoring orphaned entries. They will remain in the gallery table for you to edit later.") print_horizontal_separator() # Back up the existing table and write out the new one: if TABLE_PATH.is_file(): print("Backing up the existing {}...".format(TABLE_PATH)) backup_suffix = "-backup-" + datetime.now().strftime('%Y-%m-%d_%H-%M-%S') os.rename(TABLE_PATH, TABLE_PATH.with_stem(TABLE_PATH.stem + backup_suffix)) print("Writing out new table...") try: GalleryTable.write_table(TABLE_PATH, table) print("Done all done!") print("Edit {} to add titles, descriptions and tags.".format(TABLE_PATH)) print("When finished, run the other script!") except Exception as error: print("Error writing table:") print("{error_name}: {error}".format(error_name=type(error).__name__, error=error)) input("Press Enter key to exit...")