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)
それでは、上記のコードがどのように動作するのか、主要な部分を分解して解説していきます。
import os
import shutil
os
モジュール: オペレーティングシステムに関する機能を提供する標準ライブラリです。ファイルの存在チェック、ディレクトリ作成、パスの操作、ディレクトリツリーの走査 (os.walk
) など、ファイルシステム関連の様々な操作に利用します。shutil
モジュール: 高レベルのファイル操作を提供する標準ライブラリです。ファイルのコピー、移動、削除など、os
モジュールよりも直感的に使える機能が多く含まれています。今回のファイルのコピーには shutil.copy2
を使用します。copy_video_files_rename_duplicates
def copy_video_files_rename_duplicates(source_root_dir, target_dir, video_extensions):
# ... 関数本体 ...
この関数がファイルコピー処理の中核を担います。引数としてコピー元ディレクトリ、コピー先ディレクトリ、対象とする拡張子リストを受け取ります。関数内に処理の大部分が記述されています。
# ターゲットディレクトリが存在しない場合は作成
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
ブロックでエラーを捕捉し、処理を中断します。
os.walk
) for dirpath, dirnames, filenames in os.walk(source_root_dir):
# ... ファイル処理 ...
ここがこのスクリプトの重要な部分の一つです。os.walk(source_root_dir)
は、source_root_dir
をルートとして、その配下にある全てのディレクトリとファイルを再帰的に探索します。ループを一回実行するごとに、現在のディレクトリ (dirpath
)、そのディレクトリ内のサブディレクトリ名のリスト (dirnames
)、そしてそのディレクトリ内のファイル名のリスト (filenames
) を返します。これにより、深い階層にあるファイルも漏れなく処理できます。
# 現在処理中のディレクトリがターゲットディレクトリ自身の場合はスキップ
if os.path.abspath(dirpath) == os.path.abspath(target_dir):
print(f"スキップ (処理対象外のディレクトリ): {dirpath} (ここはコピー先フォルダです)")
dirnames[:] = [] # このディレクトリ配下の探索を止める
continue
もしコピー先ディレクトリがコピー元ディレクトリのサブフォルダになっている場合、そのまま os.walk
を実行すると、コピーしたファイルが再び探索対象となり、無限ループに陥る可能性があります。これを防ぐため、os.path.abspath
を使って正規化された絶対パスで比較し、現在の処理対象ディレクトリがコピー先ディレクトリと同一であれば、そのディレクトリ内のファイル処理をスキップし、さらに dirnames[:] = []
として、そのディレクトリのさらに下にあるサブディレクトリの探索も停止させます。これは os.walk
の戻り値 dirnames
をインプレースで変更すると、その後の探索に影響を与えるという性質を利用したテクニックです。
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
リストに含まれているかを確認します。リストに含まれていれば、動画ファイルとしてコピー対象と判断します。
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)
で、指定のコピー先パスにファイルが既に存在するかを確認します。
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
) に分割します。カウンタ counter
を 1
から始め、filename (1).ext
のような新しいファイル名を生成します。そして、その新しい名前のファイルがコピー先に存在するか os.path.exists
で確認します。もし存在すればカウンタを増やし、再び新しい名前 filename (2).ext
, filename (3).ext
… を生成してチェックします。この while
ループは、存在しないファイル名が見つかるまで繰り返されます。
最終的に決定した current_target_file_path
(元のファイル名か、生成された別名)を使ってファイルコピーを実行します。
# ... 別名でコピーを実行 (重複時) ...
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
を使用して、例外が発生した場合にプログラムがクラッシュするのを防ぎ、どのファイルでどのようなエラーが発生したかを分かりやすく表示するようにしています。成功/失敗に関わらず、それぞれのカウントをインクリメントします。
print("\n--- 処理結果 ---")
print(f"コピーされたファイル数: {copied_count}")
print(f"エラーが発生したファイル数: {error_count}")
print("処理が完了しました。")
全ての探索とコピー処理が完了した後、最終的に何個のファイルがコピーされ、何個のファイルでエラーが発生したかの合計数を表示します。
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の標準ライブラリのみを使用しています。
os
モジュール: ファイル、ディレクトリ操作、パス操作、ディレクトリツリー探索 (os.walk
) など。ファイルシステムとの基本的なやり取りを司ります。shutil
モジュール: 高レベルのファイル操作(コピー、移動、削除など)。特に shutil.copy2
はメタデータを保持してコピーできる点がファイル整理やバックアップにおいて非常に有用です。これらのライブラリはPythonをインストールすれば追加のインストールなしで利用できます。外部ライブラリへの依存がないため、環境構築の手間が少ないのが利点です。
Pythonの標準ライブラリは非常に豊富で、ファイル操作以外にも様々な用途に利用できるモジュールが揃っています。詳しく学びたい場合は、Pythonの公式ドキュメントを参照することをおすすめします。
このスクリプトを実行するためのセットアップと手順は非常に簡単です。
copy_videos.py
のように.py
拡張子を付けて保存します。設定の変更: 保存した 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
スクリプトが実行され、コンソールに進行状況や結果が表示されます。指定したコピー先ディレクトリに、コピー元から集められた動画ファイルが、必要に応じて別名でコピーされていることを確認してください。
このスクリプトは、様々な場面で応用できます。
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
)を用意し、そこから設定を読み込むようにすると、管理がしやすくなります。この記事では、Pythonと標準ライブラリ os
および shutil
を使用して、指定ディレクトリ配下の動画ファイルを重複を避けながらコピーするスクリプトを作成・解説しました。
このスクリプトは、再帰的なディレクトリ探索、ファイル拡張子によるフィルタリング、コピー先のファイル名重複時の自動リネームといった機能を持ち合わせており、大量の動画ファイル整理やバックアップ作業を大幅に効率化できます。
コードの解説を通して、Pythonでのファイルシステム操作の基本や、os.walk
のような便利な機能、そしてエラーハンドリングの重要性についても触れました。ぜひこのスクリプトを実際に動かしてみて、ご自身のファイル整理に役立ててください。
また、今回紹介したコードはあくまで出発点です。必要に応じて拡張子のリストを変更したり、写真やドキュメントなど他のファイル形式に応用したり、さらに高度な機能(ファイルサイズのフィルタリング、コピー後の検証など)を追加することで、より強力でパーソナルなファイル管理ツールへと発展させることが可能です。
Pythonはファイル操作のような日常的なタスクの自動化にも非常に強力な言語です。このスクリプトをきっかけに、Pythonを使った作業効率化に興味を持っていただけたら嬉しいです。