収録した音声に「ジー」というハムノイズが乗っている、室内の残響でセリフが聞き取りにくい、屋外収録で風切り音が入った——こういった問題、これまでは 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キーが無効 |
.env の STEMSPLIT_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で並列化でき、エピソード量産・アーカイブ整理に向いています
参考リンク