hysts HF Staff commited on
Commit
9314cdc
·
1 Parent(s): 2f4c162
Files changed (9) hide show
  1. README.md +0 -1
  2. app.py +91 -170
  3. en.txt +0 -0
  4. frankenstein5k.md +0 -11
  5. gatsby5k.md +0 -17
  6. pyproject.toml +6 -0
  7. requirements.txt +3 -1
  8. style.css +4 -0
  9. uv.lock +21 -2
README.md CHANGED
@@ -8,7 +8,6 @@ sdk_version: 5.31.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- short_description: Upgraded to v1.0!
12
  disable_embedding: true
13
  ---
14
 
 
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
 
11
  disable_embedding: true
12
  ---
13
 
app.py CHANGED
@@ -1,104 +1,13 @@
1
- import os
2
- import random
3
-
4
  import gradio as gr
 
5
  import spaces
6
- import torch
7
  from kokoro import KModel, KPipeline
8
 
9
- IS_DUPLICATE = not os.getenv("SPACE_ID", "").startswith("hexgrad/")
10
- CUDA_AVAILABLE = torch.cuda.is_available()
11
- if not IS_DUPLICATE:
12
- import kokoro
13
- import misaki
14
-
15
- print("DEBUG", kokoro.__version__, CUDA_AVAILABLE, misaki.__version__) # noqa: T201
16
-
17
- CHAR_LIMIT = None if IS_DUPLICATE else 5000
18
- models = {gpu: KModel().to("cuda" if gpu else "cpu").eval() for gpu in [False] + ([True] if CUDA_AVAILABLE else [])}
19
  pipelines = {lang_code: KPipeline(lang_code=lang_code, model=False) for lang_code in "ab"}
20
  pipelines["a"].g2p.lexicon.golds["kokoro"] = "kˈOkəɹO" # noqa: RUF001
21
  pipelines["b"].g2p.lexicon.golds["kokoro"] = "kˈQkəɹQ" # noqa: RUF001
22
 
23
-
24
- @spaces.GPU(duration=30)
25
- def forward_gpu(ps, ref_s, speed): # noqa: ANN001, ANN201
26
- return models[True](ps, ref_s, speed)
27
-
28
-
29
- def generate_first(text, voice="af_heart", speed=1, use_gpu=CUDA_AVAILABLE): # noqa: ANN001, ANN201
30
- text = text if CHAR_LIMIT is None else text.strip()[:CHAR_LIMIT]
31
- pipeline = pipelines[voice[0]]
32
- pack = pipeline.load_voice(voice)
33
- use_gpu = use_gpu and CUDA_AVAILABLE
34
- for _, ps, _ in pipeline(text, voice, speed):
35
- ref_s = pack[len(ps) - 1]
36
- try:
37
- audio = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
38
- except gr.exceptions.Error as e:
39
- if use_gpu:
40
- gr.Warning(str(e))
41
- gr.Info("Retrying with CPU. To avoid this error, change Hardware to CPU.")
42
- audio = models[False](ps, ref_s, speed)
43
- else:
44
- raise gr.Error(e) # noqa: B904
45
- return (24000, audio.numpy()), ps
46
- return None, ""
47
-
48
-
49
- # Arena API
50
- def predict(text, voice="af_heart", speed=1): # noqa: ANN001, ANN201
51
- return generate_first(text, voice, speed, use_gpu=False)[0]
52
-
53
-
54
- def tokenize_first(text, voice="af_heart"): # noqa: ANN001, ANN201
55
- pipeline = pipelines[voice[0]]
56
- for _, ps, _ in pipeline(text, voice):
57
- return ps
58
- return ""
59
-
60
-
61
- def generate_all(text, voice="af_heart", speed=1, use_gpu=CUDA_AVAILABLE): # noqa: ANN001, ANN201
62
- text = text if CHAR_LIMIT is None else text.strip()[:CHAR_LIMIT]
63
- pipeline = pipelines[voice[0]]
64
- pack = pipeline.load_voice(voice)
65
- use_gpu = use_gpu and CUDA_AVAILABLE
66
- first = True
67
- for _, ps, _ in pipeline(text, voice, speed):
68
- ref_s = pack[len(ps) - 1]
69
- try:
70
- audio = forward_gpu(ps, ref_s, speed) if use_gpu else models[False](ps, ref_s, speed)
71
- except gr.exceptions.Error as e:
72
- if use_gpu:
73
- gr.Warning(str(e))
74
- gr.Info("Switching to CPU")
75
- audio = models[False](ps, ref_s, speed)
76
- else:
77
- raise gr.Error(e) # noqa: B904
78
- yield 24000, audio.numpy()
79
- if first:
80
- first = False
81
- yield 24000, torch.zeros(1).numpy()
82
-
83
-
84
- with open("en.txt") as r: # noqa: PTH123
85
- random_quotes = [line.strip() for line in r]
86
-
87
-
88
- def get_random_quote(): # noqa: ANN201
89
- return random.choice(random_quotes) # noqa: S311
90
-
91
-
92
- def get_gatsby(): # noqa: ANN201
93
- with open("gatsby5k.md") as r: # noqa: PTH123
94
- return r.read().strip()
95
-
96
-
97
- def get_frankenstein(): # noqa: ANN201
98
- with open("frankenstein5k.md") as r: # noqa: PTH123
99
- return r.read().strip()
100
-
101
-
102
  CHOICES = {
103
  "🇺🇸 🚺 Heart ❤️": "af_heart",
104
  "🇺🇸 🚺 Bella 🔥": "af_bella",
@@ -132,93 +41,105 @@ CHOICES = {
132
  for v in CHOICES.values():
133
  pipelines[v[0]].load_voice(v)
134
 
135
- TOKEN_NOTE = """
136
- 💡 Customize pronunciation with Markdown link syntax and /slashes/ like `[Kokoro](/kˈOkəɹO/)`
137
-
138
- 💬 To adjust intonation, try punctuation `;:,.!?—…"()“”` or stress `ˈ` and `ˌ`
139
-
140
- ⬇️ Lower stress `[1 level](-1)` or `[2 levels](-2)`
141
-
142
- ⬆️ Raise stress 1 level `[or](+2)` 2 levels (only works on less stressed, usually short words)
143
- """ # noqa: S105, RUF001
144
-
145
- with gr.Blocks() as generate_tab:
146
- out_audio = gr.Audio(label="Output Audio", interactive=False, streaming=False, autoplay=True)
147
- generate_btn = gr.Button("Generate", variant="primary")
148
- with gr.Accordion("Output Tokens", open=True):
149
- out_ps = gr.Textbox(
150
- interactive=False, show_label=False, info="Tokens used to generate the audio, up to 510 context length."
151
- )
152
- tokenize_btn = gr.Button("Tokenize", variant="secondary")
153
- gr.Markdown(TOKEN_NOTE)
154
- predict_btn = gr.Button("Predict", variant="secondary", visible=False)
155
-
156
- STREAM_NOTE_LIST = ["⚠️ There is an unknown Gradio bug that might yield no audio the first time you click `Stream`."]
157
- if CHAR_LIMIT is not None:
158
- STREAM_NOTE_LIST.append(f"✂️ Each stream is capped at {CHAR_LIMIT} characters.")
159
- STREAM_NOTE_LIST.append(
160
- "🚀 Want more characters? You can [use Kokoro directly](https://huggingface.co/hexgrad/Kokoro-82M#usage) or duplicate this space:"
161
- )
162
- STREAM_NOTE = "\n\n".join(STREAM_NOTE_LIST)
163
 
164
- with gr.Blocks() as stream_tab:
165
- out_stream = gr.Audio(label="Output Audio Stream", interactive=False, streaming=True, autoplay=True)
166
- with gr.Row():
167
- stream_btn = gr.Button("Stream", variant="primary")
168
- stop_btn = gr.Button("Stop", variant="stop")
169
- with gr.Accordion("Note", open=True):
170
- gr.Markdown(STREAM_NOTE)
171
- gr.DuplicateButton()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- BANNER_TEXT = """
174
- [***Kokoro*** **is an open-weight TTS model with 82 million parameters.**](https://huggingface.co/hexgrad/Kokoro-82M)
175
 
176
- This demo only showcases English, but you can directly use the model to access other languages.
177
- """
178
- API_OPEN = os.getenv("SPACE_ID") != "hexgrad/Kokoro-TTS"
179
- API_NAME = None if API_OPEN else False
180
- with gr.Blocks() as app:
181
- with gr.Row():
182
- gr.Markdown(BANNER_TEXT, container=True)
183
  with gr.Row():
184
  with gr.Column():
185
  text = gr.Textbox(
186
  label="Input Text",
187
- info=f"Up to ~500 characters per Generate, or {'∞' if CHAR_LIMIT is None else CHAR_LIMIT} characters per Stream",
 
 
 
 
 
 
188
  )
189
- with gr.Row():
190
- voice = gr.Dropdown(
191
- list(CHOICES.items()),
192
- value="af_heart",
193
- label="Voice",
194
- info="Quality and availability vary by language",
195
- )
196
- use_gpu = gr.Dropdown(
197
- [("ZeroGPU 🚀", True), ("CPU 🐌", False)],
198
- value=CUDA_AVAILABLE,
199
- label="Hardware",
200
- info="GPU is usually faster, but has a usage quota",
201
- interactive=CUDA_AVAILABLE,
202
- )
203
- speed = gr.Slider(minimum=0.5, maximum=2, value=1, step=0.1, label="Speed")
204
- random_btn = gr.Button("🎲 Random Quote 💬", variant="secondary")
205
- with gr.Row():
206
- gatsby_btn = gr.Button("🥂 Gatsby 📕", variant="secondary")
207
- frankenstein_btn = gr.Button("💀 Frankenstein 📗", variant="secondary")
208
  with gr.Column():
209
- gr.TabbedInterface([generate_tab, stream_tab], ["Generate", "Stream"])
210
- random_btn.click(fn=get_random_quote, inputs=[], outputs=[text], api_name=API_NAME)
211
- gatsby_btn.click(fn=get_gatsby, inputs=[], outputs=[text], api_name=API_NAME)
212
- frankenstein_btn.click(fn=get_frankenstein, inputs=[], outputs=[text], api_name=API_NAME)
213
- generate_btn.click(
214
- fn=generate_first, inputs=[text, voice, speed, use_gpu], outputs=[out_audio, out_ps], api_name=API_NAME
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  )
216
- tokenize_btn.click(fn=tokenize_first, inputs=[text, voice], outputs=[out_ps], api_name=API_NAME)
217
- stream_event = stream_btn.click(
218
- fn=generate_all, inputs=[text, voice, speed, use_gpu], outputs=[out_stream], api_name=API_NAME
 
 
219
  )
220
- stop_btn.click(fn=None, cancels=stream_event)
221
- predict_btn.click(fn=predict, inputs=[text, voice, speed], outputs=[out_audio], api_name=API_NAME)
222
 
223
  if __name__ == "__main__":
224
- app.queue(api_open=API_OPEN).launch(show_api=API_OPEN, ssr_mode=True)
 
 
 
 
1
  import gradio as gr
2
+ import numpy as np
3
  import spaces
 
4
  from kokoro import KModel, KPipeline
5
 
6
+ model = KModel(repo_id="hexgrad/Kokoro-82M").to("cuda")
 
 
 
 
 
 
 
 
 
7
  pipelines = {lang_code: KPipeline(lang_code=lang_code, model=False) for lang_code in "ab"}
8
  pipelines["a"].g2p.lexicon.golds["kokoro"] = "kˈOkəɹO" # noqa: RUF001
9
  pipelines["b"].g2p.lexicon.golds["kokoro"] = "kˈQkəɹQ" # noqa: RUF001
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  CHOICES = {
12
  "🇺🇸 🚺 Heart ❤️": "af_heart",
13
  "🇺🇸 🚺 Bella 🔥": "af_bella",
 
41
  for v in CHOICES.values():
42
  pipelines[v[0]].load_voice(v)
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ @spaces.GPU(duration=30)
46
+ def generate(text: str, voice: str = "af_heart", speed: float = 1.0) -> tuple[tuple[int, np.ndarray], str]:
47
+ """Generate audio from text using Kokoro TTS model.
48
+
49
+ Available voices:
50
+ - af_heart
51
+ - af_bella
52
+ - af_nicole
53
+ - af_aoede
54
+ - af_kore
55
+ - af_sarah
56
+ - af_nova
57
+ - af_sky
58
+ - af_alloy
59
+ - af_jessica
60
+ - af_river
61
+ - am_michael
62
+ - am_fenrir
63
+ - am_puck
64
+ - am_echo
65
+ - am_eric
66
+ - am_liam
67
+ - am_onyx
68
+ - am_santa
69
+ - am_adam
70
+ - bf_emma
71
+ - bf_isabella
72
+ - bf_alice
73
+ - bf_lily
74
+ - bm_george
75
+ - bm_fable
76
+ - bm_lewis
77
+ - bm_daniel
78
+
79
+ Args:
80
+ text: The text to generate audio from.
81
+ voice: The voice to use. Defaults to "af_heart".
82
+ speed: The speed of the audio. Defaults to 1.0.
83
+
84
+ Returns:
85
+ A tuple containing the audio and the tokens used to generate the audio.
86
+ """
87
+ pipeline = pipelines[voice[0]]
88
+ pack = pipeline.load_voice(voice)
89
+ generator = pipeline(text, voice, speed)
90
+ # Only use the first batch of tokens
91
+ _, ps, _ = next(generator)
92
+ ref_s = pack[len(ps) - 1]
93
+ audio = model(ps, ref_s, speed)
94
+ return (24000, audio.numpy()), ps
95
 
 
 
96
 
97
+ with gr.Blocks(css_paths="style.css") as demo:
98
+ gr.Markdown("# Kokoro TTS")
 
 
 
 
 
99
  with gr.Row():
100
  with gr.Column():
101
  text = gr.Textbox(
102
  label="Input Text",
103
+ info="Up to ~500 characters.",
104
+ )
105
+ voice = gr.Dropdown(
106
+ label="Voice",
107
+ choices=list(CHOICES.items()),
108
+ value="af_heart",
109
+ info="Quality and availability vary by language",
110
  )
111
+ speed = gr.Slider(label="Speed", minimum=0.5, maximum=2, step=0.1, value=1)
112
+ generate_btn = gr.Button("Generate", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  with gr.Column():
114
+ out_audio = gr.Audio(label="Output Audio", interactive=False, streaming=False, autoplay=True)
115
+ out_ps = gr.Textbox(
116
+ label="Output Tokens",
117
+ info="Tokens used to generate the audio, up to 510 context length.",
118
+ )
119
+
120
+ gr.Examples(
121
+ examples=[
122
+ [
123
+ "She sells seashells by the seashore. The shells she sells are surely seashells. So if she sells shells on the seashore, I'm sure she sells seashore shells.",
124
+ "af_heart",
125
+ 1.0,
126
+ ],
127
+ [
128
+ "Peter Piper picked a peck of pickled peppers. A peck of pickled peppers Peter Piper picked. If Peter Piper picked a peck of pickled peppers, Where's the peck of pickled peppers Peter Piper picked?",
129
+ "af_heart",
130
+ 1.0,
131
+ ],
132
+ ],
133
+ fn=generate,
134
+ inputs=[text, voice, speed],
135
+ outputs=[out_audio, out_ps],
136
  )
137
+
138
+ generate_btn.click(
139
+ fn=generate,
140
+ inputs=[text, voice, speed],
141
+ outputs=[out_audio, out_ps],
142
  )
 
 
143
 
144
  if __name__ == "__main__":
145
+ demo.launch(mcp_server=True)
en.txt DELETED
The diff for this file is too large to render. See raw diff
 
frankenstein5k.md DELETED
@@ -1,11 +0,0 @@
1
- You will rejoice to hear that no disaster has accompanied the commencement of an enterprise which you have regarded with such evil forebodings. I arrived here yesterday, and my first task is to assure my dear sister of my welfare and increasing confidence in the success of my undertaking.
2
-
3
- I am already far north of London, and as I walk in the streets of Petersburgh, I feel a cold northern breeze play upon my cheeks, which braces my nerves and fills me with delight. Do you understand this feeling? This breeze, which has travelled from the regions towards which I am advancing, gives me a foretaste of those icy climes. Inspirited by this wind of promise, my daydreams become more fervent and vivid. I try in vain to be persuaded that the pole is the seat of frost and desolation; it ever presents itself to my imagination as the region of beauty and delight. There, Margaret, the sun is for ever visible, its broad disk just skirting the horizon and diffusing a perpetual splendour. There—for with your leave, my sister, I will put some trust in preceding navigators—there snow and frost are banished; and, sailing over a calm sea, we may be wafted to a land surpassing in wonders and in beauty every region hitherto discovered on the habitable globe. Its productions and features may be without example, as the phenomena of the heavenly bodies undoubtedly are in those undiscovered solitudes. What may not be expected in a country of eternal light? I may there discover the wondrous power which attracts the needle and may regulate a thousand celestial observations that require only this voyage to render their seeming eccentricities consistent for ever. I shall satiate my ardent curiosity with the sight of a part of the world never before visited, and may tread a land never before imprinted by the foot of man. These are my enticements, and they are sufficient to conquer all fear of danger or death and to induce me to commence this laborious voyage with the joy a child feels when he embarks in a little boat, with his holiday mates, on an expedition of discovery up his native river. But supposing all these conjectures to be false, you cannot contest the inestimable benefit which I shall confer on all mankind, to the last generation, by discovering a passage near the pole to those countries, to reach which at present so many months are requisite; or by ascertaining the secret of the magnet, which, if at all possible, can only be effected by an undertaking such as mine.
4
-
5
- These reflections have dispelled the agitation with which I began my letter, and I feel my heart glow with an enthusiasm which elevates me to heaven, for nothing contributes so much to tranquillise the mind as a steady purpose—a point on which the soul may fix its intellectual eye. This expedition has been the favourite dream of my early years. I have read with ardour the accounts of the various voyages which have been made in the prospect of arriving at the North Pacific Ocean through the seas which surround the pole. You may remember that a history of all the voyages made for purposes of discovery composed the whole of our good Uncle Thomas’s library. My education was neglected, yet I was passionately fond of reading. These volumes were my study day and night, and my familiarity with them increased that regret which I had felt, as a child, on learning that my father’s dying injunction had forbidden my uncle to allow me to embark in a seafaring life.
6
-
7
- These visions faded when I perused, for the first time, those poets whose effusions entranced my soul and lifted it to heaven. I also became a poet and for one year lived in a paradise of my own creation; I imagined that I also might obtain a niche in the temple where the names of Homer and Shakespeare are consecrated. You are well acquainted with my failure and how heavily I bore the disappointment. But just at that time I inherited the fortune of my cousin, and my thoughts were turned into the channel of their earlier bent.
8
-
9
- Six years have passed since I resolved on my present undertaking. I can, even now, remember the hour from which I dedicated myself to this great enterprise. I commenced by inuring my body to hardship. I accompanied the whale-fishers on several expeditions to the North Sea; I voluntarily endured cold, famine, thirst, and want of sleep; I often worked harder than the common sailors during the day and devoted my nights to the study of mathematics, the theory of medicine, and those branches of physical science from which a naval adventurer might derive the greatest practical advantage. Twice I actually hired myself as an under-mate in a Greenland whaler, and acquitted myself to admiration. I must own I felt a little proud when my captain offered me the second dignity in the vessel and entreated me to remain with the greatest earnestness, so valuable did he consider my services.
10
-
11
- And now, dear Margaret, do I not deserve to accomplish some great purpose?
 
 
 
 
 
 
 
 
 
 
 
 
gatsby5k.md DELETED
@@ -1,17 +0,0 @@
1
- In my younger and more vulnerable years my father gave me some advice that I’ve been turning over in my mind ever since.
2
-
3
- “Whenever you feel like criticizing anyone,” he told me, “just remember that all the people in this world haven’t had the advantages that you’ve had.”
4
-
5
- He didn’t say any more, but we’ve always been unusually communicative in a reserved way, and I understood that he meant a great deal more than that. In consequence, I’m inclined to reserve all judgements, a habit that has opened up many curious natures to me and also made me the victim of not a few veteran bores. The abnormal mind is quick to detect and attach itself to this quality when it appears in a normal person, and so it came about that in college I was unjustly accused of being a politician, because I was privy to the secret griefs of wild, unknown men. Most of the confidences were unsought—frequently I have feigned sleep, preoccupation, or a hostile levity when I realized by some unmistakable sign that an intimate revelation was quivering on the horizon; for the intimate revelations of young men, or at least the terms in which they express them, are usually plagiaristic and marred by obvious suppressions. Reserving judgements is a matter of infinite hope. I am still a little afraid of missing something if I forget that, as my father snobbishly suggested, and I snobbishly repeat, a sense of the fundamental decencies is parcelled out unequally at birth.
6
-
7
- And, after boasting this way of my tolerance, I come to the admission that it has a limit. Conduct may be founded on the hard rock or the wet marshes, but after a certain point I don’t care what it’s founded on. When I came back from the East last autumn I felt that I wanted the world to be in uniform and at a sort of moral attention forever; I wanted no more riotous excursions with privileged glimpses into the human heart. Only Gatsby, the man who gives his name to this book, was exempt from my reaction—Gatsby, who represented everything for which I have an unaffected scorn. If personality is an unbroken series of successful gestures, then there was something gorgeous about him, some heightened sensitivity to the promises of life, as if he were related to one of those intricate machines that register earthquakes ten thousand miles away. This responsiveness had nothing to do with that flabby impressionability which is dignified under the name of the “creative temperament”—it was an extraordinary gift for hope, a romantic readiness such as I have never found in any other person and which it is not likely I shall ever find again. No—Gatsby turned out all right at the end; it is what preyed on Gatsby, what foul dust floated in the wake of his dreams that temporarily closed out my interest in the abortive sorrows and short-winded elations of men.
8
-
9
- My family have been prominent, well-to-do people in this Middle Western city for three generations. The Carraways are something of a clan, and we have a tradition that we’re descended from the Dukes of Buccleuch, but the actual founder of my line was my grandfather’s brother, who came here in fifty-one, sent a substitute to the Civil War, and started the wholesale hardware business that my father carries on today.
10
-
11
- I never saw this great-uncle, but I’m supposed to look like him—with special reference to the rather hard-boiled painting that hangs in father’s office. I graduated from New Haven in 1915, just a quarter of a century after my father, and a little later I participated in that delayed Teutonic migration known as the Great War. I enjoyed the counter-raid so thoroughly that I came back restless. Instead of being the warm centre of the world, the Middle West now seemed like the ragged edge of the universe—so I decided to go East and learn the bond business. Everybody I knew was in the bond business, so I supposed it could support one more single man. All my aunts and uncles talked it over as if they were choosing a prep school for me, and finally said, “Why—[ye-es](/jˈɛ ɛs/),” with very grave, hesitant faces. Father agreed to finance me for a year, and after various delays I came East, permanently, I thought, in the spring of twenty-two.
12
-
13
- The practical thing was to find rooms in the city, but it was a warm season, and I had just left a country of wide lawns and friendly trees, so when a young man at the office suggested that we take a house together in a commuting town, it sounded like a great idea. He found the house, a weather-beaten cardboard bungalow at eighty a month, but at the last minute the firm ordered him to Washington, and I went out to the country alone. I had a dog—at least I had him for a few days until he ran away—and an old Dodge and a Finnish woman, who made my bed and cooked breakfast and muttered Finnish wisdom to herself over the electric stove.
14
-
15
- It was lonely for a day or so until one morning some man, more recently arrived than I, stopped me on the road.
16
-
17
- “How do you get to West Egg village?” he asked helplessly.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -8,6 +8,7 @@ dependencies = [
8
  "gradio[mcp]>=5.31.0",
9
  "hf-transfer>=0.1.9",
10
  "kokoro>=0.9.4",
 
11
  "spaces>=0.36.0",
12
  "torch==2.5.1",
13
  ]
@@ -52,3 +53,8 @@ convention = "google"
52
 
53
  [tool.ruff.format]
54
  docstring-code-format = true
 
 
 
 
 
 
8
  "gradio[mcp]>=5.31.0",
9
  "hf-transfer>=0.1.9",
10
  "kokoro>=0.9.4",
11
+ "misaki[en]>=0.9.4",
12
  "spaces>=0.36.0",
13
  "torch==2.5.1",
14
  ]
 
53
 
54
  [tool.ruff.format]
55
  docstring-code-format = true
56
+
57
+ [dependency-groups]
58
+ dev = [
59
+ "pip>=25.1.1",
60
+ ]
requirements.txt CHANGED
@@ -158,7 +158,9 @@ mcp==1.9.0
158
  mdurl==0.1.2
159
  # via markdown-it-py
160
  misaki==0.9.4
161
- # via kokoro
 
 
162
  mpmath==1.3.0
163
  # via sympy
164
  murmurhash==1.0.13
 
158
  mdurl==0.1.2
159
  # via markdown-it-py
160
  misaki==0.9.4
161
+ # via
162
+ # kokoro-tts (pyproject.toml)
163
+ # kokoro
164
  mpmath==1.3.0
165
  # via sympy
166
  murmurhash==1.0.13
style.css ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ h1 {
2
+ text-align: center;
3
+ display: block;
4
+ }
uv.lock CHANGED
@@ -409,7 +409,7 @@ name = "exceptiongroup"
409
  version = "1.3.0"
410
  source = { registry = "https://pypi.org/simple" }
411
  dependencies = [
412
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
413
  ]
414
  sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
415
  wheels = [
@@ -732,19 +732,29 @@ dependencies = [
732
  { name = "gradio", extra = ["mcp"] },
733
  { name = "hf-transfer" },
734
  { name = "kokoro" },
 
735
  { name = "spaces" },
736
  { name = "torch" },
737
  ]
738
 
 
 
 
 
 
739
  [package.metadata]
740
  requires-dist = [
741
  { name = "gradio", extras = ["mcp"], specifier = ">=5.31.0" },
742
  { name = "hf-transfer", specifier = ">=0.1.9" },
743
  { name = "kokoro", specifier = ">=0.9.4" },
 
744
  { name = "spaces", specifier = ">=0.36.0" },
745
  { name = "torch", specifier = "==2.5.1" },
746
  ]
747
 
 
 
 
748
  [[package]]
749
  name = "langcodes"
750
  version = "3.5.0"
@@ -1421,6 +1431,15 @@ wheels = [
1421
  { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" },
1422
  ]
1423
 
 
 
 
 
 
 
 
 
 
1424
  [[package]]
1425
  name = "preshed"
1426
  version = "3.0.10"
@@ -2410,7 +2429,7 @@ name = "triton"
2410
  version = "3.1.0"
2411
  source = { registry = "https://pypi.org/simple" }
2412
  dependencies = [
2413
- { name = "filelock", marker = "python_full_version < '3.13'" },
2414
  ]
2415
  wheels = [
2416
  { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013, upload-time = "2024-10-14T16:05:32.106Z" },
 
409
  version = "1.3.0"
410
  source = { registry = "https://pypi.org/simple" }
411
  dependencies = [
412
+ { name = "typing-extensions", marker = "python_full_version < '3.12'" },
413
  ]
414
  sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
415
  wheels = [
 
732
  { name = "gradio", extra = ["mcp"] },
733
  { name = "hf-transfer" },
734
  { name = "kokoro" },
735
+ { name = "misaki", extra = ["en"] },
736
  { name = "spaces" },
737
  { name = "torch" },
738
  ]
739
 
740
+ [package.dev-dependencies]
741
+ dev = [
742
+ { name = "pip" },
743
+ ]
744
+
745
  [package.metadata]
746
  requires-dist = [
747
  { name = "gradio", extras = ["mcp"], specifier = ">=5.31.0" },
748
  { name = "hf-transfer", specifier = ">=0.1.9" },
749
  { name = "kokoro", specifier = ">=0.9.4" },
750
+ { name = "misaki", extras = ["en"], specifier = ">=0.9.4" },
751
  { name = "spaces", specifier = ">=0.36.0" },
752
  { name = "torch", specifier = "==2.5.1" },
753
  ]
754
 
755
+ [package.metadata.requires-dev]
756
+ dev = [{ name = "pip", specifier = ">=25.1.1" }]
757
+
758
  [[package]]
759
  name = "langcodes"
760
  version = "3.5.0"
 
1431
  { url = "https://files.pythonhosted.org/packages/21/2c/5e05f58658cf49b6667762cca03d6e7d85cededde2caf2ab37b81f80e574/pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044", size = 2674751, upload-time = "2025-04-12T17:49:59.628Z" },
1432
  ]
1433
 
1434
+ [[package]]
1435
+ name = "pip"
1436
+ version = "25.1.1"
1437
+ source = { registry = "https://pypi.org/simple" }
1438
+ sdist = { url = "https://files.pythonhosted.org/packages/59/de/241caa0ca606f2ec5fe0c1f4261b0465df78d786a38da693864a116c37f4/pip-25.1.1.tar.gz", hash = "sha256:3de45d411d308d5054c2168185d8da7f9a2cd753dbac8acbfa88a8909ecd9077", size = 1940155, upload-time = "2025-05-02T15:14:02.057Z" }
1439
+ wheels = [
1440
+ { url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl", hash = "sha256:2913a38a2abf4ea6b64ab507bd9e967f3b53dc1ede74b01b0931e1ce548751af", size = 1825227, upload-time = "2025-05-02T15:13:59.102Z" },
1441
+ ]
1442
+
1443
  [[package]]
1444
  name = "preshed"
1445
  version = "3.0.10"
 
2429
  version = "3.1.0"
2430
  source = { registry = "https://pypi.org/simple" }
2431
  dependencies = [
2432
+ { name = "filelock" },
2433
  ]
2434
  wheels = [
2435
  { url = "https://files.pythonhosted.org/packages/98/29/69aa56dc0b2eb2602b553881e34243475ea2afd9699be042316842788ff5/triton-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b0dd10a925263abbe9fa37dcde67a5e9b2383fc269fdf59f5657cac38c5d1d8", size = 209460013, upload-time = "2024-10-14T16:05:32.106Z" },