A Google Play store listing page can include one video: a promo of around 30 seconds.

I made mine without opening a video editor. The only input is screen recordings from a real device. Everything else is shell scripts and ffmpeg, and even the background music is synthesized as waveforms with Python’s standard library. Zero external assets.

I made 3 of them, so here’s how, written so you can reproduce it.

Structure

The build has 2 stages.

build_assets.sh   Generates device frames, rounded-corner masks, shadows, and caption images
build_promo.sh    Lines up the scenes, concatenates them, and adds the BGM
make_bgm.py       Synthesizes the BGM from waveforms (standard library only)

There are 6 scenes, 30.2 seconds in total. For the diary app version:

1. Hook: "Diaries? Over in 3 days."
2. You only need to write one line
3. Write it, and the AI replies with a short comment
4. Your calendar fills with color
5. A graph shows how your mood changes
6. End card

The goal of the first 2 seconds is to let the people it won’t resonate with drop off, so the hook isn’t a feature but the problem that person has.

Trap 1: zoompan with x='0' crops the right edge of the screen

For a slow push-in on a still image (the Ken Burns effect), I use zoompan. Written the obvious way, it looks like this:

zoompan=z='min(zoom+0.0008,1.2)':x='0':y='0':d=200

This zooms in anchored to the top-left, so the right side of the screen gets cut off. On a phone screenshot, the top right is where the time, the battery, and the Premium badge were. The very thing I wanted to show was the first to disappear.

Fix it to zoom from the center:

x='iw*0.5-(iw/zoom*0.5)'
y='ih*0.5-(ih/zoom*0.5)'

Trap 2: Test ads show up in the footage

If you screen-record a development build, AdMob’s test ad banner gets recorded as-is. A strip saying “Test Ad” in an official store listing video is clearly not OK.

Instead of re-shooting, I painted over it with the app’s background color.

drawbox=x=68:y=770:w=584:h=102:color=0xF7F8FC:t=fill

There are 2 points here.

Paint with the app’s background color, not black. A black box sitting there looks unnatural in its own way.

Make the mask a little larger than the ad’s actual size. If you paint exactly its size, a 1px border around the edge of the banner remains. I failed this way once and only noticed after exporting.

Trap 3: Don’t stop at removing the ad

In the picture book app’s footage, after removing the test ad, I found one more thing.

The IME suggestion bar showed a wrong conversion.

公園で無視を見つけ

The text was supposed to mean “found a bug in the park,” but the Japanese input method had turned 虫 (“bug”) into 無視 (“ignoring”), which is pronounced the same. Since it’s a scene demonstrating text input, the whole keyboard is in the shot. I painted this over with the background color as well.

drawbox=x=0:y=1014:w=720:h=72:color=0xEAEEF2:t=fill

Don’t check the footage once and call it done. Right next to the thing you want to remove, there’s another thing you want to remove. A recording on a real device captures everything, whatever your intentions.

Trap 4: Video whose resolution changes midway

In the picture book app, input and generation happen in portrait, and read-aloud is in landscape. Within a single recording file, the resolution changes from 720x1600 to 1600x720.

The annoying part is that ffprobe only reports the resolution at the start. If you build your processing assuming portrait, it falls apart in the second half.

I noticed when hstack failed while I was making a contact sheet (an overview image of the footage). The fix is to make the sizes match before stacking.

scale=480:480:force_original_aspect_ratio=decrease,pad=480:480:(ow-iw)/2:(oh-ih)/2

By the way, in the end I kept this portrait/landscape mix. Reading a picture book on a landscape screen feels like a picture book in itself. I made 2 kinds of device frames, one for portrait and one for landscape; landscape scenes sit large in the center, with the captions pushed toward the top. Sometimes it’s faster to take on a footage constraint as part of the direction.

Trap 5: A function with a fixed output filename breaks on the second call

I called a shell function that creates rounded-corner masks and shadows for both landscape and portrait.

roundrect 1920 1080 out.png   # landscape
roundrect 1080 1920 out.png   # portrait ← overwrites the first one

The second call clobbers the first, and the downstream alphamerge fails on a size mismatch. The error points at the compositing step, so it took me a detour to realize the cause was the function’s output path.

Adding a suffix per call fixed it. A function with a fixed intermediate filename breaks the moment it’s called twice.

When footage is too short, use a still

In the diary app, the real footage of the screen where the AI’s comment appears was only 1.4 seconds long: from 59.4 to 60.8 seconds into the recording. After that, it transitions to a different screen.

The scene I wanted needed 6.6 seconds. Instead of re-shooting, I pulled a still frame and slowly pushed in with zoompan. Because there’s movement, you won’t notice it’s a still unless someone tells you.

For the same reason, the graph screen is also a still. With real footage, it transitions to a different screen partway through. If you ask “does this need to be moving?” one scene at a time, most re-shoots turn out to be unnecessary.

BGM can be made with the standard library

Stock music requires checking licenses, and for a store listing video you also have to check whether commercial use is allowed. So I made it in Python.

Just changing the chord progression and the tones gives each of the 3 its own mood.

AppBPMPercussionTone
Wait-time prediction112With kickBright pad
Diary100No kickCalm pad
Bedtime picture book76None (shaker removed too)Music-box-like pluck

Taking the kick out of the bedtime app’s BGM was the right call. With percussion, it stops being a bedtime video. 76 BPM is slower than a heartbeat, and that’s deliberate.

Color schemes can be philosophically opposite from app to app

When making the 3rd one, I tried to reuse the 1st one’s settings and failed.

  • Wait-time prediction app: dark navy theme
  • Diary app: light theme in pale orange (changed partway through)
  • Picture book app: dark navy with a purple and gold glow

Only the diary app has a light background. Bring over the text colors from the dark theme as-is and nothing is readable. I inverted all the text colors, and because the device blended into the background, I made a dedicated warm brown shadow for it.

On a dark background, shadows work naturally, but on a light background, unless you deliberately design the shadow, elements sink instead of standing out.

Burn the disclaimer into the video

These apps use credits or free quotas for AI generation, so I put one line in the video.

※無料プランは毎日1話。広告を見ると、もう1話つくれます。

It reads: “Free plan: 1 story per day. Watch an ad and you can make 1 more.”

The key is to match the wording used on the in-app paywall. If the video and the app phrase it differently, that alone creates distrust.

On the other hand, I don’t write specific credit counts. Showing numbers creates an incentive for abuse, and I’d have to remake the listing video every time the spec changes.


I also made vertical Shorts versions from the same footage. That, along with where I got stuck automating uploads to YouTube, is here. The time personal information showed up in the recorded footage is here.