Pythonで動画整理!サブフォルダ奥深くの動画を重複なしで一括コピー

大量の動画ファイルをスマートに整理!Pythonスクリプトで重複も自動回避してコピーする方法

日頃から動画コンテンツを扱っている方にとって、素材ファイルの整理は悩みの種ですよね。特に、様々なフォルダに散らばった動画ファイルを一箇所に集めたい、あるいはバックアップを取りたい、といった場面は多いはずです。しかし、手作業でコピーしていると、既に同じ名前のファイルがコピー先に存在した場合に、上書きするかスキップするかといった確認ダイアログが何度も表示され、非常に手間がかかります。

この記事では、このような悩みを解決するため、Pythonを使って指定したフォルダ配下にある全ての動画ファイルを、コピー先のフォルダにまとめてコピーし、さらに同名ファイルが存在する場合は自動で別名を付けてくれる便利なスクリプトとその使い方、そしてコードの解説を行います。プログラミング初心者の方でも理解できるように、基本的なことから丁寧に解説していきます。

このスクリプトを活用することで、動画ファイルの整理・バックアップ作業が劇的に効率化されるでしょう。ぜひ最後までお読みください。

目次

このスクリプトの概要と目的

今回ご紹介するPythonスクリプトの主な目的は以下の通りです。

  • 指定されたコピー元フォルダのサブフォルダを含め、再帰的に動画ファイルを探索する。
  • 探索で見つかった動画ファイルを、指定されたコピー先フォルダにコピーする。
  • コピー先に同名のファイルが既に存在する場合でも、上書きせずに ファイル名 (1).拡張子, ファイル名 (2).拡張子 のように自動的に別名を付けてコピーする。
  • 対応する動画ファイルの拡張子はカスタマイズ可能。

これにより、「あの動画ファイル、どこに保存したっけ?」「複数のプロジェクトで同じ素材を使っているから、一箇所に集めたい」「大量の素材をバックアップしたいけど、いちいち重複チェックするのは面倒」といった状況から解放されます。

プログラムの全文をご紹介

まずは、今回使用するPythonスクリプトの全文をご紹介します。このコードをコピー&ペーストして、お使いの環境に合わせて設定を少し変更すれば、すぐに利用できます。

import os
import shutil

def copy_video_files_rename_duplicates(source_root_dir, target_dir, video_extensions):
    """
    指定されたソースディレクトリの配下にある動画ファイルを
    ターゲットディレクトリにコピーする関数。
    サブフォルダの奥深くにあるファイルも全て探索します。
    コピー先に同名ファイルが存在する場合は、別名(例: filename (1).ext)でコピーします。

    Args:
        source_root_dir (str): コピー元のルートディレクトリ。
        target_dir (str): コピー先のディレクトリ。
        video_extensions (list): 対象とする動画ファイルの拡張子リスト(例: ['.mp4', '.mov'])。
                                 ドットから始まる小文字で指定。
    """
    # ターゲットディレクトリが存在しない場合は作成
    if not os.path.exists(target_dir):
        try:
            os.makedirs(target_dir)
            print(f"作成しました: {target_dir}")
        except OSError as e:
            print(f"エラー: ターゲットディレクトリを作成できませんでした - {target_dir} ({e})")
            return

    copied_count = 0
    error_count = 0

    print(f"'{source_root_dir}' から '{target_dir}' へ動画ファイルをコピーします...")
    print("同名ファイルが存在する場合は、別名でコピーします。")

    # source_root_dir 自身とその配下の全てのディレクトリとファイルを再帰的に探索
    # これにより「配下の配下の配下ファイル」も対象となります
    for dirpath, dirnames, filenames in os.walk(source_root_dir):
        # 現在処理中のディレクトリがターゲットディレクトリ自身の場合はスキップ
        # (ターゲットがソースのサブフォルダにある場合に無限ループや意図しないコピーを防ぐため)
        # 正規化された絶対パスで比較して確実に判定します
        if os.path.abspath(dirpath) == os.path.abspath(target_dir):
            print(f"スキップ (処理対象外のディレクトリ): {dirpath} (ここはコピー先フォルダです)")
            # このディレクトリ配下の探索を止めるためにdirnamesをクリア
            dirnames[:] = []
            continue

        for filename in filenames:
            # ファイルの拡張子を取得し、小文字に変換して比較
            file_ext_lower = os.path.splitext(filename)[1].lower()

            if file_ext_lower in video_extensions:
                source_file_path = os.path.join(dirpath, filename)

                # コピー先のファイルパスを決定
                target_file_path_original = os.path.join(target_dir, filename)
                current_target_file_path = target_file_path_original

                # 同名ファイルが存在するか確認し、存在すれば別名を生成
                if os.path.exists(current_target_file_path):
                    base, ext = os.path.splitext(filename)
                    counter = 1
                    # 新しいファイル名を生成: base (counter).ext
                    new_filename = f"{base} ({counter}){ext}"
                    current_target_file_path = os.path.join(target_dir, new_filename)

                    # 生成した別名がさらに存在するかチェックし、存在しなくなるまでカウンターを増やす
                    while os.path.exists(current_target_file_path):
                        counter += 1
                        new_filename = f"{base} ({counter}){ext}"
                        current_target_file_path = os.path.join(target_dir, new_filename)

                    # 別名でコピーを実行
                    try:
                        shutil.copy2(source_file_path, current_target_file_path)
                        print(f"コピー完了 (別名): {source_file_path} -> {current_target_file_path}")
                        copied_count += 1
                    except Exception as e:
                        print(f"エラー (別名コピー失敗): {source_file_path} -> {current_target_file_path} - {e}")
                        error_count += 1
                else:
                    # 同名ファイルが存在しない場合、そのままコピー
                    try:
                        shutil.copy2(source_file_path, current_target_file_path)
                        print(f"コピー完了: {source_file_path} -> {current_target_file_path}")
                        copied_count += 1
                    except Exception as e:
                        print(f"エラー (コピー失敗): {source_file_path} - {e}")
                        error_count += 1
            # else:
            #     print(f"スキップ (対象外の拡張子): {os.path.join(dirpath, filename)}")


    print("\n--- 処理結果 ---")
    print(f"コピーされたファイル数: {copied_count}")
    print(f"エラーが発生したファイル数: {error_count}")
    print("処理が完了しました。")

if __name__ == "__main__":
    # --- 設定ここから ---
    # コピー元のルートディレクトリを指定してください
    source_directory = r"D:\動画制作\素材"
    # コピー先のディレクトリを指定してください
    destination_directory = r"D:\動画制作\素材\カラーグレーディング前素材"

    # 対象とする動画ファイルの拡張子リスト (ドットから始まる小文字で)
    # 必要に応じて追加・削除してください
    target_extensions = [
        '.mp4', '.mov', '.avi', '.mkv', '.wmv',
        '.flv', '.webm', '.mpg', '.mpeg', '.m2ts', '.mts'
        # 例: '.mts', '.m2ts' などを追加できます
    ]
    # --- 設定ここまで ---

    # コピー元ディレクトリの存在チェック
    if not os.path.isdir(source_directory):
        print(f"エラー: コピー元ディレクトリが見つかりません - {source_directory}")
    else:
        copy_video_files_rename_duplicates(source_directory, destination_directory, target_extensions)

主要な機能とロジック解説

それでは、上記のコードがどのように動作するのか、主要な部分を分解して解説していきます。

1. 必要なライブラリのインポート

import os
import shutil
  • os モジュール: オペレーティングシステムに関する機能を提供する標準ライブラリです。ファイルの存在チェック、ディレクトリ作成、パスの操作、ディレクトリツリーの走査 (os.walk) など、ファイルシステム関連の様々な操作に利用します。
  • shutil モジュール: 高レベルのファイル操作を提供する標準ライブラリです。ファイルのコピー、移動、削除など、os モジュールよりも直感的に使える機能が多く含まれています。今回のファイルのコピーには shutil.copy2 を使用します。

2. メイン関数の定義 copy_video_files_rename_duplicates

def copy_video_files_rename_duplicates(source_root_dir, target_dir, video_extensions):
    # ... 関数本体 ...

この関数がファイルコピー処理の中核を担います。引数としてコピー元ディレクトリ、コピー先ディレクトリ、対象とする拡張子リストを受け取ります。関数内に処理の大部分が記述されています。

3. コピー先ディレクトリの作成

    # ターゲットディレクトリが存在しない場合は作成
    if not os.path.exists(target_dir):
        try:
            os.makedirs(target_dir)
            print(f"作成しました: {target_dir}")
        except OSError as e:
            print(f"エラー: ターゲットディレクトリを作成できませんでした - {target_dir} ({e})")
            return

os.path.exists(target_dir) でコピー先ディレクトリが存在するか確認し、存在しない場合は os.makedirs(target_dir) で作成します。os.makedirs は、途中の親ディレクトリもまとめて作成してくれるため便利です。ディレクトリ作成に失敗した場合(権限がないなど)のために try...except ブロックでエラーを捕捉し、処理を中断します。

4. ファイルシステムツリーの探索 (os.walk)

    for dirpath, dirnames, filenames in os.walk(source_root_dir):
        # ... ファイル処理 ...

ここがこのスクリプトの重要な部分の一つです。os.walk(source_root_dir) は、source_root_dir をルートとして、その配下にある全てのディレクトリとファイルを再帰的に探索します。ループを一回実行するごとに、現在のディレクトリ (dirpath)、そのディレクトリ内のサブディレクトリ名のリスト (dirnames)、そしてそのディレクトリ内のファイル名のリスト (filenames) を返します。これにより、深い階層にあるファイルも漏れなく処理できます。

5. コピー先ディレクトリのスキップ処理

        # 現在処理中のディレクトリがターゲットディレクトリ自身の場合はスキップ
        if os.path.abspath(dirpath) == os.path.abspath(target_dir):
            print(f"スキップ (処理対象外のディレクトリ): {dirpath} (ここはコピー先フォルダです)")
            dirnames[:] = [] # このディレクトリ配下の探索を止める
            continue

もしコピー先ディレクトリがコピー元ディレクトリのサブフォルダになっている場合、そのまま os.walk を実行すると、コピーしたファイルが再び探索対象となり、無限ループに陥る可能性があります。これを防ぐため、os.path.abspath を使って正規化された絶対パスで比較し、現在の処理対象ディレクトリがコピー先ディレクトリと同一であれば、そのディレクトリ内のファイル処理をスキップし、さらに dirnames[:] = [] として、そのディレクトリのさらに下にあるサブディレクトリの探索も停止させます。これは os.walk の戻り値 dirnames をインプレースで変更すると、その後の探索に影響を与えるという性質を利用したテクニックです。

6. ファイルの拡張子チェック

        for filename in filenames:
            file_ext_lower = os.path.splitext(filename)[1].lower()

            if file_ext_lower in video_extensions:
                # ... 動画ファイルに対する処理 ...

現在のディレクトリで見つかったファイル (filenames) のリストをループ処理します。os.path.splitext(filename) はファイル名を名前と拡張子に分割します(例: ('video', '.mp4'))。その結果の拡張子部分 [1] を取り出し、.lower() で小文字に変換します。これにより、.MP4.Mov のような大文字・小文字の違いを吸収し、事前に定義した video_extensions リストに含まれているかを確認します。リストに含まれていれば、動画ファイルとしてコピー対象と判断します。

7. コピー先ファイルパスの決定と重複チェック

                source_file_path = os.path.join(dirpath, filename)
                target_file_path_original = os.path.join(target_dir, filename)
                current_target_file_path = target_file_path_original

                # 同名ファイルが存在するか確認し、存在すれば別名を生成
                if os.path.exists(current_target_file_path):
                    # ... 別名生成ロジック ...
                else:
                    # ... そのままコピー ...

コピー元のフルパス (source_file_path) と、もし同名ファイルが存在しなければ使われるであろうコピー先のフルパス (target_file_path_original) を os.path.join で生成します。os.path.join は、OSに合わせて適切な区切り文字(Windowsなら\、macOS/Linuxなら/)を使ってパスを連結してくれるため、環境によらず動作するコードを書く上で非常に重要です。

os.path.exists(current_target_file_path) で、指定のコピー先パスにファイルが既に存在するかを確認します。

8. 重複ファイル名の別名生成ロジック

                if os.path.exists(current_target_file_path):
                    base, ext = os.path.splitext(filename)
                    counter = 1
                    new_filename = f"{base} ({counter}){ext}"
                    current_target_file_path = os.path.join(target_dir, new_filename)

                    while os.path.exists(current_target_file_path):
                        counter += 1
                        new_filename = f"{base} ({counter}){ext}"
                        current_target_file_path = os.path.join(target_dir, new_filename)

                    # 別名でコピーを実行
                    try:
                        shutil.copy2(source_file_path, current_target_file_path)
                        # ... 成功/失敗の表示 ...
                    except Exception as e:
                        # ... エラー処理 ...

コピー先に同名ファイルが存在した場合の処理です。
元のファイル名を名前部分 (base) と拡張子部分 (ext) に分割します。カウンタ counter1 から始め、filename (1).ext のような新しいファイル名を生成します。そして、その新しい名前のファイルがコピー先に存在するか os.path.exists で確認します。もし存在すればカウンタを増やし、再び新しい名前 filename (2).ext, filename (3).ext… を生成してチェックします。この while ループは、存在しないファイル名が見つかるまで繰り返されます。

最終的に決定した current_target_file_path (元のファイル名か、生成された別名)を使ってファイルコピーを実行します。

9. ファイルのコピーとエラーハンドリング

                    # ... 別名でコピーを実行 (重複時) ...
                    try:
                        shutil.copy2(source_file_path, current_target_file_path)
                        print(f"コピー完了 (別名): {source_file_path} -> {current_target_file_path}")
                        copied_count += 1
                    except Exception as e:
                        print(f"エラー (別名コピー失敗): {source_file_path} -> {current_target_file_path} - {e}")
                        error_count += 1
                else:
                    # 同名ファイルが存在しない場合、そのままコピー
                    try:
                        shutil.copy2(source_file_path, current_target_file_path)
                        print(f"コピー完了: {source_file_path} -> {current_target_file_path}")
                        copied_count += 1
                    except Exception as e:
                        print(f"エラー (コピー失敗): {source_file_path} - {e}")
                        error_count += 1

ファイルのコピーには shutil.copy2(source, destination) を使用します。この関数は、単にファイル内容をコピーするだけでなく、元のファイルのメタデータ(最終更新日時、アクセス日時、パーミッションなど)も可能な限り引き継いでコピーしてくれます。これは、バックアップ用途などで元のファイル情報を保持したい場合に非常に有用です。

コピー処理はネットワークの問題やファイルシステムのエラーなど様々な要因で失敗する可能性があります。そのため、ここでも try...except Exception as e を使用して、例外が発生した場合にプログラムがクラッシュするのを防ぎ、どのファイルでどのようなエラーが発生したかを分かりやすく表示するようにしています。成功/失敗に関わらず、それぞれのカウントをインクリメントします。

10. 処理結果の表示

    print("\n--- 処理結果 ---")
    print(f"コピーされたファイル数: {copied_count}")
    print(f"エラーが発生したファイル数: {error_count}")
    print("処理が完了しました。")

全ての探索とコピー処理が完了した後、最終的に何個のファイルがコピーされ、何個のファイルでエラーが発生したかの合計数を表示します。

11. スクリプトのエントリーポイント (if __name__ == "__main__":)

if __name__ == "__main__":
    # --- 設定ここから ---
    source_directory = r"D:\動画制作\素材"
    destination_directory = r"D:\動画制作\素材\カラーグレーディング前素材"
    target_extensions = [ ... ]
    # --- 設定ここまで ---

    if not os.path.isdir(source_directory):
        print(f"エラー: コピー元ディレクトリが見つかりません - {source_directory}")
    else:
        copy_video_files_rename_duplicates(source_directory, destination_directory, target_extensions)

このブロックは、このスクリプトファイルが直接実行された場合にのみ処理される部分です。他のPythonスクリプトからこの関数をインポートして使用する場合には、このブロック内のコードは実行されません。

ここでは、ユーザーが設定すべきコピー元ディレクトリ (source_directory)コピー先ディレクトリ (destination_directory)、そして対象とする動画ファイルの拡張子リスト (target_extensions) を指定しています。パスを指定する際には、Windows環境であれば \ がエスケープシーケンスとして解釈されないように、パス文字列の前に r を付けた raw文字列 (r"C:\Users\...") にするか、\\\ と二重にするか、/ に置き換えるといった工夫が必要です。ここでは r を使う方法が記述されています。

最後に、設定したコピー元ディレクトリが存在するか確認し、存在すればメイン関数 copy_video_files_rename_duplicates を呼び出して処理を開始します。

使用されている技術・ライブラリ

このスクリプトは、Pythonの標準ライブラリのみを使用しています。

  • Python 標準ライブラリ:
    • os モジュール: ファイル、ディレクトリ操作、パス操作、ディレクトリツリー探索 (os.walk) など。ファイルシステムとの基本的なやり取りを司ります。
    • shutil モジュール: 高レベルのファイル操作(コピー、移動、削除など)。特に shutil.copy2 はメタデータを保持してコピーできる点がファイル整理やバックアップにおいて非常に有用です。

これらのライブラリはPythonをインストールすれば追加のインストールなしで利用できます。外部ライブラリへの依存がないため、環境構築の手間が少ないのが利点です。

Pythonの標準ライブラリは非常に豊富で、ファイル操作以外にも様々な用途に利用できるモジュールが揃っています。詳しく学びたい場合は、Pythonの公式ドキュメントを参照することをおすすめします。

セットアップと実行方法

このスクリプトを実行するためのセットアップと手順は非常に簡単です。

  1. Pythonのインストール: まだお使いの環境にPythonがインストールされていない場合は、Python公式サイト からインストーラーをダウンロードしてインストールしてください。インストール時に「Add Python to PATH」のようなオプションがあればチェックを入れておくと、コマンドプロンプトやターミナルから実行しやすくなります。
  2. コードの保存: 上記のプログラム全文をコピーし、テキストエディタ(メモ帳、VS Code, Sublime Textなど)に貼り付けます。ファイル名を copy_videos.py のように.py拡張子を付けて保存します。
  3. 設定の変更: 保存した copy_videos.py ファイルを開き、if __name__ == "__main__": ブロック内の以下の3行を、ご自身の環境に合わせて編集します。

    “`python

    コピー元のルートディレクトリを指定してください

    source_directory = r”ここにコピー元のパスを記述”

    コピー先のディレクトリを指定してください

    destination_directory = r”ここにコピー先のパスを記述”

    対象とする動画ファイルの拡張子リスト (ドットから始まる小文字で)

    target_extensions = [ … ] # 必要に応じてリストの内容を変更
    “`

    パスを指定する際は、Windowsの場合 C:\Users\YourName\Videos\素材 のように \ で区切られますが、Pythonコード内で文字列として扱う場合はエスケープが必要になります。最も簡単なのは、文字列の前に r を付けて r"C:\Users\YourName\Videos\素材" のように「raw文字列」として扱う方法です。または、スラッシュ / を使う ("C:/Users/YourName/Videos/素材") か、バックスラッシュを二重にする ("C:\\Users\\YourName\\Videos\\素材") 方法もあります。コード例ではraw文字列を使用しています。
    4. 実行: コマンドプロンプト(Windows)またはターミナル(macOS, Linux)を開きます。保存した copy_videos.py ファイルがあるディレクトリに移動し、以下のコマンドを実行します。

    bash
    python copy_videos.py

    スクリプトが実行され、コンソールに進行状況や結果が表示されます。指定したコピー先ディレクトリに、コピー元から集められた動画ファイルが、必要に応じて別名でコピーされていることを確認してください。

使用例・応用例

このスクリプトは、様々な場面で応用できます。

  • 動画編集プロジェクトの素材集約: 複数のフォルダに散らばった大量の動画素材を、一つのプロジェクトフォルダ内にまとめてコピーする。この際、ファイル名が重複していても自動で処理されるため、手間なく作業を開始できます。
  • 大切な動画のバックアップ: PC内の様々な場所に保存されている家族の思い出動画や旅行動画などを、外付けHDDやNASにまとめてバックアップする。既にバックアップ済みのファイルがあっても、別名で保存されるため安心です。
  • 写真や他のファイル形式への応用: target_extensions リストを .jpg, .png, .gif などに変更すれば、写真ファイルをまとめてコピーするスクリプトとして利用できます。同様に、ドキュメントファイルや音楽ファイルなど、様々なファイル形式に応用可能です。
  • ファイル移動への変更: コピーではなく、元の場所からファイルを移動させたい場合は、shutil.copy2 の代わりに shutil.move を使用することで簡単に変更できます。ただし、移動は元のファイルがなくなるため、実行前には十分な確認と注意が必要です。

学習者向けのポイント・発展的なトピック

このスクリプトは、Pythonを使ったファイル操作やディレクトリ探索の基本を学ぶのに非常に良い教材です。

  • ファイル・ディレクトリ操作の基本: os モジュールを使ったパス操作(結合、分割、存在チェック)、ディレクトリ作成、shutil モジュールを使ったファイルコピーといった基本的な操作を学べます。
  • os.walk() の使い方: ディレクトリツリーを効率的に探索する方法を理解できます。ファイル整理ツールや、特定のファイルを探すスクリプトを作成する際に非常に役立ちます。
  • 例外処理: try...except ブロックを使ったエラーハンドリングの重要性を学べます。ファイル操作は外部要因(ファイルロック、権限、ディスク容量など)で失敗しやすいため、堅牢なスクリプトを書く上で必須のテクニックです。
  • パス操作の重要性: os.path.join を使うことで、OSに依存しないパス生成ができることを理解できます。異なるOSでスクリプトを使いたい場合に重要です。

さらに学習を進めたい場合は、以下の発展的なトピックに挑戦してみましょう。

  • コマンドライン引数: 現在はスクリプト内の設定値を直接編集していますが、argparse ライブラリを使えば、スクリプト実行時にコマンドラインからコピー元・コピー先ディレクトリを指定できるように改善できます。より汎用性の高いスクリプトになります。
  • 進捗表示: 大量のファイルをコピーする場合、処理がどれくらい進んでいるか分からないと不安になります。tqdm のようなライブラリを使えば、プログレスバーを表示して処理状況を分かりやすく伝えることができます。
  • 設定ファイルの利用: 拡張子のリストなどが長くなってきたら、スクリプト本体とは別に設定ファイル(例: .ini.json)を用意し、そこから設定を読み込むようにすると、管理がしやすくなります。
  • GUI化: TkinterやPyQt/PySideなどのGUIライブラリを使えば、コマンド操作に慣れていない人でも簡単に使えるデスクトップアプリケーションにすることも可能です。

まとめ

この記事では、Pythonと標準ライブラリ os および shutil を使用して、指定ディレクトリ配下の動画ファイルを重複を避けながらコピーするスクリプトを作成・解説しました。

このスクリプトは、再帰的なディレクトリ探索、ファイル拡張子によるフィルタリング、コピー先のファイル名重複時の自動リネームといった機能を持ち合わせており、大量の動画ファイル整理やバックアップ作業を大幅に効率化できます。

コードの解説を通して、Pythonでのファイルシステム操作の基本や、os.walk のような便利な機能、そしてエラーハンドリングの重要性についても触れました。ぜひこのスクリプトを実際に動かしてみて、ご自身のファイル整理に役立ててください。

また、今回紹介したコードはあくまで出発点です。必要に応じて拡張子のリストを変更したり、写真やドキュメントなど他のファイル形式に応用したり、さらに高度な機能(ファイルサイズのフィルタリング、コピー後の検証など)を追加することで、より強力でパーソナルなファイル管理ツールへと発展させることが可能です。

Pythonはファイル操作のような日常的なタスクの自動化にも非常に強力な言語です。このスクリプトをきっかけに、Pythonを使った作業効率化に興味を持っていただけたら嬉しいです。

撮影に使用している機材【PR】

【無料】撮った写真でWEBページを作りませんか?

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
目次