Creating a Ghost in the Shell screensaver for macOS
Ghost in the Shell (1998) has a brief interlude containing gorgeously drawn scenes of a dystopian city modelled after Hong Kong.
I've wanted to create a screen saver that loops this scene over and over for a while, and I finally got around to doing it.
My first idea was to fork JohnCoates/Aerial and repurpose it for this. But I stumbled on SaveHollywood, which is a macOS screensaver that allows you to supply any custom video(s) to play. So that was a significant chunk of work done.
Producing the video turned out to be tricker than I initially thought. I first scrubbed my copy of GitS to find the exact timestamps for the scenes that I want to export:
33:04 to 33:53
34:04 to 36:20
I wrote a quick ffmpeg
invocation to slice it:
ffmpeg -i ghost-full.mp4 -ss "00:33:04" -to "00:33:53" \
-c copy ghost-1.mp4
But the resulting video didn't work correctly. It had a few seconds of no output in the beginning. I found out that this was because of a concept called "index keyframes", or i-frames
, which for this task were the only frames that I could crop from without re-encoding and without exporting gaps. Re-encoding has the potential to introduce a loss of quality which is something I wanted to avoid.
Finding the i-frames
can be done with a tool that is part of the ffmpeg
brew
distribution, ffprobe
:
ffprobe -select_streams v -show_frames \
-show_entries frame=pict_type \
-of csv ghost-full.mp4 > ghost-frames.csv
This will output the full list of frames to a ghost-frames.csv
file. To get the i-frames
:
cat ghost-frames.csv | grep -n I | cut -d ':' -f 1 > ghost-i-frames.csv
Then you can peek around vim i-frames.csv
to see the exact frame numbers. However, what I knew was the timestamp, 00:33:04
, not the frame number.
The timestamp in seconds is:
33 * 60 + 4 = 1984
To get the frame, you multiply by the framerate. To get the framerate:
ffprobe -v 0 -of compact=p=0 -select_streams 0 \
-show_entries stream=r_frame_rate ghost-full.mp4
Which returns 24000/1001
, which is 23.976023976
. The former is better for arithmetic, so:
1984 * (24000 / 1001) = 47568.431568432
So I needed an i-frame
somewhere around frame 47568
. Browsing i-frames.csv
I settled on 47556
, but after some trial and error I found the right one to be at the 00:33:02.898
timestamp.
Apply the same process to the end section for my first clip and to both start and end of my second clip, and I got:
ffmpeg -i ghost-full.mp4 -ss "00:33:02.898" -to "00:33:52.905" -c copy ghost-1.mp4
ffmpeg -i ghost-full.mp4 -ss "00:34:03.500" -to "00:36:19.969" -c copy ghost-2.mp4
Plugging these into SaveHollywood
produced great results, but the transition between the two videos did include a half-second of a black screen, which was slightly jarring. So I concatenated them:
$ cat filelist.txt
file 'ghost-1.mp4'
file 'ghost-2.mp4'
$ ffmpeg -f concat -safe 0 -i filelist.txt -c copy ghost-concat.mp4
And now I had what I set out to do, but I felt that it could also do with some different music. I downloaded the OST from YouTube using youtube-dl
, plugged it into Audacity, and exported a crop of the soundtrack that I wanted to use. Then finally:
ffmpeg -i ghost-concat.mp4 -i ghost-audio.mp3 -codec copy -shortest ghost-saver.mov
I had to export the final version as .mov
to get the sound to play in QuickLook/QuickTime/SaveHollywood.
And that's it.