Find the longest seamlessly-loopable segment in a video via normalized cross-correlation.
Project description
loopsmith
Find the longest seamlessly-loopable segment in a video.
Given a clip, loopsmith figures out the longest stretch you can pull out and
play on repeat without a visible jump — the point where some later frame looks
almost exactly like an earlier one, so playback can snap back to the start with
no seam. Handy for turning B-roll, animations, or background footage into clean
loops (looping wallpapers, GIFs, video backdrops, and so on).
How it works
The core of it is a single matrix multiply.
- Sample every Nth frame, shrink each to a 160px thumbnail, convert to grayscale, and z-score normalize it (subtract the mean, divide by the standard deviation).
- Stack the normalized frames into a matrix and multiply it by its own
transpose. For z-scored vectors, each dot product (divided by the pixel
count) is the normalized cross-correlation between two frames — a
similarity score from -1 to 1 where 1 means "visually identical." So one
mat @ mat.Tproduces the score for every pair of frames at once. - For each pair of frames, the gap between them is a candidate loop length. Among all pairs whose similarity clears a threshold, the one with the largest gap wins: those two near-identical frames are the in- and out-points of the longest seam-free loop.
Because the expensive step is one BLAS matmul, it stays quick even though it's effectively comparing every frame against every other frame.
Install
pip install loopsmith
From source, for development (includes the test deps):
pip install -e ".[dev]"
Needs Python 3.9+, OpenCV, and NumPy — the last two are pulled in automatically.
Usage (CLI)
# Analyze a single video
loopsmith clip.mp4
# Batch every .mp4/.mov in a directory
loopsmith ./footage/
# Loosen or tighten the similarity threshold (default 0.85)
loopsmith clip.mp4 --threshold 0.80
# Sample every 5th frame instead of every 3rd (faster, coarser)
loopsmith clip.mp4 --downsample 5
# Detailed report: top 10 loops by similarity and by length
loopsmith clip.mp4 --detail
# Find the best loop closest to a target length, in seconds
loopsmith clip.mp4 --detail --target-length 6
Batch mode prints the best loop per file as a table; --detail (single file)
prints the top candidates plus a yes/no "is this clip loopable" verdict.
Usage (library)
from loopsmith import extract_frames, find_best_loop
rows, frame_indices, fps, total = extract_frames("clip.mp4", downsample=3)
loop = find_best_loop(rows, frame_indices, threshold=0.85)
if loop:
start_frame, end_frame, ncc = loop
seconds = (end_frame - start_frame) / fps
print(f"Loop {start_frame}->{end_frame} ({seconds:.1f}s) at {ncc:.1%} similarity")
Other helpers: find_best_for_target() (the loop closest to a target
duration), find_top_loops() (ranked candidates), and analyze_video() (a
one-call summary dict).
Notes & limitations
- Visual only. It compares frames, not audio. A visually seamless loop can still have an audible seam — check the sound separately if that matters.
- Cut resolution. In/out points are accurate to within
--downsampleframes, since that's the sampling step. Lower it for tighter cuts at the cost of speed. - Scales as O(N²) in sampled frames. It builds an N×N similarity matrix, so
for long videos raise
--downsampleto keep time and memory in check. - It reports, it doesn't cut. Output is frame indices / timestamps and similarity scores; trimming the actual clip (e.g. with ffmpeg) is up to you.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file loopsmith-0.1.0.tar.gz.
File metadata
- Download URL: loopsmith-0.1.0.tar.gz
- Upload date:
- Size: 8.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
045ac2f6d6d8b2fd6b39fc1de07ec6d67a0131a75bb8d2395b9c83ad08831b61
|
|
| MD5 |
839b2447fc79c66267773ae43cf6d476
|
|
| BLAKE2b-256 |
e12bc09b4e9150b3e3a72c44747546ee374d73e6a526d29c2da61c77a6628ef8
|
Provenance
The following attestation bundles were made for loopsmith-0.1.0.tar.gz:
Publisher:
publish.yml on jpratt9/loopsmith
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopsmith-0.1.0.tar.gz -
Subject digest:
045ac2f6d6d8b2fd6b39fc1de07ec6d67a0131a75bb8d2395b9c83ad08831b61 - Sigstore transparency entry: 1614346268
- Sigstore integration time:
-
Permalink:
jpratt9/loopsmith@3f5f2212a5740feeb441f74168712aa13f285680 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jpratt9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3f5f2212a5740feeb441f74168712aa13f285680 -
Trigger Event:
release
-
Statement type:
File details
Details for the file loopsmith-0.1.0-py3-none-any.whl.
File metadata
- Download URL: loopsmith-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87c906aec9f72b5bcb3c7f3a0996e6e3a87943931f46fb35a27e066a7809ae54
|
|
| MD5 |
c89c882ae2fbbef2d4b46cf07377bbd2
|
|
| BLAKE2b-256 |
9cd1736d3751f1b49ecc0e6fe7c9acbab187b000ca447680e552de7dc4f92c7d
|
Provenance
The following attestation bundles were made for loopsmith-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on jpratt9/loopsmith
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
loopsmith-0.1.0-py3-none-any.whl -
Subject digest:
87c906aec9f72b5bcb3c7f3a0996e6e3a87943931f46fb35a27e066a7809ae54 - Sigstore transparency entry: 1614346334
- Sigstore integration time:
-
Permalink:
jpratt9/loopsmith@3f5f2212a5740feeb441f74168712aa13f285680 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jpratt9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3f5f2212a5740feeb441f74168712aa13f285680 -
Trigger Event:
release
-
Statement type: