Pythonで動画のメタデータを取得する


press
Pythonで動画のメタデータを取得する

Pythonで動画のメタデータを取得する

動画ファイルの管理をしていると、ファイル名や拡張子、フレームレート、音声の有無、ビットレートなどの情報をまとめて確認したい場面があります。本記事では、Pythonの ffmpeg ライブラリを活用し、指定したディレクトリ内の動画ファイルのメタデータを抽出し、JSON形式で保存するスクリプトについて解説します。

コードはGitHubリポジトリにあげています。

開発環境

PC: MacBook Pro (14, 2021)
OS: macOS Monterey 12.6.7
Python: 3.12.0

スクリプトの概要

このスクリプトは以下の機能を備えています。

  1. 指定したディレクトリ内の動画ファイルを検索
  2. ffmpeg を用いて各動画ファイルのメタデータを取得
  3. フレームレート、音声の有無、再生時間などの情報を整理
  4. 取得したメタデータをJSONファイルとして保存

動画のメタデータをjson形式で保存するコード

import json
import os
import ffmpeg
from typing import List, Dict, Any, Optional

# 対象のディレクトリのパスを指定
DIRECTORY_PATH = "/DIR/PATH"
OUTPUT_JSON = "metadata.json"

# 取得するファイルの拡張子を指定
SUPPORTED_EXTENSIONS = (".mp4", ".mkv", ".avi", ".mov", ".flv")


def list_files(directory_path: str) -> List[str]:
    """指定したディレクトリから指定した拡張子のファイル一覧を取得"""
    try:
        with os.scandir(directory_path) as entries:
            filtered_files = [
                entry.path
                for entry in entries
                if entry.is_file() and entry.name.lower().endswith(SUPPORTED_EXTENSIONS)
            ]

            # ファイル名でソートして返す
            return sorted(filtered_files, key=lambda x: os.path.basename(x).lower())

    except FileNotFoundError:
        print(f"ディレクトリ内にファイルがありません: {directory_path}")
    except PermissionError:
        print(f"アクセス権限がありません: {directory_path}")
    return []


def calculate_frame_rate(stream: Dict[str, Any]) -> Optional[float]:
    """動画のストリームからフレームレートを計算"""
    r_frame_rate = stream.get("r_frame_rate")
    if not r_frame_rate:
        return None
    try:
        numerator, denominator = map(int, r_frame_rate.split("/"))
        return numerator / denominator if denominator != 0 else None
    except (ValueError, ZeroDivisionError):
        return None


def format_duration(duration: Optional[float]) -> str:
    """再生時間を 時:分:秒 形式に整形"""
    if not duration:
        return "Unknown"
    total_seconds = int(duration)
    hours, remainder = divmod(total_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    if hours > 0:
        return f"{hours}時間{minutes}分{seconds}秒"
    elif minutes > 0:
        return f"{minutes}分{seconds}秒"
    return f"{seconds}秒"


def extract_metadata(entry_file: str) -> Optional[Dict[str, Any]]:
    """動画ファイルからメタデータを抽出"""
    try:
        # ffmpegでファイルのメタデータを取得
        probe = ffmpeg.probe(entry_file)
        format_info = probe.get("format", {})
        streams = probe.get("streams", [])

        # 音声ストリームの有無を確認
        has_audio = any(stream["codec_type"] == "audio" for stream in streams)

        # フレームレートを動画ストリームから計算
        frame_rate = next(
            (
                calculate_frame_rate(stream)
                for stream in streams
                if stream.get("codec_type") == "video"
            ),
            None,
        )

        # 再生時間を取得(秒単位)
        try:
            duration = float(format_info.get("duration", 0.0))
        except (ValueError, TypeError):
            duration = None

        # メタデータを辞書で返す
        return {
            "file_name": os.path.basename(entry_file),
            "extension": os.path.splitext(entry_file)[1].lower(),
            "has_audio": has_audio,
            "frame_rate": frame_rate,
            "duration": format_duration(duration),
            "bit_rate": format_info.get("bit_rate", "Unknown"),
        }
    except ffmpeg.Error as e:
        print(f"ffmpegのエラーが発生しました {entry_file}: {e}")
    except Exception as e:
        print(f"予期しないエラーで失敗しました {entry_file}: {e}")
    return None


def get_metadata(directory_path: str) -> List[Dict[str, Any]]:
    """指定したディレクトリ内のすべてのファイルのメタデータを取得"""
    entry_files = list_files(directory_path)
    return [
        metadata
        for entry_file in entry_files
        if (metadata := extract_metadata(entry_file))
    ]


def save_to_json(file_path: str, data: List[Dict[str, Any]]) -> None:
    """メタデータをJSONファイルに保存"""
    try:
        with open(file_path, "w", encoding="utf-8") as json_file:
            json.dump(data, json_file, ensure_ascii=False, indent=4)
        print(f"{file_path}を保存しました")
    except Exception as e:
        print(f"保存に失敗しました: {e}")


def main(directory_path: str, output_filename: str):
    """ディレクトリ内の動画ファイルからメタデータを取得して出力"""
    if not os.path.exists(directory_path):
        print("ディレクトリがありませんでした")
        return

    # メタデータの取得
    metadata_list = get_metadata(directory_path)

    # JSONファイルに保存
    save_to_json(output_filename, metadata_list)


if __name__ == "__main__":
    main(DIRECTORY_PATH, OUTPUT_JSON)

解説

import json
import os
import ffmpeg
from typing import List, Dict, Any, Optional

モジュール・ライブラリのインポート

# 対象のディレクトリのパスを指定
DIRECTORY_PATH = "/DIR/PATH"
OUTPUT_JSON = "metadata.json"

# 取得するファイルの拡張子を指定
SUPPORTED_EXTENSIONS = (".mp4", ".mkv", ".avi", ".mov", ".flv")
  • 下記の定数を指定
    • メタデータを取得するファイルがあるディレクトリ
    • 出力するjsonファイル名
    • 取得するファイルの拡張子
def list_files(directory_path: str) -> List[str]:
    """指定したディレクトリから指定した拡張子のファイル一覧を取得"""
    try:
        with os.scandir(directory_path) as entries:
            filtered_files = [
                entry.path
                for entry in entries
                if entry.is_file() and entry.name.lower().endswith(SUPPORTED_EXTENSIONS)
            ]

            # ファイル名でソートして返す
            return sorted(filtered_files, key=lambda x: os.path.basename(x).lower())

    except FileNotFoundError:
        print(f"ディレクトリ内にファイルがありません: {directory_path}")
    except PermissionError:
        print(f"アクセス権限がありません: {directory_path}")
    return []
  • 指定したディレクトリから指定した拡張子のファイル一覧を取得
  • 取得したファイルはファイル名でソート
  • ファイルがない場合や権限がない場合はエラーを返却
def calculate_frame_rate(stream: Dict[str, Any]) -> Optional[float]:
    """動画のストリームからフレームレートを計算"""
    r_frame_rate = stream.get("r_frame_rate")
    if not r_frame_rate:
        return None
    try:
        numerator, denominator = map(int, r_frame_rate.split("/"))
        return numerator / denominator if denominator != 0 else None
    except (ValueError, ZeroDivisionError):
        return None
  • 動画のストリームからフレームレートを計算
  • 計算に失敗してエラーが発生した場合はNoneを返却
def format_duration(duration: Optional[float]) -> str:
    """再生時間を 時:分:秒 形式に整形"""
    if not duration:
        return "Unknown"
    total_seconds = int(duration)
    hours, remainder = divmod(total_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    if hours > 0:
        return f"{hours}時間{minutes}分{seconds}秒"
    elif minutes > 0:
        return f"{minutes}分{seconds}秒"
    return f"{seconds}秒"
  • 秒単位で取得した動画の再生時間を時 : 分 : 秒に整形
def extract_metadata(entry_file: str) -> Optional[Dict[str, Any]]:
    """動画ファイルからメタデータを抽出"""
    try:
        # ffmpegでファイルのメタデータを取得
        probe = ffmpeg.probe(entry_file)
        format_info = probe.get("format", {})
        streams = probe.get("streams", [])

        # 音声ストリームの有無を確認
        has_audio = any(stream["codec_type"] == "audio" for stream in streams)

        # フレームレートを動画ストリームから計算
        frame_rate = next(
            (
                calculate_frame_rate(stream)
                for stream in streams
                if stream.get("codec_type") == "video"
            ),
            None,
        )

        # 再生時間を取得(秒単位)
        try:
            duration = float(format_info.get("duration", 0.0))
        except (ValueError, TypeError):
            duration = None

        # メタデータを辞書で返す
        return {
            "file_name": os.path.basename(entry_file),
            "extension": os.path.splitext(entry_file)[1].lower(),
            "has_audio": has_audio,
            "frame_rate": frame_rate,
            "duration": format_duration(duration),
            "bit_rate": format_info.get("bit_rate", "Unknown"),
        }
    except ffmpeg.Error as e:
        print(f"ffmpegのエラーが発生しました {entry_file}: {e}")
    except Exception as e:
        print(f"予期しないエラーで失敗しました {entry_file}: {e}")
    return None
  • ffmpegを使って動画ファイルからメタデータを抽出して辞書形式に整形
  • フレームレートの計算、再生時間の整形の関数を実行
  • 抽出に失敗した場合はエラーを返却
def save_to_json(file_path: str, data: List[Dict[str, Any]]) -> None:
    """メタデータをJSONファイルに保存"""
    try:
        with open(file_path, "w", encoding="utf-8") as json_file:
            json.dump(data, json_file, ensure_ascii=False, indent=4)
        print(f"{file_path}を保存しました")
    except Exception as e:
        print(f"保存に失敗しました: {e}")
  • 取得したメタデータをjson形式で保存
  • 保存に失敗した場合はエラーを返却
def main(directory_path: str, output_filename: str):
    """ディレクトリ内の動画ファイルからメタデータを取得して出力"""
    if not os.path.exists(directory_path):
        print("ディレクトリがありませんでした")
        return

    # メタデータの取得
    metadata_list = get_metadata(directory_path)

    # JSONファイルに保存
    save_to_json(output_filename, metadata_list)


if __name__ == "__main__":
    main(DIRECTORY_PATH, OUTPUT_JSON)
  • 動画のメタデータをjson形式で保存するスクリプトを実行するメイン関数

株式会社ファントムへのお問い合わせ

群馬県でPythonを使ったAIやソフトウェアを開発している株式会社ファントムが運営しています。




    Related Articles

    Django

    Pandasで作ったCSVをダウンロードする

    Django上でPandasで作ったCSVをダウンロードさせる方法です。df.to_csv(“filename.csv”)でも出力は出来ますがダウンロードは出来ないので、ダウンロード機能が必要な場 […]

    Posted on by press
    Python

    複数の画像からgifアニメーションを作る

    複数の画像からgifアニメーションを作る 定点撮影などで連番になっている複数の画像をまとめてgifアニメーションに変換する方法です。 コードはGitHubリポジトリにあげています。 コード 結果 解説 import gl […]

    Posted on by press