this post was submitted on 17 Jun 2026
-40 points (10.0% liked)

Programming

27318 readers
308 users here now

Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!

Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.

Hope you enjoy the instance!

Rules

Rules

  • Follow the programming.dev instance rules
  • Keep content related to programming in some way
  • If you're posting long videos try to add in some form of tldr for those who don't want to watch videos

Wormhole

Follow the wormhole through a path of communities !webdev@programming.dev



founded 3 years ago
MODERATORS
 

EDIT - I had a typo in the title that may have set the wrong impression, fixed (along with a copying pasting error earlier, thank the devs we have edits here)

EDIT 2 - I don't think it's a bash vs dash thing after all, changing the shebang to bash didn't help

I am not a coder. I have been staring at a screen the past two weeks trying to "vibe code" for the first time with unpaid chat bots, for a seemingly simple project. But I still haven't even gotten a single version to the real-world testing stage, keep getting failures on more basic self-tests.

The project is supposed to convert files to QR code videos and back. Right now, it can complete a round-trip self test (which please bewarned is meaningless to real world results) in the Debian environment I developed it in. However, every chat bot I've consulted tells me this script should be so "portable" it would run on everything from Linux to BSD to termux, and instead it's failed on the first additional Linux distro I tried it on: devuan.

I spent most of this morning figuring out where the issue probably is: in the extract_bin_folder logic, devuan seems to lose the final payload chunk every time. As a result, the file hits EOF exactly one chunk early when reassembled. Oddly, the extract_bin_folder does get the "footer" QR code (which comes after the missing payload chunk) from the encoder's default looped videos. If the user calls -l 0 in the encoder to disable the loop, then extract_bin_folder will miss both the footer and the final payload chunk. (I don't think the footer chunk is being used for anything regardless, just noting the odd behavior).

I've spent the rest of the day trying to fix it, but I simply lack the knowledge/skills to understand or fix the behavior difference between my Debian environment and my Devuan environment. I guess something to do with bash vs dash, but my chat bots and I just aren't making progress. Any human help would be appreciated.

Let me reiterate this is not ready for release at all, it isn't even passing these basic tests, hence the version number 0.0.0. I'll also mention a lot of the comments and echo statements from the chat bot are stupidly inaccurate, I can't clean up the phrasing as fast as it generates slop. I would consider the comments and echo statements just as important to fix as the actual functionality before calling this "ready to use."

#!/bin/sh  

# SPDX-License-Identifier: CC0-1.0  
# Public Domain  

# SnowyQR 0.0.0 by Digital Assistant  
# 56 years after UNIX epoch  

# Instruction to the shell to exit if any command fails.  
set -e  

# Default configuration for cleanup and temporary workspace.  
DO_CLEANUP=true  
USER_WORK_DIR=""  

# Help menu explaining the various operational modes and flags.  
usage() {  
    echo "Usage:"  
    echo "  Encode input file to video"  
    echo "  $0 -f <input_file> <output_file.mkv>"  
    echo ""  
    echo "  Decode file(s) from video"  
    echo "  $0 -v <input_vid.mkv> <output_dir>"  
    echo ""  
    echo "  Encode file to QR code directory (for test/debug)"  
    echo "  $0 -f <input_file> <output_dir>"  
    echo ""  
    echo "  Compile QR code dir to video (for test/debug)"  
    echo "  $0 -d <input_dir> <output_file.mkv>"  
    echo ""  
    echo "  Extract .bin folder from video (for test/debug)"  
    echo "  $0 -b <input_vid.mkv> <output_dir>"  
    echo ""  
    echo "  Reassemble .bin folder to file(s) (for test/debug)"  
    echo "  $0 -b <input_dir> <output_dir>"  
    echo ""  
    echo "Options:"  
    echo "  -l N Video mode: loop N times (default: 1, plays twice). Set 0 to disable."  
    echo "  -c FG BG  Set custom Hex colors for Foreground and Background"  
    echo "            Defaults: 000000 6B6B6B"  
    echo "            Use 000000 FFFFFF for standard black and white."  
    echo "  -w DIR    Specify a permanent work directory. Skips cleanup and uses"  
    echo "            numbered subfolders inside DIR."  
    echo "            (For debugging and systems without mktemp)"  
    exit 1  
}  

#
***
Shared Utility Functions
***
 

# Verifies the presence of 'zint', required for barcode/QR generation.  
check_zint() {  
    if ! command -v zint >/dev/null 2>&1; then  
        echo "Error: can't find tool 'zint'" >&2  
        exit 1  
    fi  
}  

# Verifies 'ffmpeg' is installed for video encoding and frame extraction tasks.  
check_ffmpeg() {  
    if ! command -v ffmpeg >/dev/null 2>&1; then  
        echo "Error: 'ffmpeg' not found for video processing." >&2  
        exit 1  
    fi  
}  

# Verifies 'zbarimg' is installed for reading data from QR and barcodes.  
check_zbarimg() {  
    if ! command -v zbarimg >/dev/null 2>&1; then  
        echo "Error: 'zbarimg' (zbar-tools) not found for QR scanning." >&2  
        exit 1  
    fi  
}  

# Ensures a temporary directory creator is available if no custom path is provided.  
check_mktemp() {  
    if [ -z "$USER_WORK_DIR" ] && ! command -v mktemp >/dev/null 2>&1; then  
        echo "Error: 'mktemp' not found. This system cannot create temporary directories automatically." >&2  
        echo "Please install 'mktemp' or use the '-w <DIR>' option to specify a work directory." >&2  
        exit 1  
    fi  
}  

# Creates or identifies a working directory, respecting user-defined paths or using mktemp.  
get_work_dir() {  
    if [ -n "$USER_WORK_DIR" ]; then  
        _i=1  
        while [ -d "$USER_WORK_DIR/$_i" ]; do _i=$((_i + 1)); done  
        _dir="$USER_WORK_DIR/$_i"  
        mkdir -p "$_dir"  
        echo "$_dir"  
    else  
        mktemp -d || { echo "Error: Failed to create a temporary directory." >&2; exit 1; }  
    fi  
}  

#
***
Core Logic Sections
***
 

# Logic from encoder script  
# encode_qr: Splits a file into binary chunks and generates a sequenced folder of QR code PNGs.  
# Includes a metadata header (index 0) and a footer.  
encode_qr() {  
    infile="$1"  
    outdir="$2"  
    fg_color="$3"  
    bg_color="$4"  

    if [ ! -f "$infile" ]; then  
        echo "Error: '$infile' does not exist?" >&2  
        exit 1  
    fi  

    # Prepare the output folder and convert paths to absolute for reliability.  
    echo "Checking output directory permissions..."  
    mkdir -p "$outdir"  
    outdir_abs="$(cd "$outdir" && pwd)"  

    # Capture file information.  
    filename=$(basename "$infile")  
    input_dir_path=$(dirname "$infile")  
    input_abs_path="$(cd "$input_dir_path" && pwd)/$(basename "$infile")"  

    echo "Creating temporary work folder for QR generation..."  
    work_dir=$(get_work_dir)  

    # Trap to ensure temporary files are removed if the process is interrupted.  
    cleanup_enc() {  
        if [ "$DO_CLEANUP" = true ] && [ -d "$work_dir" ]; then  
            echo "Cleaning up temporary files..."  
            rm -rf "$work_dir"  
        fi  
    }  
    trap cleanup_enc EXIT INT TERM  

    orig_pwd=$(pwd)  
    cd "$work_dir"  

    # Divide the source file into 362-byte segments (max capacity for this QR configuration).  
    echo "Splitting '$filename' into 362-byte chunks..."  
    mkdir -p 1  
    split -b 362 "$input_abs_path" 1/_  

    # Iterate through segments and generate high-density binary QR codes.  
    echo "Generating data QR codes..."  
    mkdir -p 1o  

    # Filename format: [index].[original_filename].qr.png  
    i=1  
    for f in 1/_*; do 
        [ -f "$f" ] || continue  
        out=$(printf "1o/%08d.%s.qr.png" "$i" "$filename")  
        zint -b 58 --binary --secure=2 --scale=2.5 --whitesp=4 --vwhitesp=4 --fg="$fg_color" --bg="$bg_color" -o "$out" -i "$f"  
        i=$((i + 1))  
    done  

    # Generate metadata frames: 00000000 as header and final index as footer.  
    echo "Generating header/footer QR code..."  
    header_filename=$(printf "00000000.%s.qr.png" "$filename")  
    footer_filename=$(printf "%08d.%s.qr.png" "$i" "$filename")  
    filesize=$(wc -c < "$input_abs_path" | tr -d ' ')  
    printf "%s\n%s\n%s bytes\n%s\n%s" "0" "$filename" "$filesize" "snowyQR 0.0.0 by Digital Assistant" "this is a metadata header/footer, payload data should be before or after this (may loop)" > header.tmp  
    zint -b 58 --secure=2 --scale=2.5 --whitesp=4 --vwhitesp=4 --fg="$fg_color" --bg="$bg_color" -i header.tmp -o "1o/$header_filename"  
    printf "%s\n%s\n%s bytes\n%s\n%s" "1" "$filename" "$filesize" "snowyQR 0.0.0 by Digital Assistant" "this is a metadata header/footer, payload data should be before or after this (may loop)" > footer.tmp  
    zint -b 58 --secure=2 --scale=2.5 --whitesp=4 --vwhitesp=4 --fg="$fg_color" --bg="$bg_color" -i footer.tmp -o "1o/$footer_filename"  

    # Transfer processed frames to the final destination.  
    echo "Moving generated QR codes to '$outdir'..."  
    cd "$orig_pwd"  
    mv "$work_dir"/1o/* "$outdir_abs"/  
    
    # Disarm trap and perform manual cleanup.  
    trap - EXIT INT TERM  
    if [ "$DO_CLEANUP" = true ] && [ -d "$work_dir" ]; then rm -rf "$work_dir"; fi  
}  

# Logic from encoder script  
# create_video: Stacks QR codes and linear barcodes side-by-side into a lossless video stream.  
create_video() {  
    indir="$1"  
    outfile="$2"  
    fg_color="$3"  
    bg_color="$4"  
    loop_count="$5"  

    if [ ! -d "$indir" ]; then  
        echo "Error: Input directory '$indir' does not exist." >&2  
        exit 1  
    fi  

    # Workspace for intermediate barcodes.  
    tmp_process_dir=$(get_work_dir)  
    barcode_dir="$tmp_process_dir/barcodes"  
    mkdir -p "$barcode_dir"  

    # Generate a matching Code-128 barcode for every QR frame to assist frame sequencing during decode.  
    echo "Generating linear barcodes for frames..."  
    for f in "$indir"/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].*.png; do  
        [ -f "$f" ] || continue  
        base_name=$(basename "$f")  
        seq_num=$(echo "$base_name" | cut -c1-8)  
        zint -b 20 --scale=2 --whitesp=4 --vwhitesp=4 --fg="$fg_color" --bg="$bg_color" -o "$barcode_dir/$base_name" -d "$seq_num"  
    done  

    # Add padding to the end of the barcode sequence to prevent video truncation.  
    highest_barcode=""  
    for f in "$barcode_dir"/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].*.png; do  
        [ -f "$f" ] && highest_barcode="$f"  
    done  

    if [ -n "$highest_barcode" ]; then  
        base_bar=$(basename "$highest_barcode")  
        bar_num=$(echo "$base_bar" | cut -c1-8)  
        bar_num_clean=$(echo "$bar_num" | sed 's/^0*//'); : "${bar_num_clean:=0}"  
        bar_suffix=$(echo "$base_bar" | cut -c9-)  
        for j in 1 2 3 4 5; do  
            next_bar_num=$((bar_num_clean + j))  
            next_bar_padded=$(printf "%08d" "$next_bar_num")  
            cp "$highest_barcode" "$barcode_dir/$next_bar_padded$bar_suffix"  
        done  

        # Add 4 copies of barcode #00000000 to complete 9 total padding frames.  
        zero_barcode="$barcode_dir/00000000$bar_suffix"  
        if [ -f "$zero_barcode" ]; then  
            for j in 6 7 8 9; do  
                next_bar_num=$((bar_num_clean + j))  
                next_bar_padded=$(printf "%08d" "$next_bar_num")  
                cp "$zero_barcode" "$barcode_dir/$next_bar_padded$bar_suffix"  
            done  
        fi  
    fi  

    # Add padding to the end of the QR sequence to prevent video truncation.  
    highest_file=""  
    for f in "$indir"/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9].*.png; do  
        [ -f "$f" ] && highest_file="$f"  
    done  

    if [ -n "$highest_file" ]; then  
        base_file=$(basename "$highest_file")  
        highest_num=$(echo "$base_file" | cut -c1-8)  
        highest_num_clean=$(echo "$highest_num" | sed 's/^0*//'); : "${highest_num_clean:=0}"  
        file_suffix=$(echo "$base_file" | cut -c9-)  
        for j in 1 2 3 4 5 6 7 8 9; do  
            next_num=$((highest_num_clean + j))  
            next_num_padded=$(printf "%08d" "$next_num")  
            target_qr="$indir/$next_num_padded$file_suffix"  
            cp "$highest_file" "$target_qr"  
            
            # Log the created path cleanly to a tracking text file  
            echo "$target_qr" >> "$tmp_process_dir/qr_to_clean.txt"  
        done  
    fi  

    # Set ffmpeg loop arguments if specified.  
    LOOP_ARGS=""  
    if [ "$loop_count" -ne 0 ]; then  
        LOOP_ARGS="-stream_loop $loop_count"  
    fi  

    # FFmpeg assembly: stacks QR on the left and Barcode on the right.  
    echo "Combining QR codes and Barcodes into video (Loop: $loop_count)..."  
    
    # Use hstack and pad to create a combined canvas, encoded to lossless VP9.  
    if ffmpeg \  
      $LOOP_ARGS -f image2 -pattern_type glob -i "$indir/*.png" \  
      $LOOP_ARGS -f image2 -pattern_type glob -i "$barcode_dir/*.png" \  
      -filter_complex "[0:v]pad=420:420:0:420-ih:color=black@0[qr]; \  
                       [1:v]pad=420:420:420-iw:420-ih:color=black@0[bar]; \  
                       [qr][bar]hstack=inputs=2,format=yuva420p[out]" \  
      -map "[out]" \  
      -c:v libvpx-vp9 -lossless 1 \  
      "$outfile"; then  
        echo "Video output: $outfile"  
    fi  

    # Remove the temporary padding files created in the input directory.  
    if [ "$DO_CLEANUP" = true ] && [ -f "$tmp_process_dir/qr_to_clean.txt" ]; then  
        echo "Cleaning up temporary QR codes from input directory..."  
        while IFS= read -r file_to_remove; do  
            rm -f "$file_to_remove"  
        done < "$tmp_process_dir/qr_to_clean.txt"  
    fi  

    [ "$DO_CLEANUP" = true ] && rm -rf "$tmp_process_dir"  
}  

# Logic from extractor script  
# extract_bin_folder: Extracts frames from video and scans them back into raw binary chunks.  
# Detects sequence loops to organize data from repeated transmissions.  
extract_bin_folder() {  
    input_source="$1"  
    output_dir="$2"  
    
    video_input=""  
    input_dir=""  

    # Determine if the input is a file or a folder of existing images.  
    if [ -f "$input_source" ]; then  
        video_input="$input_source"  
    elif [ -d "$input_source" ]; then  
        input_dir="$input_source"  
    else  
        echo "Error: Extraction input source '$input_source' not found." >&2  
        exit 1  
    fi  

    # Create an internal work directory for the extraction process  
    # This respects -w and skips mktemp if requested.  
    _internal_work=$(get_work_dir)  
    
    cleanup_ext() { if [ "$DO_CLEANUP" = true ] && [ -d "$_internal_work" ]; then rm -rf "$_internal_work"; fi; }  
    trap cleanup_ext EXIT INT TERM  

    # Unpack video frames into images.  
    if [ -n "$video_input" ]; then  
        input_dir="$_internal_work/video_frames"  
        mkdir -p "$input_dir"  
        ffmpeg -i "$video_input" "$input_dir/snowyqr_%08d.png" || {  
            echo "Error: ffmpeg failed." >&2; exit 1;  
        }  
    fi  

    [ -d "$input_dir" ] || { echo "Error: Input directory does not exist." >&2; exit 1; }  

    # Setup output folder for extracted .bin files.  
    [ -z "$output_dir" ] && output_dir="snowyQR_$(date +%H%M%S)_$$"  
    mkdir -p "$output_dir"  

    # Initialize set folders for handling looped playback.  
    folder_index=1  
    current_temp_dir="$_internal_work/chunk_set_${folder_index}"  
    mkdir -p "$current_temp_dir"  

    echo "Extracting binary chunks. Please wait, may take hours. (Do not run without zbarimg and ffmpeg)"  
    # Scanning is intensive.  

    prev_barcode=""  

    for img in "$input_dir"/*; do  
        [ -f "$img" ] || continue  
        tmp_bin="$current_temp_dir/tmp.qr.bin"  
        
        # Scan frame for QR data.  
        if zbarimg -q --raw -S*.disable -Sqr.enable -Sbinary "$img" 2>/dev/null > "$tmp_bin" && [ -s "$tmp_bin" ]; then  
            dd if="$tmp_bin" of="$_internal_work/dd.tmp" bs=1 count=362 2>/dev/null  
            mv "$_internal_work/dd.tmp" "$tmp_bin"  
        else  
            rm -f "$tmp_bin"; continue  
        fi  

        # Scan frame for sequence index barcode.  
        barcode=$(zbarimg -q --raw -S*.disable -Scode128.enable "$img" 2>/dev/null | tr -d '\n\r')  
        # Skip if sequencing data is missing.  
        if [ -z "$barcode" ]; then  
            rm -f "$tmp_bin"  
        else  
            target_name="$current_temp_dir/${barcode}.qr.bin"  
            
            if [ "$barcode" = "$prev_barcode" ]; then  
                rm -f "$tmp_bin"  
                continue  
            fi  
            
            # Start a new folder if we detect a repeat index (indicating a loop start).  
            if [ -f "$target_name" ]; then  
                folder_index=$((folder_index + 1))  
                current_temp_dir="$_internal_work/chunk_set_${folder_index}"  
                mkdir -p "$current_temp_dir"  
                target_name="$current_temp_dir/${barcode}.qr.bin"  
                prev_barcode=""  
            else  
                prev_barcode="$barcode"  
            fi  
            
            mv "$tmp_bin" "$target_name"  
        fi  
    done  

    [ -n "$video_input" ] && [ "$DO_CLEANUP" = true ] && rm -rf "$input_dir"  

    # Final renaming: parse the header chunk (index 0) to recover the original filename.  
    i=1  
    while [ $i -le "$folder_index" ]; do  
        dir="$_internal_work/chunk_set_${i}"  
        if [ -d "$dir" ]; then  
            zero_file="$dir/00000000.qr.bin"  
            file_prefix=""  

            # Peek at metadata chunk for filename recovery.  
            if [ -f "$zero_file" ]; then  
                insert_name=$(sed -n '2p' "$zero_file" | tr -d '\n\r/')  
                [ -n "$insert_name" ] && file_prefix="${insert_name}."  
            fi  

            for bin_file in "$dir"/*; do  
                [ -f "$bin_file" ] || continue  
                base_name=${bin_file##*/}  
                [ "$base_name" = "tmp.qr.bin" ] && continue  
                
                barcode_part="${base_name%%.*}"  
                mv "$bin_file" "$output_dir/${barcode_part}.${file_prefix}qr.bin"  
            done  
            [ "$DO_CLEANUP" = true ] && rm -rf "$dir"  
        fi  
        i=$((i + 1))  
    done  

    echo "Extraction complete. Output folder available at: $output_dir"  

    trap - EXIT INT TERM  
    cleanup_ext  
}  

# Logic from reassembler script  
# reassemble_bin_folder: Concatenates binary chunks back into their original file format.  
reassemble_bin_folder() {  
    input_dir="$1"  
    output_dir="$2"  

    if [ ! -d "$input_dir" ]; then  
        echo "Error: Input directory '$input_dir' does not exist." >&2  
        exit 1  
    fi  

    mkdir -p "$output_dir"  

    _internal_work=$(get_work_dir)  
    processed_targets="$_internal_work/targets.$$"  

    cleanup_re() { [ "$DO_CLEANUP" = true ] && rm -f "$processed_targets"; }  
    trap cleanup_re EXIT INT TERM  

    # Catalog all unique target files based on chunk naming patterns.  
    for filepath in "$input_dir"/*.bin; do  
        [ -f "$filepath" ] || continue  
        
        filename=${filepath##*/}  
        length=$(printf "%s" "$filename" | wc -c)  
        # Convention check.  
        
        if [ "$length" -lt 16 ]; then  
            continue  
        fi  
        
        rest=$(printf "%s" "$filename" | cut -c 10-)  
        target_name=$(printf "%s" "$rest" | cut -c 1-$((length - 15)))  
        
        echo "$target_name" >> "$processed_targets"  
    done  

    # Deduplicate the list of targets to process.  
    if [ -s "$processed_targets" ]; then  
        unique_targets=$(sort -u "$processed_targets")  
    else  
        echo "No .bin files matching the requirements were found."  
        exit 0  
    fi  

    # Process each unique target by stitching its chunks together in order.  
    echo "$unique_targets" | while read -r target; do  
        [ -z "$target" ] && continue  
        
        echo "Processing $target..."  
        
        # Collate chunks for this specific target.  
        set --  
        for chunk in "$input_dir"/????????."$target"??????; do  
            [ -f "$chunk" ] && set -- "$@" "$chunk"  
        done  
        
        total_chunks=$#  
        
        # A valid sequence must contain at least Header, Footer, and one data chunk.  
        if [ "$total_chunks" -le 2 ]; then  
            echo "Skipping $target: Has $total_chunks chunk(s). Ignoring the boundaries leaves no data."  
            continue  
        fi  
        
        output_target=${target%.}  
        output_file="$output_dir/$output_target"  
        # Skip the metadata header chunk.  
        : > "$output_file"  
        
        shift 1  
        
        count=1  
        limit=$((total_chunks - 2))  
        # Iterate through the data chunks only.  
        
        while [ "$count" -le "$limit" ]; do  
            current_chunk="$1"  
            dd if="$current_chunk" bs=1 count=362 2>/dev/null >> "$output_file"  
            shift 1  
            count=$((count + 1))  
        done  
        
        echo "Reassembled file: $output_target (skipped boundaries, 362 bytes per chunk)."  
    done  

    echo "Reassembly complete. Output directory: '$output_dir'."  
    trap - EXIT INT TERM  
    cleanup_re  
}  

#
***
CLI Routing Arguments Parsing
***
 

MODE=""  
INPUT_ARG=""  
OUTPUT_ARG=""  
FG_COLOR="000000"  
BG_COLOR="6B6B6B"  
LOOP_COUNT=1  

# Argument parsing loop.  
while [ $# -gt 0 ]; do  
    case "$1" in  
        -l)  
            if [ -z "$2" ] || [ "${2#-}" != "$2" ]; then  
                echo "Error: -l requires a numeric argument." >&2  
                usage  
            fi  
            LOOP_COUNT="$2"  
            shift 2  
            ;;  
        -c)  
            if [ -z "$2" ] || [ "${2#-}" != "$2" ] || [ -z "$3" ] || [ "${3#-}" != "$3" ]; then  
                echo "Error: -c requires two color hex arguments (FG and BG)." >&2  
                usage  
            fi  
            if ! echo "$2" | grep -Eq '^[0-9a-fA-F]{6}$' || ! echo "$3" | grep -Eq '^[0-9a-fA-F]{6}$'; then  
                echo "Error: Colors must be valid 6-digit hex values (e.g., 000000)." >&2  
                usage  
            fi  
            FG_COLOR="$2"  
            BG_COLOR="$3"  
            shift 3  
            ;;  
        -w)  
            if [ -z "$2" ] || [ "${2#-}" != "$2" ]; then  
                echo "Error: -w requires a directory argument." >&2  
                usage  
            fi  
            USER_WORK_DIR="$2"  
            DO_CLEANUP=false  
            mkdir -p "$USER_WORK_DIR"  
            shift 2  
            ;;  
        -f|-v|-d|-b)  
            if [ -n "$MODE" ]; then  
                echo "Error: Multiple operation mode flags specified." >&2  
                usage  
            fi  
            MODE="$1"  
            if [ -z "$2" ] || [ "${2#-}" != "$2" ]; then  
                echo "Error: Mode option $1 requires a parameter path." >&2  
                usage  
            fi  
            INPUT_ARG="$2"  
            shift 2  
            ;;  
        -*)  
            echo "Unknown option: $1" >&2  
            usage  
            ;;  
        *)  
            if [ -n "$OUTPUT_ARG" ]; then  
                echo "Error: Too many trailing arguments arguments." >&2  
                usage  
            fi  
            OUTPUT_ARG="$1"  
            shift  
            ;;  
    esac  
done  

if [ -z "$MODE" ] || [ -z "$INPUT_ARG" ] || [ -z "$OUTPUT_ARG" ]; then  
    echo "Error: Missing required mode flags, input path, or output destination targets." >&2  
    usage  
fi  

#
***
Dependency Validation
***
 

# Validate required binaries are installed for the chosen operation mode.  
check_mktemp  

case "$MODE" in  
    -f)  
        check_zint  
        case "$OUTPUT_ARG" in  
            *.mkv|*.webm|*.mp4|*.avi|*.mov|*.flv) check_ffmpeg ;;  
        esac  
        ;;  
    -v)  
        check_ffmpeg  
        check_zbarimg  
        ;;  
    -d)  
        check_zint  
        check_ffmpeg  
        ;;  
    -b)  
        if [ -f "$INPUT_ARG" ]; then  
            check_ffmpeg  
            check_zbarimg  
        fi  
        ;;  
esac  

#
***
Flow Execution Management
***
 

case "$MODE" in  
    -f)  
        # Check if output specifies a common video format extension  
        IS_VIDEO=false  
        case "$OUTPUT_ARG" in  
            *.mkv|*.webm|*.mp4|*.avi|*.mov|*.flv) IS_VIDEO=true ;;  
        esac  

        if [ "$IS_VIDEO" = true ]; then  
            # Workflow 1: Encode input file directly to video  
            # Automated extension fix-ups and validation for video encoding.  
			case "$OUTPUT_ARG" in  
    		*.mkv|*.webm) ;;  
    		*)  
        	echo "Warning: Only .webm and .mkv video formats are officially supported." >&2  
        
      		  # Check if stdin is NOT connected to an interactive terminal  
      		  if [ ! -t 0 ]; then  
      	      echo "Error: Non-interactive shell detected. Cannot prompt for input. Aborting." >&2  
   	          exit 1  
     	      fi  

	 	        echo "Please choose an action:" >&2  
    	    	echo "  [1] Swap output extension to .mkv (Recommended)" >&2  
        		echo "  [2] Cancel render" >&2  
        		echo "  [3] Ignore and attempt video rendering anyway" >&2  
        		printf "Choice (1/2/3): " >&2  
        		read -r choice  
        		if [ "$choice" = "1" ]; then  
            	case "$OUTPUT_ARG" in  
                	*/*)  
                    	_dir="${OUTPUT_ARG%/*}"  
                    	_file="${OUTPUT_ARG##*/}"  
                    	case "$_file" in  
                        	*.*) _file="${_file%.*}.mkv" ;;  
                        	*)   _file="$_file.mkv" ;;  
                    	esac  
                    	OUTPUT_ARG="$_dir/$_file"  
                    	;;  
                	*.*) OUTPUT_ARG="${OUTPUT_ARG%.*}.mkv" ;;  
                	*)   OUTPUT_ARG="$OUTPUT_ARG.mkv" ;;  
            	esac  
            	echo "Updated output format target name to: $OUTPUT_ARG" >&2  
        	elif [ "$choice" = "2" ]; then  
            echo "Aborting operation."; exit 1  
        	fi  
        	;;  
			esac  

            # Create a sandboxed QR directory for the two-step video build.  
            TEMP_QR_DIR=$(get_work_dir)  
            cleanup_workflow() { if [ "$DO_CLEANUP" = true ] && [ -d "$TEMP_QR_DIR" ]; then rm -rf "$TEMP_QR_DIR"; fi; }  
            trap cleanup_workflow EXIT INT TERM  

            echo "Phase 1: Splitting and encoding data files to temporary raw QR configurations..."  
            encode_qr "$INPUT_ARG" "$TEMP_QR_DIR" "$FG_COLOR" "$BG_COLOR"  
            
            echo "Phase 2: Translating temporary raw frames into final targeted sequence compilation..."  
            create_video "$TEMP_QR_DIR" "$OUTPUT_ARG" "$FG_COLOR" "$BG_COLOR" "$LOOP_COUNT"  
            
            [ "$DO_CLEANUP" = true ] && rm -rf "$TEMP_QR_DIR"  
            trap - EXIT INT TERM  
        else  
            # Workflow 3: Encode file directly to standard folder directory (Debug mode)  
            encode_qr "$INPUT_ARG" "$OUTPUT_ARG" "$FG_COLOR" "$BG_COLOR"  
            echo "Raw storage configuration generated inside output directory: '$OUTPUT_ARG'."  
        fi  
        ;;  

    -v)  
        # Workflow 2: Complete direct sequential Decode file(s) right out of video package container  
        TEMP_EXTRACT_DIR=$(get_work_dir)  
        cleanup_dec() { if [ "$DO_CLEANUP" = true ] && [ -d "$TEMP_EXTRACT_DIR" ]; then rm -rf "$TEMP_EXTRACT_DIR"; fi; }  
        trap cleanup_dec EXIT INT TERM  

        # Execute the two-phase recovery process: extract frames then reassemble binary data.  
        echo "Phase 1: Running deep visual scanner to unpack assets into structural chunks..."  
        extract_bin_folder "$INPUT_ARG" "$TEMP_EXTRACT_DIR"  

        echo "Phase 2: Executing assembly rules to recover targeted file data boundaries..."  
        reassemble_bin_folder "$TEMP_EXTRACT_DIR" "$OUTPUT_ARG"  

        [ "$DO_CLEANUP" = true ] && rm -rf "$TEMP_EXTRACT_DIR"  
        trap - EXIT INT TERM  
        ;;  

    -d)  
        # Workflow 4: Compile existing raw directories into complete matrix video frame structures  
        create_video "$INPUT_ARG" "$OUTPUT_ARG" "$FG_COLOR" "$BG_COLOR" "$LOOP_COUNT"  
        ;;  

    -b)  
        # Handle overlap parsing based on filesystem layout configurations  
        if [ -f "$INPUT_ARG" ]; then  
            # Workflow 5: Extract .bin metadata directories right from underlying video container tracks  
            extract_bin_folder "$INPUT_ARG" "$OUTPUT_ARG"  
        else  
            # Workflow 6: Stitch pre-existing standalone binary component directory elements  
            reassemble_bin_folder "$INPUT_ARG" "$OUTPUT_ARG"  
        fi  
        ;;  
esac  

exit 0  
top 46 comments
sorted by: hot top controversial new old
[–] SuperUserDO@piefed.ca 22 points 13 hours ago (1 children)

I'm gonna echo some of the other people here. While I don't debug code for fun (thank you to everyone who does), I represent the security/operations side of the house (eventually you'll learn to love or hate people like me). If you don't understand what your writing, the odds of it getting shipped in my environment are zero.

Now some honest advice. Stop the project, and put on your learning hat. You need to learn some basic fundamentals. If you are good at self study something like learnpythonthehardway.org might be a good jumping off pont.

Once the fundamentals are out of the way, all projects follow a similar trend: start with a desired goal, break that down into components, repeat breaking down components til you have discreet tasks, understand your dependency tree, and start tackling the tasks.

[–] cappielung@lemmy.world 46 points 16 hours ago (1 children)

My friend. I am a professional programmer. I get paid to read code. I even enjoy debugging code, no matter who wrote it.

I will not slog through pages of AI slop that you can't explain or understand.

I say this not to put you down. If you want to go down this road, AI can bootstrap you. It can legitimate help you learn and grow if you are willing to engage and think and understand. But you simply can't dump a wall of script in a random sub, claim complete ignorance, and expect "the community" to swoop in and save you.

Break down your problem. Ask specific, targeted questions. Use AI? Sure. But understand what it writes, or you'll end up... Well, exactly where you are.

Ask again once you have a question. Good luck.

[–] grandel@lemmy.ml 6 points 8 hours ago

Waiting for OP's inevitable "fuck you" response...

[–] JackbyDev@programming.dev 3 points 9 hours ago (1 children)

Hey OP, I'd suggest not using CC0 for code. The only "public domain declaration with permissive fallback license" that FSF and OSI approve is the Unlicense. https://unlicense.org/ I hate the name. It's a crayon license (a license not made by lawyers). But it's still actually approved by FSF and OSI unlike CC0.

The reason CC0 is not approved is because it explicitly does not grant patent rights. Compare that to permissive licenses like Apache (which explicitly does) and MIT (which implicitly does). Because CC0 says it doesn't (as opposed to not saying it does, like MIT) that makes it awful for software.

My advice is to just use extremely permissive and simple licences if you really want something in the public domain. The most widely used is MIT.

[–] iloveDigit@piefed.social 1 points 4 hours ago* (last edited 4 hours ago) (1 children)

Didn't know all that, thank you. CC0 might still be best though because I definitely don't believe in patents.

I also considered using a Pirate License, which is a crayon license that denies anyone ever any permission to use/look at the thing, not even the creator. That way you can only use it if you're a pirate. That's the best FOSS license by far imo

[–] JackbyDev@programming.dev 2 points 2 hours ago* (last edited 2 hours ago) (1 children)

You're getting it backwards. If you hate the idea of patents, then you should use something like Apache which protects people from patent holders, as opposed to CC0 which protects the parent holders. (Not that anyone with a patent would likely contribute to your script and sue people for using their patent.) Whether or not you philosophically agree that patents should exist, they do exist and CC0's fall back license says:

No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.

Compare that to Apache which says this:

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

CC0 protect's the creator's patent rights. Apache protects others from patent litigation.

Also, I'd strongly avoid joke licenses. At that point, why use a license at all? Just use the default protections ensured by copyright. You don't need to give a license your work if you don't want people to be able to use it. If you want people to be able to use your work, you're not actually stopping them if you don't include a license. You're only stopping them from doing it illegally. If you aren't interested in pursuing legal action against people using your stuff, you don't actually need to provide a license. The only benefit you get is encouraging more people to use it (generally more corporate minded folks).

[–] iloveDigit@piefed.social 1 points 1 hour ago* (last edited 1 hour ago)

Pirate is like Apache except instead of making it "illegal" to file certain patent lawsuits, it makes it dishonorable to file any patent lawsuits ever again, because yer a pirate now.

[–] rimu@piefed.social 15 points 16 hours ago (2 children)

While it is technically possible to write shell scripts that do as much as what you're attempting to, it's not a good idea.

Shell scripts generally don't have the development environment necessary for debugging. You can't pause execution based on a breakpoint and inspect variables, or step through the code line by line to watch the execution flow as it happens. Well, bashdb exists, but it's quite hard to use.

Without proper debugging tooling you're limited to printing stuff out on the screen and trying to figure out what that means. That's ok for short scripts (< 50 lines?) but yours is 700+ lines.

[–] iloveDigit@piefed.social 2 points 16 hours ago (1 children)

Thank you for the reply. Python keeps being suggested, what would you suggest if I end up changing rails here?

[–] rimu@piefed.social 7 points 16 hours ago (1 children)

I was going to suggest Python too but you'll need to be careful to just use functionality that is part of the standard library because once you start pulling in 3rd party packages deployment is much harder and portability seems to be a requirement here. That should be doable with your case.

The free edition of PyCharm is great for debugging.

[–] iloveDigit@piefed.social 1 points 16 hours ago* (last edited 15 hours ago)

Keeping python in mind then. For now I think I've gotta keep trying with a shell script because it can fit the whole decoder in 2 QR codes (1 for each step, extracting+reassembling) with zbarimg+ffmpeg as the only dependencies

Maybe I should use python for the main version but keep a shell script mini decoder option available

[–] mech@feddit.org 12 points 15 hours ago (2 children)

You asked AI to do a thing, it didn't work, so now you ask us to fix it.
How about you start writing something yourself which you're able to understand, and go from there?

[–] atzanteol@sh.itjust.works -1 points 14 hours ago* (last edited 14 hours ago) (1 children)

Don't be shitty. This could be how people get into coding now - figuring out why something like this doesn't work. They asked for help understanding what's going on which is a good place to start.

[–] Miaou@jlai.lu 4 points 10 hours ago

I have enough colleagues who are paid for a job they apparently can't do. If you have the patience to debug vibe coded bash script go ahead, but I don't think anyone should expect any professional willing to do this for free

[–] MagicShel@lemmy.zip 9 points 16 hours ago (1 children)

For being not a coder, this is fairly ambitious. I've been coding software for thirty years and I'm not sure I can offer anything technical. Personally, I'd probably try to do this in Python rather than shell script. Shell script is fine if you have tools you're just trying to stitch together, but this is far beyond that.

That being said, I get it. The first coding I ever did was making a character sheet for roleplaying by sending raw commands to my dot matrix printer from my Commodore 64. You have a vision in your head and a computer at your fingertips and you have to bring it all together.

I respect that and if part of your goal is to achieve this using only bash, then god speed. Otherwise I might look at a language that gives you library support and modularity and unit testing. I write Python scripts all the time, and the truth is I don't know Python at all, so I know it can be done. Define a unit of functionality and build that piece. When that piece works build another component. Then build a parent program that calls those two things. Then build more components. Define functions that can be reused when you have code that needs to do similar things.

The key is to build standalone functionality — what is the smallest useful thing you can do? Build it. Now you can invoke that every time you need to do the thing. Construct your program out of components you create.

This is kinda like if I told AI to build me a web service but keep everything in a single Java class. It would be a wild and unreadable, untestable mess.

[–] iloveDigit@piefed.social 1 points 16 hours ago* (last edited 16 hours ago) (1 children)

Thank you. For the record, the main goal of using a shell script instead of a python script is maximum portability via the QR code itself. Instead of telling someone to scan a QR code and download hundreds of megabytes of python environment, all most users will have to download for my decoder is zbarimg (because everyone already has ffmpeg)

If I can get it working, anyway

[–] MagicShel@lemmy.zip 0 points 14 hours ago (1 children)

You can still build components and assemble them once each individual piece works. It's easier to start with something that works and add to it than start with a "fully realized" script none of which actually works.

Asking folks to review hundreds of lines of vibe-coded slop (anything, really, but especially vibe coded slop) is a big ask. I sincerely wish you luck, though. We all started somewhere and maybe as you work to fix this you'll become something of a coder yourself.

[–] iloveDigit@piefed.social -3 points 13 hours ago* (last edited 13 hours ago) (1 children)

I don't get what's with these replies where people don't seem to have read the post. If none of the script actually works, idk why I'm trying to identify a Devuan behavior difference and seeing a basic round trip test passing on regular Debian. Building components and assembling them when individual pieces work is what I'm trying to do.

[–] deegeese@sopuli.xyz 1 points 2 hours ago (1 children)

If none of the script works, we have to read all of it to understand the bug in slop code.

You’ve overloaded our own “context window”.

[–] iloveDigit@piefed.social 1 points 1 hour ago* (last edited 1 hour ago)

I think all of it except the extract_bin_folder function probably works. Encoder and reassembler seem fine. Even extract_bin_folder works at a basic level on Debian.

I do understand maybe nobody can help though. It seems like whatever the issue is, it's hard to pin down because shell scripting is finnicky or something.

[–] tal@lemmy.today 3 points 15 hours ago* (last edited 14 hours ago) (3 children)

The project is supposed to convert files to QR code videos and back.

I don't really understand what you're trying to do.

I don't think that you're trying to just encode an URL, from a glance.

If you want to encode a video file as a QR code, you're going to face the pretty fundamental limitation of information content.

A QR code can store a maximum of (checks) under 3 KiB. Virtually any video file cannot fit in those space constraints.

I am not a coder. I have been staring at a screen the past two weeks trying to "vibe code" for the first time with unpaid chat bots, for a seemingly simple project. But I still haven't even gotten a single version to the real-world testing stage, keep getting failures on more basic self-tests.

So, chatbots in 2026 are not really a drop-in replacement for a computer programmer. My guess is that while we'll probably get there, it's probably going to require something more elaborate than just an plain old LLM. Like, it's not going to be a minor refinement of what's currently there, but a pretty fundamentally different system (though that system might potentially incorporate an LLM).

EDIT: Ah, it's a series of QR codes. I mean, that's going to be a pretty huge number for any real-world video.

EDIT2: Oh, you want a video of QR codes from a file. I think that a problem that you're gonna hit there is the transport, because your video is gonna be playing at a constant rate and real-time QR code recognition isn't perfectly reliable, and you're probably not going to reliably capture every frame. Maybe someone could make something with FEC so that dropping a certain percentage of frames is viable, like, use PAR2 or something.

Also, assuming that you're trying to make something that runs automatically, the generated code has stuff that will bail out if used other than if run interactively.

EDIT3: Also, it sounds like you want something portable. The script itself may (or may not be) portable, but it is trying to run external tools that are not going to be guaranteed to be present wherever.

EDIT4: I don't know why you'd be running dash on denuvan, as it sounds like its default shell is bash, but if /bin/sh is a symlink to dash on that system, what you could try doing is generating a bash-specific version. It...might work if you just switch the shebang line to bash at the top of the file, but bash in sh compatibility mode is not identical to bash in bash mode.

If you want some sort of generic portability debugging tool, I'd add set -x as the second line of the file. That will enable trace mode. Every line that runs will be printed to stderr. Then run it like so:

$ yourscript.sh 2>debug1.log

That'll output every line being run. Run it on both systems, and you'll get a log on each. Then you can run:

$ diff -u debug1.log debug2.log|less

That'll show you where the behavior starts to differ on the two systems.

But...I'm going to be blunt. I think that you're asking more of current AI chatbots than is reasonable given their current state, and that it's going to wind up being pretty frustrating if you're aiming to do it with zero programming.

[–] Ephera@lemmy.ml 1 points 13 hours ago

From what I understand, on Debian and therefore presumably also Devuan, the system-wide default shell is dash. So, sh symlinks to dash.

But dash is virtually unusable for interactive use, so they configure the terminal emulator to launch bash on start-up.

In effect, scripts get executed with dash by default, but commands you type into a terminal get run by bash.

[–] iloveDigit@piefed.social 1 points 14 hours ago* (last edited 14 hours ago)

Didn't see edit 4 before replying originally - might try a bash script instead of generic posix shell, thank you

I have been surprised how much progress has been possible by guiding the chat bot through 1 step at a time (and occasionally humanly digging through a problem it refuses to handle)

[–] iloveDigit@piefed.social 1 points 14 hours ago (1 children)

If successfully implemented, my design would rely on users screen recording or downloading the video with the files in it (or recording a video with a camera pointed at the screen if even more successful)

Because you're right that decoding the QR codes definitely does seem too slow to do it in real time

[–] tal@lemmy.today 2 points 13 hours ago* (last edited 13 hours ago) (1 children)

So, are you going to require matching the framerate between the user's camera and the played video? Because if not, there won't be a 1:1 correspondence between the two, and even if they're at the same framerate, additionally you're going to need to identify the start frame.

I didn't poke around too deeply, but if it assigns a frame number to each QR code and encodes it in the code, maybe you can scan each frame of user video and discard duplicates.

If this is going to work reliably, you're going to need many QR reads to work without failure or it'll fail. Like, aggregate failure rate is going to be higher than failure rate for a single QR code. I think that I'd again probably look into some kind of FEC


I don't know if PAR2 can generate chunks small enough to be individually stored for a QR code, but if it can, maybe use one of those chunks for each QR code instead of the output of split. That way, you only need N% of the QR frames to be successfully read.

searches

Hmm. Setting aside minimum chunk size, PAR2 may not support enough chunks for your use case


only 32k. I don't know how large the files are that you're trying to encode.

[–] iloveDigit@piefed.social 1 points 13 hours ago* (last edited 13 hours ago)

So, are you going to require matching the framerate between the user’s camera and the played video? Because if not, there won’t be a 1:1 correspondence between the two, and even if they’re at the same framerate, additionally you’re going to need to identify the start frame.

I didn’t poke around too deeply, but if it assigns a frame number to each QR code and encodes it in the code, maybe you can scan each frame of user video and discard duplicates.

Yeah, that's what the code is trying to do. Each QR code is accompanied by a barcode with its sequence number, to ignore duplicate frames and try to collect missing chunks from each repeated loop of the video. But like I said in the main post, I only know it decodes its own rendered video at native framerate on Debian right now, haven't been able to test the duplicate-frame-discarding logic too much due to being distracted trying to figure out this Devuan behavior difference.

An error correction mechanism (like PAR2 or something else) would be hugely beneficial to improving performance in suboptimal (i.e. real world) conditions, but I picked a simple split because I didn't want the decoding process to be too complicated for someone to potentially re-implement from scratch if they're ever stuck on a desert island with only a video file.

My test payload so far has been about 1.5MB but that takes more than 2 minutes for each loop at 30FPS so I would expect most use cases to be for smaller files

[–] ghodawalaaman@programming.dev 3 points 15 hours ago (2 children)
[–] iloveDigit@piefed.social -1 points 4 hours ago

What the fuck is with the 9 downvotes on my reply about this? How retarded are people?

[–] deegeese@sopuli.xyz 3 points 15 hours ago (1 children)

Is this intended to be actually usable by people, or is it just a proof of concept?

Any real videos will need to split over so many QR codes nobody would bother.

[–] iloveDigit@piefed.social 1 points 15 hours ago (1 children)

Intended to be usable, but it's for putting small files in the corner of a video

[–] deegeese@sopuli.xyz 0 points 2 hours ago (1 children)

Embedding files into videos is a bad idea for a bunch of reasons. Any LLM could tell you why.

[–] iloveDigit@piefed.social 1 points 1 hour ago
[–] refalo@programming.dev 1 points 13 hours ago* (last edited 13 hours ago) (1 children)

Based on the real-time performance goal you have, a shell script calling external programs millions of times will have no chance.

You can do QR encode/decode of video data in real-time like you want, but it's going to require (most likely) using a compiled language instead of an interpreted one, and also you're going to need to buffer the video frames and/or QR codes in memory instead of via files on a (slow) disk.

The end result should be able to stream data directly from one format to another without creating any intermediate files at all.

Perhaps you can use some of that information to steer your AI.

[–] iloveDigit@piefed.social 0 points 13 hours ago

Not trying to do it in real-time, not sure where you're getting that

[–] e0qdk@reddthat.com 2 points 16 hours ago (1 children)

I'd suggest replacing the shell script with a Python script as a first step. If nothing else, it will probably be a hell of a lot easier to read... Your bots ought to be able to do a first pass at that. If it's actually tripping up on some particular bash/dash-ism like you think, that might even just bypass the issue entirely; if not it should be easier to debug with better readability.

[–] chocrates@piefed.world 4 points 15 hours ago (1 children)

Also put it on gitlab or codeberg. If I'm gonna read slop at least give me line numbers

[–] iloveDigit@piefed.social -1 points 13 hours ago* (last edited 13 hours ago)

If this project ever gets ready I might distribute it purely through itself. The only git network I might use would probably be radicle if anything. I can't be a coder because I hate using well established tools