cartesia 1.0.3__tar.gz → 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Cartesia AI, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cartesia
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: The official Python library for the Cartesia API.
5
5
  Home-page:
6
6
  Author: Cartesia, Inc.
@@ -12,6 +12,7 @@ Requires-Python: >=3.8.0
12
12
  Description-Content-Type: text/markdown
13
13
  Provides-Extra: dev
14
14
  Provides-Extra: all
15
+ License-File: LICENSE.md
15
16
 
16
17
 
17
18
  # Cartesia Python API Library
@@ -80,10 +81,10 @@ voice = client.voices.get(id=voice_id)
80
81
 
81
82
  transcript = "Hello! Welcome to Cartesia"
82
83
 
83
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
84
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
84
85
  model_id = "sonic-english"
85
86
 
86
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
87
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
87
88
  output_format = {
88
89
  "container": "raw",
89
90
  "encoding": "pcm_f32le",
@@ -131,10 +132,10 @@ async def write_stream():
131
132
  voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091"
132
133
  voice = client.voices.get(id=voice_id)
133
134
  transcript = "Hello! Welcome to Cartesia"
134
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
135
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
135
136
  model_id = "sonic-english"
136
137
 
137
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
138
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
138
139
  output_format = {
139
140
  "container": "raw",
140
141
  "encoding": "pcm_f32le",
@@ -186,10 +187,10 @@ voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091"
186
187
  voice = client.voices.get(id=voice_id)
187
188
  transcript = "Hello! Welcome to Cartesia"
188
189
 
189
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
190
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
190
191
  model_id = "sonic-english"
191
192
 
192
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
193
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
193
194
  output_format = {
194
195
  "container": "raw",
195
196
  "encoding": "pcm_f32le",
@@ -227,6 +228,179 @@ p.terminate()
227
228
  ws.close() # Close the websocket connection
228
229
  ```
229
230
 
231
+ #### Conditioning speech on previous generations using WebSocket
232
+
233
+ In some cases, input text may need to be streamed in. In these cases, it would be slow to wait for all the text to buffer before sending it to Cartesia's TTS service.
234
+
235
+ To mitigate this, Cartesia offers audio continuations. In this setting, users can send input text, as it becomes available, over a websocket connection.
236
+
237
+ To do this, we will create a `context` and send multiple requests without awaiting the response. Then you can listen to the responses in the order they were sent.
238
+
239
+ Each `context` will be closed automatically after 5 seconds of inactivity or when the `no_more_inputs` method is called. `no_more_inputs` sends a request with the `continue_=False`, which indicates no more inputs will be sent over this context
240
+
241
+ ```python
242
+ import asyncio
243
+ import os
244
+ import pyaudio
245
+ from cartesia import AsyncCartesia
246
+
247
+ async def send_transcripts(ctx):
248
+ # Check out voice IDs by calling `client.voices.list()` or on https://play.cartesia.ai/
249
+ voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
250
+
251
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
252
+ model_id = "sonic-english"
253
+
254
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
255
+ output_format = {
256
+ "container": "raw",
257
+ "encoding": "pcm_f32le",
258
+ "sample_rate": 44100,
259
+ }
260
+
261
+ transcripts = [
262
+ "Sonic and Yoshi team up in a dimension-hopping adventure! ",
263
+ "Racing through twisting zones, they dodge Eggman's badniks and solve ancient puzzles. ",
264
+ "In the Echoing Caverns, they find the Harmonic Crystal, unlocking new powers. ",
265
+ "Sonic's speed creates sound waves, while Yoshi's eggs become sonic bolts. ",
266
+ "As they near Eggman's lair, our heroes charge their abilities for an epic boss battle. ",
267
+ "Get ready to spin, jump, and sound-blast your way to victory in this high-octane crossover!"
268
+ ]
269
+
270
+ for transcript in transcripts:
271
+ # Send text inputs as they become available
272
+ await ctx.send(
273
+ model_id=model_id,
274
+ transcript=transcript,
275
+ voice_id=voice_id,
276
+ continue_=True,
277
+ output_format=output_format,
278
+ )
279
+
280
+ # Indicate that no more inputs will be sent. Otherwise, the context will close after 5 seconds of inactivity.
281
+ await ctx.no_more_inputs()
282
+
283
+ async def receive_and_play_audio(ctx):
284
+ p = pyaudio.PyAudio()
285
+ stream = None
286
+ rate = 44100
287
+
288
+ async for output in ctx.receive():
289
+ buffer = output["audio"]
290
+
291
+ if not stream:
292
+ stream = p.open(
293
+ format=pyaudio.paFloat32,
294
+ channels=1,
295
+ rate=rate,
296
+ output=True
297
+ )
298
+
299
+ stream.write(buffer)
300
+
301
+ stream.stop_stream()
302
+ stream.close()
303
+ p.terminate()
304
+
305
+ async def stream_and_listen():
306
+ client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
307
+
308
+ # Set up the websocket connection
309
+ ws = await client.tts.websocket()
310
+
311
+ # Create a context to send and receive audio
312
+ ctx = ws.context() # Generates a random context ID if not provided
313
+
314
+ send_task = asyncio.create_task(send_transcripts(ctx))
315
+ listen_task = asyncio.create_task(receive_and_play_audio(ctx))
316
+
317
+ # Call the two coroutine tasks concurrently
318
+ await asyncio.gather(send_task, listen_task)
319
+
320
+ await ws.close()
321
+ await client.close()
322
+
323
+ asyncio.run(stream_and_listen())
324
+ ```
325
+
326
+ You can also use continuations on the synchronous Cartesia client to stream in text as it becomes available. To do this, pass in a text generator that produces text chunks at intervals of less than 1 second, as shown below. This ensures smooth audio playback.
327
+
328
+ Note: the sync client has a different API for continuations compared to the async client.
329
+
330
+ ```python
331
+ from cartesia import Cartesia
332
+ import pyaudio
333
+ import os
334
+
335
+ client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
336
+
337
+ transcripts = [
338
+ "The crew engaged in a range of activities designed to mirror those "
339
+ "they might perform on a real Mars mission. ",
340
+ "Aside from growing vegetables and maintaining their habitat, they faced "
341
+ "additional stressors like communication delays with Earth, ",
342
+ "up to twenty-two minutes each way, to simulate the distance from Mars to our planet. ",
343
+ "These exercises were critical for understanding how astronauts can "
344
+ "maintain not just physical health but also mental well-being under such challenging conditions. ",
345
+ ]
346
+
347
+ # Ending each transcript with a space makes the audio smoother
348
+ def chunk_generator(transcripts):
349
+ for transcript in transcripts:
350
+ if transcript.endswith(" "):
351
+ yield transcript
352
+ else:
353
+ yield transcript + " "
354
+
355
+
356
+ # You can check out voice IDs by calling `client.voices.list()` or on https://play.cartesia.ai/
357
+ voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
358
+
359
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
360
+ model_id = "sonic-english"
361
+
362
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
363
+ output_format = {
364
+ "container": "raw",
365
+ "encoding": "pcm_f32le",
366
+ "sample_rate": 44100,
367
+ }
368
+
369
+ p = pyaudio.PyAudio()
370
+ rate = 44100
371
+
372
+ stream = None
373
+
374
+ # Set up the websocket connection
375
+ ws = client.tts.websocket()
376
+
377
+ # Create a context to send and receive audio
378
+ ctx = ws.context() # Generates a random context ID if not provided
379
+
380
+ # Pass in a text generator to generate & stream the audio
381
+ output_stream = ctx.send(
382
+ model_id=model_id,
383
+ transcript=chunk_generator(transcripts),
384
+ voice_id=voice_id,
385
+ output_format=output_format,
386
+ )
387
+
388
+ for output in output_stream:
389
+ buffer = output["audio"]
390
+
391
+ if not stream:
392
+ stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)
393
+
394
+ # Write the audio data to the stream
395
+ stream.write(buffer)
396
+
397
+ stream.stop_stream()
398
+ stream.close()
399
+ p.terminate()
400
+
401
+ ws.close() # Close the websocket connection
402
+ ```
403
+
230
404
  ### Multilingual Text-to-Speech [Alpha]
231
405
 
232
406
  You can use our `sonic-multilingual` model to generate audio in multiple languages. The languages supported are available at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
@@ -244,10 +418,10 @@ voice = client.voices.get(id=voice_id)
244
418
  transcript = "Hola! Bienvenido a Cartesia"
245
419
  language = "es" # Language code corresponding to the language of the transcript
246
420
 
247
- # Make sure you use the multilingual model! You can check out all models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
421
+ # Make sure you use the multilingual model! You can check out all models at https://docs.cartesia.ai/getting-started/available-models
248
422
  model_id = "sonic-multilingual"
249
423
 
250
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
424
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
251
425
  output_format = {
252
426
  "container": "raw",
253
427
  "encoding": "pcm_f32le",
@@ -64,10 +64,10 @@ voice = client.voices.get(id=voice_id)
64
64
 
65
65
  transcript = "Hello! Welcome to Cartesia"
66
66
 
67
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
67
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
68
68
  model_id = "sonic-english"
69
69
 
70
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
70
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
71
71
  output_format = {
72
72
  "container": "raw",
73
73
  "encoding": "pcm_f32le",
@@ -115,10 +115,10 @@ async def write_stream():
115
115
  voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091"
116
116
  voice = client.voices.get(id=voice_id)
117
117
  transcript = "Hello! Welcome to Cartesia"
118
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
118
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
119
119
  model_id = "sonic-english"
120
120
 
121
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
121
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
122
122
  output_format = {
123
123
  "container": "raw",
124
124
  "encoding": "pcm_f32le",
@@ -170,10 +170,10 @@ voice_id = "a0e99841-438c-4a64-b679-ae501e7d6091"
170
170
  voice = client.voices.get(id=voice_id)
171
171
  transcript = "Hello! Welcome to Cartesia"
172
172
 
173
- # You can check out our models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
173
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
174
174
  model_id = "sonic-english"
175
175
 
176
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
176
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
177
177
  output_format = {
178
178
  "container": "raw",
179
179
  "encoding": "pcm_f32le",
@@ -211,6 +211,179 @@ p.terminate()
211
211
  ws.close() # Close the websocket connection
212
212
  ```
213
213
 
214
+ #### Conditioning speech on previous generations using WebSocket
215
+
216
+ In some cases, input text may need to be streamed in. In these cases, it would be slow to wait for all the text to buffer before sending it to Cartesia's TTS service.
217
+
218
+ To mitigate this, Cartesia offers audio continuations. In this setting, users can send input text, as it becomes available, over a websocket connection.
219
+
220
+ To do this, we will create a `context` and send multiple requests without awaiting the response. Then you can listen to the responses in the order they were sent.
221
+
222
+ Each `context` will be closed automatically after 5 seconds of inactivity or when the `no_more_inputs` method is called. `no_more_inputs` sends a request with the `continue_=False`, which indicates no more inputs will be sent over this context
223
+
224
+ ```python
225
+ import asyncio
226
+ import os
227
+ import pyaudio
228
+ from cartesia import AsyncCartesia
229
+
230
+ async def send_transcripts(ctx):
231
+ # Check out voice IDs by calling `client.voices.list()` or on https://play.cartesia.ai/
232
+ voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
233
+
234
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
235
+ model_id = "sonic-english"
236
+
237
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
238
+ output_format = {
239
+ "container": "raw",
240
+ "encoding": "pcm_f32le",
241
+ "sample_rate": 44100,
242
+ }
243
+
244
+ transcripts = [
245
+ "Sonic and Yoshi team up in a dimension-hopping adventure! ",
246
+ "Racing through twisting zones, they dodge Eggman's badniks and solve ancient puzzles. ",
247
+ "In the Echoing Caverns, they find the Harmonic Crystal, unlocking new powers. ",
248
+ "Sonic's speed creates sound waves, while Yoshi's eggs become sonic bolts. ",
249
+ "As they near Eggman's lair, our heroes charge their abilities for an epic boss battle. ",
250
+ "Get ready to spin, jump, and sound-blast your way to victory in this high-octane crossover!"
251
+ ]
252
+
253
+ for transcript in transcripts:
254
+ # Send text inputs as they become available
255
+ await ctx.send(
256
+ model_id=model_id,
257
+ transcript=transcript,
258
+ voice_id=voice_id,
259
+ continue_=True,
260
+ output_format=output_format,
261
+ )
262
+
263
+ # Indicate that no more inputs will be sent. Otherwise, the context will close after 5 seconds of inactivity.
264
+ await ctx.no_more_inputs()
265
+
266
+ async def receive_and_play_audio(ctx):
267
+ p = pyaudio.PyAudio()
268
+ stream = None
269
+ rate = 44100
270
+
271
+ async for output in ctx.receive():
272
+ buffer = output["audio"]
273
+
274
+ if not stream:
275
+ stream = p.open(
276
+ format=pyaudio.paFloat32,
277
+ channels=1,
278
+ rate=rate,
279
+ output=True
280
+ )
281
+
282
+ stream.write(buffer)
283
+
284
+ stream.stop_stream()
285
+ stream.close()
286
+ p.terminate()
287
+
288
+ async def stream_and_listen():
289
+ client = AsyncCartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
290
+
291
+ # Set up the websocket connection
292
+ ws = await client.tts.websocket()
293
+
294
+ # Create a context to send and receive audio
295
+ ctx = ws.context() # Generates a random context ID if not provided
296
+
297
+ send_task = asyncio.create_task(send_transcripts(ctx))
298
+ listen_task = asyncio.create_task(receive_and_play_audio(ctx))
299
+
300
+ # Call the two coroutine tasks concurrently
301
+ await asyncio.gather(send_task, listen_task)
302
+
303
+ await ws.close()
304
+ await client.close()
305
+
306
+ asyncio.run(stream_and_listen())
307
+ ```
308
+
309
+ You can also use continuations on the synchronous Cartesia client to stream in text as it becomes available. To do this, pass in a text generator that produces text chunks at intervals of less than 1 second, as shown below. This ensures smooth audio playback.
310
+
311
+ Note: the sync client has a different API for continuations compared to the async client.
312
+
313
+ ```python
314
+ from cartesia import Cartesia
315
+ import pyaudio
316
+ import os
317
+
318
+ client = Cartesia(api_key=os.environ.get("CARTESIA_API_KEY"))
319
+
320
+ transcripts = [
321
+ "The crew engaged in a range of activities designed to mirror those "
322
+ "they might perform on a real Mars mission. ",
323
+ "Aside from growing vegetables and maintaining their habitat, they faced "
324
+ "additional stressors like communication delays with Earth, ",
325
+ "up to twenty-two minutes each way, to simulate the distance from Mars to our planet. ",
326
+ "These exercises were critical for understanding how astronauts can "
327
+ "maintain not just physical health but also mental well-being under such challenging conditions. ",
328
+ ]
329
+
330
+ # Ending each transcript with a space makes the audio smoother
331
+ def chunk_generator(transcripts):
332
+ for transcript in transcripts:
333
+ if transcript.endswith(" "):
334
+ yield transcript
335
+ else:
336
+ yield transcript + " "
337
+
338
+
339
+ # You can check out voice IDs by calling `client.voices.list()` or on https://play.cartesia.ai/
340
+ voice_id = "87748186-23bb-4158-a1eb-332911b0b708"
341
+
342
+ # You can check out our models at https://docs.cartesia.ai/getting-started/available-models
343
+ model_id = "sonic-english"
344
+
345
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
346
+ output_format = {
347
+ "container": "raw",
348
+ "encoding": "pcm_f32le",
349
+ "sample_rate": 44100,
350
+ }
351
+
352
+ p = pyaudio.PyAudio()
353
+ rate = 44100
354
+
355
+ stream = None
356
+
357
+ # Set up the websocket connection
358
+ ws = client.tts.websocket()
359
+
360
+ # Create a context to send and receive audio
361
+ ctx = ws.context() # Generates a random context ID if not provided
362
+
363
+ # Pass in a text generator to generate & stream the audio
364
+ output_stream = ctx.send(
365
+ model_id=model_id,
366
+ transcript=chunk_generator(transcripts),
367
+ voice_id=voice_id,
368
+ output_format=output_format,
369
+ )
370
+
371
+ for output in output_stream:
372
+ buffer = output["audio"]
373
+
374
+ if not stream:
375
+ stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=True)
376
+
377
+ # Write the audio data to the stream
378
+ stream.write(buffer)
379
+
380
+ stream.stop_stream()
381
+ stream.close()
382
+ p.terminate()
383
+
384
+ ws.close() # Close the websocket connection
385
+ ```
386
+
214
387
  ### Multilingual Text-to-Speech [Alpha]
215
388
 
216
389
  You can use our `sonic-multilingual` model to generate audio in multiple languages. The languages supported are available at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
@@ -228,10 +401,10 @@ voice = client.voices.get(id=voice_id)
228
401
  transcript = "Hola! Bienvenido a Cartesia"
229
402
  language = "es" # Language code corresponding to the language of the transcript
230
403
 
231
- # Make sure you use the multilingual model! You can check out all models at [docs.cartesia.ai](https://docs.cartesia.ai/getting-started/available-models).
404
+ # Make sure you use the multilingual model! You can check out all models at https://docs.cartesia.ai/getting-started/available-models
232
405
  model_id = "sonic-multilingual"
233
406
 
234
- # You can find the supported `output_format`s in our [API Reference](https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events).
407
+ # You can find the supported `output_format`s at https://docs.cartesia.ai/api-reference/endpoints/stream-speech-server-sent-events
235
408
  output_format = {
236
409
  "container": "raw",
237
410
  "encoding": "pcm_f32le",
@@ -45,15 +45,16 @@ class DeprecatedOutputFormatMapping:
45
45
  "mulaw_8000": {"container": "raw", "encoding": "pcm_mulaw", "sample_rate": 8000},
46
46
  "alaw_8000": {"container": "raw", "encoding": "pcm_alaw", "sample_rate": 8000},
47
47
  }
48
-
48
+
49
+ @classmethod
49
50
  @deprecated(
50
51
  vdeprecated="1.0.1",
51
52
  vremove="1.2.0",
52
53
  reason="Old output format names are being deprecated in favor of names aligned with the Cartesia API. Use names from `OutputFormatMapping` instead.",
53
54
  )
54
- def get_format_deprecated(self, format_name):
55
- if format_name in self._format_mapping:
56
- return self._format_mapping[format_name]
55
+ def get_format_deprecated(cls, format_name):
56
+ if format_name in cls._format_mapping:
57
+ return cls._format_mapping[format_name]
57
58
  else:
58
59
  raise ValueError(f"Unsupported format: {format_name}")
59
60