Make a single gallery from nested folders

:placard: Summary Make a single uncompressed zip file from all images in a folder and its subfolders, without also including videos.
:link: Repository https://github.com/jjclark1982/scripts/blob/main/make-cbz

I often see a single photo/art gallery with a nested folder structure that Stash treats as multiple galleries.

For example, Stash would look at a structure like this and make galleries of the 6 innermost folders that have pictures:

Cosplayer/
    March 2022/
        Gwen Stacy/
            Cosplay Photoset/
            Boudoir Photoset/
            Backstage/
            Videos/
        Marin Kitagawa/
            Cosplay Photoset/
            Boudoir Photoset/
            Backstage/
            Videos/

The innermost folder structure is important, but it is not the most natural grouping, and causes related items to get separated in the app.

If you create a zip file of each related gallery, it will preserve the file structure within that zip file and Stash will treat it as a single gallery. But you don’t want to include videos in the zip file as they will not be seen by Stash or CBZ viewers.

        Gwen Stacy/
            Gwen Stacy.cbz
            Videos/
        Marin Kitagawa/
            Marin Kitagawa.cbz
            Videos/

Here is a script to make a single uncompressed zip file from all images in a folder and its subfolders, without also including videos.

Repository on GitHub: scripts/make-cbz at main Β· jjclark1982/scripts Β· GitHub

Updated August 13 to run image identification and zip creation in parallel.

#!/bin/sh -uo pipefail

# make-cbz.sh
# Create a .CBZ file of all the images in a directory.
# Leave non-image files in place.
#
# Example:
# > make-cbz images/
# ...
# Created images.cbz
#
# Example: zip all subdirectories individually
# > make-cbz */
# ...
# Created a.cbz
# Created b.cbz
# Created c.cbz
#
# Example: use a different extension (default .cbz)
# > EXTENSION=zip make-cbz images/
# ...
# Created images.zip


EXTENSION=".${EXTENSION:-cbz}"

# Function to identify image files based on content
function list_images() {
	find "$1" \
		| sort --version-sort \
		| filter_images
}

function filter_images() {
    local filename
    while read -r filename; do
        [ -f "$filename" ] || continue # must be a regular file
    	case "$(file "$filename")" in
    		*"image"* |\
			*"Image"* |\
			*"Picture"* )
				echo "$filename"
				;;
		esac
    done
}

function zip_images_in_dir() { (
  	# Run in a subshell that will exit on the first error
  	set -e

	DIR="$1"
	if [ ! -d "$DIR" ]; then
		echo Error: "$DIR" is not a directory. Skipped.
		return 1
	fi

	# Work relative to parent folder of DIR, to standardize paths within zip files.
	cd "$DIR"
	DIR="$(basename "$PWD")"
	cd ..

	# Check whether this zipfile has already been created.
	ZIPFILE="${DIR%/}$EXTENSION"
	if [ -f "$ZIPFILE" ]; then
		echo Error: "$ZIPFILE" already exists. Skipped.
		return 2
	fi
	if [ -f "${DIR}/$(basename "$ZIPFILE")" ]; then
		echo "$ZIPFILE" already exists within "$DIR". Skipped.
		return 3
	fi
	echo Creating "$ZIPFILE"...

	# Simple command to zip only common image extensions
	# zip -0 --latest-time -m -r "$ZIPFILE" "$DIR" \
	# 	-i '*.jpeg' -i '*.jpg' -i '*.gif' -i '*.png' -i '*.svg' -i '*.webp' -i '*.heic' -i '*.avif' \
	# 	-i '*.JPEG' -i '*.JPG' -i '*.GIF' -i '*.PNG' -i '*.SVG' -i '*.WEBP' -i '*.HEIC' -i '*.AVIF'

	# if [ -z "$(list_images .)" ]; then
	# 	echo No image files found in "$DIR". Skipped.
	# 	return 4
	# fi

	# Zip identified images with no compression, and remove original files
	list_images "$DIR" | zip -0 --latest-time -m "$ZIPFILE" -@

	if [ ! -f "$ZIPFILE" ]; then
		echo Error: "$ZIPFILE" was not created.
		return 5
	fi
	echo Created "$ZIPFILE"

	# Remove empty folders.
	# (zip -m is supposed to handle this but find is more thorough)
	# TODO: check how this handles @eaDir
	find "$DIR" -name ".DS_Store" -delete
	find "$DIR" -type d -empty -delete

	# If main directory was not empty, move zipfile into it to stay with related files.
	if [ -d "$DIR" ]; then
		mv "$ZIPFILE" "$DIR"/
		echo Moved "$ZIPFILE" into "$DIR/"
	fi
	return 0
) }

# Main routine: loop over command-line arguments
for DIR; do
	echo
	zip_images_in_dir "$DIR"
done

2 Likes

Moved to more appropriate category.