M: magick montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image" ...if this was the old version of the command, what would be the equivalent for if i wanted to use the 'montage' (free-standing utility) command to accomplish the same thing?

G: Short answer: it’s basically the same command — just drop the magick wrapper.

montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

What changed (and didn’t)

Subtle caveats (worth knowing)

One edge case

If you’re on a system with both versions installed, montage might point to v6 while magick montage uses v7. Most of the time this doesn’t matter for simple grids like yours, but it can matter if you later add:

For your current use: no functional difference.

M: here's the --help results. ... how do i use a command line (in ubuntu) to update to v7?

G: TL;DR: You’re on ImageMagick 6 (montage-im6.q16). To get v7 on Ubuntu, install it alongside or replace it via apt or a PPA.


What your output tells us

That header line:

Usage: montage-im6.q16 ...

means you’re running IM6, not IM7. fileciteturn0file0


Ubuntu often already has IM7 available:

sudo apt update
sudo apt install imagemagick

Then check:

magick -version

If you see ImageMagick 7.x, you’re done.


Option 2 — Install IM7 alongside IM6

Ubuntu sometimes keeps IM6 as default but installs IM7 tools separately.

After installing, try:

magick montage ...

instead of:

montage ...

That’s the key difference.


Option 3 — If apt still gives you IM6 (common on older LTS)

Use a PPA:

sudo add-apt-repository ppa:ubuntuhandbook1/imagemagick
sudo apt update
sudo apt install imagemagick

Option 4 — Nuclear option (compile from source)

Only if you need absolute latest:

sudo apt install build-essential
wget https://imagemagick.org/download/ImageMagick.tar.gz
tar xf ImageMagick.tar.gz
cd ImageMagick-*
./configure
make -j$(nproc)
sudo make install

Reality check (important)

You don’t actually need v7 for your command.

Your current command:

montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

works identically on IM6 and IM7.

So upgrading is only worth it if you:


Third angle (you didn’t ask, but matters)

If your goal is future-proof scripts, don’t replace IM6—support both:

if command -v magick >/dev/null; then
    magick montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"
else
    montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"
fi

That avoids breaking on systems like yours.


If you want, I can check exactly what Ubuntu version you’re on and tell you which of these will actually work without friction.

M: sudo add-apt-repository ppa:ubuntuhandbook1/imagemagick sudo apt update sudo apt install imagemagick ERROR: ppa 'ubuntuhandbook1/imagemagick' not found (use --login if private) Hit:1 https://repo.radeon.com/amdgpu/6.3/ubuntu noble InRelease Hit:2 https://repo.radeon.com/rocm/apt/6.3 noble InRelease
Hit:3 http://us.archive.ubuntu.com/ubuntu noble InRelease
Hit:4 http://us.archive.ubuntu.com/ubuntu noble-updates InRelease
Hit:5 http://us.archive.ubuntu.com/ubuntu noble-backports InRelease
Hit:6 http://security.ubuntu.com/ubuntu noble-security InRelease
Reading package lists... Done Building dependency tree... Done Reading state information... Done 1 package can be upgraded. Run 'apt list --upgradable' to see it. Reading package lists... Done Building dependency tree... Done Reading state information... Done imagemagick is already the newest version (8:6.9.12.98+dfsg1-5.2build2). 0 upgraded, 0 newly installed, 0 to remove and 1 not upgraded.

G: TL;DR: You’re on Ubuntu 24.04 (noble), and Canonical is still shipping ImageMagick 6 by default. That PPA doesn’t exist anymore for noble, so your failure is expected—not user error.


What’s actually happening

So:

You did everything correctly. The ecosystem just hasn’t moved.


The cleanest way forward (no nonsense)

This avoids breaking system packages that depend on IM6.

sudo apt install build-essential libjpeg-dev libpng-dev libtiff-dev libwebp-dev libheif-dev

wget https://imagemagick.org/download/ImageMagick.tar.gz
tar xf ImageMagick.tar.gz
cd ImageMagick-*

./configure
make -j$(nproc)
sudo make install
sudo ldconfig

Then check:

magick -version

Important nuance (this is where people mess up)

You will now have:

This is good, not a problem.


sudo apt remove imagemagick

…but this can break other packages. Ubuntu still expects IM6 in some places.


If you just want IM7 behavior without installing anything

You can often already do:

convert -version

If it’s still 6.x, then yeah—you don’t have IM7 at all.


Third option (probably the smartest long-term)

Don’t upgrade. Standardize your scripts.

You already saw:

magick montage ...

Just wrap it:

command -v magick >/dev/null && CMD="magick montage" || CMD="montage"
$CMD @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

Reality check (pushing back a bit)

You’re trying to upgrade something that:

So unless you need:

…this is mostly a “nice to have,” not a functional requirement.


If you want, I can show you a minimal install of just the magick binary without full system integration, which is cleaner than full compilation.

M: you know, you act like the simple act of removing the 'magick' will work; it doesn't. i was porting a script from my fedora laptop, and tried exactly that. it's not using the right list or something, because nothing is being produced.

G: [Non-text content: thoughts; content_type=thoughts]

G: [Non-text content: thoughts; content_type=thoughts]

G: [Non-text content: thoughts; content_type=thoughts]

G: [Non-text content: reasoning_recap; content_type=reasoning_recap]

G: Yeah. Fair correction. I overstated the “just remove magick” part.

Your montage-im6.q16 absolutely can behave differently here, and the issue is probably the @"$temp_file" input list, not montage itself. In IM6, @file means “replace this filename with the contents of that file,” and for image input it reads a list of image filenames from that file. That feature exists in IM6, but the list has to be in the format IM expects. fileciteturn0file0 citeturn183825view3turn183825view1

The most important part is this:

So if your Fedora-side script wrote the temp file in some other format, IM6 may silently fail to read the inputs the way you expect.

Try these checks in Ubuntu:

printf 'TEMP FILE CONTENTS:\n'; cat "$temp_file"

Then test whether IM6 can read the list at all:

identify @"$temp_file"

If that produces errors or no images, the list file is the problem, not montage.

The safest IM6-friendly way is to make the temp file contain plain image paths separated by whitespace, like this:

printf '%s\n' "/path/to/1.jpg" "/path/to/2.jpg" "/path/to/3.jpg" > "$temp_file"

and then run:

montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

But there’s a catch: if any filenames contain spaces or shell-sensitive characters, the list-file approach can get messy. In that case, the more reliable Ubuntu-side approach is usually to avoid @file entirely and let the shell pass an array:

montage "${images[@]}" -tile 3x3 -geometry +0+0 "$output_image"

That is usually the real fix.

So the likely answer is:

No, “remove magick” was not enough.
Your script is probably relying on a list-file format that works in your Fedora/ImageMagick 7 setup but is not being parsed the same way by Ubuntu’s IM6. citeturn183825view3turn183825view1

If you paste the part of the script that creates temp_file, I can tell you exactly what to change.

M: let's try a different approach. i ran the script with the 'magick' removed, so it just uses the 'montage' command. here's the last pair of error message batches i could identify and the elements between them: Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'A2M_8_Scene_6.mp4': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: mp42isom creation_time : 2013-01-08T06:55:17.000000Z encoder : HandBrake 0.9.3 2008112300 copyright : copyright-eng : Duration: 00:27:24.99, start: 0.000000, bitrate: 1208 kb/s Stream #0:00x1: Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, smpte170m/smpte170m/bt709, progressive), 640x480, 1128 kb/s, 29.96 fps, 59.94 tbr, 22050 tbn (default) Metadata: creation_time : 2013-01-08T06:55:17.000000Z vendor_id : [0][0][0][0] encoder : JVT/AVC Coding Stream #0:10x2: Audio: aac (LC) (mp4a / 0x6134706D), 22050 Hz, stereo, fltp, 75 kb/s (default) Metadata: creation_time : 2013-01-08T06:55:17.000000Z vendor_id : [0][0][0][0] Stream mapping: Stream #0:0 -> #0:0 (h264 (native) -> mjpeg (native)) Press [q] to stop, [?] for help [swscaler @ 0x58acea2593c0] deprecated pixel format used, make sure you did set range correctly [swscaler @ 0x58acea488e00] deprecated pixel format used, make sure you did set range correctly Last message repeated 2 times Output #0, image2, to 'frame_9.jpg': Metadata: major_brand : mp42 minor_version : 0 compatible_brands: mp42isom copyright-eng : copyright : encoder : Lavf60.16.100 Stream #0:0(und): Video: mjpeg, yuvj420p(pc, smpte170m/smpte170m/bt709, progressive), 426x320, q=2-31, 200 kb/s, 29.97 fps, 29.97 tbn (default) Metadata: creation_time : 2013-01-08T06:55:17.000000Z vendor_id : [0][0][0][0] encoder : Lavc60.31.102 mjpeg Side data: cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: N/A [image2 @ 0x58ace9379f80] The specified filename 'frame_9.jpg' does not contain an image sequence pattern or a pattern is invalid. [image2 @ 0x58ace9379f80] Use a pattern such as %03d for an image sequence or use the -update option (with -frames:v 1 if needed) to write a single image. [out#0/image2 @ 0x58ace937b0c0] video:13kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown frame= 1 fps=0.0 q=3.7 Lsize=N/A time=00:00:00.00 bitrate=N/A dup=1 drop=0 speed= 0x
montage-im6.q16: attempt to perform an operation not allowed by the security policy @temp_images.txt' @ error/blob.c/OpenBlob/2702. montage-im6.q16: scr-A2M_8_Scene_6.mp4.jpg' @ error/montage.c/MontageImageCommand/1809.

G: [Non-text content: thoughts; content_type=thoughts]

G: [Non-text content: reasoning_recap; content_type=reasoning_recap]

G: There it is. The real problem is not montage syntax. It’s this line:

montage-im6.q16: attempt to perform an operation not allowed by the security policy `@temp_images.txt'

That means Ubuntu’s ImageMagick 6 policy is blocking @file list expansion entirely. So when you removed magick, the command itself was fine, but IM6 refused to read @temp_images.txt. The second error is just fallout because no valid input images got loaded. Your earlier --help output confirms this is montage-im6.q16. fileciteturn0file0

What this means

This:

montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

is being blocked specifically because of the @... input mechanism on this Ubuntu install.

Best fix: stop using @temp_file

Use a shell array instead of ImageMagick’s list-file feature.

If your temp file contains one image path per line, do this:

mapfile -t images < "$temp_file"
montage "${images[@]}" -tile 3x3 -geometry +0+0 "$output_image"

That bypasses the policy block completely.

If you want it as a one-liner:

mapfile -t images < "$temp_file"; montage "${images[@]}" -tile 3x3 -geometry +0+0 "$output_image"

If you want to keep @temp_file

You’d have to relax ImageMagick’s security policy, probably in something like:

/etc/ImageMagick-6/policy.xml

or:

/etc/ImageMagick/policy.xml

But I would not start there unless you specifically want that behavior system-wide.

Separate issue in your ffmpeg output

This part:

The specified filename 'frame_9.jpg' does not contain an image sequence pattern
...
use the -update option ... to write a single image

is a different problem. FFmpeg is warning that image2 expects a sequence pattern unless you explicitly tell it you’re writing one frame.

So for single-frame output, use something like:

ffmpeg -i input.mp4 -frames:v 1 -update 1 frame_9.jpg

or often just:

ffmpeg -ss ... -i input.mp4 -frames:v 1 frame_9.jpg

depending on your exact command structure.

Bottom line

Two separate issues:

1. montage

Ubuntu IM6 is blocking @temp_images.txt by security policy.

2. ffmpeg

You’re writing a single image through the image2 muxer in a way it complains about.

So the direct answer is: your script did not fail because magick was removed; it failed because Ubuntu’s IM6 refuses @file input lists.

The practical fix is to replace the montage call with:

mapfile -t images < "$temp_file"
montage "${images[@]}" -tile 3x3 -geometry +0+0 "$output_image"

If you want, paste the exact block that writes temp_images.txt and calls ffmpeg/montage, and I’ll rewrite only the broken part.

M: look, this is the script, tell me what should go where please: #!/bin/bash

/home/david/Documents/shell-script-backups/repeat-in-all-dirs.sh /home/david/Documents/shell-script-backups/cover-clip-local-anime.sh

THE ONLY DIFFERENCE IS A 0-FRAME START INSTEAD OF 0.1 - because the clips always have a 'start'

Function to extract frames from a video clip at specified percentages and merge them into a single image.

@param $1: The input video file path.

@param $2: The output image file name for the merged image.

This function uses ffmpeg to extract frames at 10%, 40%, 60%, and 90% of the video duration,

saves them as image files, and then merges those images into a single output image.

extract_and_merge_frames() { local input_video="$1" # The input video file path. local output_image="$2" # The output image file name for the merged image.

# Check if the input video file exists.
if [[ ! -f "$input_video" ]]; then
    echo "Error: Input video file '$input_video' does not exist."
    exit 1
fi

# Get the duration of the video in seconds.
local duration
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_video")
duration=${duration%.*}  # Convert to integer by removing decimal part.

# Calculate frame extraction times based on percentages.

local frame_times=( $(echo "$duration * 0" | bc) $(printf "%.3f" $(echo "$duration * 0.4" | bc)) $(printf "%.3f" $(echo "$duration * 0.6" | bc)) $(printf "%.3f" $(echo "$duration * 0.99" | bc)) )

old version: 5 frame line local frame_times=( $(echo "$duration * 0" | bc) $(printf "%.3f" $(echo "$duration * 0.25" | bc)) $(printf "%.3f" $(echo "$duration * 0.5" | bc)) $(printf "%.3f" $(echo "$duration * 0.75" | bc)) $(printf "%.3f" $(echo "$duration * 0.99" | bc)) )

local frame_times=( $(echo "$duration * 0" | bc) $(printf "%.3f" $(echo "$duration * 0.125" | bc)) $(printf "%.3f" $(echo "$duration * 0.25" | bc)) $(printf "%.3f" $(echo "$duration * 0.375" | bc)) $(printf "%.3f" $(echo "$duration * 0.5" | bc)) $(printf "%.3f" $(echo "$duration * 0.625" | bc)) $(printf "%.3f" $(echo "$duration * 0.75" | bc)) $(printf "%.3f" $(echo "$duration * 0.875" | bc)) $(printf "%.3f" $(echo "$duration * 0.95" | bc)) )
echo "$frame_times"

# Array to hold the names of the extracted image files.
local image_files=()

# Extract frames at specified times and save them as image files.
for i in "${!frame_times[@]}"; do
    local frame_time="${frame_times[$i]}"
    local image_file="frame_$((i + 1)).jpg"  # Generate image file name.
    image_files+=("$image_file")  # Add to the array of image files.

    # Use ffmpeg to extract the frame.

ffmpeg -ss "$frame_time" -i "$input_video" -s 640x480 -frames:v 1 "$image_file" -y # -s "$3"x"$4" # to resize

    ffmpeg -ss "$frame_time" -i "$input_video" -vf "scale=-2:320" -frames:v 1 "$image_file" -y  # -s "$3"x"$4" # to resize
done

# Merge the extracted images into a single image.
# Create a temporary file to hold the list of images to merge.
local temp_file="temp_images.txt"
for img in "${image_files[@]}"; do
    echo "$img" >> "$temp_file"
done

# Use ImageMagick's convert command to merge the images.
# convert @"$temp_file" -append "$output_image"
# magick montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"
montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

# Clean up temporary files.
rm "$temp_file"
for img in "${image_files[@]}"; do
    rm "$img"  # Remove the individual frame images.
done

echo "Merged image created: '$output_image'"

}

Example usage of the extract_and_merge_frames function.

Uncomment the following line to run the function with a sample video file and output image name.

extract_and_merge_frames "input_video.mp4" "merged_image.png"

extract_and_merge_frames "$1" "$2"

for f in *.mp4 *.MP$ *.avi *.AVI *.wmv *.WMV *.mpg *.MPG *.mpeg *.MPEG *.mov *.MOV; do [ -e "$f" ] || continue extract_and_merge_frames "$f" "scr-$f.jpg" done

G: [Non-text content: thoughts; content_type=thoughts]

G: [Non-text content: reasoning_recap; content_type=reasoning_recap]

G: Replace only two parts.

1. Fix the ffmpeg line

Change this:

ffmpeg -ss "$frame_time" -i "$input_video" -vf "scale=-2:320" -frames:v 1 "$image_file" -y

to this:

ffmpeg -ss "$frame_time" -i "$input_video" -vf "scale=-2:320" -frames:v 1 -update 1 -y "$image_file"

That shuts up the single-image complaint and makes the intent explicit.


2. Stop using temp_images.txt with @

Delete this whole block:

    # Merge the extracted images into a single image.
    # Create a temporary file to hold the list of images to merge.
    local temp_file="temp_images.txt"
    for img in "${image_files[@]}"; do
        echo "$img" >> "$temp_file"
    done

    # Use ImageMagick's convert command to merge the images.
    # convert @"$temp_file" -append "$output_image"
    # magick montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"
    montage @"$temp_file" -tile 3x3 -geometry +0+0 "$output_image"

    # Clean up temporary files.
    rm "$temp_file"

and replace it with this:

    # Merge the extracted images into a single image.
    montage "${image_files[@]}" -tile 3x3 -geometry +0+0 "$output_image"

That is the actual fix for the Ubuntu/ImageMagick 6 problem.


So the corrected middle of the function becomes:

    # Extract frames at specified times and save them as image files.
    for i in "${!frame_times[@]}"; do
        local frame_time="${frame_times[$i]}"
        local image_file="frame_$((i + 1)).jpg"
        image_files+=("$image_file")

        ffmpeg -ss "$frame_time" -i "$input_video" -vf "scale=-2:320" -frames:v 1 -update 1 -y "$image_file"
    done

    # Merge the extracted images into a single image.
    montage "${image_files[@]}" -tile 3x3 -geometry +0+0 "$output_image"

    # Clean up temporary files.
    for img in "${image_files[@]}"; do
        rm "$img"
    done

Full script with only those fixes applied

#!/bin/bash

# /home/david/Documents/shell-script-backups/repeat-in-all-dirs.sh /home/david/Documents/shell-script-backups/cover-clip-local-anime.sh

# THE ONLY DIFFERENCE IS A 0-FRAME START INSTEAD OF 0.1 - because the clips always have a 'start'

extract_and_merge_frames() {
    local input_video="$1"
    local output_image="$2"

    if [[ ! -f "$input_video" ]]; then
        echo "Error: Input video file '$input_video' does not exist."
        exit 1
    fi

    local duration
    duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_video")
    duration=${duration%.*}

    local frame_times=( $(echo "$duration * 0" | bc) $(printf "%.3f" $(echo "$duration * 0.125" | bc)) $(printf "%.3f" $(echo "$duration * 0.25" | bc)) $(printf "%.3f" $(echo "$duration * 0.375" | bc)) $(printf "%.3f" $(echo "$duration * 0.5" | bc)) $(printf "%.3f" $(echo "$duration * 0.625" | bc)) $(printf "%.3f" $(echo "$duration * 0.75" | bc)) $(printf "%.3f" $(echo "$duration * 0.875" | bc)) $(printf "%.3f" $(echo "$duration * 0.95" | bc)) )
    echo "$frame_times"

    local image_files=()

    for i in "${!frame_times[@]}"; do
        local frame_time="${frame_times[$i]}"
        local image_file="frame_$((i + 1)).jpg"
        image_files+=("$image_file")

        ffmpeg -ss "$frame_time" -i "$input_video" -vf "scale=-2:320" -frames:v 1 -update 1 -y "$image_file"
    done

    montage "${image_files[@]}" -tile 3x3 -geometry +0+0 "$output_image"

    for img in "${image_files[@]}"; do
        rm "$img"
    done

    echo "Merged image created: '$output_image'"
}

for f in *.mp4 *.MP4 *.avi *.AVI *.wmv *.WMV *.mpg *.MPG *.mpeg *.MPEG *.mov *.MOV; do
    [ -e "$f" ] || continue
    extract_and_merge_frames "$f" "scr-$f.jpg"
done

One thing you should also fix

You have this typo in the file loop:

*.MP$

That should be:

*.MP4

So your original loop was accidentally wrong there.

Third thing

If you want the montage tiles to have no border and no label space at all, this is fine:

montage "${image_files[@]}" -tile 3x3 -geometry +0+0 "$output_image"

If it still pads more than you want, use:

montage "${image_files[@]}" -tile 3x3 -geometry 426x320+0+0 "$output_image"

But I would test the simpler version first.