夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Geminiを用いてBlenderで独自プログラムを実行する

本日はBlenderPython枠です。

 筆者は元エンジニアでもありプログラミングになれていますが、最近はAIを用いたViveCodingばかりを行っています。

 AIを用いることである程度の知識であっても簡単にプログラムを作成、実行することができ、業務効率化などを行うことができますが、環境構築などがとても大変です。

 簡単に書くと"AIに任せればコードは生成してくれるけど、それを実行するのは人間"というハードルがあります。

またPythonは環境が壊れやすいことで知られ、きちんと運用するためには仮想環境を作るなど専門的知識と経験が必要です。今回はBlenderを用いることでこのハードルを下げ、AIを用いて初心者でも効率的に作業を行うことを行っていきます。

〇環境

・Windows11PC

・Blender4.5

・Gemini

 ・筆者はGeminiProプランを使用しています。

〇Geminiにプログラムを作らせ実行する

理屈より先に実際の挙動を紹介します。

プログラミングするためには、目的(=やりたいこと)が必要です。

今回は複数のシチュエーションで紹介していきます。

基本的なプロンプトはBlender Pythonを使用して○○を行いたい。で大丈夫です。

また実行方法としてBlenderを起動したのちに上部のタブからScriptingに切り替えます。

新規ボタンを押します。

Geminiが生成したプログラムをスペルミスなしでコピペします。

後はプログラム内容ごとに一部を書き換え設定を行います。

また実行は上部の再生アイコンから行うことができます。

"ある条件のファイルを削除する"や"ある条件のファイル名を修正する"というように直接ファイルを編集するのではなく、"○○を教えて"など結果がテキストで表示される処理の場合はウィンドウメニューから開くことができるシステムコンソール切り替えから表示できるコンソールと呼ばれるウィンドウに結果が表示されます。

〇あるフォルダにある100MBを超えるファイルだけ教えてくれる。

プロンプトは

BlenderPythonを用いてあるフォルダにある100MBを超えるファイルだけ教えてくれることを実現したい

になります。

生成されるコードは以下になります。

import os

# --- ユーザー設定 ---
# ↓↓↓ ここのパスを調べたいフォルダのパスに書き換えてください ↓↓↓
# (Windows例: "C:/Users/YourName/Documents")
# (Mac/Linux例: "/Users/YourName/Documents")
folder_path = "ここに調べたいフォルダの絶対パスを入力してください"
# --- ユーザー設定ここまで ---

# 100MBをバイト単位で定義 (100 * 1024 * 1024)
size_threshold = 100 * 1024 * 1024

print(f"--- 100MBを超えるファイルを検索中 ---")
print(f"対象フォルダ: {folder_path}")

found_files = []

try:
    # os.scandirを使ってフォルダ内のエントリを効率的に処理
    with os.scandir(folder_path) as entries:
        for entry in entries:
            # ファイルであり、ディレクトリではないことを確認
            if entry.is_file():
                try:
                    # ファイルの統計情報(サイズなど)を取得
                    file_stat = entry.stat()
                    file_size = file_stat.st_size
                    
                    # ファイルサイズがしきい値(100MB)を超えているかチェック
                    if file_size > size_threshold:
                        # サイズをMBに変換して表示
                        size_mb = file_size / (1024 * 1024)
                        found_files.append((entry.name, size_mb))
                        
                except OSError as e:
                    print(f"エラー: {entry.name} のサイズを取得できませんでした - {e}")

    # --- 結果の表示 ---
    if found_files:
        print(f"\n見つかったファイル ({len(found_files)} 件):")
        for name, size_mb in found_files:
            # "{size_mb:.2f}" は小数点以下2桁まで表示するフォーマット
            print(f"  - {name} ({size_mb:.2f} MB)")
    else:
        print("\n100MBを超えるファイルは見つかりませんでした。")

except FileNotFoundError:
    print(f"\nエラー: 指定されたフォルダが見つかりません: {folder_path}")
except NotADirectoryError:
    print(f"\nエラー: 指定されたパスはフォルダではありません: {folder_path}")
except PermissionError:
    print(f"\nエラー: フォルダにアクセスする権限がありません: {folder_path}")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")

print("--- 検索完了 ---")

実行方法は基本的にGeminiが提供してくれる場合が多いです。

実行すると結果が出力されます。

また100MBを超えるファイルがない場合もその旨を表示してくれます。

〇あるフォルダにある日本語ファイルだけ教えてくれる。

Geminiへのプロンプトは次になります

BlenderPythonを用いてあるフォルダにある日本語ファイルだけ教えてくれる

これによって出力されるプログラムは以下になります。

import os
import re

# --- ユーザー設定 ---
# ↓↓↓ ここのパスを調べたいフォルダのパスに書き換えてください ↓↓↓
# (Windows例: "C:/Users/YourName/Documents")
# (Mac/Linux例: "/Users/YourName/Documents")
folder_path = "ここに調べたいフォルダの絶対パスを入力してください"
# --- ユーザー設定ここまで ---

# 日本語(ひらがな、カタカナ、漢字、日本の句読点など)を検出するための正規表現パターン
japanese_pattern = re.compile(r'[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF]')

print(f"--- 日本語ファイル名を検索中 ---")
print(f"対象フォルダ: {folder_path}")

found_files = []

try:
    # os.scandirを使ってフォルダ内のエントリを効率的に処理
    with os.scandir(folder_path) as entries:
        for entry in entries:
            # ファイルであり、ディレクトリではないことを確認
            if entry.is_file():
                filename = entry.name
                
                try:
                    # re.search()を使って、ファイル名に日本語パターンが含まれているか検索
                    if japanese_pattern.search(filename):
                        found_files.append(filename)
                        
                except Exception as e:
                    print(f"エラー: {filename} の処理中に問題が発生しました - {e}")

    # --- 結果の表示 ---
    if found_files:
        print(f"\n見つかった日本語ファイル ({len(found_files)} 件):")
        for name in found_files:
            print(f"  - {name}")
    else:
        print("\n日本語ファイル名のファイルは見つかりませんでした。")

except FileNotFoundError:
    print(f"\nエラー: 指定されたフォルダが見つかりません: {folder_path}")
except NotADirectoryError:
    print(f"\nエラー: 指定されたパスはフォルダではありません: {folder_path}")
except PermissionError:
    print(f"\nエラー: フォルダにアクセスする権限がありません: {folder_path}")
except Exception as e:
    print(f"\n予期せぬエラーが発生しました: {e}")

print("--- 検索完了 ---")

対象フォルダを指定して実行すると次のような結果が得られます。

目視よりも圧倒的に早いです。

〇Wav形式の動画をMp3形式に一括変換する

筆者のよくあるシチュエーションとしてWav形式の音声ファイルをmp3に変換したいというものがあります。

前提として今までのシチュエーションと異なりBlenderのプログラミングでは直接音源ファイルを扱うということができない点に注意が必要です。

このため一般的に使用されるffmpegと呼ばれるツールをCドライブ直下に配置している前提です。

Geminiには次のように師事しています。

BlenderPythonであるディレクトリパスの.wavデータ複数をそれぞれ別の256ハッシュ値の名前に書き換え.mp3に変換したい。 Blenderを使用しなくてもPythonが使えればいいのだけど環境構築等が大変だから なおFFMpegのパスは"C:\ffmpeg\bin\ffmpeg.exe"

なおハッシュ値というのは変換したmp3の名前が一意の値になるようにするための指示になります。

import os
import hashlib
import subprocess
import sys

# --- ユーザー設定 (ここを環境に合わせて書き換えてください) ---

# 1. 処理したい.wavファイルが保存されているディレクトリのパス
# (例: r"C:\Users\YourUser\Music\MyWavs")
target_dir = r"C:\path\to\your\wav_files" 

# 2. FFmpeg実行ファイルのパス (ご指定のパスを記載しています)
ffmpeg_path = r"C:\ffmpeg\bin\ffmpeg.exe"

# 3. 変換後の.mp3ファイルを保存するディレクトリ
# (target_dir と同じ場所でも構いません)
output_dir = r"C:\path\to\your\output_mp3s"

# --- 設定ここまで ---


def get_file_sha256(file_path):
    """
    ファイル内容のSHA-256ハッシュを計算して16進数文字列で返します。
    ファイルサイズが大きくてもメモリを圧迫しないよう、チャンクで読み込みます。
    """
    sha256_hash = hashlib.sha256()
    try:
        with open(file_path, "rb") as f:
            # 4KBごとにファイルを読み込んでハッシュを更新
            for byte_block in iter(lambda: f.read(4096), b""):
                sha256_hash.update(byte_block)
        return sha256_hash.hexdigest()
    except IOError as e:
        print(f"Error: ファイルが読み込めませんでした {file_path}: {e}", file=sys.stderr)
        return None
    except Exception as e:
        print(f"Error: ハッシュ計算中にエラーが発生しました {file_path}: {e}", file=sys.stderr)
        return None

def convert_wav_to_mp3(wav_path, mp3_path):
    """
    FFmpegを使って.wavを.mp3に変換します。
    """
    # FFmpegに渡すコマンドリストを作成
    command = [
        ffmpeg_path,
        "-i", wav_path,      # 入力ファイル
        "-n",                # 注意: 同名ファイルが既にあれば上書きしない (エラーにもしない)
        "-q:a", "0",         # 可変ビットレート(VBR)で最高品質 (0〜9で指定)
        mp3_path           # 出力ファイル
    ]
    
    print(f"変換中: {os.path.basename(wav_path)} -> {os.path.basename(mp3_path)}")
    
    try:
        # FFMpegを実行
        # check=True: コマンドがエラー(リターンコード0以外)なら例外を発生
        # capture_output=True: 標準出力と標準エラーを取得
        # text=True: 出力をテキストとしてデコード
        result = subprocess.run(command, capture_output=True, text=True, check=True, encoding='utf-8')
        
        # print(f"FFmpeg STDOUT: {result.stdout}") # 詳細ログが必要ならコメント解除
        if result.stderr:
             # FFmpegは進捗情報などをstderrに出すことがあるため、エラーとは限らない
             print(f"FFmpeg Info: {result.stderr.strip()}")
             
        print(f"変換成功: {os.path.basename(mp3_path)}")
        return True
        
    except FileNotFoundError:
        print(f"Error: FFMpeg が見つかりません '{ffmpeg_path}'.", file=sys.stderr)
        print("スクリプト内の 'ffmpeg_path' の設定を確認してください。", file=sys.stderr)
        return False
    except subprocess.CalledProcessError as e:
        # FFmpegがエラーコードを返した場合
        print(f"Error: {wav_path} の変換中にFFmpegがエラーを返しました。", file=sys.stderr)
        print(f"FFmpeg STDERR: {e.stderr}", file=sys.stderr)
        return False
    except Exception as e:
        print(f"Error: 予期せぬエラーが発生しました: {e}", file=sys.stderr)
        return False

def process_wav_files():
    """
    指定されたディレクトリ内の.wavファイルを処理するメイン関数
    """
    
    # --- 実行前チェック ---
    if target_dir == r"C:\path\to\your\wav_files" or \
       output_dir == r"C:\path\to\your\output_mp3s":
        print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=sys.stderr)
        print("!! 注意: 'target_dir' または 'output_dir' が変更されていません。  !!", file=sys.stderr)
        print("!! スクリプト上部の「ユーザー設定」を実際のパスに書き換えてください。 !!", file=sys.stderr)
        print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=sys.stderr)
        return

    if not os.path.exists(ffmpeg_path):
         print(f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=sys.stderr)
         print(f"!! 注意: 'ffmpeg_path' が見つかりません。             !!", file=sys.stderr)
         print(f"!!      パスを確認してください: {ffmpeg_path}  !!", file=sys.stderr)
         print(f"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", file=sys.stderr)
         return
         
    if not os.path.isdir(target_dir):
         print(f"Error: 'target_dir' が有効なディレクトリではありません: {target_dir}", file=sys.stderr)
         return

    # 出力ディレクトリが存在しない場合は作成する
    if not os.path.exists(output_dir):
        try:
            os.makedirs(output_dir)
            print(f"作成しました: {output_dir}")
        except OSError as e:
            print(f"Error: 出力ディレクトリの作成に失敗しました {output_dir}: {e}", file=sys.stderr)
            return

    print(f"処理を開始します...")
    print(f"入力元: {target_dir}")
    print(f"出力先: {output_dir}")
    print("---")
    
    processed_count = 0
    skipped_count = 0
    
    # ディレクトリ内のファイルをループ
    for filename in os.listdir(target_dir):
        # .wav ファイル (大文字小文字問わず) のみを対象
        if filename.lower().endswith(".wav"):
            original_wav_path = os.path.join(target_dir, filename)
            
            # 1. ファイルのハッシュ値を計算
            file_hash = get_file_sha256(original_wav_path)
            
            if file_hash:
                # 2. 新しいファイル名(ハッシュ値.mp3)を決定
                new_mp3_filename = f"{file_hash}.mp3"
                new_mp3_path = os.path.join(output_dir, new_mp3_filename)
                
                # 3. 変換実行 (既に同名ファイルが存在しないか確認)
                if not os.path.exists(new_mp3_path):
                    if convert_wav_to_mp3(original_wav_path, new_mp3_path):
                        processed_count += 1
                else:
                    print(f"スキップ (出力ファイルが既に存在): {new_mp3_filename}")
                    skipped_count += 1
            else:
                print(f"ハッシュ計算失敗 (スキップ): {filename}")
                skipped_count += 1
        
        else:
            # .wav 以外は無視
            pass

    print("---")
    print(f"処理が完了しました。")
    print(f"変換成功: {processed_count} 件")
    print(f"スキップ: {skipped_count} 件")

# --- メイン処理の実行 ---
if __name__ == "__main__":
    process_wav_files()

変換が必要なのは

# --- ユーザー設定 (ここを環境に合わせて書き換えてください) ---

# 1. 処理したい.wavファイルが保存されているディレクトリのパス
# (例: r"C:\Users\YourUser\Music\MyWavs")
target_dir = r"C:\path\to\your\wav_files" 

# 2. FFmpeg実行ファイルのパス (ご指定のパスを記載しています)
ffmpeg_path = r"C:\ffmpeg\bin\ffmpeg.exe"

# 3. 変換後の.mp3ファイルを保存するディレクトリ
# (target_dir と同じ場所でも構いません)
output_dir = r"C:\path\to\your\output_mp3s"

# --- 設定ここまで ---

の部分です。

ここで1.3は.wav形式の返還前データ、変換したデータをどこに保存するかのディレクトリ(ファイルの住所)を記述します。

基本的にはスペルミスをなくすためにファイルエクスプローラーでファイルのパス自体をコピペすることをお勧めします。

もしくは対象のフォルダを右クリックしてパスのコピーによってもコピーができます。

このコードを実行すると名前がかぶることなく指定したフォルダ内に.mp3で音源データが出力されます。

名前を変えたくない場合などはプロンプトを調整してください。

Blenderを用いたプログラミング

BlenderではPythonと呼ばれるプログラミング環境が整っており、加えて自身の独自プログラムを実行可能な環境が用意されています。

これはblenderをインストールするだけで自動的に準備が行われ、自身でその他環境構築を行う必要がないという点に加え、BlenderPython環境はCUI(テキストだけで画面操作をする)のではなくボタンで実行等ができるという点を利用して今回はGeminiに作成させたPythonコードを実行させるという点を紹介しています。

Blender自体は3DCG作成などで一般的に用いられていますが、活用すればこのように様々なことができる万能ツールとして無料で使用できる点が魅力です。

〇エラーが発生する場合

AIにプログラミングをさせれば完璧であるかというとそうではありません。

1つはAIがまだ過渡期の技術であり、完成をしていないという点、2つ目はいくらAIが完ぺきな技術であっても"必要な情報"がプロンプト内に提示されていないとAIが期待する結果を構築できないというものがあります。

 後者に関して補足すると、例えばWindowsOSではなくMacOSで実行している場合や、Blenderのバージョン、最新の機能等AIが知らないことはある程度AIが補足して回答を行います。

 そのため、実際の環境と異なりエラーが発生してしまうことがあるのです。

 エラーは一般的に英語で返されるため、それが初学者にとってハードルとなってしまいますが、大半のエラーはAIにそのまま投げてあげれば解消できます。

エラーが発生した場合は左下のウィンドウにエラー内容が表示されるため、これをコピーします。

Geminiのプロンプトとして提供されたチャットでそのまま返信としてペーストして送ります。

またプログラムコード自体に問題があると言われた場合は以下のようにプロンプトを投げます

コピペで使用できるエラーが出ない全文をください

これによってたいていの場合はコピペで使用できる形でコードを提供してくれます。