Years ago, Tony Zhou and Taylor Ramos made 28 video essays about film form called Every Frame a Painting and it’s incredible in teaching outsiders a whole new way to think about the art of film. The title is perfect. The content is just stunning, simple ways to look at masters of a form at work.
Lots of folks are known for one-shot takes, but this shows how Spielberg sneaks in gorgeous “oners” that do work without calling attention to themselves.
This essay on Fincher is great, but I love the little golden nugget about how spacing shows the evolving relationship between Mills and Somerset
That title always struck me. Every Frame a Painting. That’s gotta be a bar filmakers strive for. Some make it.
Some movies are just so damn beautiful. Just gorgeous.
Like Across the Spider-Verse. Yowza!





Like Sita Sings the Blues! Beautiful.




Like The Fountain




Like the one that you like that isn’t my cup of tea.
Might be nice to see an image from it, right there behind all of your terminals and windows and such, set as your wallpaper. If every frame’s a painting, set a random one as your wallpaper whenever I like it.
So here’s the plan. I want it. So I made it for me. You can have it. But here’s the terms of the deal. I made it for me, so if it doesn’t work for you, you have to make it work for you. If it causes you problems, those are not my problems. If you don’t agree, this isn’t for you.
This will take as an input a movie file, anything that ffmpeg can deal with. You’ll need to install ffmpeg – look on the official site for instructions.
By default, it won’t use the first 5 or last 10 minutes since that’s often the credits. But you can override this.
We’ll find out how many frames are in that remaining part of the movie.
We’ll pick one randomly and extract it from the movie.
Then we’ll set it as your wallpaper. Nice!
Want to change this often? Set up a cron job!
Pulling a single frame out the middle of a movie is CPU intense, so you probably want to use nice in your cron job so it doesn’t interfere with the rest of your work.
Here’s the code, save this in a file called every_frame_a_wallpaper.zsh
and then chmod u+x every_frame_wallpaper.zsh
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | #! /bin/zsh # This is a pretty processor intensive set of tasks! You should probably nice this script # as in call it with nice -n 10 "every_frame_a_wallpaper.zsh /path/to/video.mkv" SCRIPT_NAME=$( basename "$0" ) # I like a nice log file for my cron jobs function LOG() { echo -e "$(date --iso-8601=seconds): [$SCRIPT_NAME] : $1" } # set up some options local begin_skip_minutes=5 local end_skip_minutes=10 local wallpaper= "$HOME/Pictures/wallpaper.png" local usage=( "$SCRIPT_NAME [-h|--help]" "$SCRIPT_NAME [-b|--begin_skip_minutes] [-e|--end_skip_minutes] [<video file path>]" "Extract a single random frame from a movie and set it as wallpaper" "By default, skips 5 minutes from the beginning and 10 from the end, but this is overridable" ) # the docs suck on zparseopts so let this be a reference for next time # -D pulls parsed flags out of $@ # -F fails if we find a flag that wasn't defined # -K allows us to set default values without zparseopts overwriting them # Remember that the first dash is automatically handled, so long options are -opt, not --opt zparseopts -D -F -K -- \ {h,-help}=flag_help \ {b,-begin_skip_minutes}:=begin_skip_minutes \ {e,-end_skip_minutes}:=end_skip_minutes \ || return 1 [[ -z "$flag_help" ]] || {print -l $usage && return } if [[ -z "$@" ]] { print -l "A video file path is required" print -l $usage && return } else { MOVIE= "$@" } if [[ $DISPLAY ]] then LOG "interactively running, not in cron" else LOG "Not running interactively, time to export the session's environment for cron" export $( xargs -0 -a "/proc/$(pgrep gnome-session -n -U $UID)/environ" ) 2> /dev/null fi LOG "skipping $begin_skip_minutes[-1] minutes from the beginning" LOG "skipping $end_skip_minutes[-1] minutes from the end" LOG "outputting the wallpaper to $wallpaper" LOG "using file $MOVIE" LOG "Let's get a frame from ${MOVIE}" ; LOG "What's the duration of the movie?" DURATION=$(ffprobe - v error -show_entries format =duration -of default=noprint_wrappers=1:nokey=1 \ $MOVIE); DURATION=$( printf '%.0f' $DURATION); LOG "Duration looks like ${DURATION} seconds" ; LOG "what's the frame rate?" FRAMERATE=$(ffprobe - v error -select_streams v :0 \ -show_entries \ stream=r_frame_rate \ -print_format default=nokey=1:noprint_wrappers=1 $MOVIE) FRAMERATE=$( bc -l <<< "$FRAMERATE" ); FRAMERATE=$( printf "%.0f" $FRAMERATE); LOG "Looks like it's roughly $FRAMERATE" FRAMECOUNT=$( bc -l <<< "${FRAMERATE} * ${DURATION}" ); FRAMECOUNT=$( printf '%.0f' $FRAMECOUNT) LOG "So the frame count should be ${FRAMECOUNT}" ; SKIP_MINUTES=$begin_skip_minutes[-1] SKIP_CREDITS_MINUTES=$end_skip_minutes[-1] LOG "We want to skip $SKIP_MINUTES from the beginning and $SKIP_CREDITS_MINUTES from the end" . SKIP_BEGINNING_FRAMES=$( bc -l <<< "${FRAMERATE} * $SKIP_MINUTES * 60" ); LOG "So $SKIP_MINUTES * 60 seconds * $FRAMERATE frames per second = $SKIP_BEGINNING_FRAMES frames to skip from the beginning." SKIP_ENDING_FRAMES=$( bc -l <<< "${FRAMERATE} * $SKIP_CREDITS_MINUTES * 60" ); LOG "So $SKIP_CREDITS_MINUTES * 60 seconds * $FRAMERATE frames per second = $SKIP_ENDING_FRAMES frames to skip from the ending." USEABLE_FRAMES=$( bc -l <<< "$FRAMECOUNT - $SKIP_BEGINNING_FRAMES - $SKIP_ENDING_FRAMES" ); UPPER_FRAME=$( bc -l <<< "$FRAMECOUNT - $SKIP_ENDING_FRAMES" ) LOG "That leaves us with ${USEABLE_FRAMES} usable frames between $SKIP_BEGINNING_FRAMES and $UPPER_FRAME" ; FRAME_NUMBER=$(shuf -i $SKIP_BEGINNING_FRAMES-$UPPER_FRAME -n 1) LOG "Extract the random frame ${FRAME_NUMBER} to ${wallpaper}" ; LOG "This takes a few minutes for large files." ; ffmpeg \ -loglevel error \ -hide_banner \ -i $MOVIE \ -vf "select=eq(n\,${FRAME_NUMBER})" \ -vframes 1 \ -y \ $wallpaper WALLPAPER_PATH= "file://$(readlink -f $wallpaper)" LOG "Set the out file as light and dark wallpaper - using ${WALLPAPER_PATH}" ; gsettings set org.gnome.desktop.background picture-uri-dark "${WALLPAPER_PATH}" ; gsettings set org.gnome.desktop.background picture-uri "${WALLPAPER_PATH}" ; |
In my crontab I call it like this:
1 2 | # generate a neat new background every morning 0 4 * * * nice -n 10 ~/crons/every_frame_a_wallpaper.zsh -b 5 -e 12 /home/mk/Videos/Movies/Spider-Man_Across_the_Spider-Verse.mkv >> ~/.logs/every_frame_a_wallpaper/`date +"\%F"`-run.log 2>&1 |