0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIボイスクリーナーをPythonから使う:ハム・ヒス・エコー・風切り音を自動除去する方法【StemSplit API 2026年版】

0
Posted at

収録した音声に「ジー」というハムノイズが乗っている、室内の残響でセリフが聞き取りにくい、屋外収録で風切り音が入った——こういった問題、これまでは Adobe Audition や iZotope RX を買うか、ノイズ除去プラグインを手動で調整するしかありませんでした。

2026年現在、AIによるノイズ除去はAPIを叩くだけで完結するようになっています。

本記事では、StemSplit が新しくリリースしたボイスクリーナー(Voice Cleaner)ツール群の概要と、/api/v1/denoise-jobs エンドポイントを使って Pythonから音声クリーニングを自動化する方法を解説します。


新機能:Voice Cleaner ツール群

StemSplit に追加されたのは、ノイズの種類に特化した6つのウェブツールです。

ツール URL 除去対象
Voice Cleaner(汎用) /voice-cleaner 複合ノイズ全般
Echo Remover /voice-cleaner/echo-remover 室内反響・リバーブ
Hiss Remover /voice-cleaner/hiss-remover ヒスノイズ(サー音・テープ雑音)
Hum Remover /voice-cleaner/hum-remover ハムノイズ(電源系の50/60Hzノイズ)
Static Noise Remover /voice-cleaner/remove-static-noise スタティック・バチバチ音
Wind Noise Remover /voice-cleaner/remove-wind-noise 風切り音

ウェブUIからファイルをドラッグ&ドロップするだけで使えます。ここではさらに、APIを使って自動化する方法を解説します。


ノイズの種類と原因を整理する

コードを書く前に「どのノイズが何から来るか」を整理しておきます。

ノイズの種類 典型的な原因
ハムノイズ(Hum) 電源ライン(50/60Hz)、蛍光灯、電磁干渉
ヒスノイズ(Hiss) アナログ機器の熱雑音、古いコンデンサマイク
エコー・残響(Echo) 壁の反響、フローリングの部屋、コンクリート壁
風切り音(Wind) 屋外収録、ウィンドスクリーンなし
スタティックノイズ(Static) 静電気、ケーブル不良、無線マイクの干渉

StemSplitのDenoiseエンジンはこれらを一括で検出・除去します。ウェブUIで「どのノイズかよく分からない」場合でも、汎用の Voice Cleaner に投げれば適切に処理されます。


セットアップ

pip install requests python-dotenv

.env ファイルを作成します:

STEMSPLIT_API_KEY=sk_live_xxxxxxxxxxxxxxxx

APIキーは StemSplit のデベロッパーページ から取得できます。無料枠で5分ぶんの処理が可能(クレジットカード登録不要)です。


APIの基本フロー

Denoise API は 3ステップで動きます。

1. POST /api/v1/upload         → presigned upload URL と uploadKey を取得
2. PUT  <uploadUrl>            → ファイルを直接 R2 にアップロード
3. POST /api/v1/denoise-jobs   → uploadKey でジョブを作成
4. GET  /api/v1/denoise-jobs/:id → ポーリングで完了を待ち、ダウンロードURLを取得

または sourceUrl でリモートのURLを直接渡せば、ステップ1・2をスキップできます。


実装:共通クライアント

# denoise_client.py
import os
import time
import requests
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()

API_BASE = "https://stemsplit.io/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['STEMSPLIT_API_KEY']}"}


def clean_audio(
    file_path: str,
    output_format: str = "MP3",
    output_dir: str = ".",
) -> Path:
    """
    ローカルの音声ファイルをノイズ除去してダウンロードする。

    Parameters
    ----------
    file_path     : 入力ファイルのパス(MP3 / WAV / FLAC / M4A / OGG / MP4対応)
    output_format : 出力フォーマット("MP3" | "WAV" | "FLAC")
    output_dir    : 出力先ディレクトリ

    Returns
    -------
    Path  クリーン後のファイルパス
    """
    file_path = Path(file_path)
    upload_key = _upload(file_path)
    job_id = _create_job(upload_key, output_format)
    download_url = _wait_for_completion(job_id)

    ext = output_format.lower()
    out_path = Path(output_dir) / f"{file_path.stem}_denoised.{ext}"
    return _download(download_url, str(out_path))


def clean_audio_from_url(
    source_url: str,
    output_format: str = "MP3",
    output_dir: str = ".",
    filename: str = "output",
) -> Path:
    """
    リモートURLの音声をノイズ除去してダウンロードする。
    ファイルのアップロードを省略できる。
    """
    job_id = _create_job_from_url(source_url, output_format)
    download_url = _wait_for_completion(job_id)

    ext = output_format.lower()
    out_path = Path(output_dir) / f"{filename}_denoised.{ext}"
    return _download(download_url, str(out_path))


# ── 内部ヘルパー ───────────────────────────────────────────────────────────────

def _upload(file_path: Path) -> str:
    """ファイルをR2にアップロードしてuploadKeyを返す。"""
    # Step 1: presigned URL を取得
    res = requests.post(
        f"{API_BASE}/upload",
        headers=HEADERS,
        json={"filename": file_path.name},
        timeout=15,
    )
    res.raise_for_status()
    data = res.json()["data"]
    upload_url = data["uploadUrl"]
    upload_key = data["uploadKey"]
    content_type = data["contentType"]

    # Step 2: presigned URL にファイルを直接PUT
    with open(file_path, "rb") as f:
        put_res = requests.put(
            upload_url,
            data=f,
            headers={"Content-Type": content_type},
            timeout=120,
        )
    put_res.raise_for_status()

    return upload_key


def _create_job(upload_key: str, output_format: str) -> str:
    """uploadKeyでデノイズジョブを作成してjob_idを返す。"""
    res = requests.post(
        f"{API_BASE}/denoise-jobs",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"uploadKey": upload_key, "outputFormat": output_format},
        timeout=30,
    )
    res.raise_for_status()
    return res.json()["data"]["id"]


def _create_job_from_url(source_url: str, output_format: str) -> str:
    """リモートURLからデノイズジョブを作成してjob_idを返す。"""
    res = requests.post(
        f"{API_BASE}/denoise-jobs",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"sourceUrl": source_url, "outputFormat": output_format},
        timeout=30,
    )
    res.raise_for_status()
    return res.json()["data"]["id"]


def _wait_for_completion(
    job_id: str,
    timeout: int = 300,
    interval: int = 3,
) -> str:
    """ジョブが完了するまでポーリングしてダウンロードURLを返す。"""
    deadline = time.time() + timeout
    while time.time() < deadline:
        res = requests.get(
            f"{API_BASE}/denoise-jobs/{job_id}",
            headers=HEADERS,
            timeout=15,
        )
        res.raise_for_status()
        data = res.json()["data"]
        status = data["status"]

        if status == "COMPLETED":
            return data["outputs"]["audio"]["url"]
        if status == "FAILED":
            raise RuntimeError(f"ジョブ失敗: {data.get('errorMessage')}")

        time.sleep(interval)

    raise TimeoutError(f"タイムアウト({timeout}秒):ジョブが完了しませんでした")


def _download(url: str, out_path: str) -> Path:
    res = requests.get(url, stream=True, timeout=60)
    res.raise_for_status()
    p = Path(out_path)
    p.parent.mkdir(parents=True, exist_ok=True)
    with open(p, "wb") as f:
        for chunk in res.iter_content(chunk_size=8192):
            f.write(chunk)
    return p

使い方

ローカルファイルをクリーニング

from denoise_client import clean_audio

# ポッドキャスト収録をクリーニング(MP3出力)
result = clean_audio("podcast_ep01_raw.mp3", output_format="MP3")
print(f"完了: {result}")
# → podcast_ep01_raw_denoised.mp3

# 楽器録音はWAVのまま処理
result = clean_audio("acoustic_guitar.wav", output_format="WAV")

リモートURLから直接処理(アップロード不要)

from denoise_client import clean_audio_from_url

result = clean_audio_from_url(
    source_url="https://example.com/meeting_recording.mp3",
    filename="meeting_ep12",
    output_format="MP3",
)

バッチ処理:フォルダ内を一括クリーニング

ポッドキャストのエピソードや、配信アーカイブをまとめて処理する例です。

# batch_denoise.py
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from denoise_client import clean_audio

SUPPORTED_EXT = {".mp3", ".wav", ".flac", ".m4a", ".ogg"}


def batch_denoise(
    input_dir: str,
    output_format: str = "MP3",
    output_dir: str = "cleaned",
    max_workers: int = 3,
) -> list[Path]:
    """
    フォルダ内の音声ファイルを並列でノイズ除去する。
    """
    files = [
        p for p in Path(input_dir).iterdir()
        if p.suffix.lower() in SUPPORTED_EXT
    ]
    if not files:
        print("対象ファイルが見つかりません")
        return []

    Path(output_dir).mkdir(exist_ok=True)
    results: list[Path] = []

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(clean_audio, str(f), output_format, output_dir): f
            for f in files
        }
        for future in as_completed(futures):
            src = futures[future]
            try:
                out = future.result()
                results.append(out)
                print(f"{src.name}{out.name}")
            except Exception as e:
                print(f"{src.name}: {e}")

    return results


if __name__ == "__main__":
    batch_denoise("./recordings", output_format="MP3", max_workers=3)

max_workers=3 程度が無難です。多すぎるとAPIのレートリミットに引っかかり、かえって遅くなります。


動画(MP4)の音声だけクリーニングして映像と合成する

Zoom録画やVlog素材など、MP4の音声だけ処理して映像はそのままというユースケースです。

import subprocess
from denoise_client import clean_audio

def denoise_video(video_path: str, output_path: str) -> str:
    """
    MP4の音声をクリーニングして元の映像と合成する。
    """
    # 音声だけ抽出してクリーニング
    audio_path = video_path.replace(".mp4", "_audio.mp3")
    subprocess.run(
        ["ffmpeg", "-y", "-i", video_path, "-vn", "-c:a", "mp3", audio_path],
        check=True, capture_output=True,
    )
    clean_path = clean_audio(audio_path, output_format="MP3")

    # クリーン後の音声と元映像を合成(映像は再エンコードなし)
    subprocess.run(
        [
            "ffmpeg", "-y",
            "-i", video_path,
            "-i", str(clean_path),
            "-c:v", "copy",
            "-c:a", "aac",
            "-map", "0:v:0",
            "-map", "1:a:0",
            output_path,
        ],
        check=True,
    )
    return output_path

result = denoise_video("zoom_call.mp4", "zoom_call_clean.mp4")
print(f"完了: {result}")

APIレスポンスの形式

参考までに各エンドポイントのレスポンス形状を示します。

POST /api/v1/denoise-jobs レスポンス

{
  "data": {
    "id": "job_xxxx",
    "status": "PENDING",
    "progress": 0,
    "createdAt": "2026-06-01T10:00:00.000Z",
    "estimatedSeconds": 12,
    "creditsRequired": 240,
    "input": {
      "fileName": "podcast_ep01_raw.mp3",
      "durationSeconds": 240,
      "fileSizeBytes": 3932160
    },
    "options": { "outputFormat": "MP3" },
    "outputs": null
  }
}

GET /api/v1/denoise-jobs/:id レスポンス(完了時)

{
  "data": {
    "id": "job_xxxx",
    "status": "COMPLETED",
    "progress": 100,
    "outputs": {
      "audio": {
        "url": "https://...",
        "expiresAt": "2026-06-01T11:00:00.000Z"
      }
    }
  }
}

ダウンロードURLは1時間で失効します。完了後はすぐダウンロードしてください。


対応フォーマット

形式 拡張子 備考
MP3 .mp3 最もよく使う
WAV .wav 無圧縮、品質最優先
FLAC .flac ロスレス圧縮
M4A / AAC .m4a iPhoneの録音に多い
OGG Vorbis .ogg
MP4(動画) .mp4 音声トラックのみ処理

よくあるエラーと対処

エラーコード 原因 対処
UNAUTHORIZED APIキーが無効 .envSTEMSPLIT_API_KEY を確認
FILE_TOO_LARGE ファイルサイズ超過 100MB 以下に圧縮して再送
AUDIO_TOO_LONG 音声が長すぎる ファイルを分割して処理
AUDIO_TOO_SHORT 音声が短すぎる 1秒以上の音声を渡す
INSUFFICIENT_CREDITS クレジット不足 購入ページでクレジットを追加
INVALID_UPLOAD_KEY アップロードキーの形式が不正 POST /api/v1/upload で取得したキーを使う

ウェブツールとAPIの使い分け

用途 おすすめ
1ファイルだけ試したい ウェブUI(ドラッグ&ドロップ)
ノイズの種類を絞って処理したい 専用ウェブツール(Hum/Hiss/Echo/Wind/Static)
バッチ処理・自動化 API(本記事のコード)
CI/CD や定期ジョブに組み込む API
GPUなしのサーバーから呼びたい API(クラウド処理)

まとめ

  • StemSplit のボイスクリーナーツール群は、ハム・ヒス・エコー・風切り音・スタティックに特化した専用ウェブツールと、汎用の Voice Cleaner の計6種類が追加されました
  • プログラムからは /api/v1/denoise-jobs エンドポイントを使ってノイズ除去を自動化できます
  • ファイルのアップロードは POST /api/v1/upload で取得した presigned URL 経由、またはリモートURLを直接指定する sourceUrl を使います
  • ダウンロードURLは1時間で失効するため、完了後は速やかにダウンロードしてください
  • バッチ処理は ThreadPoolExecutor で並列化でき、エピソード量産・アーカイブ整理に向いています

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?