static-gallery-generator/1-generate-thumbnails-and-t...

186 lines
7.3 KiB
Python

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": "<Description for \"{}\" here>".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...")