Cinematic websites|INEMA.CLUB-PRO

Lesson 2 · 15 min

From video to frames: extract, compress, and order the sequence

The quality of the scroll motion is decided here. Too few frames and the film turns into a slideshow; frames that are too big and the page won't open on a phone; names without zeros and the film jumps. You make a rehearsal clip with ffmpeg from a photo, extract every frame, and check count, order, and weight with a script.

Start the lesson
Strip of cinema negatives on a light table, with a magnifying glass over it.

In this lesson you will

  1. 1A rehearsal clip from a photo, no generator
  2. 2Extract every frame, at the clip's rate, with the width and quality you chose
  3. 3Why f_0001.jpg and not f_1.jpg, and what manifest.json does
  4. 4Check count, order, and weight before taking it to the page

Goal: turn a clip into frames at the native rate, with names in order and weight checked, ready for scroll.

01From the clip to the folder the page reads

Click each step.

Click each block in the diagram to see what it does.

Rule from INEMA's two skills: extract at the native rate. A 72 s film at 24 frames per second has 1,728 frames; taking "about 300" becomes 4 frames per second on scroll (scroll-film-studio, references/engine.md).

02A rehearsal clip, no generator

6 s, 24 frames per second, the camera going from 1.0 to 1.35 zoom over the kit's lens photo.

Terminal · make the rehearsal clip
K=~/projetos/formacao-cinema-ia-sites-v72/kit
E=${TMPDIR:-/tmp}/ensaio-sites/a2; rm -rf "$E"; mkdir -p "$E"
bash "$K/scripts/clipe.sh" "$K/exemplos/lente.jpg" "$E/clipe.mp4" 6 1.0 1.35
Example from the repository
clipe.mp4: 6,0 s · 24 fps · 1920×1080

Swap the photo for one of your product and you have a clip to practice the whole path without spending anything.

03Extract and check

quadros.sh extracts; conferir-quadros.py compares the folder with the clip.

Terminal · extract the frames and check
K=~/projetos/formacao-cinema-ia-sites-v72/kit
E=${TMPDIR:-/tmp}/ensaio-sites/a2
bash "$K/scripts/quadros.sh" "$E/clipe.mp4" "$E/quadros" 1024 6
ls "$E/quadros" | head -3
python3 "$K/scripts/conferir-quadros.py" "$E/quadros" "$E/clipe.mp4"; echo "saída: $?"
File · kit/scripts/quadros.sh
#!/usr/bin/env bash
# quadros.sh — extrai TODOS os quadros de um clipe, na taxa nativa dele, para o site rolar.
# Uso: bash quadros.sh <clipe.mp4> <pasta> [largura=1024] [qualidade=6]
#   largura: 1024–1280 px no computador, 720 px no filme vertical do celular.
#   qualidade: -q:v do JPEG (2 = melhor e mais pesado, 31 = pior); 5–6 é o ponto do curso.
# Grava <pasta>/f_0001.jpg, f_0002.jpg… (zeros à esquerda: a ordem alfabética é a ordem do filme)
# e <pasta>/manifest.json {count, fps, largura}, que a página lê para saber quantos quadros pedir.
set -euo pipefail
CLIPE=$1; PASTA=$2; LARG=${3:-1024}; Q=${4:-6}
FPS=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of csv=p=0 "$CLIPE" | python3 -c "import sys; n,d=sys.stdin.read().strip().split('/'); print(round(int(n)/int(d)))")
mkdir -p "$PASTA"; rm -f "$PASTA"/f_*.jpg
# -fps_mode passthrough: um JPEG por quadro do clipe, sem duplicar nem pular.
ffmpeg -v error -i "$CLIPE" -vf "scale=${LARG}:-2" -fps_mode passthrough -q:v "$Q" "$PASTA/f_%04d.jpg"
python3 - "$PASTA" "$FPS" "$LARG" <<'PY'
import glob, json, os, sys
from PIL import Image
pasta, fps, larg = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
q = sorted(glob.glob(os.path.join(pasta, 'f_*.jpg')))
json.dump({'count': len(q), 'fps': fps, 'largura': larg}, open(os.path.join(pasta, 'manifest.json'), 'w'))
w, h = Image.open(q[0]).size
mb = f"{sum(os.path.getsize(f) for f in q) / 1e6:.1f}".replace('.', ',')
print(f"QUADROS: {len(q)} · {w}×{h} · {mb} MB · {fps} fps")
PY
Example from the repository
QUADROS: 144 · 1024×576 · 1,5 MB · 24 fps
f_0001.jpg
f_0002.jpg
f_0003.jpg
OK: 144 quadros · 24 fps · 1024 px · 1,5 MB
saída: 0

144 frames = 6 s × 24. The folder weighs 1.5 MB at 1024 px with -q:v 6.

04The two mistakes that ruin the film the most

Too few frames and names without zeros. Both go unnoticed when you just look at the folder.

Terminal · extract wrong on purpose
K=~/projetos/formacao-cinema-ia-sites-v72/kit
E=${TMPDIR:-/tmp}/ensaio-sites/a2; mkdir -p "$E/poucos" "$E/sem-zero"
# mistake 1: "a few frames per second is enough" (6 per second from a 24 fps clip)
ffmpeg -v error -i "$E/clipe.mp4" -vf "fps=6,scale=1024:-2" -q:v 6 "$E/poucos/f_%04d.jpg"
python3 "$K/scripts/conferir-quadros.py" "$E/poucos" "$E/clipe.mp4"; echo "saída: $?"
# mistake 2: names without leading zeros (f_%d instead of f_%04d)
ffmpeg -v error -i "$E/clipe.mp4" -vf "scale=1024:-2" -q:v 6 "$E/sem-zero/f_%d.jpg"
python3 "$K/scripts/conferir-quadros.py" "$E/sem-zero" "$E/clipe.mp4"; echo "saída: $?"
Example from the repository
FALHA: 36 quadros = 6 fps (fonte 24): esperava 144
FALHA: sem manifest.json
saída: 1
FALHA: nomes sem zeros à esquerda (f_109.jpg vem antes de f_11.jpg na ordem alfabética)
FALHA: sem manifest.json
saída: 1

Both exit with code 1. The checker also demands the manifest.json, which only quadros.sh writes.

05Practice

From the clip to the checked folder, and a version of your own.

E1 · Rehearsal clip

5 min

Run the "make the rehearsal clip" block.

Done when: clipe.mp4: 6,0 s · 24 fps · 1920×1080.

E2 · Checked frames

5 min

Run the "extract the frames and check" block.

Done when: OK: 144 quadros · 24 fps · 1024 px and saída: 0.

E3 · Your own photo

15 min

Make an 8 s clip from a photo of yourself (bash clipe.sh sua-foto.jpg seu.mp4 8), extract at 1280 px with -q:v 5 and check it. Note the folder's weight.

Done when: The checker answers OK: 192 quadros · 24 fps · 1280 px with saída: 0.

06Lesson quiz

The explanation appears after you answer.

Question 1 of 4

A 6 s clip at 24 frames per second. How many frames should the folder have?

07In three sentences

Native rateOne JPEG per frame of the clip; to weigh less, adjust width and -q:v.
Orderf_0001.jpg with leading zeros and manifest.json with the count.
Checkconferir-quadros.py before taking them to the page.

Going further

Previous lessonNext lesson