aws: add wrapper for the polly text to speech API

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1899>
This commit is contained in:
Mathieu Duponchelle 2024-10-30 12:10:34 +01:00 committed by GStreamer Marge Bot
parent ce7d314349
commit 5f8e8b4873
9 changed files with 1821 additions and 10 deletions

25
Cargo.lock generated
View file

@ -418,6 +418,29 @@ dependencies = [
"tracing",
]
[[package]]
name = "aws-sdk-polly"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6985182fbfde1ba022b4c3bd0c13fe28dd5cbaec6c2b326836c0c376afc0f373"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http 0.2.12",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-s3"
version = "1.59.0"
@ -2387,9 +2410,11 @@ dependencies = [
name = "gst-plugin-aws"
version = "0.14.0-alpha.1"
dependencies = [
"anyhow",
"async-stream",
"aws-config",
"aws-credential-types",
"aws-sdk-polly",
"aws-sdk-s3",
"aws-sdk-transcribestreaming",
"aws-sdk-translate",

View file

@ -2,6 +2,130 @@
"aws": {
"description": "GStreamer Amazon Web Services plugin",
"elements": {
"awspolly": {
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
"description": "Text to Speech filter, using AWS polly",
"hierarchy": [
"GstAwsPolly",
"GstElement",
"GstObject",
"GInitiallyUnowned",
"GObject"
],
"klass": "Audio/Text/Filter",
"pad-templates": {
"sink": {
"caps": "text/x-raw:\n format: utf8\napplication/ssml+xml:\n",
"direction": "sink",
"presence": "always"
},
"src": {
"caps": "audio/x-raw:\n rate: 16000\n channels: 1\n layout: interleaved\n format: S16LE\n",
"direction": "src",
"presence": "always"
}
},
"properties": {
"access-key": {
"blurb": "AWS Access Key",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "NULL",
"mutable": "ready",
"readable": true,
"type": "gchararray",
"writable": true
},
"engine": {
"blurb": "Defines what engine to use",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "neural (1)",
"mutable": "ready",
"readable": true,
"type": "GstAwsPollyEngine",
"writable": true
},
"language-code": {
"blurb": "Defines what language code to use",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "none (0)",
"mutable": "ready",
"readable": true,
"type": "GstAwsPollyLanguageCode",
"writable": true
},
"latency": {
"blurb": "Amount of milliseconds to allow AWS Polly",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "2000",
"max": "-1",
"min": "0",
"mutable": "ready",
"readable": true,
"type": "guint",
"writable": true
},
"lexicon-names": {
"blurb": "List of lexicon names to use",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"mutable": "ready",
"readable": true,
"type": "GstValueArray",
"writable": true
},
"secret-access-key": {
"blurb": "AWS Secret Access Key",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "NULL",
"mutable": "ready",
"readable": true,
"type": "gchararray",
"writable": true
},
"session-token": {
"blurb": "AWS temporary Session Token from STS",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "NULL",
"mutable": "ready",
"readable": true,
"type": "gchararray",
"writable": true
},
"voice-id": {
"blurb": "Defines what voice id to use",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "aria (2)",
"mutable": "ready",
"readable": true,
"type": "GstAwsPollyVoiceId",
"writable": true
}
},
"rank": "none"
},
"awss3hlssink": {
"author": "Daily. Co",
"description": "Streams HLS data to S3",
@ -1226,6 +1350,571 @@
"filename": "gstaws",
"license": "MPL",
"other-types": {
"GstAwsPollyEngine": {
"kind": "enum",
"values": [
{
"desc": "Standard",
"name": "standard",
"value": "0"
},
{
"desc": "Neural",
"name": "neural",
"value": "1"
}
]
},
"GstAwsPollyLanguageCode": {
"kind": "enum",
"values": [
{
"desc": "None",
"name": "none",
"value": "0"
},
{
"desc": "Arb",
"name": "arb",
"value": "1"
},
{
"desc": "CaEs",
"name": "ca-ES",
"value": "2"
},
{
"desc": "CmnCn",
"name": "cmn-CN",
"value": "3"
},
{
"desc": "CyGb",
"name": "cy-GB",
"value": "4"
},
{
"desc": "DaDk",
"name": "da-DK",
"value": "5"
},
{
"desc": "DeAt",
"name": "de-AT",
"value": "6"
},
{
"desc": "DeDe",
"name": "de-DE",
"value": "7"
},
{
"desc": "EnAu",
"name": "en-AU",
"value": "8"
},
{
"desc": "EnGb",
"name": "en-GB",
"value": "9"
},
{
"desc": "EnGbWls",
"name": "en-GB-WLS",
"value": "10"
},
{
"desc": "EnIn",
"name": "en-IN",
"value": "11"
},
{
"desc": "EnNz",
"name": "en-NZ",
"value": "12"
},
{
"desc": "EnUs",
"name": "en-US",
"value": "13"
},
{
"desc": "EnZa",
"name": "en-ZA",
"value": "14"
},
{
"desc": "EsEs",
"name": "es-ES",
"value": "15"
},
{
"desc": "EsMx",
"name": "es-MX",
"value": "16"
},
{
"desc": "EsUs",
"name": "es-US",
"value": "17"
},
{
"desc": "FrCa",
"name": "fr-CA",
"value": "18"
},
{
"desc": "FrFr",
"name": "fr-FR",
"value": "19"
},
{
"desc": "HiIn",
"name": "hi-IN",
"value": "20"
},
{
"desc": "IsIs",
"name": "is-IS",
"value": "21"
},
{
"desc": "ItIt",
"name": "it-IT",
"value": "22"
},
{
"desc": "JaJp",
"name": "ja-JP",
"value": "23"
},
{
"desc": "KoKr",
"name": "ko-KR",
"value": "24"
},
{
"desc": "NbNo",
"name": "nb-NO",
"value": "25"
},
{
"desc": "NlNl",
"name": "nl-NL",
"value": "26"
},
{
"desc": "PlPl",
"name": "pl-PL",
"value": "27"
},
{
"desc": "PtBr",
"name": "pt-BR",
"value": "28"
},
{
"desc": "PtPt",
"name": "pt-PT",
"value": "29"
},
{
"desc": "RoRo",
"name": "ro-RO",
"value": "30"
},
{
"desc": "RuRu",
"name": "ru-RU",
"value": "31"
},
{
"desc": "SvSe",
"name": "sv-SE",
"value": "32"
},
{
"desc": "TrTr",
"name": "tr-TR",
"value": "33"
},
{
"desc": "YueCn",
"name": "yue-CN",
"value": "34"
}
]
},
"GstAwsPollyVoiceId": {
"kind": "enum",
"values": [
{
"desc": "Aditi",
"name": "aditi",
"value": "0"
},
{
"desc": "Amy",
"name": "amy",
"value": "1"
},
{
"desc": "Aria",
"name": "aria",
"value": "2"
},
{
"desc": "Arlet",
"name": "arlet",
"value": "3"
},
{
"desc": "Arthur",
"name": "arthur",
"value": "4"
},
{
"desc": "Astrid",
"name": "astrid",
"value": "5"
},
{
"desc": "Ayanda",
"name": "ayanda",
"value": "6"
},
{
"desc": "Bianca",
"name": "bianca",
"value": "7"
},
{
"desc": "Brian",
"name": "brian",
"value": "8"
},
{
"desc": "Camila",
"name": "camila",
"value": "9"
},
{
"desc": "Carla",
"name": "carla",
"value": "10"
},
{
"desc": "Carmen",
"name": "carmen",
"value": "11"
},
{
"desc": "Celine",
"name": "celine",
"value": "12"
},
{
"desc": "Chantal",
"name": "chantal",
"value": "13"
},
{
"desc": "Conchita",
"name": "conchita",
"value": "14"
},
{
"desc": "Cristiano",
"name": "cristiano",
"value": "15"
},
{
"desc": "Daniel",
"name": "daniel",
"value": "16"
},
{
"desc": "Dora",
"name": "dora",
"value": "17"
},
{
"desc": "Emma",
"name": "emma",
"value": "18"
},
{
"desc": "Enrique",
"name": "enrique",
"value": "19"
},
{
"desc": "Ewa",
"name": "ewa",
"value": "20"
},
{
"desc": "Filiz",
"name": "filiz",
"value": "21"
},
{
"desc": "Gabrielle",
"name": "gabrielle",
"value": "22"
},
{
"desc": "Geraint",
"name": "geraint",
"value": "23"
},
{
"desc": "Giorgio",
"name": "giorgio",
"value": "24"
},
{
"desc": "Gwyneth",
"name": "gwyneth",
"value": "25"
},
{
"desc": "Hannah",
"name": "hannah",
"value": "26"
},
{
"desc": "Hans",
"name": "hans",
"value": "27"
},
{
"desc": "Hiujin",
"name": "hiujin",
"value": "28"
},
{
"desc": "Ines",
"name": "ines",
"value": "29"
},
{
"desc": "Ivy",
"name": "ivy",
"value": "30"
},
{
"desc": "Jacek",
"name": "jacek",
"value": "31"
},
{
"desc": "Jan",
"name": "jan",
"value": "32"
},
{
"desc": "Joanna",
"name": "joanna",
"value": "33"
},
{
"desc": "Joey",
"name": "joey",
"value": "34"
},
{
"desc": "Justin",
"name": "justin",
"value": "35"
},
{
"desc": "Kajal",
"name": "kajal",
"value": "36"
},
{
"desc": "Karl",
"name": "karl",
"value": "37"
},
{
"desc": "Kendra",
"name": "kendra",
"value": "38"
},
{
"desc": "Kevin",
"name": "kevin",
"value": "39"
},
{
"desc": "Kimberly",
"name": "kimberly",
"value": "40"
},
{
"desc": "Lea",
"name": "lea",
"value": "41"
},
{
"desc": "Liam",
"name": "liam",
"value": "42"
},
{
"desc": "Liv",
"name": "liv",
"value": "43"
},
{
"desc": "Lotte",
"name": "lotte",
"value": "44"
},
{
"desc": "Lucia",
"name": "lucia",
"value": "45"
},
{
"desc": "Lupe",
"name": "lupe",
"value": "46"
},
{
"desc": "Mads",
"name": "mads",
"value": "47"
},
{
"desc": "Maja",
"name": "maja",
"value": "48"
},
{
"desc": "Marlene",
"name": "marlene",
"value": "49"
},
{
"desc": "Mathieu",
"name": "mathieu",
"value": "50"
},
{
"desc": "Matthew",
"name": "matthew",
"value": "51"
},
{
"desc": "Maxim",
"name": "maxim",
"value": "52"
},
{
"desc": "Mia",
"name": "mia",
"value": "53"
},
{
"desc": "Miguel",
"name": "miguel",
"value": "54"
},
{
"desc": "Mizuki",
"name": "mizuki",
"value": "55"
},
{
"desc": "Naja",
"name": "naja",
"value": "56"
},
{
"desc": "Nicole",
"name": "nicole",
"value": "57"
},
{
"desc": "Olivia",
"name": "olivia",
"value": "58"
},
{
"desc": "Pedro",
"name": "pedro",
"value": "59"
},
{
"desc": "Penelope",
"name": "penelope",
"value": "60"
},
{
"desc": "Raveena",
"name": "raveena",
"value": "61"
},
{
"desc": "Ricardo",
"name": "ricardo",
"value": "62"
},
{
"desc": "Ruben",
"name": "ruben",
"value": "63"
},
{
"desc": "Russell",
"name": "russell",
"value": "64"
},
{
"desc": "Salli",
"name": "salli",
"value": "65"
},
{
"desc": "Seoyeon",
"name": "seoyeon",
"value": "66"
},
{
"desc": "Takumi",
"name": "takumi",
"value": "67"
},
{
"desc": "Tatyana",
"name": "tatyana",
"value": "68"
},
{
"desc": "Vicki",
"name": "vicki",
"value": "69"
},
{
"desc": "Vitoria",
"name": "vitoria",
"value": "70"
},
{
"desc": "Zeina",
"name": "zeina",
"value": "71"
},
{
"desc": "Zhiyu",
"name": "zhiyu",
"value": "72"
}
]
},
"GstAwsTranscriberResultStability": {
"kind": "enum",
"values": [

View file

@ -19,6 +19,7 @@ aws-sdk-transcribestreaming = "1.0"
aws-sdk-translate = "1.0"
aws-types = "1.0"
aws-credential-types = "1.0"
aws-sdk-polly = "1.0"
bytes = "1.0"
futures = "0.3"
gio.workspace = true
@ -33,6 +34,7 @@ serde_json = "1"
url = "2"
gst-video = { workspace = true, features = ["v1_22"] }
sprintf = "0.2"
anyhow = "1"
[dev-dependencies]
chrono = { version = "0.4", features = [ "alloc" ] }

View file

@ -14,6 +14,7 @@
*/
use gst::glib;
mod polly;
mod s3hlssink;
mod s3sink;
mod s3src;
@ -30,6 +31,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
transcribe_parse::register(plugin)?;
transcriber::register(plugin)?;
s3hlssink::register(plugin)?;
polly::register(plugin)?;
Ok(())
}

677
net/aws/src/polly/imp.rs Normal file
View file

@ -0,0 +1,677 @@
// Copyright (C) 2024 Mathieu Duponchelle <mathieu@centricular.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
//! AWS Polly element.
//!
//! This element calls AWS Polly to generate audio speech from text.
use gst::subclass::prelude::*;
use gst::{glib, prelude::*};
use aws_sdk_s3::config::StalledStreamProtectionConfig;
use futures::future::{abortable, AbortHandle};
use std::sync::Mutex;
use std::sync::LazyLock;
use super::{AwsPollyEngine, AwsPollyLanguageCode, AwsPollyVoiceId, CAT};
use crate::s3utils::RUNTIME;
use anyhow::{anyhow, Error};
#[allow(deprecated)]
static AWS_BEHAVIOR_VERSION: LazyLock<aws_config::BehaviorVersion> =
LazyLock::new(aws_config::BehaviorVersion::v2023_11_09);
const DEFAULT_REGION: &str = "us-east-1";
const DEFAULT_LATENCY: gst::ClockTime = gst::ClockTime::from_seconds(2);
const DEFAULT_ENGINE: AwsPollyEngine = AwsPollyEngine::Neural;
const DEFAULT_LANGUAGE_CODE: AwsPollyLanguageCode = AwsPollyLanguageCode::None;
const DEFAULT_VOICE_ID: AwsPollyVoiceId = AwsPollyVoiceId::Aria;
#[derive(Debug, Clone)]
pub(super) struct Settings {
latency: gst::ClockTime,
access_key: Option<String>,
secret_access_key: Option<String>,
session_token: Option<String>,
engine: AwsPollyEngine,
language_code: AwsPollyLanguageCode,
voice_id: AwsPollyVoiceId,
lexicon_names: gst::Array,
}
impl Default for Settings {
fn default() -> Self {
Self {
latency: DEFAULT_LATENCY,
access_key: None,
secret_access_key: None,
session_token: None,
engine: DEFAULT_ENGINE,
language_code: DEFAULT_LANGUAGE_CODE,
voice_id: DEFAULT_VOICE_ID,
lexicon_names: gst::Array::default(),
}
}
}
struct State {
out_segment: gst::FormattedSegment<gst::ClockTime>,
client: Option<aws_sdk_polly::Client>,
send_abort_handle: Option<AbortHandle>,
in_format: Option<aws_sdk_polly::types::TextType>,
}
impl Default for State {
fn default() -> Self {
Self {
out_segment: gst::FormattedSegment::new(),
client: None,
send_abort_handle: None,
in_format: None,
}
}
}
pub struct Polly {
srcpad: gst::Pad,
sinkpad: gst::Pad,
settings: Mutex<Settings>,
state: Mutex<State>,
pub(super) aws_config: Mutex<Option<aws_config::SdkConfig>>,
}
impl Polly {
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
gst::log!(CAT, obj = pad, "Handling event {event:?}");
use gst::EventView::*;
match event.view() {
FlushStart(_) => {
gst::info!(CAT, imp = self, "Received flush start, disconnecting");
let ret = gst::Pad::event_default(pad, Some(&*self.obj()), event);
self.disconnect();
ret
}
Segment(e) => {
let segment = match e.segment().clone().downcast::<gst::ClockTime>() {
Err(segment) => {
gst::element_imp_error!(
self,
gst::StreamError::Format,
["Only Time segments supported, got {:?}", segment.format(),]
);
return false;
}
Ok(segment) => segment,
};
let mut state = self.state.lock().unwrap();
state.out_segment = segment;
gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
Caps(c) => {
let format = c.caps().structure(0).map(|s| s.name().as_str());
let mut state = self.state.lock().unwrap();
state.in_format = format.and_then(|f| match f {
"text/x-raw" => Some(aws_sdk_polly::types::TextType::Text),
"application/ssml+xml" => Some(aws_sdk_polly::types::TextType::Ssml),
_ => None,
});
drop(state);
let caps = gst_audio::AudioCapsBuilder::new()
.format(gst_audio::AudioFormat::S16le)
.rate(16_000)
.channels(1)
.layout(gst_audio::AudioLayout::Interleaved)
.build();
let event = gst::event::Caps::builder(&caps).seqnum(c.seqnum()).build();
self.srcpad.push_event(event)
}
Gap(g) => {
let (pts, duration) = g.get();
let mut state = self.state.lock().unwrap();
state.out_segment.set_position(match duration {
Some(duration) => duration + pts,
_ => pts,
});
drop(state);
gst::Pad::event_default(pad, Some(&*self.obj()), event)
}
_ => gst::Pad::event_default(pad, Some(&*self.obj()), event),
}
}
async fn send(&self, inbuf: gst::Buffer) -> Result<gst::Buffer, Error> {
let pts = inbuf
.pts()
.ok_or_else(|| anyhow!("Stream with timestamped buffers required"))?;
let duration = inbuf
.duration()
.ok_or_else(|| anyhow!("Buffers of stream need to have a duration"))?;
let data = inbuf
.map_readable()
.map_err(|_| anyhow!("Can't map buffer readable"))?;
let data =
std::str::from_utf8(&data).map_err(|err| anyhow!("Can't decode utf8: {}", err))?;
let (client, in_format) = {
let state = self.state.lock().unwrap();
(
state.client.as_ref().expect("connected").clone(),
state.in_format.as_ref().expect("received caps").clone(),
)
};
let job = {
let settings = self.settings.lock().unwrap();
let mut task = client
.synthesize_speech()
.engine(settings.engine.into())
.output_format(aws_sdk_polly::types::OutputFormat::Pcm)
.text_type(in_format)
.text(data)
.voice_id(settings.voice_id.into())
.set_lexicon_names(Some(
settings
.lexicon_names
.iter()
.map(|v| v.get::<String>().unwrap())
.collect(),
));
if settings.language_code != AwsPollyLanguageCode::None {
task = task.language_code(settings.language_code.into());
}
task.send()
};
let resp = job.await.map_err(|err| {
if let Some(err) = err.as_service_error() {
gst::error!(CAT, imp = self, "Failed sending text chunk: {}", err.meta());
} else {
gst::error!(CAT, imp = self, "Failed sending text chunk: {}", err);
}
err
})?;
let blob = resp.audio_stream.collect().await?;
let mut buf = gst::Buffer::from_slice(blob.into_bytes());
let mut state = self.state.lock().unwrap();
let discont = state
.out_segment
.position()
.map(|position| position < pts + duration)
.unwrap_or(true);
{
let buf_mut = buf.get_mut().unwrap();
buf_mut.set_pts(pts);
buf_mut.set_duration(duration);
if discont {
gst::log!(CAT, imp = self, "Marking buffer discont");
buf_mut.set_flags(gst::BufferFlags::DISCONT);
}
inbuf.foreach_meta(|meta| {
if meta.tags().is_empty() {
if let Err(err) =
meta.transform(buf_mut, &gst::meta::MetaTransformCopy::new(false, ..))
{
gst::trace!(CAT, imp = self, "Could not copy meta {}: {err}", meta.api());
}
}
std::ops::ControlFlow::Continue(())
});
}
state.out_segment.set_position(pts + duration);
Ok(buf)
}
fn sink_chain(
&self,
pad: &gst::Pad,
buffer: gst::Buffer,
) -> Result<gst::FlowSuccess, gst::FlowError> {
gst::log!(CAT, obj = pad, "Handling {buffer:?}");
self.ensure_connection().map_err(|err| {
gst::element_imp_error!(self, gst::StreamError::Failed, ["Streaming failed: {err}"]);
gst::FlowError::Error
})?;
let (future, abort_handle) = abortable(self.send(buffer));
self.state.lock().unwrap().send_abort_handle = Some(abort_handle);
match RUNTIME.block_on(future) {
Err(_) => {
gst::debug!(CAT, imp = self, "send aborted, returning flushing");
Err(gst::FlowError::Flushing)
}
Ok(res) => match res {
Err(e) => {
gst::element_imp_error!(
self,
gst::StreamError::Failed,
["Failed sending data: {}", e]
);
Err(gst::FlowError::Error)
}
Ok(buf) => self.srcpad.push(buf),
},
}
}
fn ensure_connection(&self) -> Result<(), gst::ErrorMessage> {
let mut state = self.state.lock().unwrap();
if state.client.is_none() {
state.client = Some(aws_sdk_polly::Client::new(
self.aws_config.lock().unwrap().as_ref().expect("prepared"),
));
}
Ok(())
}
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
gst::debug!(CAT, imp = self, "Preparing");
let (access_key, secret_access_key, session_token) = {
let settings = self.settings.lock().unwrap();
(
settings.access_key.clone(),
settings.secret_access_key.clone(),
settings.session_token.clone(),
)
};
gst::info!(CAT, imp = self, "Loading aws config...");
let _enter_guard = RUNTIME.enter();
let config_loader = match (access_key, secret_access_key) {
(Some(key), Some(secret_key)) => {
gst::debug!(CAT, imp = self, "Using settings credentials");
aws_config::defaults(*AWS_BEHAVIOR_VERSION).credentials_provider(
aws_sdk_polly::config::Credentials::new(
key,
secret_key,
session_token,
None,
"translate",
),
)
}
_ => {
gst::debug!(CAT, imp = self, "Attempting to get credentials from env...");
aws_config::defaults(*AWS_BEHAVIOR_VERSION)
}
};
let config_loader = config_loader.region(
aws_config::meta::region::RegionProviderChain::default_provider()
.or_else(DEFAULT_REGION),
);
let config_loader =
config_loader.stalled_stream_protection(StalledStreamProtectionConfig::disabled());
let config = futures::executor::block_on(config_loader.load());
gst::debug!(CAT, imp = self, "Using region {}", config.region().unwrap());
*self.aws_config.lock().unwrap() = Some(config);
gst::debug!(CAT, imp = self, "Prepared");
Ok(())
}
fn disconnect(&self) {
gst::info!(CAT, imp = self, "Disconnecting");
let mut state = self.state.lock().unwrap();
if let Some(abort_handle) = state.send_abort_handle.take() {
abort_handle.abort();
}
*state = State::default();
gst::info!(CAT, imp = self, "Disconnected");
}
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
gst::log!(CAT, obj = pad, "Handling query {:?}", query);
match query.view_mut() {
gst::QueryViewMut::Latency(ref mut q) => {
let mut peer_query = gst::query::Latency::new();
let ret = self.sinkpad.peer_query(&mut peer_query);
if ret {
let (live, min, max) = peer_query.result();
let our_latency = self.settings.lock().unwrap().latency;
if live {
q.set(true, min + our_latency, max.map(|max| max + our_latency));
} else {
q.set(live, min, max);
}
}
ret
}
gst::QueryViewMut::Position(ref mut q) => {
if q.format() == gst::Format::Time {
let state = self.state.lock().unwrap();
q.set(
state
.out_segment
.to_stream_time(state.out_segment.position()),
);
true
} else {
false
}
}
_ => gst::Pad::query_default(pad, Some(&*self.obj()), query),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for Polly {
const NAME: &'static str = "GstAwsPolly";
type Type = super::Polly;
type ParentType = gst::Element;
fn with_class(klass: &Self::Class) -> Self {
let templ = klass.pad_template("sink").unwrap();
let sinkpad = gst::Pad::builder_from_template(&templ)
.chain_function(|pad, parent, buffer| {
Polly::catch_panic_pad_function(
parent,
|| Err(gst::FlowError::Error),
|polly| polly.sink_chain(pad, buffer),
)
})
.event_function(|pad, parent, event| {
Polly::catch_panic_pad_function(
parent,
|| false,
|polly| polly.sink_event(pad, event),
)
})
.build();
let templ = klass.pad_template("src").unwrap();
let srcpad = gst::PadBuilder::<gst::Pad>::from_template(&templ)
.query_function(|pad, parent, query| {
Polly::catch_panic_pad_function(
parent,
|| false,
|polly| polly.src_query(pad, query),
)
})
.flags(gst::PadFlags::FIXED_CAPS)
.build();
Self {
srcpad,
sinkpad,
settings: Default::default(),
state: Default::default(),
aws_config: Default::default(),
}
}
}
impl ObjectImpl for Polly {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
vec![
glib::ParamSpecUInt::builder("latency")
.nick("Latency")
.blurb("Amount of milliseconds to allow AWS Polly")
.default_value(DEFAULT_LATENCY.mseconds() as u32)
.mutable_ready()
.deprecated()
.build(),
glib::ParamSpecString::builder("access-key")
.nick("Access Key")
.blurb("AWS Access Key")
.mutable_ready()
.build(),
glib::ParamSpecString::builder("secret-access-key")
.nick("Secret Access Key")
.blurb("AWS Secret Access Key")
.mutable_ready()
.build(),
glib::ParamSpecString::builder("session-token")
.nick("Session Token")
.blurb("AWS temporary Session Token from STS")
.mutable_ready()
.build(),
glib::ParamSpecEnum::builder_with_default("engine", DEFAULT_ENGINE)
.nick("Engine")
.blurb("Defines what engine to use")
.mutable_ready()
.build(),
glib::ParamSpecEnum::builder_with_default("voice-id", DEFAULT_VOICE_ID)
.nick("Voice Id")
.blurb("Defines what voice id to use")
.mutable_ready()
.build(),
glib::ParamSpecEnum::builder_with_default("language-code", DEFAULT_LANGUAGE_CODE)
.nick("Language Code")
.blurb("Defines what language code to use")
.mutable_ready()
.build(),
gst::ParamSpecArray::builder("lexicon-names")
.nick("Lexicon Names")
.blurb("List of lexicon names to use")
.element_spec(
&glib::ParamSpecString::builder("lexicon-name")
.nick("Lexicon Name")
.blurb("The lexicon name")
.build(),
)
.mutable_ready()
.build(),
]
});
PROPERTIES.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.add_pad(&self.sinkpad).unwrap();
obj.add_pad(&self.srcpad).unwrap();
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"latency" => {
let mut settings = self.settings.lock().unwrap();
settings.latency = gst::ClockTime::from_mseconds(
value.get::<u32>().expect("type checked upstream").into(),
);
}
"access-key" => {
let mut settings = self.settings.lock().unwrap();
settings.access_key = value.get().expect("type checked upstream");
}
"secret-access-key" => {
let mut settings = self.settings.lock().unwrap();
settings.secret_access_key = value.get().expect("type checked upstream");
}
"session-token" => {
let mut settings = self.settings.lock().unwrap();
settings.session_token = value.get().expect("type checked upstream");
}
"engine" => {
let mut settings = self.settings.lock().unwrap();
settings.engine = value
.get::<AwsPollyEngine>()
.expect("type checked upstream");
}
"voice-id" => {
let mut settings = self.settings.lock().unwrap();
settings.voice_id = value
.get::<AwsPollyVoiceId>()
.expect("type checked upstream");
}
"language-code" => {
let mut settings = self.settings.lock().unwrap();
settings.language_code = value
.get::<AwsPollyLanguageCode>()
.expect("type checked upstream");
}
"lexicon-names" => {
let mut settings = self.settings.lock().unwrap();
settings.lexicon_names = value.get::<gst::Array>().expect("type checked upstream");
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"latency" => {
let settings = self.settings.lock().unwrap();
(settings.latency.mseconds() as u32).to_value()
}
"access-key" => {
let settings = self.settings.lock().unwrap();
settings.access_key.to_value()
}
"secret-access-key" => {
let settings = self.settings.lock().unwrap();
settings.secret_access_key.to_value()
}
"session-token" => {
let settings = self.settings.lock().unwrap();
settings.session_token.to_value()
}
"engine" => {
let settings = self.settings.lock().unwrap();
settings.engine.to_value()
}
"voice-id" => {
let settings = self.settings.lock().unwrap();
settings.voice_id.to_value()
}
"language-code" => {
let settings = self.settings.lock().unwrap();
settings.language_code.to_value()
}
"lexicon-names" => {
let settings = self.settings.lock().unwrap();
settings.lexicon_names.to_value()
}
_ => unimplemented!(),
}
}
}
impl GstObjectImpl for Polly {}
impl ElementImpl for Polly {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: LazyLock<gst::subclass::ElementMetadata> = LazyLock::new(|| {
gst::subclass::ElementMetadata::new(
"Polly",
"Audio/Text/Filter",
"Text to Speech filter, using AWS polly",
"Mathieu Duponchelle <mathieu@centricular.com>",
)
});
Some(&*ELEMENT_METADATA)
}
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: LazyLock<Vec<gst::PadTemplate>> = LazyLock::new(|| {
let sink_caps = gst::Caps::builder_full()
.structure(
gst::Structure::builder("text/x-raw")
.field("format", "utf8")
.build(),
)
.structure(gst::Structure::new_empty("application/ssml+xml"))
.build();
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&sink_caps,
)
.unwrap();
let src_caps = gst_audio::AudioCapsBuilder::new()
.format(gst_audio::AudioFormat::S16le)
.rate(16_000)
.channels(1)
.layout(gst_audio::AudioLayout::Interleaved)
.build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&src_caps,
)
.unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
fn change_state(
&self,
transition: gst::StateChange,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::info!(CAT, imp = self, "Changing state {transition:?}");
match transition {
gst::StateChange::NullToReady => {
self.prepare().map_err(|err| {
self.post_error_message(err);
gst::StateChangeError
})?;
}
gst::StateChange::PausedToReady => {
self.disconnect();
}
_ => (),
}
self.parent_change_state(transition)
}
fn provide_clock(&self) -> Option<gst::Clock> {
Some(gst::SystemClock::obtain())
}
}

418
net/aws/src/polly/mod.rs Normal file
View file

@ -0,0 +1,418 @@
// Copyright (C) 2024 Mathieu Duponchelle <mathieu@centricular.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at
// <https://mozilla.org/MPL/2.0/>.
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib;
use gst::prelude::*;
mod imp;
use std::sync::LazyLock;
static CAT: LazyLock<gst::DebugCategory> = LazyLock::new(|| {
gst::DebugCategory::new(
"awspolly",
gst::DebugColorFlags::empty(),
Some("AWS Polly element"),
)
});
use aws_sdk_polly::types::{Engine, LanguageCode, VoiceId};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstAwsPollyEngine")]
#[non_exhaustive]
pub enum AwsPollyEngine {
#[enum_value(name = "Standard", nick = "standard")]
Standard = 0,
#[enum_value(name = "Neural", nick = "neural")]
Neural = 1,
}
impl From<AwsPollyEngine> for Engine {
fn from(val: AwsPollyEngine) -> Self {
use AwsPollyEngine::*;
match val {
Standard => Engine::Standard,
Neural => Engine::Neural,
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstAwsPollyVoiceId")]
#[non_exhaustive]
pub enum AwsPollyVoiceId {
#[enum_value(name = "Aditi", nick = "aditi")]
Aditi,
#[enum_value(name = "Amy", nick = "amy")]
Amy,
#[enum_value(name = "Aria", nick = "aria")]
Aria,
#[enum_value(name = "Arlet", nick = "arlet")]
Arlet,
#[enum_value(name = "Arthur", nick = "arthur")]
Arthur,
#[enum_value(name = "Astrid", nick = "astrid")]
Astrid,
#[enum_value(name = "Ayanda", nick = "ayanda")]
Ayanda,
#[enum_value(name = "Bianca", nick = "bianca")]
Bianca,
#[enum_value(name = "Brian", nick = "brian")]
Brian,
#[enum_value(name = "Camila", nick = "camila")]
Camila,
#[enum_value(name = "Carla", nick = "carla")]
Carla,
#[enum_value(name = "Carmen", nick = "carmen")]
Carmen,
#[enum_value(name = "Celine", nick = "celine")]
Celine,
#[enum_value(name = "Chantal", nick = "chantal")]
Chantal,
#[enum_value(name = "Conchita", nick = "conchita")]
Conchita,
#[enum_value(name = "Cristiano", nick = "cristiano")]
Cristiano,
#[enum_value(name = "Daniel", nick = "daniel")]
Daniel,
#[enum_value(name = "Dora", nick = "dora")]
Dora,
#[enum_value(name = "Emma", nick = "emma")]
Emma,
#[enum_value(name = "Enrique", nick = "enrique")]
Enrique,
#[enum_value(name = "Ewa", nick = "ewa")]
Ewa,
#[enum_value(name = "Filiz", nick = "filiz")]
Filiz,
#[enum_value(name = "Gabrielle", nick = "gabrielle")]
Gabrielle,
#[enum_value(name = "Geraint", nick = "geraint")]
Geraint,
#[enum_value(name = "Giorgio", nick = "giorgio")]
Giorgio,
#[enum_value(name = "Gwyneth", nick = "gwyneth")]
Gwyneth,
#[enum_value(name = "Hannah", nick = "hannah")]
Hannah,
#[enum_value(name = "Hans", nick = "hans")]
Hans,
#[enum_value(name = "Hiujin", nick = "hiujin")]
Hiujin,
#[enum_value(name = "Ines", nick = "ines")]
Ines,
#[enum_value(name = "Ivy", nick = "ivy")]
Ivy,
#[enum_value(name = "Jacek", nick = "jacek")]
Jacek,
#[enum_value(name = "Jan", nick = "jan")]
Jan,
#[enum_value(name = "Joanna", nick = "joanna")]
Joanna,
#[enum_value(name = "Joey", nick = "joey")]
Joey,
#[enum_value(name = "Justin", nick = "justin")]
Justin,
#[enum_value(name = "Kajal", nick = "kajal")]
Kajal,
#[enum_value(name = "Karl", nick = "karl")]
Karl,
#[enum_value(name = "Kendra", nick = "kendra")]
Kendra,
#[enum_value(name = "Kevin", nick = "kevin")]
Kevin,
#[enum_value(name = "Kimberly", nick = "kimberly")]
Kimberly,
#[enum_value(name = "Lea", nick = "lea")]
Lea,
#[enum_value(name = "Liam", nick = "liam")]
Liam,
#[enum_value(name = "Liv", nick = "liv")]
Liv,
#[enum_value(name = "Lotte", nick = "lotte")]
Lotte,
#[enum_value(name = "Lucia", nick = "lucia")]
Lucia,
#[enum_value(name = "Lupe", nick = "lupe")]
Lupe,
#[enum_value(name = "Mads", nick = "mads")]
Mads,
#[enum_value(name = "Maja", nick = "maja")]
Maja,
#[enum_value(name = "Marlene", nick = "marlene")]
Marlene,
#[enum_value(name = "Mathieu", nick = "mathieu")]
Mathieu,
#[enum_value(name = "Matthew", nick = "matthew")]
Matthew,
#[enum_value(name = "Maxim", nick = "maxim")]
Maxim,
#[enum_value(name = "Mia", nick = "mia")]
Mia,
#[enum_value(name = "Miguel", nick = "miguel")]
Miguel,
#[enum_value(name = "Mizuki", nick = "mizuki")]
Mizuki,
#[enum_value(name = "Naja", nick = "naja")]
Naja,
#[enum_value(name = "Nicole", nick = "nicole")]
Nicole,
#[enum_value(name = "Olivia", nick = "olivia")]
Olivia,
#[enum_value(name = "Pedro", nick = "pedro")]
Pedro,
#[enum_value(name = "Penelope", nick = "penelope")]
Penelope,
#[enum_value(name = "Raveena", nick = "raveena")]
Raveena,
#[enum_value(name = "Ricardo", nick = "ricardo")]
Ricardo,
#[enum_value(name = "Ruben", nick = "ruben")]
Ruben,
#[enum_value(name = "Russell", nick = "russell")]
Russell,
#[enum_value(name = "Salli", nick = "salli")]
Salli,
#[enum_value(name = "Seoyeon", nick = "seoyeon")]
Seoyeon,
#[enum_value(name = "Takumi", nick = "takumi")]
Takumi,
#[enum_value(name = "Tatyana", nick = "tatyana")]
Tatyana,
#[enum_value(name = "Vicki", nick = "vicki")]
Vicki,
#[enum_value(name = "Vitoria", nick = "vitoria")]
Vitoria,
#[enum_value(name = "Zeina", nick = "zeina")]
Zeina,
#[enum_value(name = "Zhiyu", nick = "zhiyu")]
Zhiyu,
}
impl From<AwsPollyVoiceId> for VoiceId {
fn from(val: AwsPollyVoiceId) -> Self {
use AwsPollyVoiceId::*;
match val {
Aditi => VoiceId::Aditi,
Amy => VoiceId::Amy,
Aria => VoiceId::Aria,
Arlet => VoiceId::Arlet,
Arthur => VoiceId::Arthur,
Astrid => VoiceId::Astrid,
Ayanda => VoiceId::Ayanda,
Bianca => VoiceId::Bianca,
Brian => VoiceId::Brian,
Camila => VoiceId::Camila,
Carla => VoiceId::Carla,
Carmen => VoiceId::Carmen,
Celine => VoiceId::Celine,
Chantal => VoiceId::Chantal,
Conchita => VoiceId::Conchita,
Cristiano => VoiceId::Cristiano,
Daniel => VoiceId::Daniel,
Dora => VoiceId::Dora,
Emma => VoiceId::Emma,
Enrique => VoiceId::Enrique,
Ewa => VoiceId::Ewa,
Filiz => VoiceId::Filiz,
Gabrielle => VoiceId::Gabrielle,
Geraint => VoiceId::Geraint,
Giorgio => VoiceId::Giorgio,
Gwyneth => VoiceId::Gwyneth,
Hannah => VoiceId::Hannah,
Hans => VoiceId::Hans,
Hiujin => VoiceId::Hiujin,
Ines => VoiceId::Ines,
Ivy => VoiceId::Ivy,
Jacek => VoiceId::Jacek,
Jan => VoiceId::Jan,
Joanna => VoiceId::Joanna,
Joey => VoiceId::Joey,
Justin => VoiceId::Justin,
Kajal => VoiceId::Kajal,
Karl => VoiceId::Karl,
Kendra => VoiceId::Kendra,
Kevin => VoiceId::Kevin,
Kimberly => VoiceId::Kimberly,
Lea => VoiceId::Lea,
Liam => VoiceId::Liam,
Liv => VoiceId::Liv,
Lotte => VoiceId::Lotte,
Lucia => VoiceId::Lucia,
Lupe => VoiceId::Lupe,
Mads => VoiceId::Mads,
Maja => VoiceId::Maja,
Marlene => VoiceId::Marlene,
Mathieu => VoiceId::Mathieu,
Matthew => VoiceId::Matthew,
Maxim => VoiceId::Maxim,
Mia => VoiceId::Mia,
Miguel => VoiceId::Miguel,
Mizuki => VoiceId::Mizuki,
Naja => VoiceId::Naja,
Nicole => VoiceId::Nicole,
Olivia => VoiceId::Olivia,
Pedro => VoiceId::Pedro,
Penelope => VoiceId::Penelope,
Raveena => VoiceId::Raveena,
Ricardo => VoiceId::Ricardo,
Ruben => VoiceId::Ruben,
Russell => VoiceId::Russell,
Salli => VoiceId::Salli,
Seoyeon => VoiceId::Seoyeon,
Takumi => VoiceId::Takumi,
Tatyana => VoiceId::Tatyana,
Vicki => VoiceId::Vicki,
Vitoria => VoiceId::Vitoria,
Zeina => VoiceId::Zeina,
Zhiyu => VoiceId::Zhiyu,
}
}
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
#[repr(u32)]
#[enum_type(name = "GstAwsPollyLanguageCode")]
#[non_exhaustive]
pub enum AwsPollyLanguageCode {
#[enum_value(name = "None", nick = "none")]
None,
#[enum_value(name = "Arb", nick = "arb")]
Arb,
#[enum_value(name = "CaEs", nick = "ca-ES")]
CaEs,
#[enum_value(name = "CmnCn", nick = "cmn-CN")]
CmnCn,
#[enum_value(name = "CyGb", nick = "cy-GB")]
CyGb,
#[enum_value(name = "DaDk", nick = "da-DK")]
DaDk,
#[enum_value(name = "DeAt", nick = "de-AT")]
DeAt,
#[enum_value(name = "DeDe", nick = "de-DE")]
DeDe,
#[enum_value(name = "EnAu", nick = "en-AU")]
EnAu,
#[enum_value(name = "EnGb", nick = "en-GB")]
EnGb,
#[enum_value(name = "EnGbWls", nick = "en-GB-WLS")]
EnGbWls,
#[enum_value(name = "EnIn", nick = "en-IN")]
EnIn,
#[enum_value(name = "EnNz", nick = "en-NZ")]
EnNz,
#[enum_value(name = "EnUs", nick = "en-US")]
EnUs,
#[enum_value(name = "EnZa", nick = "en-ZA")]
EnZa,
#[enum_value(name = "EsEs", nick = "es-ES")]
EsEs,
#[enum_value(name = "EsMx", nick = "es-MX")]
EsMx,
#[enum_value(name = "EsUs", nick = "es-US")]
EsUs,
#[enum_value(name = "FrCa", nick = "fr-CA")]
FrCa,
#[enum_value(name = "FrFr", nick = "fr-FR")]
FrFr,
#[enum_value(name = "HiIn", nick = "hi-IN")]
HiIn,
#[enum_value(name = "IsIs", nick = "is-IS")]
IsIs,
#[enum_value(name = "ItIt", nick = "it-IT")]
ItIt,
#[enum_value(name = "JaJp", nick = "ja-JP")]
JaJp,
#[enum_value(name = "KoKr", nick = "ko-KR")]
KoKr,
#[enum_value(name = "NbNo", nick = "nb-NO")]
NbNo,
#[enum_value(name = "NlNl", nick = "nl-NL")]
NlNl,
#[enum_value(name = "PlPl", nick = "pl-PL")]
PlPl,
#[enum_value(name = "PtBr", nick = "pt-BR")]
PtBr,
#[enum_value(name = "PtPt", nick = "pt-PT")]
PtPt,
#[enum_value(name = "RoRo", nick = "ro-RO")]
RoRo,
#[enum_value(name = "RuRu", nick = "ru-RU")]
RuRu,
#[enum_value(name = "SvSe", nick = "sv-SE")]
SvSe,
#[enum_value(name = "TrTr", nick = "tr-TR")]
TrTr,
#[enum_value(name = "YueCn", nick = "yue-CN")]
YueCn,
}
impl From<AwsPollyLanguageCode> for LanguageCode {
fn from(val: AwsPollyLanguageCode) -> Self {
use AwsPollyLanguageCode::*;
match val {
Arb => LanguageCode::Arb,
CaEs => LanguageCode::CaEs,
CmnCn => LanguageCode::CmnCn,
CyGb => LanguageCode::CyGb,
DaDk => LanguageCode::DaDk,
DeAt => LanguageCode::DeAt,
DeDe => LanguageCode::DeDe,
EnAu => LanguageCode::EnAu,
EnGb => LanguageCode::EnGb,
EnGbWls => LanguageCode::EnGbWls,
EnIn => LanguageCode::EnIn,
EnNz => LanguageCode::EnNz,
EnUs => LanguageCode::EnUs,
EnZa => LanguageCode::EnZa,
EsEs => LanguageCode::EsEs,
EsMx => LanguageCode::EsMx,
EsUs => LanguageCode::EsUs,
FrCa => LanguageCode::FrCa,
FrFr => LanguageCode::FrFr,
HiIn => LanguageCode::HiIn,
IsIs => LanguageCode::IsIs,
ItIt => LanguageCode::ItIt,
JaJp => LanguageCode::JaJp,
KoKr => LanguageCode::KoKr,
NbNo => LanguageCode::NbNo,
NlNl => LanguageCode::NlNl,
PlPl => LanguageCode::PlPl,
PtBr => LanguageCode::PtBr,
PtPt => LanguageCode::PtPt,
RoRo => LanguageCode::RoRo,
RuRu => LanguageCode::RuRu,
SvSe => LanguageCode::SvSe,
TrTr => LanguageCode::TrTr,
YueCn => LanguageCode::YueCn,
None => unreachable!(),
}
}
}
glib::wrapper! {
pub struct Polly(ObjectSubclass<imp::Polly>) @extends gst::Element, gst::Object;
}
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
#[cfg(feature = "doc")]
{
AwsPollyEngine::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
AwsPollyVoiceId::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
AwsPollyLanguageCode::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
}
gst::Element::register(
Some(plugin),
"awspolly",
gst::Rank::NONE,
Polly::static_type(),
)
}

View file

@ -28,7 +28,7 @@ pub const DEFAULT_S3_REGION: &str = "us-west-2";
pub static AWS_BEHAVIOR_VERSION: LazyLock<aws_config::BehaviorVersion> =
LazyLock::new(aws_config::BehaviorVersion::v2023_11_09);
static RUNTIME: LazyLock<runtime::Runtime> = LazyLock::new(|| {
pub static RUNTIME: LazyLock<runtime::Runtime> = LazyLock::new(|| {
runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(2)

View file

@ -27,7 +27,7 @@ use aws_sdk_transcribestreaming as aws_transcribe;
use futures::channel::mpsc;
use futures::future::AbortHandle;
use futures::prelude::*;
use tokio::{runtime, sync::broadcast, task};
use tokio::{sync::broadcast, task};
use std::collections::{BTreeSet, VecDeque};
use std::sync::{Arc, Mutex};
@ -40,13 +40,7 @@ use super::{
AwsTranscriberResultStability, AwsTranscriberVocabularyFilterMethod,
TranslationTokenizationMethod, CAT,
};
static RUNTIME: LazyLock<runtime::Runtime> = LazyLock::new(|| {
runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
});
use crate::s3utils::RUNTIME;
#[allow(deprecated)]
static AWS_BEHAVIOR_VERSION: LazyLock<aws_config::BehaviorVersion> =

View file

@ -12,6 +12,10 @@ trun = "trun"
# net/rtp/src/ac3 - "5/8ths" - not sure how to allow this without also letting through all typos of 'this'
ths = "ths"
# net/aws
ines = "ines"
Ines = "Ines"
[files]
extend-exclude = [
"*.mcc",
@ -21,4 +25,4 @@ extend-exclude = [
# third party code
"video/closedcaption/src/c/*",
"net/webrtc/gstwebrtc-api/third-party/*",
]
]