Tag Archives: code

Every Frame a Wallpaper

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

#! /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:

# 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

Automated export of your goodreads library

Goodreads used to have an API but they stopped giving access and it looks like they are shutting it down. A real garbage move.

I like to be able to use my data that I put in so I wrote a script to automatically download my data regularly. Then I can do stuff like check to see if books I want are in the library or keep my own list or analytics, etc.

Here’s the python script to export your good reads library, hope it helps you. I’ll put it in the public domain.

Updated to add: I got tired of dealing with places that do garbage moves. I left GoodReads for BookWyrm and it’s better.

Week 3038

Tap Tap…

So, I’ve been a busy boy. Biking has started up again and I got a few days in this week.

Kids

Max has been learning to read pretty well and is sounding out words he sees all over the place. I like that he’s teaching himself to some extent. At school he’s only in the B reading group, but at home he can get through E and F books. It’s really exciting because reading is such a big part of my life that I really want to be able to share this with him.

As a result of showing Max some bees and bombs gifs he was into asking for that every night for months. I almost ran out of internet art! Slowly I got him into just listening to me as I read The Watch aloud.

Wednesdays Sam and I go to special therapy with Beryl Nightingale to learn how to deal with Swale’s twice exceptional behavior. I think we’re making progress because this is the first week back from his spring break and when I picked him up from school his teacher said he participated in everything and has really improved from the beginning of the year.

Zelda has turned into a little chatterbox. She just started naming circles and triangles, she’s got opinions about everything and she’s super funny and super cute. She’s doing a lot of pretending to be dogs or lions and is always ready to go on a walk.

Code

At work I’ve managed to spend some time doing interesting code work. I probably shouldn’t but it was too juicy to not grab. We’re changing the namespace of our client libraries to better brand them. That’s good! But it means that anyone who’s currently using our client libraries would have the code completely break for them. That’s bad!

So I’ve written a little utility that wraps the client library python wheel and outputs a set of legacy wrapper scripts that bridges everything and does it dynamically!

I call it snowchains (a wheel wrapper, getit?) and it was also a good chance to get to know sphinx, tox, RestructuredText, Abstract Syntax Trees and python reflection internals – a real geekfest of learning. Hope to get it open sourced if possible.

Speaking of a real geekfest of learning, I snapped up the python humblebundle super quick.

Kyle at work helped me figure out an issue with jumpstart having problems installing z. It seems that if you run a script in non-interactive mode it isn’t sourcing all the resource files so it doesn’t see certain paths so it can’t tell that my tool is already installed.

Grunting and Sweating

On Thursday I registered for the City Challenge New York Obstacle Race. Some folks from work are joining me and I’m hoping you might as well. The last one was fun other than it raining. I put in some work and Vimal, the proprietor of Cafe Hudson came out to be my personal photographer. He got some verrrry sweet photos.

Not bad for an old guy

Cinco de Mala

If you can make it to Szechuan Mountain House 6pm on Sunday we’re going to go eat some crazy fizzy spicy Szechuan food. This place specializes in delicious numbing Mala food – I stumbled on it while exploring on my free night out a week or 2 ago and was really impressed. I’ve never eaten anything quite like it. Also I will turn as old as an actual mountain.

How to show WordPress page hierarchies

Got a request for help from some the Eyebeam list. 1 The folks organizing the Urban Wilderness Action Center for the international Electrosmog festival 2 wanted some usability help.  Navigating between subpages of their projects was too confusing.

If you like the way the sub pages are listed out on, say, the Berlin Micro-Turf Expedition – here you go.

<?php
// Does this even have a parent?
if($post->post_parent) {
$parent_title = get_the_title($post->post_parent);
?>
<a href="<?php echo get_permalink($post->post_parent) ?>"><?php echo $parent_title;?></a>:
<?php } ?>
<?php
//ok let's find our sibling pages, but we don't need to list the current page
if ( ($post->post_parent) && (get_post($post->post_parent)->post_parent) )
$children = wp_list_pages("title_li=&child_of=".$post->post_parent."&exclude=".$post->ID."&echo=0");
else
$children = wp_list_pages("title_li=&child_of=".$post->ID."&echo=0");
if ($children) { ?>
<ul style="display:inline">
<?php echo $children; ?>
</ul>
<?php } ?>
  1. The Eyebeam folks are doing a cool skillshare on how “Tools and Models for Online Collaboration”  (back)
  2. It starts March 20th!  It’s completely free!  Go!   (back)

The beauty of Ruby’s array subtraction operator

Today I had to a set of email addresses, one per line, from which I had to remove the addresses of folks that said “Don’t email me.” Those emails were in a separate file, one address per line.

I figured I’d have to do this again, so I wrote a ruby script to automate it. Below, stripper.rb

#put each address in an array, remove whitespace and make it all lowercase
 potential_emails = IO.readlines("potentials.txt").map! {|email| email.strip.downcase}
 delete_emails = IO.readlines("donotemail.txt").map! {|email| email.strip.downcase}

#use the beauty of ruby's array subtraction operator
 puts potential_emails - delete_emails

Simple, terse and readable.  Lovely!