#!/bin/bash

# checksum.sh — Generate or verify checksums recursively
# Usage:
#   To generate checksums: ./checksum.sh generate
#   To verify: ./checksum.sh verify [--quiet | --no-quiet]

VERIFY_OUTPUT="verify.txt"
MODE=""
QUIET_MODE=true  # DEFAULT QUIET FOR VERIFY

# Parse all arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        generate)
            MODE="generate"
            shift
            ;;
        verify)
            MODE="verify"
            shift
            ;;
        --quiet)
            QUIET_MODE=true
            shift
            ;;
        --no-quiet)
            QUIET_MODE=false
            shift
            ;;
        --help|-h)
            MODE="help"
            shift
            break
            ;;
        *)
            echo "Unknown option: $1"
            exit 1
            ;;
    esac
done

show_help() {
    cat << EOF
checksum.sh — Generate or verify checksums recursively for media files

USAGE:
  ./checksum.sh generate
  ./checksum.sh verify [--quiet | --no-quiet]
  ./checksum.sh --help

COMMANDS:

generate
  Scans current directory and subdirectories for files matching:
  - Images: *.jpeg, *.jpg, *.png
  - Videos: *.mp4, *.mkv, *.avi, *.webm
  Excludes hidden files/directories (starting with .).
  Creates individual .txt checksum files next to each media file:
  - For filename.ext, creates filename.txt (strips the media extension)
  - Contains cksum and sha256 hashes

verify
  Scans current directory and subdirectories for media files, checks for
  matching .txt checksum files (filename.txt), and verifies hashes.
  DEFAULT: --quiet (no console output, saves to verify.txt only)
  --quiet:  Suppress console output (default)
  --no-quiet: Show full console output with colors
  Shows: match status, file paths, partial hashes, file sizes, summary stats.

NOTES:
- Recursive search from current directory.
- Handles Linux/macOS stat differences.
- Color-coded console output when --no-quiet (green ✓ verified, red ✗ failed).

EOF
}

# Find files recursively (excluding RAR files)
find_files() {
    find . -type f \(        -name "*.jpeg" -o        -name "*.jpg" -o        -name "*.png" -o        -name "*.mp4" -o        -name "*.mkv" -o        -name "*.avi" -o        -name "*.webm"    \) ! -path "*/\.*" | sort
}

# Strip media extension and add .txt
get_checksum_filename() {
    local file="$1"
    # Remove the media extension (.jpeg, .jpg, .png, .mp4, .mkv, .avi, .webm)
    echo "${file%.*}.txt"
}

generate_checksums() {
    echo "--- Generating checksums for JPEG and video files (recursive) ---"
    echo "Creating individual .txt checksum files next to each media file"
    echo "Checksum files will be named: filename.txt (original extension stripped)"

    files_found=0
    files_processed=0

    while IFS= read -r file; do
        ((files_found++))
        clean_path="${file#./}"
        checksum_file=$(get_checksum_filename "$file")

        # Create checksum file with both cksum and sha256
        {
            echo "# cksum hash (format: cksum_value inode_size)"
            cksum "$file" 2>/dev/null | awk '{print $1 " " $2}'
            echo "# sha256 hash (format: sha256_hash)"
            sha256sum "$file" 2>/dev/null | awk '{print $1}'
        } > "$checksum_file"

        if [ $? -eq 0 ]; then
            ((files_processed++))
            echo "✓ Created: ${checksum_file#./}"
        else
            echo "✗ Failed: ${checksum_file#./}"
        fi
    done < <(find_files)

    echo ""
    echo "----------------------------------------"
    echo "Checksum generation complete."
    echo "Files found: $files_found"
    echo "Checksum files created: $files_processed"
}

verify_files() {
    # Initialize verify.txt
    > "$VERIFY_OUTPUT"
    echo "Verification Report" >> "$VERIFY_OUTPUT"
    echo "===================" >> "$VERIFY_OUTPUT"
    echo "Timestamp: $(date)" >> "$VERIFY_OUTPUT"
    echo "" >> "$VERIFY_OUTPUT"

    # Console header only if not quiet
    if [ "$QUIET_MODE" = false ]; then
        echo "--- Starting Smart Verification (Recursive) ---"
        echo "Verifying each media file against its .txt checksum file"
        echo "Looking for: filename.txt (media extension stripped)"
        echo "------------------------------------------------------"
        echo "Verification Report"
        echo "===================" 
        echo "Timestamp: $(date)"
        echo ""
    fi

    files=()
    while IFS= read -r file; do
        files+=("$file")
    done < <(find_files)

    verified_count=0
    failed_count=0
    missing_checksum=0
    failed_files=()
    missing_checksum_files=()

    if [ ${#files[@]} -eq 0 ]; then
        msg="No image or video files found in this directory or subdirectories."
        echo "$msg" >> "$VERIFY_OUTPUT"
        if [ "$QUIET_MODE" = false ]; then echo "$msg"; fi
    else
        for file in "${files[@]}"; do
            [ -f "$file" ] || continue

            display_path="${file#./}"
            checksum_file=$(get_checksum_filename "$file")

            # Check if checksum file exists
            if [ ! -f "$checksum_file" ]; then
                plain_msg="⚠ $display_path - No checksum file found (expected: ${checksum_file#./})"
                echo "$plain_msg" >> "$VERIFY_OUTPUT"
                if [ "$QUIET_MODE" = false ]; then
                    echo -e "\033[33m$plain_msg\033[0m"
                fi
                ((missing_checksum++))
                missing_checksum_files+=("$display_path")
                continue
            fi

            # Calculate current hashes
            current_sha=$(sha256sum "$file" | awk '{print $1}')
            current_ck=$(cksum "$file" | awk '{print $1}')
            size=$(stat -c %s "$file" 2>/dev/null || stat -f %z "$file" 2>/dev/null)

            # Extract stored hashes from checksum file
            stored_sha=$(grep -v "^#" "$checksum_file" | grep -E "^[a-f0-9]{64}$" | head -1)
            stored_ck=$(grep -v "^#" "$checksum_file" | grep -E "^[0-9]+ [0-9]+$" | head -1 | awk '{print $1}')

            # Verify
            sha_match=0
            ck_match=0

            if [ -n "$stored_sha" ] && [ "$current_sha" = "$stored_sha" ]; then
                sha_match=1
            fi

            if [ -n "$stored_ck" ] && [ "$current_ck" = "$stored_ck" ]; then
                ck_match=1
            fi

            short_sha=$(echo "$current_sha" | cut -c1-8)
            short_ck=$(echo "$current_ck" | cut -c1-8)

            if [ $sha_match -eq 1 ] || [ $ck_match -eq 1 ]; then
                # Verified (at least one hash matches)
                match_type=""
                if [ $sha_match -eq 1 ] && [ $ck_match -eq 1 ]; then
                    match_type="(both hashes match)"
                elif [ $sha_match -eq 1 ]; then
                    match_type="(SHA256 matches)"
                else
                    match_type="(CKSUM matches)"
                fi

                plain_msg="✓ $display_path - VERIFIED $match_type (SHA: $short_sha, Size: $size bytes)"
                echo "$plain_msg" >> "$VERIFY_OUTPUT"
                if [ "$QUIET_MODE" = false ]; then
                    echo -e "\033[32m$plain_msg\033[0m"
                fi
                ((verified_count++))
            else
                # Failed verification
                plain_msg="✗ $display_path - VERIFICATION FAILED"
                echo "$plain_msg" >> "$VERIFY_OUTPUT"
                echo "  Expected SHA: $stored_sha" >> "$VERIFY_OUTPUT"
                echo "  Current SHA: $current_sha (partial: $short_sha)" >> "$VERIFY_OUTPUT"
                echo "  Expected CKSUM: $stored_ck" >> "$VERIFY_OUTPUT"
                echo "  Current CKSUM: $current_ck (partial: $short_ck)" >> "$VERIFY_OUTPUT"

                if [ "$QUIET_MODE" = false ]; then
                    echo -e "\033[31m$plain_msg\033[0m"
                    echo "  Expected SHA: $stored_sha"
                    echo "  Current SHA: $current_sha (partial: $short_sha)"
                    echo "  Expected CKSUM: $stored_ck"
                    echo "  Current CKSUM: $current_ck (partial: $short_ck)"
                fi
                ((failed_count++))
                failed_files+=("$display_path")
            fi
        done
    fi

    # Summary to file (always)
    echo "------------------------------------------------------" >> "$VERIFY_OUTPUT"
    echo "Verification complete." >> "$VERIFY_OUTPUT"
    echo "" >> "$VERIFY_OUTPUT"
    echo "SUMMARY:" >> "$VERIFY_OUTPUT"
    echo "========" >> "$VERIFY_OUTPUT"
    echo "Total files scanned: ${#files[@]}" >> "$VERIFY_OUTPUT"
    echo "✓ Verified files: $verified_count" >> "$VERIFY_OUTPUT"
    echo "✗ Failed files: $failed_count" >> "$VERIFY_OUTPUT"
    echo "⚠ Missing checksum files: $missing_checksum" >> "$VERIFY_OUTPUT"

    if [ $failed_count -gt 0 ]; then
        echo "" >> "$VERIFY_OUTPUT"
        echo "Failed files:" >> "$VERIFY_OUTPUT"
        for failed in "${failed_files[@]}"; do
            echo "  - $failed" >> "$VERIFY_OUTPUT"
        done
    fi

    if [ $missing_checksum -gt 0 ]; then
        echo "" >> "$VERIFY_OUTPUT"
        echo "Files missing checksum files:" >> "$VERIFY_OUTPUT"
        for missing in "${missing_checksum_files[@]}"; do
            checksum_file=$(get_checksum_filename "$missing")
            echo "  - $missing (expected: ${checksum_file#./})" >> "$VERIFY_OUTPUT"
        done
    fi

    echo "" >> "$VERIFY_OUTPUT"
    echo "Detailed report saved to: $VERIFY_OUTPUT" >> "$VERIFY_OUTPUT"

    # Final console message
    if [ "$QUIET_MODE" = false ]; then
        echo "------------------------------------------------------"
        echo "Verification complete."
        echo ""
        echo "SUMMARY:"
        echo "========"
        echo "Total files scanned: ${#files[@]}"
        echo "✓ Verified files: $verified_count"
        echo "✗ Failed files: $failed_count"
        echo "⚠ Missing checksum files: $missing_checksum"
        if [ $failed_count -gt 0 ]; then
            echo ""
            echo "Failed files:"
            for failed in "${failed_files[@]}"; do
                echo "  - $failed"
            done
        fi
        if [ $missing_checksum -gt 0 ]; then
            echo ""
            echo "Files missing checksum files:"
            for missing in "${missing_checksum_files[@]}"; do
                checksum_file=$(get_checksum_filename "$missing")
                echo "  - $missing (expected: ${checksum_file#./})"
            done
        fi
        echo ""
        echo "Detailed report saved to: $VERIFY_OUTPUT"
    else
        echo "Verification complete (quiet mode). Check $VERIFY_OUTPUT for details."
    fi
}

case "$MODE" in
    generate)
        generate_checksums
        ;;
    verify)
        verify_files
        ;;
    help|--help|-h)
        show_help
        ;;
    "")
        echo "Usage:"
        echo "  ./checksum.sh generate"
        echo "  ./checksum.sh verify [--quiet|--no-quiet]"
        echo "  ./checksum.sh --help"
        exit 1
        ;;
esac
Edit

Pub: 30 Mar 2026 16:26 UTC

Views: 7