deepfakes/faceswap · critical · FaceswapError

There is a mismatch between the number of frames found in…

Error message

There is a mismatch between the number of frames found in the video file ({len(pts_time)}) and the number of frames found in the alignments file ({len(self.data)}).\nThis can be caused by a number of issues:\n  - The video has a Variable Frame Rate and FFMPEG is having a hard time calculating the correct number of frames.\n  - You are working with a Merged Alignments file. This is not supported for your current use case.\nYou should either extract the video to individual frames, re-encode the video at a constant frame rate and re-run extraction or work with a dedicated alignments file for your requested video.

What it means

Raised by Alignments.save_timecodes/update flow (lib/align/alignments.py) after merging video frame timestamps (pts_time from FFProbe) into the alignments data: the number of keys in the alignments file must equal the number of frames FFProbe found in the video. A mismatch means the alignments were generated against a different frame set than the current video.

Solutions

  1. Extract the video to individual PNG frames and run the job against the frames folder instead of the video file
  2. Re-encode the video at a constant frame rate (e.g. ffmpeg -i in.mp4 -vsync cfr -r <fps> out.mp4) and re-run extraction
  3. Use a dedicated alignments file for this video rather than a merged alignments file (check with the alignments tool that frame counts match)

Example fix

# before: VFR video used directly
faceswap.py align -a alignments.fsa -v input.mov  # -> frame count mismatch

# after: re-encode CFR first
ffmpeg -i input.mov -vsync cfr -r 30 -pix_fmt yuv420p input_cfr.mp4
faceswap.py align -a alignments_cfr.fsa -v input_cfr.mp4
Defensive patterns

Strategy: validation

Validate before calling

import subprocess, json

def ffprobe_frame_count(video):
    out = subprocess.check_output([
        "ffprobe", "-v", "error", "-select_streams", "v:0",
        "-count_packets", "-show_entries", "stream=nb_read_packets",
        "-of", "json", video])
    return json.loads(out)["streams"][0]["nb_read_packets"]

# compare against len(alignments.frames) before running the video-meta job

Try / catch

try:
    alignments.save_timecodes(...)
except FaceswapError as err:
    if "mismatch between the number of frames" in str(err):
        # fall back to frames-based workflow
        extract_to_frames(video)
    else:
        raise

Prevention

When it happens

Trigger: Calling the alignments tool job that writes video metadata (e.g. 'extract'/'sort' video-meta update) on a Variable Frame Rate video where FFProbe miscounts frames, or supplying an alignments file that covers more than one video (merged alignments) or was built from extracted frames then reused against a re-encoded video.

Common situations: Processing a screen recording or phone-captured VFR video; using one combined alignments file across multiple clips; re-encoding a video (changing frame count) after extraction; ffmpeg/ffprobe version differences in frame counting.

Related errors


AI-assisted analysis of deepfakes/faceswap@f530cb7508 (2026-08-15). Data as JSON: /api/errors/0159eada119a10ce. Report an issue: GitHub.

Appendix: source

Thrown at lib/align/alignments.py:198

        sample_filename = next(fname for fname in self.data)
        basename = sample_filename[:sample_filename.rfind("_")]
        ext = os.path.splitext(sample_filename)[-1]
        logger.debug("sample filename: '%s', base filename: '%s' extension: '%s'",
                     sample_filename, basename, ext)
        logger.info("Saving video meta information to Alignments file")

        for idx, pts in enumerate(pts_time):
            meta:  dict[T.Literal["pts_time", "keyframe"], int] = {"pts_time": pts,
                                                                   "keyframe": idx in keyframes}
            key = f"{basename}_{idx + 1:06d}{ext}"
            if key not in self.data:
                self.data[key] = AlignmentsEntry(video_meta=meta)
            else:
                self.data[key].video_meta = meta

        logger.debug("Alignments count: %s, timestamp count: %s", len(self.data), len(pts_time))
        if len(self.data) != len(pts_time):
            raise FaceswapError(
                "There is a mismatch between the number of frames found in the video file "
                f"({len(pts_time)}) and the number of frames found in the alignments file "
                f"({len(self.data)}).\nThis can be caused by a number of issues:"
                "\n  - The video has a Variable Frame Rate and FFMPEG is having a hard time "
                "calculating the correct number of frames."
                "\n  - You are working with a Merged Alignments file. This is not supported for "
                "your current use case."
                "\nYou should either extract the video to individual frames, re-encode the "
                "video at a constant frame rate and re-run extraction or work with a dedicated "
                "alignments file for your requested video.")
        self._io.save()

    # << VALIDATION >> #
    def frame_exists(self, frame_name: str) -> bool:
        """Check whether a given frame_name exists within the alignments :attr:`data`.

        Parameters
        ----------

View on GitHub (pinned to f530cb7508)