File size: 6,567 Bytes
c685172
 
 
 
a062bed
c685172
a062bed
c685172
a062bed
c685172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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
    )