Skip to content

Commit

Permalink
+ speak only selected text(closes #5)
Browse files Browse the repository at this point in the history
+ add pauses on empty lines(closes #6)
~ better link filtering
  • Loading branch information
joethei committed Nov 24, 2021
1 parent 5329ddd commit bd783b5
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 184 deletions.
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Text to Speech

Plugin for [Obsidian](https://obsidian.md)

![GitHub package.json version](https://img.shields.io/github/package-json/v/joethei/obsidian-tts)
Expand All @@ -8,35 +9,48 @@ Plugin for [Obsidian](https://obsidian.md)
---
**This plugin is currently in beta**

You can create language specific voices, which will be used when you have a note
with
You can create language specific voices, which will be used when you have a note with
```lang: {languageCode}```
in the [Frontmatter](https://help.obsidian.md/Advanced+topics/YAML+front+matter).
The language code can be seen in the settings and is a two letter [ISO 639-1](https://www.loc.gov/standards/iso639-2/php/English_list.php) code.
in the [Frontmatter](https://help.obsidian.md/Advanced+topics/YAML+front+matter). The language code can be seen in the
settings and is a two letter [ISO 639-1](https://www.loc.gov/standards/iso639-2/php/English_list.php) code.

This plugin will **NOT** work on android due
to [this bug in the Webview](https://bugs.chromium.org/p/chromium/issues/detail?id=487255).

This plugin will **NOT** work on android due to [this bug in the Webview](https://bugs.chromium.org/p/chromium/issues/detail?id=487255).
## Adding languages

This plugin uses the native API of your Operating System, to add a new language reference the documentation accordingly:

## Adding languages
This plugin uses the native API of your Operating System,
to add a new language reference the documentation accordingly:
- [Windows 10](https://support.microsoft.com/en-us/topic/how-to-download-text-to-speech-languages-for-windows-10-d5a6b612-b3ae-423f-afa5-4f6caf1ec5d3)
- [MacOS](https://support.apple.com/guide/mac-help/change-the-system-language-mh26684/mac)
- [iOS](https://support.apple.com/guide/iphone/change-the-language-and-region-iphce20717a3/ios)

<!--- [Android](https://support.google.com/accessibility/android/answer/6006983?hl=en)-->

## Installing the plugin

<!--- `Settings > Third-party plugins > Community Plugins > Browse` and search for `Text to Speech`-->

- Using the [Beta Reviewers Auto-update Tester](https://github.com/TfTHacker/obsidian42-brat) plugin with the repo
path: `joethei/obsidian-tts`
- Copy over `main.js`, `styles.css`, `manifest.json` from the releases to your
vault `VaultFolder/.obsidian/plugins/obsidian-tts/`.

## API

You can use this plugins API to add Text to Speech capabilities to your plugin.

```js
//@ts-ignore
if(this.app.plugins.plugins["obsidian-tts"]) {//check if the plugin is loaded
//@ts-ignore
await this.app.plugins.plugins["obsidian-tts"].say(title, text, language);//language is optional
if (this.app.plugins.plugins["obsidian-tts"]) {//check if the plugin is loaded
//@ts-ignore
const tts = this.app.plugins.plugins["obsidian-tts"];
await tts.say(title, text, language);//language is optional, use a ISO 639-1 code
tts.pause();
tts.resume();
tts.stop();
tts.isSpeaking();
tts.isPaused();

}
```
Parameters:
- title: Title of your text, will only be spoken if the the user has the setting enabled
- text
- language(optional): language code according to the [ISO 639-1](https://www.loc.gov/standards/iso639-2/php/English_list.php), if there is no voice configured for that language, the plugin will use the default voice.
2 changes: 1 addition & 1 deletion src/LanguageVoiceModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class LanguageVoiceModal extends Modal {
const input = new TextInputPrompt(this.app, "What do you want to hear?", "", "Hello world this is Text to speech running in obsidian", "Hello world this is Text to speech running in obsidian");
await input.openAndGetValue((async value => {
if (value.getValue().length === 0) return;
await this.plugin.sayWithVoice('', value.getValue(), this.voice);
await this.plugin.ttsService.sayWithVoice('', value.getValue(), this.voice);
}));


Expand Down
129 changes: 129 additions & 0 deletions src/TTSService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {MarkdownView, Notice, parseYaml} from "obsidian";
import {LanguageVoiceMap} from "./settings";
import TTSPlugin from "./main";

export class TTSService {
plugin: TTSPlugin;

constructor(plugin: TTSPlugin) {
this.plugin = plugin;
}

stop(): void {
if(!this.isSpeaking()) return;
window.speechSynthesis.cancel();
}

pause(): void {
if(!this.isSpeaking()) return;
window.speechSynthesis.pause();
}

resume(): void {
if(!this.isSpeaking()) return;
window.speechSynthesis.resume();
}

isSpeaking() : boolean {
return window.speechSynthesis.speaking;
}

isPaused() : boolean {
return window.speechSynthesis.paused;
}

async sayWithVoice(title: string, text: string, voice: string): Promise<void> {
let content = text;
if (!this.plugin.settings.speakSyntax) {
content = content.replace(/#/g, "");
content = content.replace(/-/g, "");
content = content.replace(/_/g, "");
content = content.replace(/\*/g, "");
}
if (!this.plugin.settings.speakLinks) {
//regex from https://stackoverflow.com/a/37462442/5589264
content = content.replace(/(?:__|[*#])|\[(.*?)]\(.*?\)/gm, '$1');
}
if (!this.plugin.settings.speakCodeblocks) {
content = content.replace(/```[\s\S]*?```/g, '');
}

if (this.plugin.settings.speakTitle) {
content = title + " " + content;
}

//add pauses, taken from https://stackoverflow.com/a/50944593/5589264
content = content.replace(/\n/g, " ! ");


//only speak link aliases.
content = content.replace(/\[\[(.*\|)(.*)]]/gm, '$2');


const msg = new SpeechSynthesisUtterance();
msg.text = content;
msg.volume = this.plugin.settings.volume;
msg.rate = this.plugin.settings.rate;
msg.pitch = this.plugin.settings.pitch;
msg.voice = window.speechSynthesis.getVoices().filter(otherVoice => otherVoice.name === voice)[0];
window.speechSynthesis.speak(msg);

this.plugin.statusbar.setText("TTS: speaking");
}


getVoice(languageCode: string): string {
const filtered = this.plugin.settings.languageVoices.filter((lang: LanguageVoiceMap) => lang.language === languageCode);
if (filtered.length === 0) return null;
return filtered[0].voice;
}

async say(title: string, text: string, languageCode?: string): Promise<void> {
let usedVoice = this.plugin.settings.defaultVoice;
if (languageCode && languageCode.length !== 0) {
const voice = this.getVoice(languageCode);
if (voice) {
usedVoice = voice;
} else {
new Notice("TTS: could not find voice for language " + languageCode + ". Using default voice.");
}
}
await this.sayWithVoice(title, text, usedVoice);
}


async play(view: MarkdownView): Promise<void> {
let content = view.getViewData();
let language: string;

//check if any language is defined in frontmatter
const frontmatter = content.match(/---[\s\S]*?---/);
if (frontmatter && frontmatter[0]) {
const parsedFrontmatter = parseYaml(frontmatter[0].replace(/---/g, ''));
if (parsedFrontmatter['lang']) {
language = parsedFrontmatter['lang'];
}
}
if (!this.plugin.settings.speakFrontmatter)
if (content.startsWith("---")) {
content = content.replace("---", "");
content = content.substring(content.indexOf("---") + 1);
}
await this.say(view.getDisplayText(), content, language);

}

getLanguageFromFrontmatter(view: MarkdownView) : string {
let language = "";
//check if any language is defined in frontmatter
const frontmatter = view.getViewData().match(/---[\s\S]*?---/);
if (frontmatter && frontmatter[0]) {
const parsedFrontmatter = parseYaml(frontmatter[0].replace(/---/g, ''));
if (parsedFrontmatter['lang']) {
language = parsedFrontmatter['lang'];
}
}
return language;
}

}
Loading

0 comments on commit bd783b5

Please sign in to comment.