Blu‑ray Hybrid ISO Image Archival Script
posts Blu-Ray ISO9660 Joliet Rock Ridge Archival ISOWas trying to streamline the process of producing archival Blu-Ray. Created a bash script that would create a hybrid ISO9660+UDF disc image using genisoimage and dvdisaster.
Below is a script for create-archive-iso.sh
that you can use to read a folder like:
./2025-01-13_Projects_2020_-_2025/
And generate a hybrid iso image like
./2025-01-13_Projects_2020_-_2025.iso
Where the disc volume title is Projects 2020 - 2025
when this command is run:
./create_iso.sh ./2025-01-13_Projects_2020_-_2025/
In addition it will run dvdisaster -i "$DEST_IMAGE" -mRS03 -o image -c
which
will enhance the iso with extra error correction to make the disc more resiliant
against bitrot (If borked, you can still recover the image using dvdisaster).
#!/bin/bash
#
# Blu‑ray Hybrid Disc Image Archival Script
#
# Brian Khuu 2025
#
# This script creates a hybrid ISO image that combines UDF with ISO9660
# (including Rock Ridge and Joliet extensions). It uses ISO‑level 3 to allow
# files larger than 4GB, though note that ISO‑level 3 only removes the size
# limit—not the filename/path length limits. Rock Ridge (-R) preserves full
# POSIX attributes (and longer names up to 255 bytes), while Joliet (-J with
# -joliet-long) creates a secondary directory tree for Windows compatibility.
#
# It then augments the generated ISO with error correction data using dvdisaster.
#
# Usage: ./create_iso.sh <source_folder> [<destination_iso_image>]
# -----------------------------------------------------------
# Step 1: Check for required dependencies
# -----------------------------------------------------------
for cmd in genisoimage dvdisaster; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: $cmd is not installed. Please install it."
exit 1
fi
done
# -----------------------------------------------------------
# Step 2: Validate script arguments
# -----------------------------------------------------------
if [ "$#" -lt 1 ]; then
echo "Got $# args"
echo "Usage: $0 <source_folder> [<destination_iso_image>]"
exit 1
fi
# -----------------------------------------------------------
# Step 3: Derive Source Folder and Default Names
# -----------------------------------------------------------
SOURCE_FOLDER="$1"
# Remove trailing slash (if any) and extract just the folder name
DEFAULT_FOLDER_NAME=${SOURCE_FOLDER%/}
DEFAULT_FOLDER_NAME=${DEFAULT_FOLDER_NAME##*/}
# Generate a default volume label based on the folder name.
# The expected folder name format is something like:
# 2025-01-13_Projects_2020_-_2025
# The script strips off the date part and converts the remaining
# text to a title-cased label (with spaces instead of underscores).
# So the folder name example above would be interpreted as:
# Projects 2020 - 2025
DEST_LABEL=${DEFAULT_FOLDER_NAME#*-*-*_} #<< Exclude date to better fit into ISO9660 volume label
DEST_LABEL=$(echo "$DEST_LABEL" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g')
echo "SOURCE_FOLDER = $SOURCE_FOLDER"
echo "DEFAULT_FOLDER_NAME = $DEFAULT_FOLDER_NAME"
echo "DEST_LABEL = $DEST_LABEL"
# ISO9660 has a 32-character limit for the volume label.
MAX_VOLID_LEN=32
if [ ${#DEST_LABEL} -gt $MAX_VOLID_LEN ]; then
echo "Volume label is longer than $MAX_VOLID_LEN characters; '$DEST_LABEL'. Exiting..."
exit 1
fi
echo "Using volume label = $DEST_LABEL"
# Get the destination ISO image filename, defaulting to <folder_name>.iso if not provided
DEST_IMAGE=${2:-${DEFAULT_FOLDER_NAME}.iso}
echo "DEST_IMAGE = $DEST_IMAGE"
# -----------------------------------------------------------
# Step 4: Create the Hybrid ISO Image using genisoimage
# -----------------------------------------------------------
echo "Creating hybrid ISO image..."
# Breakdown of key options:
# -udf : Include UDF support (useful for DVD/BD formats)
# -R : Enable Rock Ridge extensions for POSIX attributes and long filenames (up to 255 bytes)
# -J -joliet-long : Create an additional Joliet tree for Windows with extended filename support (up to 64/103 characters)
# -allow-lowercase : Retain lowercase letters in filenames
# -allow-multidot : Allow filenames with multiple dots
# -allow-limited-size : Prevent issues with files larger than 4GB in hybrid ISO/UDF images.
# -iso-level 3 : Use ISO9660 level 3 to remove the 4GB file size limit
# -V "$DEST_LABEL" : Set the volume label (must be 32 characters or less)
# -o "$DEST_IMAGE" : Specify the output file for the ISO image
# "$SOURCE_FOLDER" : Source directory to be included in the ISO image
genisoimage -udf -R -J -joliet-long -allow-lowercase -allow-multidot -allow-limited-size -iso-level 3 -V "$DEST_LABEL" -o "$DEST_IMAGE" "$SOURCE_FOLDER"
if [ $? -ne 0 ]; then
echo "Error: Failed to create ISO image with genisoimage."
exit 1
fi
echo "ISO image created at $DEST_IMAGE"
# -----------------------------------------------------------
# Step 5: Enhance the ISO Image with Error Correction using dvdisaster
# -----------------------------------------------------------
echo "Enhancing image with error correction using dvdisaster..."
# Breakdown of dvdisaster options:
# -i "$DEST_IMAGE" : Specify the input ISO image
# -mRS03 : Use error correction method RS03 (suitable for larger images)
# -o image : Specify that the output should be an augmented image (i.e. the ECC data is added to the ISO)
# -c : Create ECC (error correction) information
dvdisaster -i "$DEST_IMAGE" -mRS03 -o image -c
if [ $? -ne 0 ]; then
echo "Warning: Failed to add error correction."
else
echo "Protected ISO image created successfully."
fi
exit 0
Faulty Script For Creating A Pure UDF Disc Image
Initially I was aiming to create a script that would create a pure UDF iso (So can burn 4gb+ video etc...) to a bluray disc with extra protection via mkudffs and dvdisaster... however upon mounting Got 'wrong fs type, bad option, bad superblock on /dev/loop1, missing codepage or helper program, or other error.' mount error.
I suspect it failed because of linux kernel impocompatibility with mounting v2.60 UDF image as read/write (The kernel module only supports ready only for v2.50 and v2.60).
If you got suggestions on making this work or how the kernel module can be patched/developed to enable read/write... or a method to master a v2.50 and v2.60 UDF image, do get in touch with me! In the meantime, this is what I got at least...
#!/bin/bash
#
# Blu‑ray Archival Script
#
# Warning: Not working... got 'wrong fs type, bad option, bad superblock on /dev/loop1, missing codepage or helper program, or other error.' mount error
#
# This script creates a blank UDF image sized for Blu‑ray media,
# formats it using mkudffs, and optionally mounts it for copying files.
# It is intended for archival to Blu‑ray only.
#
# Usage: ./create_bluray_udf.sh <source_folder> [<image_name>]
# Check for required dependencies
for cmd in mkudffs dvdisaster sudo dd truncate; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: $cmd is not installed. Please install it."
exit 1
fi
done
# Check for correct number of arguments
if [ "$#" -lt 1 ]; then
echo "Got $# args"
echo "Usage: $0 <source_folder> [<image_name>]"
exit 1
fi
# Get Source Folder
SOURCE_FOLDER="$1"
# Derive default folder name from the source folder
DEFAULT_FOLDER_NAME=${SOURCE_FOLDER%/}
DEFAULT_FOLDER_NAME=${DEFAULT_FOLDER_NAME##*/}
# Generate a default disc title from the folder name
DEST_TITLE=$(echo "$DEFAULT_FOLDER_NAME" | sed 's/[^_]\+/\L\u&/g' | sed 's/_/ /g')
# Get destination image; if not specified, default to <foldername>.udf
DEST_IMAGE=${2:-${DEFAULT_FOLDER_NAME}.udf}
echo "SOURCE_FOLDER = $SOURCE_FOLDER"
echo "DEFAULT_FOLDER_NAME = $DEFAULT_FOLDER_NAME"
echo "DEST_TITLE = $DEST_TITLE"
echo "DEST_IMAGE = $DEST_IMAGE"
# mkudffs settings for Blu‑ray
MEDIA_TYPE=bdr # bdr – BD-R (Blu-ray Disc Recordable)
UDF_REV=2.60 # Use highest supported UDF version (Blu-ray requires UDF 2.50+)
echo "MEDIA_TYPE = $MEDIA_TYPE"
echo "UDF_REV = $UDF_REV"
# Calculate the size needed (in bytes) for the source folder and add 10% overhead
RAW_SIZE=$(du -sb "$SOURCE_FOLDER" | cut -f1)
OVERHEAD=$(echo "$RAW_SIZE * 0.10" | bc -l | cut -d. -f1)
TOTAL_SIZE=$(echo "$RAW_SIZE + $OVERHEAD" | bc)
echo "Source folder size: $RAW_SIZE bytes"
echo "Caculate 10% UDF metadata overhead: $OVERHEAD bytes"
echo "Allocating image size (with overhead): $TOTAL_SIZE bytes"
# Create a blank file of the calculated size
echo "Creating blank image file..."
truncate -s "$TOTAL_SIZE" "$DEST_IMAGE"
if [ $? -ne 0 ]; then
echo "Error: Failed to create blank image file."
exit 1
fi
# Format the blank image as a UDF filesystem using mkudffs
echo "Formatting image as UDF..."
mkudffs --media-type=$MEDIA_TYPE --udfrev=$UDF_REV --label="$DEST_TITLE" "$DEST_IMAGE"
if [ $? -ne 0 ]; then
echo "Error: Failed to format the image with mkudffs."
exit 1
fi
# Create a temporary mount point and mount the image
MOUNT_POINT=$(mktemp -d)
echo "Mounting image at $MOUNT_POINT..."
sudo mount -t udf -o loop,rw "$DEST_IMAGE" "$MOUNT_POINT"
if [ $? -ne 0 ]; then
echo "Error: Failed to mount the image."
rmdir "$MOUNT_POINT"
rm "$DEST_IMAGE"
exit 1
fi
# Copy the source files into the mounted image
echo "Copying files from $SOURCE_FOLDER to the UDF image..."
sudo cp -a "$SOURCE_FOLDER"/. "$MOUNT_POINT"
if [ $? -ne 0 ]; then
echo "Error: Failed to copy files."
sudo umount "$MOUNT_POINT"
rmdir "$MOUNT_POINT"
exit 1
fi
sync || echo "Warning: sync command failed"
# Unmount the image and clean up the temporary mount point
echo "Unmounting image..."
sudo umount "$MOUNT_POINT"
rmdir "$MOUNT_POINT"
echo "UDF image created at $DEST_IMAGE"
# Optional: Enhance the image with error correction using dvdisaster
echo "Enhancing image with error correction using dvdisaster..."
dvdisaster -i "$DEST_IMAGE" -mRS02 -n 15% -o image
if [ $? -ne 0 ]; then
echo "Warning: Failed to add error correction."
else
echo "Protected image created successfully."
fi
exit 0
A good comment about this problem is made by dlarge6510. Below is encoded as ROT13 so you are encouraged to read his original comment unless reddit takes it down somehow due to bitrot etc...
HQS vf n "qhzcfgre sver" ba nyy gur znva bcrengvat flfgrzf.
V bayl guvax vg jbexf cebcreyl ba SerrOFQ.
Ba jvaqbjf lbh pna'g rira sbezng n qvfp nf HQS ivn gur THV, lbh unir gb hfr gur pbzznaq yvar. Vg pna nyfb bayl or hfrq ba UQQ vs lbh cnegvgvba gur UQQ va n fcrpvsvp jnl. Jura qbvat fb, ZnpBF jvyy abg or noyr gb ernq vg.
Ba ZnpBF ntnva, HQS vf s*pxrq hc. Gb hfr vg ba n UQQ be synfu qevir lbh unir gb cnegvgvba gur qevir va n jnl gung znxrf vg ol qrsnhyg nyzbfg hahfnoyr ba jvaqbjf.
N svyrflfgrz qrfvtarq gb nyybj vagrepunatr bs svyrf orgjrra gur znva BF pbzcrgvgbef raqf hc hanoyr gb npghnyyl qb gung. Uzz, V jbaqre jul
Ubjrire, Yvahk orvat gur fhcrevbe BF vg vf ertneqvat svyrflfgrzf naq cnegvgvbavat pna ernq HQS jvgu cnegvgvbaf be ab cnegvgvbaf.
Vs lbh Tbbtyr nebhaq lbh'yy svaq n cebwrpg ba TvgUho jurer fbzrbar jebgr n fpevcg sbe nyy guerr BF' gung jvyy sbezng n synfu qevir va n jnl gung nyy 3 BF' jvyy npghnyyl or noyr gb hfr.
Nabgure jnl gung HQS unf yvgrenyyl orra fynzzrq ntnvafg gur jnyy vf vg vf bayl cbffvoyr gb ercnve n HQS svyrflfgrz ba... Jvaqbjf. Arvgure Yvahk abe ZnpBF unf na sfpx.
HQS nf n svyrflfgrz jnf nonaqbarq ba Jvaqbjf, Yvahk naq ZnpBF. Jvaqbjf naq ZnpBF obgu ungrq vg nf vg nyybjrq vagrepunatr bs svyrf orgjrra gurzfryirf. V zrna, ubj qner fhpu n svyrflfgrz rkvfg! Lbh arrq gb nonaqba Zvpebfbsg naq orpbzr n Znp hfre!
Abobql jnagrq gb unir HQS or gur havirefny svyrflfgrz. Vg jnf orsber vg'f gvzr. Bayl ba bcgvpny zrqvn qvq vg znantr gb npuvrir nal fbeg bs npprcgnapr.
Ba Yvahk, HQS unf orra nonaqbarq fvzcyl orpnhfr fbzr fbq arrq gb fgrc hc naq nqq jevgr fhccbeg gb gur xreary qeviref (be znxr n SHFR qevire). Ubjrire, nf HQS vf rssrpgviryl n qrnq svyrflfgrz gung'f bayl hfrq ba bcgvpny zrqvn vg frrzf crbcyr gung pna qb fhpu n guvat fvzcyl ner abg obgurevat.
V zrna, jr nyy unir urneq gur "jub gur s*PX hfrf bcgvpny zrqvn gurfr qnlf"? Unira'g jr.
HQS unf orra eryrtngrq gb n svyrflfgrz hfr ba OQ zrqvn gb ubyq UQ zbivrf. Gung'f vg'f ubzr va 2025 naq nf n gval unaqshy bs crbcyr npebff gur cynarg jnag gb znxr ubzrznqr UQ zbivr qvfpf gurer fvzcyl vf abobql vagrerfgrq va yrggvat nalbar qb gung ba Yvahk. Gehfg zr, V ybbxrq ng guvf 10 lrnef ntb. Nyy nal BF unf gb qb, juvpu Yvahk qbrf, vf gb cebivqr ernq bayl fhccbeg sbe nyy HQS irefvbaf.
Vs rabhtu crbcyr jnag jevgr fhccbeg sbe HQS 2.6 gura, jurer vf gur obhagl? Jurer vf gur Xvpxfgnegre? Nabgure BF V ybir; Evfp BF Bcra, unf arj qrirybczrag qbar ol hfref sebagvat hc gur pnfu gb uver n qrirybcre gb npghnyyl jevgr gur pbqr rgp.
Ohg abobql frrzf gb rira jnag gb tb gung sbe HQS. Gur pbqr nyernql rkvfgf va SerrOFQ naq pna rnfvyl or cbegrq.
Onfvpnyyl zngr, nf zhpu nf jr nyy jnag gb unir HQS, nf zhpu nf jr ernq nobhg gur cebzvfrf vg bssrerq gb n jbeyq bs vagragvbany vapbzcngvoyl, n jbeyq gung jnf nyernql hfvat Sng32 naq AGSF qeviref vyyrtnyyl, n jbeyq gung nonaqbarq bcgvpny zrqvn naq npprcgrq nyy gur pehq gung pnzr jvgu zbivat svyrf orgjrra flfgrzf jvgu Sng32 naq vg fvmr yvzvgf rgp fvzcyl orpnhfr gurl jnagrq gb hfr n synfu qevir.
Phr gur ivbyva 🎻 HQS vf ynlvat va n qvgpu, nonaqbarq nsgre orvat orng hc ol gur gjb znva ohyyvrf. Ur jnagrq gb svanyyl oevat crbcyr gbtrgure ohg gur ohyyvrf ehyrq gur cynltebhaq naq onpx gura gurl jnagrq gb fgnzc gung fbeg bs guvat bhg.
Vg'f n erny pelvat funzr.
Ohg vg vf cbffvoyr gb znfgre n HQS 2.6 vzntr. Lbh whfg arrq gb abg zbhag vg nf n ybbconpx naq gel gb jevgr vagb vg. Ubjrire, V qrpvqrq gung V qvqa'g arrq HQS ba zl OQ-Ef nf yvxr V fnvq vfb9660 yriry 3 qbrf vg nyy jvgu Ebpxevqtr naq Wbyvrg rkgrafvbaf. V'q bayl jnag HQS sbe OQ-ER jurer V jnag gb znavchyngr svyrf. Nf gung jnf vaperqvoyl fybbbbbj, V fvzcyl punatrq zl hfr pnfr.
V qb hfr gung fpevcg gb sbezng synfu qevirf nf HQS gb zbir ynetr svyrf orgjrra jvaqbjf naq Yvahk znpuvarf ng jbex.