OmniAICreator commited on
Commit
9c203ce
·
verified ·
1 Parent(s): 8784673

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +5 -4
  2. app.py +239 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
- title: Anime Speech Japanese Refiner Demo
3
- emoji: 🐨
4
- colorFrom: yellow
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
10
  license: cc-by-nc-4.0
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Anime-Speech-Japanese-Refiner-Demo
3
+ emoji:
4
+ colorFrom: green
5
+ colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.49.1
8
  app_file: app.py
9
  pinned: false
10
  license: cc-by-nc-4.0
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import os
4
+ import re
5
+ from typing import Dict, List, Optional, Tuple
6
+
7
+ import gradio as gr
8
+ import spaces
9
+ import torch
10
+ from qwen_omni_utils import process_mm_info
11
+ from transformers import Qwen3OmniMoeForConditionalGeneration, Qwen3OmniMoeProcessor
12
+
13
+ # -------------------------
14
+ # Config
15
+ # -------------------------
16
+
17
+ # Model ID to use for captioning
18
+ MODEL_ID = "NandemoGHS/Anime-Speech-Japanese-Refiner"
19
+
20
+ # Generation defaults
21
+ DEFAULT_TEMPERATURE = 0.6
22
+ DEFAULT_TOP_P = 0.95
23
+ DEFAULT_MAX_NEW_TOKENS = 1024
24
+
25
+ model = Qwen3OmniMoeForConditionalGeneration.from_pretrained(
26
+ MODEL_ID,
27
+ trust_remote_code=True,
28
+ device_map="auto",
29
+ dtype="auto",
30
+ )
31
+
32
+ processor = Qwen3OmniMoeProcessor.from_pretrained(MODEL_ID)
33
+
34
+
35
+ # -------------------------
36
+ # Utilities
37
+ # -------------------------
38
+ def _build_prompt(original_transcription: str) -> str:
39
+ """Build the Japanese instruction prompt (kept from your notebook, minor formatting)."""
40
+ return f"""これから与えられる音声クリップとその文字起こしについて、声の特徴と読み上げスタイル、感情などをアノテーションしたうえで、日本語の短いキャプションで要約してください。
41
+ 出力には以下の項目を含めてください。
42
+
43
+ profile: 話者プロファイル(例: お姉さん的な女性声/落ち着いた男性声/少女声 等)
44
+ mood: 感情・ムード(例: 明るい/落ち着いた/緊張/怒り/恐怖/悲しみ/快楽 等)
45
+ speed: 話速(例: とても遅い/やや速い/一定/(1.2×) 等)
46
+ prosody: 抑揚・リズム(例: 平坦/メリハリ/語尾上げ下げ/ため息混じり 等)
47
+ pitch_timbre: ピッチ/声質(例: 高め/低め/息多め/張りのある/囁き 等)
48
+ style: 発話スタイル(例: ナレーション風/会話調/朗読調/プレゼン調/囁き/喘ぎ/嗚咽/叫び 等)
49
+ emotion: 感情タグ(次のリストから1つ選択: ["angry", "sad", "disdainful", "excited", "surprised", "satisfied", "unhappy", "anxious", "hysterical", "delighted", "scared", "worried", "indifferent", "upset", "impatient", "nervous", "guilty", "scornful", "frustrated", "depressed", "panicked", "furious", "empathetic", "embarrassed", "reluctant", "disgusted", "keen", "moved", "proud", "relaxed", "grateful", "confident", "interested", "curious", "confused", "joyful", "disapproving", "negative", "denying", "astonished", "serious", "sarcastic", "conciliative", "comforting", "sincere", "sneering", "hesitating", "yielding", "painful", "awkward", "amused", "loving", "dating", "longing", "aroused", "seductive", "ecstatic", "shy"])
50
+ notes: 特記事項(間の取り方、笑い・ため・ブレス、ノイズ感、キス音、効果音、チュパ音 等)
51
+ caption: 上記を1〜2文・全角30〜80文字で自然文に要約
52
+ refined_text: 元の文字起こしテキストに、必要に応じて特殊タグを音声中のイベントの描写として文章のどこかに挿入したもの(必要なければ元テキストをそのまま出力)。
53
+
54
+ 元の文字起こしテキスト: {original_transcription}
55
+ 元の音声クリップ:"""
56
+
57
+
58
+ KEYS = [
59
+ "profile",
60
+ "mood",
61
+ "speed",
62
+ "prosody",
63
+ "pitch_timbre",
64
+ "style",
65
+ "emotion",
66
+ "notes",
67
+ "caption",
68
+ "refined_text",
69
+ ]
70
+
71
+
72
+ def _decode_sequences(processor, sequences, input_ids_len: int) -> str:
73
+ """Decode generated tokens into text, skipping prompt tokens."""
74
+ out = processor.batch_decode(
75
+ sequences[:, input_ids_len:],
76
+ skip_special_tokens=True,
77
+ clean_up_tokenization_spaces=False,
78
+ )
79
+ return out[0] if out else ""
80
+
81
+
82
+ def _run_transformers_inference(
83
+ audio_path: str,
84
+ original_transcription: str,
85
+ temperature: float,
86
+ top_p: float,
87
+ max_new_tokens: int,
88
+ ) -> str:
89
+ """
90
+ Prepare chat messages, encode with processor, and generate with the model.
91
+ Returns raw text output from the model.
92
+ """
93
+
94
+ # Compose the chat content (text + audio) following the Omni chat template.
95
+ prompt = _build_prompt(original_transcription)
96
+ messages = [
97
+ {
98
+ "role": "user",
99
+ "content": [
100
+ {"type": "text", "text": prompt},
101
+ {"type": "audio", "audio": audio_path},
102
+ ],
103
+ }
104
+ ]
105
+
106
+ # Prepare inputs
107
+ text = processor.apply_chat_template(
108
+ messages, add_generation_prompt=True, tokenize=False
109
+ )
110
+ audios, images, videos = process_mm_info(messages, use_audio_in_video=True)
111
+
112
+ inputs = processor(
113
+ text=text,
114
+ audio=audios,
115
+ images=images,
116
+ videos=videos,
117
+ return_tensors="pt",
118
+ padding=True,
119
+ use_audio_in_video=True,
120
+ )
121
+
122
+ inputs = inputs.to(model.device).to(model.dtype)
123
+
124
+ gen_kwargs = dict(
125
+ thinker_max_new_tokens=max_new_tokens,
126
+ thinker_do_sample=True,
127
+ thinker_top_p=top_p,
128
+ thinker_temperature=temperature,
129
+ )
130
+
131
+ # Some Qwen Omni variants support extra kwargs; try them first, then fall back.
132
+ sequences = None
133
+ with torch.no_grad():
134
+ text_ids, _ = model.generate(
135
+ **inputs,
136
+ thinker_return_dict_in_generate=True,
137
+ output_scores=False,
138
+ use_audio_in_video=True,
139
+ return_audio=False,
140
+ **gen_kwargs,
141
+ )
142
+ sequences = text_ids.sequences
143
+
144
+ input_ids_len = inputs["input_ids"].shape[1]
145
+ response_text = _decode_sequences(processor, sequences, input_ids_len)
146
+ return response_text.strip()
147
+
148
+
149
+ # -------------------------
150
+ # Gradio Inference
151
+ # -------------------------
152
+
153
+
154
+ @spaces.GPU(duration=120) # Request ephemeral GPU for up to 120s per call
155
+ def infer(
156
+ audio_file: Optional[str],
157
+ original_transcription: str,
158
+ temperature: float,
159
+ top_p: float,
160
+ max_new_tokens: int,
161
+ progress=gr.Progress(track_tqdm=True),
162
+ ):
163
+ # Validate inputs
164
+ if not original_transcription or not original_transcription.strip():
165
+ gr.Warning(
166
+ "元の文字起こしテキストは必須です。/ The original transcription is required."
167
+ )
168
+ return None, None, None
169
+
170
+ if not audio_file:
171
+ gr.Warning(
172
+ "音声ファイルをアップロードしてください。/ Please upload an audio file."
173
+ )
174
+ return None, None, None
175
+
176
+ progress(0.45, desc="Encoding & generating...")
177
+ raw_text = _run_transformers_inference(
178
+ audio_path=audio_file,
179
+ original_transcription=original_transcription.strip(),
180
+ temperature=temperature,
181
+ top_p=top_p,
182
+ max_new_tokens=int(max_new_tokens),
183
+ )
184
+
185
+ progress(1.0, desc="Done.")
186
+ return raw_text
187
+
188
+
189
+ # -------------------------
190
+ # Gradio UI
191
+ # -------------------------
192
+
193
+ DESCRIPTION = """
194
+ # Anime Speech Japanese Captioner (Transformers / ZeroGPU)
195
+
196
+ Upload an audio clip and provide its **original transcription (required)**.
197
+ The model analyzes voice profile, mood, prosody, style, and produces a concise Japanese caption along with a refined text.
198
+
199
+ **Model:** `NandemoGHS/Anime-Speech-Japanese-Refiner`
200
+ """
201
+
202
+ with gr.Blocks(theme="soft") as demo:
203
+ gr.Markdown(DESCRIPTION)
204
+
205
+ with gr.Row():
206
+ audio_in = gr.Audio(
207
+ label="Audio (required)",
208
+ type="filepath", # we pass the local path to the processor
209
+ sources=["upload", "microphone"],
210
+ waveform_options={"show_controls": True},
211
+ )
212
+ text_in = gr.Textbox(
213
+ label="Original Transcription (required)",
214
+ placeholder="音声の元の文字起こしを入力してください。",
215
+ lines=6,
216
+ )
217
+
218
+ with gr.Row():
219
+ temperature = gr.Slider(
220
+ 0.0, 1.0, step=0.05, value=DEFAULT_TEMPERATURE, label="Temperature"
221
+ )
222
+ top_p = gr.Slider(0.0, 1.0, step=0.05, value=DEFAULT_TOP_P, label="Top-p")
223
+ max_new_tokens = gr.Slider(
224
+ 64, 2048, step=32, value=DEFAULT_MAX_NEW_TOKENS, label="Max new tokens"
225
+ )
226
+
227
+ run_btn = gr.Button("Caption", variant="primary")
228
+
229
+ out = gr.Textbox(label="Output", lines=16)
230
+
231
+ run_btn.click(
232
+ infer,
233
+ inputs=[audio_in, text_in, temperature, top_p, max_new_tokens],
234
+ outputs=[out],
235
+ api_name="infer",
236
+ )
237
+
238
+ if __name__ == "__main__":
239
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ transformers
2
+ accelerate
3
+ soundfile
4
+ qwen_omni_utils
5
+ torchvision