Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for listing voices and languages #7

Draft
wants to merge 47 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f76d089
setup typescript, import and parse JSON file to generate a data js file
panaC Aug 6, 2024
ef18a9d
working on getVoices() with filter and sorter
panaC Aug 9, 2024
ecb0418
add tests on sortByLanguage and filterOnRecommended
panaC Aug 12, 2024
2780645
groupBy fonction + test
panaC Aug 13, 2024
b7cb0fa
github ci
panaC Aug 13, 2024
0f9c4c3
update demo
panaC Aug 13, 2024
8a5c0f6
deploy to github pages github/actions
panaC Aug 13, 2024
0171924
gh-pages : try root folder to include demo
panaC Aug 13, 2024
864c467
typo root folder gh-pages
panaC Aug 13, 2024
1eeb4ed
no need to have a gh-pages action statically build at each push commit
panaC Aug 13, 2024
94a05b0
fix: preferredLanguage and disable the language sorting function
panaC Aug 13, 2024
e36bbe7
fix: typo on broken test and lowerQuality typo
panaC Aug 13, 2024
5850a28
fixes some issue and add localization to groupBy
panaC Aug 13, 2024
50c15ae
add localization to the demo
panaC Aug 13, 2024
79b6872
fix: demo utterance URL
panaC Aug 13, 2024
0d9c6c2
fix: label demo
panaC Aug 13, 2024
b88c1f2
fix filterOnRecommended: push to lowerQuality the altNames voices found
panaC Aug 15, 2024
68449c8
fix: bcp47 language matching between 'fi' and 'fil' with strict compa…
panaC Aug 16, 2024
05b2831
chore: comment on demo file
panaC Aug 16, 2024
6fa11ad
chore: switch the data json src to the main branch
panaC Aug 16, 2024
b269ca4
pull and run data src repo
panaC Aug 19, 2024
0e1165a
chore extract-json-data: add --depth=1 to git clone cmd
panaC Aug 19, 2024
5d8d518
fix extract-json-data: disabling recommendedPitch and recommendedRat…
panaC Aug 19, 2024
4a410ce
chore build: build esm and cjs package
panaC Aug 19, 2024
3da6aeb
fix build: include test build and package.json for both cjs and mjs
panaC Aug 19, 2024
17e47b2
fix build: emit ts declaration
panaC Aug 19, 2024
a3e58a8
fix #8: improve demo with offline availability and gender filter
panaC Aug 23, 2024
4f8c108
fix filterOnRecommended: change name comparaison from startsWith to s…
panaC Aug 23, 2024
15f15bb
fix demo: up
panaC Aug 23, 2024
405ad07
fix demo: add push to logs server button
panaC Aug 23, 2024
d5e4a20
fix: handle Intl.displayName?.of exception
panaC Aug 23, 2024
d329173
fix demo: quick fix on voicesGroupedByRegions maybe undefined
panaC Aug 23, 2024
9d585ac
fix demo: up previous commit
panaC Aug 23, 2024
597d389
fix: demo gender update
panaC Aug 23, 2024
30405ba
new demo
panaC Aug 29, 2024
05eab69
update README
panaC Aug 29, 2024
019328c
update dataset
panaC Aug 29, 2024
1d26554
update dataset
panaC Aug 29, 2024
fae7627
fix demo: text input
panaC Aug 29, 2024
b8d5fc2
Fixes: sortByLanguage and sortByRegion
panaC Aug 30, 2024
5c6d54c
fix: window.navigator.language?s potentially undefined in node
panaC Aug 30, 2024
88aa770
chore: update dataset
panaC Aug 30, 2024
ac6127b
chore: update dataset with new json voice and build cjs,mjs
panaC Aug 30, 2024
3512190
fix: demo textToRead formated
panaC Aug 30, 2024
eef198d
chore: update dataset grec
panaC Aug 30, 2024
c9fcfad
fix: demo
panaC Aug 30, 2024
55e98e6
fix demo
panaC Aug 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Node.js CI

on:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci
- name: Run Build
run: npm run build
- name: Run tests
run: npm test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

node_modules/
script/web-speech-recommended-voices/
.DS_Store
120 changes: 119 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,122 @@ Readium Speech was spun out as a separate project in order to facilitate its int

For our initial work on this project, we're focusing on voice selection based on [recommended voices](https://github.com/HadrienGardeur/web-speech-recommended-voices).

The outline of this work has been explored in a [GitHub discussion](https://github.com/HadrienGardeur/web-speech-recommended-voices/discussions/9) and through a [best practices document](https://github.com/HadrienGardeur/read-aloud-best-practices/blob/main/voice-selection.md).
The outline of this work has been explored in a [GitHub discussion](https://github.com/HadrienGardeur/web-speech-recommended-voices/discussions/9) and through a [best practices document](https://github.com/HadrienGardeur/read-aloud-best-practices/blob/main/voice-selection.md).


## QuickStart

`npm install readium-speech`

#### node :
```
import { getVoices } from "readium-speech/build/cjs/voices";

const voices = await getVoices();
console.log(voices);
```

#### web :
```
import { getVoices } from "readium-speech/build/mjs/voices";

const voices = await getVoices();
console.log(voices);
```

## API

### Interface

```
export interface IVoices {
label: string;
voiceURI: string;
name: string;
language: string;
gender?: TGender | undefined;
age?: string | undefined;
offlineAvailability: boolean;
quality?: TQuality | undefined;
pitchControl: boolean;
recommendedPitch?: number | undefined;
recommendedRate?: number | undefined;
}

export interface ILanguages {
label: string;
code: string;
count: number;
}
```


#### Parse and Extract IVoices from speechSynthesis WebAPI
```
function getVoices(preferredLanguage?: string[] | string, localization?: string): Promise<IVoices[]>
```

#### List languages from IVoices
```
function getLanguages(voices: IVoices[], preferredLanguage?: string[] | string, localization?: string | undefined): ILanguages[]
```

#### helpers

```
function listLanguages(voices: IVoices[], localization?: string): ILanguages[]

function ListRegions(voices: IVoices[], localization?: string): ILanguages[]

function parseSpeechSynthesisVoices(speechSynthesisVoices: SpeechSynthesisVoice[]): IVoices[]

function getSpeechSynthesisVoices(): Promise<SpeechSynthesisVoice[]>
```

#### groupBy

```
function groupByKindOfVoices(allVoices: IVoices[]): TGroupVoices

function groupByRegions(voices: IVoices[], language: string, preferredRegions?: string[] | string, localization?: string): TGroupVoices

function groupByLanguage(voices: IVoices[], preferredLanguage?: string[] | string, localization?: string): TGroupVoices
```

#### sortBy

```
function sortByLanguage(voices: IVoices[], preferredLanguage?: string[] | string): IVoices[]

function sortByRegion(voices: IVoices[], preferredRegions?: string[] | string, localization?: string | undefined): IVoices[]

function sortByGender(voices: IVoices[], genderFirst: TGender): IVoices[]

function sortByName(voices: IVoices[]): IVoices[]

function sortByQuality(voices: IVoices[]): IVoices[]
```

#### filterOn

```
function filterOnRecommended(voices: IVoices[], _recommended?: IRecommended[]): TReturnFilterOnRecommended

function filterOnVeryLowQuality(voices: IVoices[]): IVoices[]

function filterOnNovelty(voices: IVoices[]): IVoices[]

function filterOnQuality(voices: IVoices[], quality: TQuality | TQuality[]): IVoices[]

function filterOnLanguage(voices: IVoices[], language: string | string[]): IVoices[]

function filterOnGender(voices: IVoices[], gender: TGender): IVoices[]

function filterOnGender(voices: IVoices[], gender: TGender): IVoices[]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove duplicate

Suggested change
function filterOnGender(voices: IVoices[], gender: TGender): IVoices[]

```

## ressources

https://www.sensedeep.com/blog/posts/2021/how-to-create-single-source-npm-module.html


148 changes: 148 additions & 0 deletions build/cjs/data.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
export declare const novelty: string[];
export declare const veryLowQuality: string[];
export type TGender = "female" | "male" | "nonbinary";
export type TQuality = "veryLow" | "low" | "normal" | "high" | "veryHigh";
export interface IRecommended {
label: string;
name: string;
altNames?: string[];
language: string;
gender?: TGender | undefined;
age?: string | undefined;
quality: TQuality[];
recommendedPitch?: number | undefined;
recommendedRate?: number | undefined;
localizedName: string;
}
export declare const recommended: Array<IRecommended>;
export declare const quality: {
ca: {
normal: string;
high: string;
};
cs: {
normal: string;
high: string;
};
da: {
normal: string;
high: string;
};
de: {
normal: string;
high: string;
};
el: {
normal: string;
high: string;
};
en: {
normal: string;
high: string;
};
es: {
normal: string;
high: string;
};
fi: {
normal: string;
high: string;
};
fr: {
normal: string;
high: string;
};
hu: {
normal: string;
high: string;
};
hr: {
normal: string;
high: string;
};
it: {
normal: string;
high: string;
};
ja: {
normal: string;
high: string;
};
ko: {
normal: string;
high: string;
};
nb: {
normal: string;
high: string;
};
nl: {
normal: string;
high: string;
};
pl: {
normal: string;
high: string;
};
pt: {
normal: string;
high: string;
};
ro: {
normal: string;
high: string;
};
ru: {
normal: string;
high: string;
};
sk: {
normal: string;
high: string;
};
sl: {
normal: string;
high: string;
};
sv: {
normal: string;
high: string;
};
tr: {
normal: string;
high: string;
};
uk: {
normal: string;
high: string;
};
};
export declare const defaultRegion: {
bg: string;
ca: string;
cs: string;
da: string;
de: string;
en: string;
es: string;
eu: string;
fi: string;
fr: string;
gl: string;
hr: string;
hu: string;
it: string;
ja: string;
ko: string;
nb: string;
nl: string;
pl: string;
pt: string;
ro: string;
ru: string;
sk: string;
sl: string;
sv: string;
tr: string;
uk: string;
};
9 changes: 9 additions & 0 deletions build/cjs/data.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build/cjs/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as voicesSelection from "./voices.js";
27 changes: 27 additions & 0 deletions build/cjs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.voicesSelection = void 0;
exports.voicesSelection = __importStar(require("./voices.js"));
3 changes: 3 additions & 0 deletions build/cjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
46 changes: 46 additions & 0 deletions build/cjs/voices.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TGender, TQuality, IRecommended } from "./data.js";
export interface IVoices {
label: string;
voiceURI: string;
name: string;
language: string;
gender?: TGender | undefined;
age?: string | undefined;
offlineAvailability: boolean;
quality?: TQuality | undefined;
pitchControl: boolean;
recommendedPitch?: number | undefined;
recommendedRate?: number | undefined;
}
export declare function getSpeechSynthesisVoices(): Promise<SpeechSynthesisVoice[]>;
export declare function parseSpeechSynthesisVoices(speechSynthesisVoices: SpeechSynthesisVoice[]): IVoices[];
export declare function filterOnOfflineAvailability(voices: IVoices[], offline?: boolean): IVoices[];
export declare function filterOnGender(voices: IVoices[], gender: TGender): IVoices[];
export declare function filterOnLanguage(voices: IVoices[], language: string | string[]): IVoices[];
export declare function filterOnQuality(voices: IVoices[], quality: TQuality | TQuality[]): IVoices[];
export declare function filterOnNovelty(voices: IVoices[]): IVoices[];
export declare function filterOnVeryLowQuality(voices: IVoices[]): IVoices[];
export type TReturnFilterOnRecommended = [voicesRecommended: IVoices[], voicesLowerQuality: IVoices[]];
export declare function filterOnRecommended(voices: IVoices[], _recommended?: IRecommended[]): TReturnFilterOnRecommended;
export declare function sortByQuality(voices: IVoices[]): IVoices[];
export declare function sortByName(voices: IVoices[]): IVoices[];
export declare function sortByGender(voices: IVoices[], genderFirst: TGender): IVoices[];
export declare function sortByLanguage(voices: IVoices[], preferredLanguage?: string[] | string, localization?: string | undefined): IVoices[];
export declare function sortByRegion(voices: IVoices[], preferredRegions?: string[] | string, localization?: string | undefined): IVoices[];
export interface ILanguages {
label: string;
code: string;
count: number;
}
export declare function listLanguages(voices: IVoices[], localization?: string | undefined): ILanguages[];
export declare function listRegions(voices: IVoices[], localization?: string | undefined): ILanguages[];
export type TGroupVoices = Map<string, IVoices[]>;
export declare function groupByLanguages(voices: IVoices[], preferredLanguage?: string[] | string, localization?: string | undefined): TGroupVoices;
export declare function groupByRegions(voices: IVoices[], preferredRegions?: string[] | string, localization?: string | undefined): TGroupVoices;
export declare function groupByKindOfVoices(allVoices: IVoices[]): TGroupVoices;
export declare function getLanguages(voices: IVoices[], preferredLanguage?: string[] | string, localization?: string | undefined): ILanguages[];
/**
* Parse and extract SpeechSynthesisVoices,
* @returns IVoices[]
*/
export declare function getVoices(preferredLanguage?: string[] | string, localization?: string): Promise<IVoices[]>;
Loading