initial commit
This commit is contained in:
Generated
+13
@@ -7,6 +7,9 @@
|
||||
"": {
|
||||
"name": "cloudflare-polly-proxy",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"aws4fetch": "^1.0.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||
"vitest": "1.3.0",
|
||||
@@ -1000,6 +1003,11 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/aws4fetch": {
|
||||
"version": "1.0.18",
|
||||
"resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.18.tgz",
|
||||
"integrity": "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -3705,6 +3713,11 @@
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"aws4fetch": {
|
||||
"version": "1.0.18",
|
||||
"resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.18.tgz",
|
||||
"integrity": "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
|
||||
+6
-3
@@ -9,8 +9,11 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.0.0",
|
||||
"@cloudflare/vitest-pool-workers": "^0.1.0",
|
||||
"vitest": "1.3.0",
|
||||
"@cloudflare/vitest-pool-workers": "^0.1.0"
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws4fetch": "^1.0.18"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-8
@@ -1,15 +1,69 @@
|
||||
import jsmodule from '../static/twitch-polly.mjs'
|
||||
import { AwsClient } from 'aws4fetch'
|
||||
|
||||
/**
|
||||
* Welcome to Cloudflare Workers! This is your first worker.
|
||||
*
|
||||
* - Run `npm run dev` in your terminal to start a development server
|
||||
* - Open a browser tab at http://localhost:8787/ to see your worker in action
|
||||
* - Run `npm run deploy` to publish your worker
|
||||
*
|
||||
* Learn more at https://developers.cloudflare.com/workers/
|
||||
* create a Response object with control headers set
|
||||
* @param {BodyInit} body
|
||||
* @param {ResponseInit} init
|
||||
*/
|
||||
function makeResponse(body=undefined,init=undefined){
|
||||
if(!init){
|
||||
init={}
|
||||
}
|
||||
if(!init.headers){
|
||||
init.headers={}
|
||||
}
|
||||
init.headers['access-control-allow-headers']='authorization,client-id'
|
||||
init.headers['access-control-allow-origin']='*'
|
||||
init.headers['access-control-allow-private-network']='true'
|
||||
init.headers['cache-control']='none'
|
||||
return new Response(body,init)
|
||||
}
|
||||
|
||||
function awsRequest(accessKeyId,secretAccessKey,host,endpoint,options){
|
||||
console.log(host+endpoint+options)
|
||||
const aws=new AwsClient({
|
||||
accessKeyId:accessKeyId,
|
||||
secretAccessKey:secretAccessKey,
|
||||
})
|
||||
return aws.fetch(host+endpoint+options)
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request, env, ctx) {
|
||||
return new Response('Hello World!');
|
||||
if(request.method==='OPTIONS'){
|
||||
return makeResponse()
|
||||
}
|
||||
const url=new URL(request.url)
|
||||
if(url.pathname==='/twitch-polly.mjs'){
|
||||
return makeResponse(jsmodule,{
|
||||
headers:{
|
||||
'content-type':'text/javascript'
|
||||
}
|
||||
})
|
||||
}
|
||||
const validation=await fetch('https://id.twitch.tv/oauth2/validate',
|
||||
{headers:request.headers})
|
||||
if(!validation.ok){
|
||||
return validation
|
||||
}
|
||||
if(url.pathname=='/describe_voices'){
|
||||
url.pathname='/v1/voices'
|
||||
}
|
||||
if(url.pathname=='/synthesize_speech'){
|
||||
url.pathname='/v1/speech'
|
||||
}
|
||||
if(url.searchParams.get('LanguageCode')==''){
|
||||
url.searchParams.delete('LanguageCode')
|
||||
}
|
||||
return makeResponse(
|
||||
await awsRequest(
|
||||
env.AWS_ACCESS_KEY_ID,
|
||||
env.AWS_SECRET_ACCESS_KEY,
|
||||
env.AWS_HOST,
|
||||
url.pathname,
|
||||
url.search
|
||||
).then(response=>response.blob())
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
Returns the list of voices that are available for use when requesting speech synthesis.
|
||||
Each voice speaks a specified language, is either male or female, and is identified by an ID, which is the ASCII version of the voice name.
|
||||
|
||||
When synthesizing speech ( SynthesizeSpeech ), you provide the voice ID for the voice you want from the list of voices returned by DescribeVoices.
|
||||
|
||||
For example, you want your news reader application to read news in a specific language, but giving a user the option to choose the voice.
|
||||
Using the DescribeVoices operation you can provide the user with a list of available voices to select from.
|
||||
|
||||
You can optionally specify a language code to filter the available voices.
|
||||
For example, if you specify en-US, the operation returns a list of all available US English voices.
|
||||
|
||||
This operation requires permissions to perform the polly:DescribeVoices action.
|
||||
|
||||
@param {string} Engine Specifies the engine ( standard or neural) used by Amazon Polly when processing input text for speech synthesis.
|
||||
@param {string} LanguageCode The language identification tag (ISO 639 code for the language name-ISO 3166 country code) for filtering the list of voices returned.
|
||||
If you don’t specify this optional parameter, all available voices are returned.
|
||||
@param {boolean} IncludeAdditionalLanguageCodes Boolean value indicating whether to return any bilingual voices that use the specified language as an additional language.
|
||||
For instance, if you request all languages that use US English (es-US),
|
||||
and there is an Italian voice that speaks both Italian (it-IT) and US English,
|
||||
that voice will be included if you specify yes but not if you specify no.
|
||||
@param {dict} auth_headers your application's authorization headers used when making Twitch API requests
|
||||
@returns {Promise<Array>} An array of dicts with the following properties:
|
||||
|
||||
Gender (string) – Gender of the voice.
|
||||
|
||||
Id (string) – Amazon Polly assigned voice ID.
|
||||
This is the ID that you specify when calling the SynthesizeSpeech operation.
|
||||
|
||||
LanguageCode (string) – Language code of the voice.
|
||||
|
||||
LanguageName (string) – Human readable name of the language in English.
|
||||
|
||||
Name (string) – Name of the voice (for example, Salli, Kendra, etc.).
|
||||
This provides a human readable voice name that you might display in your application.
|
||||
|
||||
AdditionalLanguageCodes (list) – Additional codes for languages available for the specified voice in addition to its default language.
|
||||
For example, the default language for Aditi is Indian English (en-IN) because it was first used for that language.
|
||||
Since Aditi is bilingual and fluent in both Indian English and Hindi, this parameter would show the code hi-IN.
|
||||
|
||||
SupportedEngines (list) – Specifies which engines ( standard or neural) that are supported by a given voice.
|
||||
*/
|
||||
export function DescribeVoices(
|
||||
auth_headers,
|
||||
Engine='standard',
|
||||
LanguageCode='',
|
||||
IncludeAdditionalLanguageCodes=true){
|
||||
const url=new URL('/v1/voices',import.meta.url)
|
||||
url.search=new URLSearchParams({
|
||||
Engine:Engine,
|
||||
LanguageCode:LanguageCode,
|
||||
IncludeAdditionalLanguageCodes:IncludeAdditionalLanguageCodes
|
||||
})
|
||||
return fetch(url,{headers:auth_headers})
|
||||
.then(response=>response.json())
|
||||
.then(json=>json.Voices)
|
||||
}
|
||||
|
||||
function debug_log(message){
|
||||
console.debug('synthesize_speech():',message)
|
||||
}
|
||||
/**
|
||||
Synthesizes UTF-8 input, plain text or SSML, to a stream of bytes.
|
||||
SSML input must be valid, well-formed SSML.
|
||||
Some alphabets might not be available with all the voices (for example, Cyrillic might not be read at all by English voices) unless phoneme mapping is used.
|
||||
@param {String} Text
|
||||
Input text to synthesize. If you specify ssml as the TextType, follow the SSML format for the input text.
|
||||
|
||||
Type: String
|
||||
|
||||
Required: Yes
|
||||
@param {String} VoiceId
|
||||
Voice ID to use for the synthesis. You can get a list of available voice IDs by calling the DescribeVoices operation.
|
||||
|
||||
Type: String
|
||||
|
||||
Required: Yes
|
||||
@param {String} TextType
|
||||
Specifies whether the input text is plain text or SSML. The default value is plain text.
|
||||
|
||||
Type: String
|
||||
|
||||
Valid Values: ssml | text
|
||||
|
||||
Required: No
|
||||
@param {string} Engine
|
||||
Specifies the engine (standard or neural) for Amazon Polly to use when processing input text for speech synthesis.
|
||||
|
||||
When using NTTS-only voices such as Kevin (en-US), this parameter is required and must be set to neural.
|
||||
If the engine is not specified, or is set to standard, this will result in an error.
|
||||
|
||||
For standard voices, this is not required; the engine parameter defaults to standard.
|
||||
|
||||
Type: String
|
||||
|
||||
Valid Values: standard | neural
|
||||
|
||||
Required: No
|
||||
@param {string} LanguageCode
|
||||
Optional language code for the Synthesize Speech request.
|
||||
This is only necessary if using a bilingual voice, such as Aditi, which can be used for either Indian English (en-IN) or Hindi (hi-IN).
|
||||
|
||||
If a bilingual voice is used and no language code is specified, Amazon Polly uses the default language of the bilingual voice.
|
||||
The default language for any voice is the one returned by the DescribeVoices operation for the LanguageCode parameter.
|
||||
For example, if no language code is specified, Aditi will use Indian English rather than Hindi.
|
||||
|
||||
Type: String
|
||||
|
||||
Required: No
|
||||
@param {string} OutputFormat
|
||||
The format in which the returned output will be encoded.
|
||||
For audio stream, this will be mp3, ogg_vorbis, or pcm. For speech marks, this will be json.
|
||||
|
||||
When pcm is used, the content returned is audio/pcm in a signed 16-bit, 1 channel (mono), little-endian format.
|
||||
|
||||
Type: String
|
||||
|
||||
Valid Values: json | mp3 | ogg_vorbis | pcm
|
||||
|
||||
Required: No
|
||||
@param {string} SampleRate
|
||||
The audio frequency specified in Hz.
|
||||
|
||||
The valid values for mp3 and ogg_vorbis are "8000", "16000", "22050", and "24000". The default value is "24000".
|
||||
|
||||
Valid values for pcm are "8000" and "16000".
|
||||
|
||||
Type: String
|
||||
|
||||
Required: No
|
||||
@param {dict} auth_headers your application's authorization headers used when making Twitch API requests
|
||||
@returns {Promise<Response>} see OutputFormat parameter
|
||||
*/
|
||||
export function SynthesizeSpeech(
|
||||
auth_headers,
|
||||
Text,
|
||||
VoiceId,
|
||||
TextType='text',
|
||||
Engine='standard',
|
||||
LanguageCode='',
|
||||
OutputFormat='mp3',
|
||||
SampleRate='24000'){
|
||||
console.debug('synthesize_speech():',Text)
|
||||
if(TextType=='ssml'){
|
||||
const openSpeak=/^< *speak *>/ig
|
||||
const closeSpeak=/< *\/ *speak *>$/ig
|
||||
if(!openSpeak.test(Text)){
|
||||
Text='<speak>'+Text
|
||||
}
|
||||
if(!closeSpeak.test(Text)){
|
||||
Text+='</speak>'
|
||||
}
|
||||
// /(?<=< *)($1)(?= *>)/ig
|
||||
const replacements={
|
||||
'<lang xml:lang=':/< *lang *=/ig,
|
||||
'<prosody $1=':/< *(volume|rate|pitch|max-duration) *=/ig,
|
||||
'</prosody>':/< *\/ *(volume|rate|pitch|max-duration) *>/ig,
|
||||
'<say-as interpret-as=':/< *(interpret-as|say-as) *=/ig,
|
||||
'</say-as>':/< *\/ *interpret-as *>/ig,
|
||||
'<sub alias=':/< *(alias|sub) *=/ig,
|
||||
'</sub>':/< *\/ *alias *>/ig,
|
||||
'<w role=':/< *role *=/ig,
|
||||
'<domain name="news">':/< *news *>/ig,
|
||||
'</domain>':/< *\/ *news *>/ig,
|
||||
'effect=$1':/(?<=< *)(drc|whispered)(?= *>)/ig,
|
||||
'<effect name=':/< *effect *=/ig,
|
||||
'effect phonation="soft"':/(?<=< *)(soft)(?= *>)/ig,
|
||||
'effect vocal-tract-length':/(?<=< *)(vocal-tract-length)(?= *=)/ig,
|
||||
'amazon:$1':/(?<=<.*= *"? *)(VBD|VB|DT|IN|JJ|NN|DEFAULT|SENSE_1)(?=.*>)/ig,
|
||||
'amazon:$1':/(?<=<.*)(?<!amazon:)(max-duration|breath|auto-breaths|domain|effect)(?=.*>)/ig,
|
||||
}
|
||||
for(const key in replacements){
|
||||
Text=Text.replaceAll(replacements[key],key)
|
||||
}
|
||||
console.debug(Text)
|
||||
}
|
||||
const url=new URL('/v1/speech',import.meta.url)
|
||||
url.search=new URLSearchParams({
|
||||
Text:Text,
|
||||
VoiceId:VoiceId,
|
||||
TextType:TextType,
|
||||
Engine:Engine,
|
||||
LanguageCode:LanguageCode,
|
||||
OutputFormat:OutputFormat,
|
||||
SampleRate:SampleRate
|
||||
})
|
||||
return fetch(url,{headers:auth_headers})
|
||||
}
|
||||
|
||||
export default SynthesizeSpeech
|
||||
+5
-102
@@ -3,106 +3,9 @@ name = "cloudflare-polly-proxy"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2024-06-05"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
rules = [
|
||||
{ type = "Data", globs = ["../static/*"], fallthrough = true }
|
||||
]
|
||||
|
||||
# Automatically place your workloads in an optimal location to minimize latency.
|
||||
# If you are running back-end logic in a Worker, running it closer to your back-end infrastructure
|
||||
# rather than the end user may result in better performance.
|
||||
# Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||
# [placement]
|
||||
# mode = "smart"
|
||||
|
||||
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
||||
# Docs:
|
||||
# - https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||
# Note: Use secrets to store sensitive data.
|
||||
# - https://developers.cloudflare.com/workers/configuration/secrets/
|
||||
# [vars]
|
||||
# MY_VARIABLE = "production_value"
|
||||
|
||||
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflare’s global network
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#workers-ai
|
||||
# [ai]
|
||||
# binding = "AI"
|
||||
|
||||
# Bind an Analytics Engine dataset. Use Analytics Engine to write analytics within your Pages Function.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#analytics-engine-datasets
|
||||
# [[analytics_engine_datasets]]
|
||||
# binding = "MY_DATASET"
|
||||
|
||||
# Bind a headless browser instance running on Cloudflare's global network.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#browser-rendering
|
||||
# [browser]
|
||||
# binding = "MY_BROWSER"
|
||||
|
||||
# Bind a D1 database. D1 is Cloudflare’s native serverless SQL database.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#d1-databases
|
||||
# [[d1_databases]]
|
||||
# binding = "MY_DB"
|
||||
# database_name = "my-database"
|
||||
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
|
||||
# Bind a dispatch namespace. Use Workers for Platforms to deploy serverless functions programmatically on behalf of your customers.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#dispatch-namespace-bindings-workers-for-platforms
|
||||
# [[dispatch_namespaces]]
|
||||
# binding = "MY_DISPATCHER"
|
||||
# namespace = "my-namespace"
|
||||
|
||||
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
|
||||
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#durable-objects
|
||||
# [[durable_objects.bindings]]
|
||||
# name = "MY_DURABLE_OBJECT"
|
||||
# class_name = "MyDurableObject"
|
||||
|
||||
# Durable Object migrations.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#migrations
|
||||
# [[migrations]]
|
||||
# tag = "v1"
|
||||
# new_classes = ["MyDurableObject"]
|
||||
|
||||
# Bind a Hyperdrive configuration. Use to accelerate access to your existing databases from Cloudflare Workers.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#hyperdrive
|
||||
# [[hyperdrive]]
|
||||
# binding = "MY_HYPERDRIVE"
|
||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#kv-namespaces
|
||||
# [[kv_namespaces]]
|
||||
# binding = "MY_KV_NAMESPACE"
|
||||
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
# Bind an mTLS certificate. Use to present a client certificate when communicating with another service.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#mtls-certificates
|
||||
# [[mtls_certificates]]
|
||||
# binding = "MY_CERTIFICATE"
|
||||
# certificate_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
|
||||
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
||||
# [[queues.producers]]
|
||||
# binding = "MY_QUEUE"
|
||||
# queue = "my-queue"
|
||||
|
||||
# Bind a Queue consumer. Queue Consumers can retrieve tasks scheduled by Producers to act on them.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#queues
|
||||
# [[queues.consumers]]
|
||||
# queue = "my-queue"
|
||||
|
||||
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#r2-buckets
|
||||
# [[r2_buckets]]
|
||||
# binding = "MY_BUCKET"
|
||||
# bucket_name = "my-bucket"
|
||||
|
||||
# Bind another Worker service. Use this binding to call another Worker without network overhead.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||
# [[services]]
|
||||
# binding = "MY_SERVICE"
|
||||
# service = "my-service"
|
||||
|
||||
# Bind a Vectorize index. Use to store and query vector embeddings for semantic search, classification and other vector search use-cases.
|
||||
# Docs: https://developers.cloudflare.com/workers/wrangler/configuration/#vectorize-indexes
|
||||
# [[vectorize]]
|
||||
# binding = "MY_INDEX"
|
||||
# index_name = "my-index"
|
||||
[vars]
|
||||
AWS_HOST = 'https://polly.us-east-1.amazonaws.com'
|
||||
Reference in New Issue
Block a user