import io import os import uuid import librosa import numpy as np import soundfile as sf import streamlit as st from huggingface_hub import HfApi, CommitOperationAdd DATASET_REPO_ID = "uchi8977/kiss-vs-tsk" TARGET_SECONDS = 0.5 TARGET_SR = 16000 SILENCE_THRESHOLD = 0.01 st.set_page_config(page_title="投げキッスと舌打ち", page_icon="😘", layout="centered") def process_audio(audio_file) -> bytes | None: try: y, sr = librosa.load(io.BytesIO(audio_file.getvalue()), sr=TARGET_SR, mono=True) peak = float(np.max(np.abs(y))) if peak < SILENCE_THRESHOLD: st.warning("⚠️ 音が小さすぎるか、無音のようです。もう一度録音してください。") return None y = y / peak target_samples = int(sr * TARGET_SECONDS) if len(y) < target_samples: y = np.pad(y, (0, target_samples - len(y))) else: peak_idx = int(np.argmax(np.abs(y))) start = max(0, min(peak_idx - target_samples // 2, len(y) - target_samples)) y = y[start:start + target_samples] buf = io.BytesIO() sf.write(buf, y, sr, format='WAV', subtype='PCM_16') return buf.getvalue() except Exception as e: st.error(f"音声処理エラー: {e}") return None def submit_dataset(kiss_bytes: bytes, tsk_bytes: bytes, hf_token: str) -> str | None: try: submission_id = uuid.uuid4().hex operations = [ CommitOperationAdd( path_in_repo=f"data/{label}/{label}_{submission_id}.wav", path_or_fileobj=wav_bytes, ) for label, wav_bytes in [("kiss", kiss_bytes), ("tsk", tsk_bytes)] ] HfApi(token=hf_token).create_commit( repo_id=DATASET_REPO_ID, repo_type="dataset", operations=operations, commit_message=f"Add submission {submission_id}", ) return submission_id except Exception as e: st.error(f"❌ 送信エラー: {str(e)}") return None def go_to(step: int): st.session_state.step = step st.rerun() def back_button(): if st.button("← 前に戻る", use_container_width=True, key="back"): go_to(st.session_state.step - 1) for key, val in {"step": 0, "kiss_bytes": None, "tsk_bytes": None, "submission_id": None}.items(): st.session_state.setdefault(key, val) st.title("投げキッスと舌打ち") st.write("機械学習のデータセット作成にご協力ください。") st.divider() step = st.session_state.step if step == 0: st.warning("⚠️ ご提供いただいた音声データは、機械学習のオープンデータセットとしてHugging Face上で一般公開されます。") agree = st.checkbox("音声データが公開されることに同意します") st.write("準備ができたら下のボタンを押して録音を開始してください。") if st.button("はじめる", type="primary", use_container_width=True, disabled=not agree): go_to(1) elif step in (1, 2): label, display_name, next_label = ( ("kiss", "投げキッス", "次へ(舌打ちの録音)➔") if step == 1 else ("tsk", "舌打ち", "送信画面へ ➔") ) st.subheader(f"Step {step}/2: {display_name}") st.write(f"下のマイクボタンを押して{display_name}を録音してください。") audio_file = st.audio_input( f"{display_name}を録音", label_visibility="collapsed", key=f"{label}_input" ) st.write(f"※最大音量の瞬間を中心に【{TARGET_SECONDS}秒】だけを自動で切り抜きます({TARGET_SECONDS}秒未満は無音で補完)。") if audio_file: wav_bytes = process_audio(audio_file) if wav_bytes: st.session_state[f"{label}_bytes"] = wav_bytes st.success("✅ 切り抜き完了!再生して確認してください。") st.audio(wav_bytes) if st.button(next_label, type="primary", use_container_width=True): go_to(step + 1) back_button() elif step == 3: hf_token = os.getenv("HF_TOKEN") if not hf_token: st.error("❌ サーバー側の設定エラー:HF_TOKEN が設定されていません。管理者にお問い合わせください。") st.stop() kiss_bytes = st.session_state.kiss_bytes tsk_bytes = st.session_state.tsk_bytes if not (kiss_bytes and tsk_bytes): st.error("❌ 録音データが見つかりません。最初からやり直してください。") if st.button("最初に戻る", use_container_width=True): st.session_state.kiss_bytes = None st.session_state.tsk_bytes = None go_to(0) st.stop() st.subheader("🚀 最終確認") st.write("以下の2つのデータを送信します。よろしいですか?") col1, col2 = st.columns(2) with col1: st.write("投げキッス") st.audio(kiss_bytes) with col2: st.write("舌打ち") st.audio(tsk_bytes) st.write("※送信ボタンを押すと、Hugging Faceの公開リポジトリにアップロードされます。") if st.button("送信する!", type="primary", use_container_width=True): with st.spinner("Hugging Faceに送信中..."): submission_id = submit_dataset(kiss_bytes, tsk_bytes, hf_token) if submission_id: st.session_state.submission_id = submission_id st.session_state.kiss_bytes = None st.session_state.tsk_bytes = None go_to(4) back_button() elif step == 4: st.balloons() st.header("🎉 送信完了!") st.write("ご協力ありがとうございました。タブを閉じて終了してください。") submission_id = st.session_state.submission_id if submission_id: st.divider() st.write(f"**あなたのデータのID: `{submission_id}`**") st.write(f"リポジトリで `kiss_{submission_id}.wav` と `tsk_{submission_id}.wav` として公開されています。") st.divider() st.write("寄せられた録音データは、Hugging Faceのリポジトリでリアルタイムに公開されています👇") st.link_button( "他の人の投げキッスと舌打ちを聴きに行く", f"https://huggingface.co/datasets/{DATASET_REPO_ID}/tree/main/data", use_container_width=True )