指定したmp3ファイルの音声をBGMに、HTML画面のCanvas上に描画したアニメーションを動画として保存するプログラムを作成してください。動作環境はTauriで、Canvasへの描画スクリプトはユーザーからの編集が出来るようにしてください。

ご要望のプログラムを作成いたします。TauriとHTML5のCanvasを使用して、指定したmp3ファイルをBGMとして再生しながら、Canvas上にアニメーションを描画し、それを動画として保存するプログラムを作成します。

まず、プロジェクトの構造を設定し、必要なコンポーネントを実装していきます。

// src-tauri/src/main.rs

use tauri::Manager;
use std::fs::File;
use std::io::Write;
use base64::decode;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let window = app.get_window("main").unwrap();
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![save_video])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command]
fn save_video(video_data: String) -> Result<String, String> {
    let bytes = decode(video_data).map_err(|e| e.to_string())?;
    let mut file = File::create("output.webm").map_err(|e| e.to_string())?;
    file.write_all(&bytes).map_err(|e| e.to_string())?;
    Ok("Video saved successfully".to_string())
}

これはTauriアプリケーションのRustバックエンドのコードです。save_video関数は、フロントエンドから送られてくる動画データを受け取り、ファイルとして保存します。

次に、HTML、JavaScript、CSSを含むフロントエンドのコードを作成します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Recorder</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
        }
        #canvas {
            border: 1px solid black;
        }
        #controls {
            margin-top: 20px;
        }
        textarea {
            width: 100%;
            height: 200px;
            margin-top: 20px;
        }
    </style>
</head>
<body>
    <canvas id="canvas" width="640" height="480"></canvas>
    <div id="controls">
        <button id="startBtn">Start Recording</button>
        <button id="stopBtn" disabled>Stop Recording</button>
        <input type="file" id="audioInput" accept="audio/mp3">
    </div>
    <textarea id="animationScript">
// Example animation script
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let x = 0;
function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.arc(x, canvas.height / 2, 20, 0, Math.PI * 2);
    ctx.fill();
    x = (x + 5) % canvas.width;
}

function animate() {
    draw();
    requestAnimationFrame(animate);
}

animate();
    </textarea>

    <script src="<https://cdnjs.cloudflare.com/ajax/libs/js-base64/3.7.2/base64.min.js>"></script>
    <script>
        const { invoke } = window.__TAURI__.tauri;

        let mediaRecorder;
        let audioContext;
        let audioSource;
        const chunks = [];

        const canvas = document.getElementById('canvas');
        const startBtn = document.getElementById('startBtn');
        const stopBtn = document.getElementById('stopBtn');
        const audioInput = document.getElementById('audioInput');
        const animationScript = document.getElementById('animationScript');

        startBtn.addEventListener('click', startRecording);
        stopBtn.addEventListener('click', stopRecording);
        audioInput.addEventListener('change', loadAudio);

        function loadAudio(e) {
            const file = e.target.files[0];
            const reader = new FileReader();
            reader.onload = function(e) {
                const arrayBuffer = e.target.result;
                audioContext = new (window.AudioContext || window.webkitAudioContext)();
                audioContext.decodeAudioData(arrayBuffer, (buffer) => {
                    audioSource = audioContext.createBufferSource();
                    audioSource.buffer = buffer;
                    audioSource.connect(audioContext.destination);
                });
            };
            reader.readAsArrayBuffer(file);
        }

        function startRecording() {
            const stream = canvas.captureStream(30);
            mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm' });

            mediaRecorder.ondataavailable = (e) => {
                if (e.data.size > 0) {
                    chunks.push(e.data);
                }
            };

            mediaRecorder.onstop = saveVideo;

            mediaRecorder.start();
            if (audioSource) {
                audioSource.start(0);
            }
            startBtn.disabled = true;
            stopBtn.disabled = false;

            // Execute user's animation script
            eval(animationScript.value);
        }

        function stopRecording() {
            mediaRecorder.stop();
            if (audioSource) {
                audioSource.stop();
            }
            startBtn.disabled = false;
            stopBtn.disabled = true;
        }

        async function saveVideo() {
            const blob = new Blob(chunks, { type: 'video/webm' });
            const reader = new FileReader();
            reader.readAsDataURL(blob);
            reader.onloadend = function() {
                const base64data = reader.result.split(',')[1];
                invoke('save_video', { videoData: base64data })
                    .then((response) => console.log(response))
                    .catch((error) => console.error(error));
            };
        }
    </script>
</body>
</html>

このHTMLファイルには、Canvas要素、コントロール、アニメーションスクリプトを編集するためのテキストエリア、そしてJavaScriptコードが含まれています。ユーザーは animationScript テキストエリアでアニメーションスクリプトを編集できます。

使い方:

  1. mp3ファイルを選択します。
  2. アニメーションスクリプトを編集します(必要に応じて)。
  3. "Start Recording" ボタンをクリックして録画を開始します。
  4. "Stop Recording" ボタンをクリックして録画を終了します。
  5. 録画が終了すると、動画が自動的に保存されます。

注意点:

  1. このコードはTauriプロジェクト内で動作するように設計されています。新しいTauriプロジェクトを作成し、これらのファイルを適切な場所に配置する必要があります。
  2. tauri.conf.json ファイルで、必要な権限(ファイルシステムへのアクセスなど)を設定する必要があります。
  3. 実際の使用時には、エラー処理やユーザーフィードバックをさらに改善することをお勧めします。
  4. このコードは基本的な機能を提供していますが、より高度な機能(例:音声と動画の同期の微調整、様々な出力形式のサポートなど)を追加することも可能です。
  5. セキュリティ上の理由から、eval() の使用には注意が必要です。実際の製品では、より安全なアプローチを検討してください。