flowspeech 0.2.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +10 -1
- data/lib/flowspeech/client.rb +30 -10
- data/lib/flowspeech/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3a94e1d6e6d165af4cbffe0b09add95d1cbe1a1e0bfef60a486f522b19eab09
|
|
4
|
+
data.tar.gz: fbb53b24d39482a7043052d7da00c7f79f33277f6e9ea860e109e90f80872d3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efb94ab0287fa9b3038ad490e109902542cacfea5884fcd950b29f32b2d4016a8c21aa3ca0738d3d239ab301c7c864791d7aa91fbeb2e981537fcf6c29d82d08
|
|
7
|
+
data.tar.gz: 5bf7e1ea264d6388d395b96e60f391132f6548911eba5cddbe8b7b1f45d87d9d12a4b0a47f5e7911cbd2d4786ead246e918e66e9c615ec01f9a4f75ff46944ef
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 - 2026-10-27
|
|
4
|
+
|
|
5
|
+
- Suppress raw download transport causes so full_message does not reveal private result URLs; preserve recovery IDs.
|
|
6
|
+
- Use generic API error messages and filter reflected key/URL values from error codes and traces, including terminal job errors.
|
|
7
|
+
- Do not retry a known permanent HTTP error or redirect when its body is interrupted; audio downloads remain single-attempt operations.
|
|
8
|
+
- Preserve received HTTP status, Retry-After and trace metadata when a response body is interrupted; retries keep the same UUID and bytes. Previously published 0.2.0 artifacts remain unchanged.
|
|
9
|
+
|
|
3
10
|
## 0.2.0
|
|
4
11
|
|
|
5
12
|
- Validate RIFF/WAVE chunk sizes, 24000 Hz mono 16-bit PCM format and complete aligned nonempty audio data; preserve valid files byte-for-byte and reject malformed downloads without resubmitting.
|
data/README.md
CHANGED
|
@@ -5,12 +5,21 @@ using only the standard library at runtime. Requires an active paid Basic, Pro
|
|
|
5
5
|
or Scale subscription (not a trial) and a key from [API settings](https://flowspeech.io/settings/apikeys).
|
|
6
6
|
|
|
7
7
|
```ruby
|
|
8
|
-
gem "flowspeech", "~> 0.2.
|
|
8
|
+
gem "flowspeech", "~> 0.2.1"
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Set `FLOWSPEECH_API_KEY` through your environment or secret manager. Use this gem
|
|
12
12
|
on your server, never in a public client. Generation consumes account credits.
|
|
13
13
|
|
|
14
|
+
Version 0.2.1 includes the safety fixes below. Previously published 0.2.0
|
|
15
|
+
artifacts remain unchanged.
|
|
16
|
+
API and terminal-job errors use local messages and filter reflected keys and
|
|
17
|
+
private URLs. Download errors suppress raw causes, including `full_message`
|
|
18
|
+
cause chains, while preserving recovery IDs. Interrupted responses retain HTTP
|
|
19
|
+
status and cooldown; permanent failures do not retry. Do not log raw request
|
|
20
|
+
objects or result URLs. Injected transports and application logging remain
|
|
21
|
+
trusted boundaries.
|
|
22
|
+
|
|
14
23
|
## Generate WAV Audio
|
|
15
24
|
|
|
16
25
|
```ruby
|
data/lib/flowspeech/client.rb
CHANGED
|
@@ -170,6 +170,15 @@ module FlowSpeech
|
|
|
170
170
|
|
|
171
171
|
private
|
|
172
172
|
|
|
173
|
+
def error_token(value, fallback = nil)
|
|
174
|
+
value.is_a?(String) && value.match?(/\A[A-Za-z0-9][A-Za-z0-9_.-]{0,127}\z/) && !value.include?(@api_key) ? value : fallback
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def job_error(job)
|
|
178
|
+
code = job["error"].is_a?(Hash) ? job["error"]["code"] : nil
|
|
179
|
+
JobError.new(job.merge("error" => { "code" => error_token(code, "job_failed") }))
|
|
180
|
+
end
|
|
181
|
+
|
|
173
182
|
def normalize_input(text, speakers, voice, segments, original)
|
|
174
183
|
raise ArgumentError, "original_text is unsupported by v1" unless original.nil?
|
|
175
184
|
unless segments.nil?
|
|
@@ -328,20 +337,20 @@ module FlowSpeech
|
|
|
328
337
|
elsif job["status"] == "success" && job["resultDeleted"] != true
|
|
329
338
|
return job
|
|
330
339
|
else
|
|
331
|
-
raise
|
|
340
|
+
raise job_error(job)
|
|
332
341
|
end
|
|
333
342
|
end
|
|
334
343
|
end
|
|
335
344
|
|
|
336
345
|
def download_bytes(job, deadline)
|
|
337
|
-
raise
|
|
346
|
+
raise job_error(job) unless job["status"] == "success" && job["resultDeleted"] != true
|
|
338
347
|
uri = safe_url(job.fetch("result").fetch("url"))
|
|
339
348
|
req = Net::HTTP::Get.new(uri)
|
|
340
349
|
response = exchange(uri, req, deadline, @max_download_bytes, @download_transport)
|
|
341
350
|
raise Error, "Download HTTP #{response.code}; retry download with the same job" unless response.code.to_i == 200
|
|
342
351
|
Wav.validate!(response.body.to_s) { remaining(deadline) }
|
|
343
352
|
rescue IOError, SystemCallError, SocketError, Timeout::Error, OpenSSL::SSL::SSLError
|
|
344
|
-
raise Error, "Audio download failed; retry download with the same job"
|
|
353
|
+
raise Error, "Audio download failed; retry download with the same job", cause: nil
|
|
345
354
|
end
|
|
346
355
|
|
|
347
356
|
def request(method, path, body: nil, id: nil, deadline:)
|
|
@@ -349,6 +358,8 @@ module FlowSpeech
|
|
|
349
358
|
attempts = 0
|
|
350
359
|
loop do
|
|
351
360
|
retry_after = nil
|
|
361
|
+
status = nil
|
|
362
|
+
trace_id = nil
|
|
352
363
|
begin
|
|
353
364
|
req = (method == :get ? Net::HTTP::Get : Net::HTTP::Post).new(uri)
|
|
354
365
|
req["Authorization"] = "Bearer #{@api_key}"
|
|
@@ -358,7 +369,11 @@ module FlowSpeech
|
|
|
358
369
|
req["Content-Type"] = "application/json"
|
|
359
370
|
req.body = body
|
|
360
371
|
end
|
|
361
|
-
response = exchange(uri, req, deadline, 1024 * 1024, @transport)
|
|
372
|
+
response = exchange(uri, req, deadline, 1024 * 1024, @transport) do |received|
|
|
373
|
+
status = received.code.to_i
|
|
374
|
+
retry_after = received["Retry-After"]
|
|
375
|
+
trace_id = error_token(received["X-Request-Id"])
|
|
376
|
+
end
|
|
362
377
|
status = response.code.to_i
|
|
363
378
|
retry_after = response["Retry-After"]
|
|
364
379
|
data = begin
|
|
@@ -371,14 +386,17 @@ module FlowSpeech
|
|
|
371
386
|
return [data, retry_after]
|
|
372
387
|
end
|
|
373
388
|
detail = data.is_a?(Hash) && data["error"].is_a?(Hash) ? data["error"] : {}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
389
|
+
retryable = status == 429 || (status >= 500 && detail["retryable"] == true)
|
|
390
|
+
error = APIError.new("FlowSpeech API returned HTTP #{status}", status: status,
|
|
391
|
+
code: error_token(detail["code"], "api_error"), retryable: retryable, retry_after: retry_after,
|
|
392
|
+
trace_id: error_token(data.is_a?(Hash) ? data["traceId"] || response["X-Request-Id"] : response["X-Request-Id"]))
|
|
393
|
+
raise error unless error.retryable
|
|
378
394
|
rescue IOError, SystemCallError, SocketError, Timeout::Error, OpenSSL::SSL::SSLError
|
|
379
|
-
|
|
395
|
+
retryable = status.nil? || (200..299).cover?(status) || status == 429 || (500..599).cover?(status)
|
|
396
|
+
error = APIError.new("API transport failed", status: status || 0,
|
|
397
|
+
error_code: "transport_error", retryable: retryable, retry_after: retry_after, trace_id: trace_id)
|
|
380
398
|
end
|
|
381
|
-
raise error if attempts >= @max_retries
|
|
399
|
+
raise error if !error.retryable || attempts >= @max_retries
|
|
382
400
|
attempts += 1
|
|
383
401
|
pause(retry_after, deadline)
|
|
384
402
|
end
|
|
@@ -395,6 +413,7 @@ module FlowSpeech
|
|
|
395
413
|
http.max_retries = 0 if http.respond_to?(:max_retries=)
|
|
396
414
|
result = nil
|
|
397
415
|
http.request(request) do |res|
|
|
416
|
+
yield res if block_given?
|
|
398
417
|
bytes = String.new(encoding: Encoding::BINARY)
|
|
399
418
|
res.read_body do |chunk|
|
|
400
419
|
remaining(deadline)
|
|
@@ -406,6 +425,7 @@ module FlowSpeech
|
|
|
406
425
|
result
|
|
407
426
|
end
|
|
408
427
|
end
|
|
428
|
+
yield response if transport && block_given?
|
|
409
429
|
remaining(deadline)
|
|
410
430
|
raise Error, "Response exceeds byte limit" if response.body.to_s.bytesize > limit
|
|
411
431
|
response
|
data/lib/flowspeech/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flowspeech
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FlowSpeech
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|