短い動画を何本か比較するとき、candidate.mp4、candidate (1).mp4 のようなファイル名だけでは、別の候補なのか、同じファイルを再ダウンロードしたのか判断しにくくなります。
ここでは生成した動画の検収を始める前に、バイト列が完全に同じファイルをまとめる方法を扱います。似た映像を検出する方法ではありません。再エンコード、音声の変更、コンテナのメタデータ変更があれば、見た目が同じでも別グループになります。
対象とする作業
Python 3.10以降の標準ライブラリだけを使い、指定フォルダ直下の .mp4、.mov、.webm を読み取ります。元ファイルの変更や削除は行いません。
写真からダンス動画を作る Bonito Bonito AIのような写真ベースの動画ツール で得た候補も、ダウンロード後は通常のファイルとして整理できます。以下は特定サービスのAPI連携ではなく、手元の動画ファイルに対する一般的な処理です。
ファイル名と内容を分けて考える
ファイル名は比較時の識別子として使い、同一性の確認にはSHA-256を使います。大きなファイルも一度にメモリへ読み込まないよう、1 MiBずつ読みます。
# duplicate_videos.py
import argparse
import hashlib
import json
from pathlib import Path
SUFFIXES = {".mp4", ".mov", ".webm"}
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def find_duplicates(folder: Path) -> list[dict]:
groups: dict[str, list[str]] = {}
for path in sorted(folder.iterdir()):
if path.is_file() and path.suffix.lower() in SUFFIXES:
digest = sha256_file(path)
groups.setdefault(digest, []).append(path.name)
return [
{"sha256": digest, "files": names}
for digest, names in groups.items()
if len(names) > 1
]
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("folder", type=Path)
args = parser.parse_args()
if not args.folder.is_dir():
parser.error("folder must be an existing directory")
print(json.dumps(find_duplicates(args.folder), ensure_ascii=False, indent=2))
実行例です。
python duplicate_videos.py ./candidates > duplicate-report.json
重複がなければ [] を出力します。サブフォルダは走査しません。読み取り権限がないファイルなどで処理に失敗した場合は例外で停止するため、終了コードも確認してください。途中で書き換えられるファイルは対象から外し、ダウンロード完了後のフォルダで実行します。
比較ロジックを小さなデータで確かめる
動画を用意する前に、同じバイト列と違うバイト列でグループ分けを確認できます。以下の .mp4 はテスト用の名前であり、再生可能な動画ではありません。
# test_duplicate_videos.py
from pathlib import Path
from tempfile import TemporaryDirectory
from duplicate_videos import find_duplicates
with TemporaryDirectory() as temp:
folder = Path(temp)
(folder / "a.mp4").write_bytes(b"same bytes")
(folder / "a-copy.MP4").write_bytes(b"same bytes")
(folder / "b.mp4").write_bytes(b"different bytes")
(folder / "memo.txt").write_bytes(b"same bytes")
result = find_duplicates(folder)
assert len(result) == 1
assert set(result[0]["files"]) == {"a.mp4", "a-copy.MP4"}
assert len(result[0]["sha256"]) == 64
(folder / "a-copy.MP4").unlink()
assert find_duplicates(folder) == []
print("OK")
python test_duplicate_videos.py
このテストで確認するのは、同一内容のグループ化、大文字拡張子の扱い、対象外ファイルの除外、重複がない場合の出力です。動画のデコードや画質は検証しません。
重複検出と映像の承認は別の工程
レポートが出ても自動削除はしません。どの名前を比較メモに使っているかを確認してから、代表ファイルを決めます。たとえば candidate-03.mp4 を代表に選んだ場合は、元の名前とハッシュをレビュー表に残すと、後で比較対象を追いやすくなります。
その後に、顔の変化、手足の動き、画面端の欠け、音声の有無と内容を人が確認します。SHA-256が同じでも、動画として適切だと判断したことにはなりません。
暗号学的ハッシュの衝突可能性はゼロではありません。また、このコードは敵対的な改ざんへの防御やファイル形式の検証を目的としていません。通常のダウンロード整理では「同じファイルを何度も見てしまう」手間を減らす補助として使い、見た目の類似判定が必要になった段階で別の処理を追加するのが扱いやすいと思います。