Skip to content

Commit 3f55c13

Browse files
committed
fix CI: cfg-gate avsynth/sapi by OS, replace w"" with HSTRING::from, fix clippy, update workflows for per-OS builds
1 parent f3d7727 commit 3f55c13

7 files changed

Lines changed: 128 additions & 71 deletions

File tree

.github/workflows/ci.yml

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,54 @@ jobs:
2020
- name: Check formatting
2121
run: cargo fmt --all -- --check
2222

23-
- name: Clippy (all features)
24-
run: cargo clippy --all-features -- -D warnings
23+
- name: Clippy (Linux features)
24+
run: cargo clippy --no-default-features --features system,cloud,sherpaonnx -- -D warnings
2525

26-
- name: Build (all features)
27-
run: cargo build --all-features
26+
- name: Build (Linux features)
27+
run: cargo build --no-default-features --features system,cloud,sherpaonnx
2828

2929
- name: Run tests
30-
run: cargo test --all-features
30+
run: cargo test --no-default-features --features system,cloud,sherpaonnx
3131

32-
- name: Build without default features (cloud only)
32+
- name: Build cloud-only
3333
run: cargo build --no-default-features --features cloud
3434

3535
- name: Upload C header
3636
uses: actions/upload-artifact@v4
3737
with:
3838
name: tts_wrapper_header
3939
path: include/tts_wrapper.h
40+
41+
macos-build:
42+
runs-on: macos-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
- uses: dtolnay/rust-toolchain@stable
46+
with:
47+
components: clippy
48+
49+
- name: Clippy (macOS features)
50+
run: cargo clippy --no-default-features --features avsynth,cloud -- -D warnings
51+
52+
- name: Build (macOS features)
53+
run: cargo build --no-default-features --features avsynth,cloud
54+
55+
- name: Run tests
56+
run: cargo test --no-default-features --features avsynth,cloud
57+
58+
windows-build:
59+
runs-on: windows-latest
60+
steps:
61+
- uses: actions/checkout@v4
62+
- uses: dtolnay/rust-toolchain@stable
63+
with:
64+
components: clippy
65+
66+
- name: Clippy (Windows features)
67+
run: cargo clippy --no-default-features --features sapi,cloud -- -D warnings
68+
69+
- name: Build (Windows features)
70+
run: cargo build --no-default-features --features sapi,cloud
71+
72+
- name: Run tests
73+
run: cargo test --no-default-features --features sapi,cloud

.github/workflows/publish.yml

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,14 @@ jobs:
3939
- uses: dtolnay/rust-toolchain@stable
4040
- name: Install system dependencies
4141
run: sudo apt-get update && sudo apt-get install -y libspeechd-dev libclang-dev
42-
- name: Clippy (all features)
43-
run: cargo clippy --all-features -- -D warnings
44-
- name: Test (all features)
45-
run: cargo test --all-features
42+
- name: Clippy (Linux features)
43+
run: cargo clippy --no-default-features --features system,cloud,sherpaonnx -- -D warnings
44+
- name: Test (Linux features)
45+
run: cargo test --no-default-features --features system,cloud,sherpaonnx
4646
- name: Build system-only
4747
run: cargo build --no-default-features --features system
4848
- name: Build cloud-only
4949
run: cargo build --no-default-features --features cloud
50-
- name: Build with sherpaonnx
51-
run: cargo build --no-default-features --features system,sherpaonnx
5250

5351
build:
5452
name: Build ${{ matrix.label }}
@@ -57,11 +55,6 @@ jobs:
5755
fail-fast: false
5856
matrix:
5957
include:
60-
- target: x86_64-unknown-linux-gnu
61-
os: ubuntu-latest
62-
features: "system"
63-
label: "system-x86_64-linux"
64-
lib_ext: so
6558
- target: x86_64-unknown-linux-gnu
6659
os: ubuntu-latest
6760
features: "system,cloud"
@@ -72,40 +65,46 @@ jobs:
7265
features: "system,cloud,sherpaonnx"
7366
label: "system-cloud-sherpaonnx-x86_64-linux"
7467
lib_ext: so
68+
- target: aarch64-unknown-linux-gnu
69+
os: ubuntu-latest
70+
features: "system,cloud"
71+
label: "system-cloud-aarch64-linux"
72+
lib_ext: so
73+
cross: true
7574
- target: x86_64-apple-darwin
7675
os: macos-latest
77-
features: "cloud"
78-
label: "cloud-x86_64-macos"
76+
features: "avsynth,cloud"
77+
label: "avsynth-cloud-x86_64-macos"
7978
lib_ext: dylib
8079
- target: aarch64-apple-darwin
8180
os: macos-latest
82-
features: "cloud"
83-
label: "cloud-aarch64-macos"
81+
features: "avsynth,cloud"
82+
label: "avsynth-cloud-aarch64-macos"
8483
lib_ext: dylib
8584
- target: x86_64-apple-darwin
8685
os: macos-latest
87-
features: "cloud,sherpaonnx"
88-
label: "cloud-sherpaonnx-x86_64-macos"
86+
features: "avsynth,cloud,sherpaonnx"
87+
label: "avsynth-cloud-sherpaonnx-x86_64-macos"
8988
lib_ext: dylib
9089
- target: aarch64-apple-darwin
9190
os: macos-latest
92-
features: "cloud,sherpaonnx"
93-
label: "cloud-sherpaonnx-aarch64-macos"
91+
features: "avsynth,cloud,sherpaonnx"
92+
label: "avsynth-cloud-sherpaonnx-aarch64-macos"
9493
lib_ext: dylib
9594
- target: x86_64-pc-windows-msvc
9695
os: windows-latest
97-
features: "cloud"
98-
label: "cloud-x86_64-windows"
96+
features: "sapi,cloud"
97+
label: "sapi-cloud-x86_64-windows"
9998
lib_ext: dll
10099
- target: aarch64-pc-windows-msvc
101100
os: windows-latest
102-
features: "cloud"
103-
label: "cloud-aarch64-windows"
101+
features: "sapi,cloud"
102+
label: "sapi-cloud-aarch64-windows"
104103
lib_ext: dll
105104
- target: x86_64-pc-windows-msvc
106105
os: windows-latest
107-
features: "cloud,sherpaonnx"
108-
label: "cloud-sherpaonnx-x86_64-windows"
106+
features: "sapi,cloud,sherpaonnx"
107+
label: "sapi-cloud-sherpaonnx-x86_64-windows"
109108
lib_ext: dll
110109
runs-on: ${{ matrix.os }}
111110
steps:

extern/avsynth_shim.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@
1313
void avsynth_destroy(void *handle) {
1414
if (!handle) return;
1515
@try {
16-
AVSpeechSynthesizer *synth = (__bridge_transfer AVSpeechSynthesizer *)handle;
17-
synth = nil;
16+
CFRelease(handle);
1817
} @catch (NSException *e) {}
1918
}
2019

include/tts_wrapper.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,34 @@ int32_t tts_synth_to_bytes(struct tts_ctx *ctx,
269269
*/
270270
void tts_free_bytes(uint8_t *bytes, uintptr_t len);
271271

272+
extern void *avsynth_create(void);
273+
274+
extern void avsynth_destroy(void *handle);
275+
276+
extern void avsynth_speak(void *handle,
277+
const uint8_t *text,
278+
const uint8_t *voice_id,
279+
float rate,
280+
float pitch,
281+
float volume);
282+
283+
extern void avsynth_stop(void *handle);
284+
285+
extern void avsynth_pause(void *handle);
286+
287+
extern void avsynth_resume(void *handle);
288+
289+
extern int32_t avsynth_voice_count(void *handle);
290+
291+
extern int32_t avsynth_get_voice(void *handle,
292+
int32_t index,
293+
uint8_t *id_buf,
294+
int32_t id_buf_len,
295+
uint8_t *name_buf,
296+
int32_t name_buf_len,
297+
uint8_t *lang_buf,
298+
int32_t lang_buf_len);
299+
272300
#ifdef __cplusplus
273301
} // extern "C"
274302
#endif // __cplusplus

src/factory.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ use crate::engine::TtsEngine;
44
use crate::types::EngineDescriptor;
55

66
// The unused-import warning is a false positive — TtsEngine is a trait used as a dyn bound.
7+
#[cfg(all(feature = "avsynth", target_os = "macos"))]
8+
use crate::avsynth_engine::AvSynthEngine;
79
#[cfg(feature = "cloud")]
810
use crate::cloud_engine;
11+
#[cfg(all(feature = "sapi", target_os = "windows"))]
12+
use crate::sapi_engine::SapiEngine;
913
#[cfg(feature = "sherpaonnx")]
1014
use crate::sherpaonnx_engine::SherpaOnnxEngine;
1115
#[cfg(feature = "system")]
1216
use crate::system_engine::SystemEngine;
13-
#[cfg(feature = "avsynth")]
14-
use crate::avsynth_engine::AvSynthEngine;
15-
#[cfg(feature = "sapi")]
16-
use crate::sapi_engine::SapiEngine;
1717

1818
/// Create an engine by its string identifier.
1919
///
@@ -26,10 +26,10 @@ pub fn create_engine(engine_id: &str, credentials_json: &str) -> Option<Box<dyn
2626
#[cfg(feature = "system")]
2727
"system" => Some(Box::new(SystemEngine::new())),
2828

29-
#[cfg(feature = "avsynth")]
29+
#[cfg(all(feature = "avsynth", target_os = "macos"))]
3030
"avsynth" => Some(Box::new(AvSynthEngine::new())),
3131

32-
#[cfg(feature = "sapi")]
32+
#[cfg(all(feature = "sapi", target_os = "windows"))]
3333
"sapi" => Some(Box::new(SapiEngine::new())),
3434

3535
#[cfg(feature = "sherpaonnx")]
@@ -63,15 +63,15 @@ pub fn engine_list() -> Vec<EngineDescriptor> {
6363
credential_keys_json: "[]".into(),
6464
});
6565

66-
#[cfg(feature = "avsynth")]
66+
#[cfg(all(feature = "avsynth", target_os = "macos"))]
6767
engines.push(EngineDescriptor {
6868
id: "avsynth".into(),
6969
name: "macOS AVSpeechSynthesizer".into(),
7070
needs_credentials: false,
7171
credential_keys_json: "[]".into(),
7272
});
7373

74-
#[cfg(feature = "sapi")]
74+
#[cfg(all(feature = "sapi", target_os = "windows"))]
7575
engines.push(EngineDescriptor {
7676
id: "sapi".into(),
7777
name: "Windows SAPI".into(),

src/lib.rs

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@
2929
dead_code
3030
)]
3131

32+
#[cfg(all(feature = "avsynth", target_os = "macos"))]
33+
mod avsynth_engine;
3234
#[cfg(feature = "cloud")]
3335
mod cloud_engine;
3436
pub mod engine;
3537
pub mod factory;
38+
#[cfg(all(feature = "sapi", target_os = "windows"))]
39+
mod sapi_engine;
3640
#[cfg(feature = "sherpaonnx")]
3741
mod sherpaonnx_engine;
3842
#[cfg(feature = "system")]
3943
mod system_engine;
40-
#[cfg(feature = "avsynth")]
41-
mod avsynth_engine;
42-
#[cfg(feature = "sapi")]
43-
mod sapi_engine;
4444
pub mod types;
4545

4646
use std::ffi::{CStr, CString};
@@ -97,19 +97,15 @@ pub extern "C" fn tts_create(
9797
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
9898
tts_create_inner(engine_id, credentials_json)
9999
}));
100-
match result {
101-
Ok(ptr) => ptr,
102-
Err(_) => {
103-
set_error("engine creation panicked");
104-
ptr::null_mut()
105-
}
100+
if let Ok(ptr) = result {
101+
ptr
102+
} else {
103+
set_error("engine creation panicked");
104+
ptr::null_mut()
106105
}
107106
}
108107

109-
fn tts_create_inner(
110-
engine_id: *const c_char,
111-
credentials_json: *const c_char,
112-
) -> *mut tts_ctx {
108+
fn tts_create_inner(engine_id: *const c_char, credentials_json: *const c_char) -> *mut tts_ctx {
113109
if engine_id.is_null() {
114110
set_error("engine_id is null");
115111
return ptr::null_mut();
@@ -324,10 +320,7 @@ pub extern "C" fn tts_get_voices(
324320
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
325321
tts_get_voices_inner(ctx, out_voices, out_count)
326322
}));
327-
match result {
328-
Ok(r) => r,
329-
Err(_) => -1,
330-
}
323+
result.unwrap_or(-1)
331324
}
332325

333326
fn tts_get_voices_inner(

src/sapi_engine.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ use crate::types::{TtsError, TtsResult, Voice};
55
use std::sync::Mutex;
66

77
#[cfg(feature = "sapi")]
8-
use windows::{
9-
core::*,
10-
Win32::Media::Speech::*,
11-
Win32::System::Com::*,
12-
};
8+
use windows::{core::*, Win32::Media::Speech::*, Win32::System::Com::*};
139

1410
#[derive(Debug)]
1511
pub struct SapiEngine {
@@ -37,7 +33,7 @@ impl SapiEngine {
3733
unsafe fn find_voice_by_id(voice_id: &str) -> Option<ISpObjectToken> {
3834
let target = HSTRING::from(voice_id);
3935
let enum_tokens = SpEnumTokens(
40-
w"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices",
36+
&HSTRING::from("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices"),
4137
None,
4238
None,
4339
)
@@ -104,7 +100,11 @@ impl TtsEngine for SapiEngine {
104100
let pitch_val = pitch_to_sapi(pitch);
105101
let pitch_str = format!(
106102
"<pitch absmiddle=\"{}\"/>",
107-
if pitch >= 1.0 { pitch_val as i32 } else { -(pitch_val as i32) }
103+
if pitch >= 1.0 {
104+
pitch_val as i32
105+
} else {
106+
-(pitch_val as i32)
107+
}
108108
);
109109
let wrapped = format!("{pitch_str}{text}");
110110
let wtext = HSTRING::from(&wrapped);
@@ -175,15 +175,16 @@ impl TtsEngine for SapiEngine {
175175
fn get_voices(&self) -> TtsResult<Vec<Voice>> {
176176
let tokens = unsafe {
177177
SpEnumTokens(
178-
w"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices",
178+
&HSTRING::from("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Speech\\Voices"),
179179
None,
180180
None,
181181
)
182182
.map_err(|e| TtsError(format!("Failed to enumerate SAPI voices: {e}")))?
183183
};
184184

185185
let count = unsafe { tokens.GetCount() }
186-
.map_err(|e| TtsError(format!("Failed to get voice count: {e}")))? as usize;
186+
.map_err(|e| TtsError(format!("Failed to get voice count: {e}")))?
187+
as usize;
187188

188189
let mut voices = Vec::with_capacity(count);
189190
for i in 0..count {
@@ -193,19 +194,22 @@ impl TtsEngine for SapiEngine {
193194
.unwrap_or_default();
194195

195196
let name = unsafe {
196-
token.GetStringValue(w"Name")
197+
token
198+
.GetStringValue(&HSTRING::from("Name"))
197199
.map(|h| h.to_string_lossy())
198200
.unwrap_or_else(|_| id.clone())
199201
};
200202

201203
let lang = unsafe {
202-
token.GetStringValue(w"Language")
204+
token
205+
.GetStringValue(&HSTRING::from("Language"))
203206
.map(|h| h.to_string_lossy())
204207
.unwrap_or_else(|_| "en-US".into())
205208
};
206209

207210
let gender_str = unsafe {
208-
token.GetStringValue(w"Gender")
211+
token
212+
.GetStringValue(&HSTRING::from("Gender"))
209213
.map(|h| h.to_string_lossy())
210214
.unwrap_or_default()
211215
};

0 commit comments

Comments
 (0)