This is an implementation of the algorithm shown in Posy's video: Motion Extraction.
| Input | Output |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Core idea:
- Duplicate the video.
- Invert the duplicate.
- Blend it at 50% opacity.
- Time-shift the duplicate.
Per-frame formula (always invert50):
output = 0.5 * current + 0.5 * (1 - shifted_reference)
- Python 3.10+
numpyopencv-pythonffmpeg(required so output always keeps/copies input audio)
python3 -m pip install -r requirements.txtRun the local desktop UI (Tkinter):
python3 ui.pyThe UI supports core processing features:
- Input/output file selection
- Non-RGB offsets (
--frame-offsetor--offset-seconds) - RGB offsets (
--rgb-offset-framesor--rgb-offset-seconds) - Overlay, grayscale, freeze-frame, contrast, pre-blur
- Overlay strength, saturation, trail accumulation
- Built-in presets (
Pure Motion Extraction,Blurry RGB,RGB Trail) - Live command preview and background processing
Build a macOS .app that bundles ffmpeg:
./scripts/build_mac.shOutputs:
dist/MotionExtractionUI.appdist/MotionExtractionUI-mac.zip
Optional: use a specific local ffmpeg binary instead of auto-download:
./scripts/build_mac.sh --ffmpeg /absolute/path/to/ffmpeg- macOS app workflow:
.github/workflows/build-macos.yml
Default run (uses videos/video.mp4 -> ~/Desktop/video_output_f1.mp4):
python3 motion_extraction.pyBasic (1-frame shift):
python3 motion_extraction.py input.mp4 output.mp4If you omit output.mp4, the file name is auto-generated as:
- Non-RGB:
~/Desktop/<input_stem>_output_f<frame_offset>.mp4 - RGB split:
~/Desktop/<input_stem>_output_rgb_r<R>_g<G>_b<B>.mp4 - Freeze-frame:
~/Desktop/<input_stem>_output_freeze<frame>.mp4 - If
--overlay-originalis enabled,_overlaidis appended before.mp4.
Choose non-RGB frame offset:
python3 motion_extraction.py input.mp4 output.mp4 --frame-offset 12(--frame-offset is an alias of --offset-frames; default remains 1.)
Shift by seconds:
python3 motion_extraction.py input.mp4 output.mp4 --offset-seconds 1.0--offset-seconds cannot be combined with --frame-offset / --offset-frames.
Freeze reference frame (show changes relative to one fixed frame):
python3 motion_extraction.py input.mp4 output.mp4 --freeze-frame 0Enhance larger/stronger motion:
python3 motion_extraction.py input.mp4 output.mp4 --pre-blur 5 --contrast 2.0Grayscale output:
python3 motion_extraction.py input.mp4 output.mp4 --grayscaleExtract R/G/B channels with different time offsets:
python3 motion_extraction.py input.mp4 output.mp4 --rgb-offset-frames 1,10,20Use default RGB offsets (R5,G10,B15):
python3 motion_extraction.py input.mp4 output.mp4 --rgb-offset-framesExtract R/G/B channels with per-channel seconds:
python3 motion_extraction.py input.mp4 output.mp4 --rgb-offset-seconds 0,0.08,0.16--rgb-offset-frames is in R,G,B order and overrides --offset-frames / --offset-seconds.
--rgb-offset-seconds is also R,G,B, converted to frames using the output FPS, and cannot be combined with --rgb-offset-frames.
Overlay extracted motion on top of the original video (works with both normal and RGB offsets):
python3 motion_extraction.py input.mp4 output.mp4 --overlay-originalpython3 motion_extraction.py input.mp4 output.mp4 --rgb-offset-frames 1,10,20 --overlay-originalAdjust overlay intensity:
python3 motion_extraction.py input.mp4 output.mp4 --overlay-original --overlay-strength 0.7Adjust color intensity of the motion result:
python3 motion_extraction.py input.mp4 output.mp4 --saturation 1.5Add temporal persistence trails:
python3 motion_extraction.py input.mp4 output.mp4 --trail-accumulation-frames 12Trail accumulation by seconds:
python3 motion_extraction.py input.mp4 output.mp4 --trail-accumulation-seconds 0.4Choose output codec:
python3 motion_extraction.py input.mp4 output.mp4 --codec avc1Override output FPS:
python3 motion_extraction.py input.mp4 output.mp4 --fps 30Full example with multiple features:
python3 motion_extraction.py input.mp4 --rgb-offset-frames 1,8,16 --overlay-original --overlay-strength 0.8 --saturation 1.4 --trail-accumulation-frames 8 --pre-blur 5 --contrast 2.0Notes:
--frame-offsetand--offset-framesare the same flag.--frame-offset/--offset-framescannot be combined with--offset-seconds.--rgb-offset-framesoverrides--frame-offset/--offset-seconds.--rgb-offset-secondscannot be combined with--rgb-offset-frames.--freeze-framecannot be combined with--rgb-offset-framesor--rgb-offset-seconds.--freeze-framecannot be combined with--overlay-original.--contrastmust be in[0,10].--pre-blurmust be an odd integer in[0,49](or0to disable).--overlay-strengthmust be in[0,1].--saturationmust be in[0,3].--trail-accumulation-frames(alias:--trail-accumulation) must be>= 0.--trail-accumulation-secondsmust be>= 0.--trail-accumulation-framesand--trail-accumulation-secondsare mutually exclusive.- If you pass an explicit output path, that path is used instead of auto naming.
- If Desktop is unavailable, auto-generated output falls back to your home directory.
- Audio is always copied from input to output automatically (video uses OpenCV, audio mux uses
ffmpeg). - For source runs,
ffmpegmust be installed orMOTION_FFMPEG_PATHmust point to it.
Run tests:
python3 -m unittest -v




