diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8eb3b06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..8c18f1a --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d919d09 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +sudo: false +language: ruby +rvm: + - 2.3.4 +before_install: gem install bundler -v 1.14.6 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6053543 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at machisuji@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..f4be8db --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in ssmd.gemspec +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..13d137e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Markus Kahl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 4672aa2..d0d74ff 100644 --- a/README.md +++ b/README.md @@ -1,311 +1,52 @@ # SSMD Speech Synthesis Markdown (SSMD) is an lightweight alternative syntax for [SSML](https://www.w3.org/TR/speech-synthesis/). +This repository contains both the reference implementation of the SSMD-to-SSML conversion tool (`ssmd`) as well +as the [specification](SPECIFICATION.md) of the language. -## Specification +## Requirements -SSMD is mapped to SSML using the following rules. +The tools and executable specification provided in this repository require **Ruby 2.3.4** or better. -* [Text](#text) -* [Emphasis](#emphasis) -* [Break](#break) -* [Language](#language) -* [Mark](#mark) -* [Paragraph](#paragraph) -* [Phoneme](#phoneme) -* [Prosody](#prosody) -* [Say-as](#say-as) -* [Substitution](#substitution) -* [Extensions](#extensions) +## Installation -*** +Add this line to your application's Gemfile: -### Text - -Any text written is implicitly wrapped in a `` root element. -This will be omitted in the rest of the examples shown in this section. - -SSMD: -``` -text -``` - -SSML: -```html -text -``` - -*** - -### Emphasis - -SSMD: -``` -*word* -``` - -SSML: -```html -word -``` - -*** - -### Break - -Pauses can be indicated by using `...`. Several modifications to the duration are allowed as shown below. - -SSMD: -``` -Hello ... world (default: x-strong break like after a paragraph) -Hello - ...0 world (skip break when there would otherwise be one like after this dash) -Hello ...c world (medium break like after a comma) -Hello ...s world (strong break like after a sentence) -Hello ...p world (extra string break like after a paragraph) -Hello ...5s world (5 second break (max 10s)) -Hello ...100ms world (100 millisecond break (max 10000ms)) -Hello ...100 world (100 millisecond break (max 10000ms)) -``` - -SSML: -```html -Hello world -Hello - world -Hello world -Hello world -Hello world -Hello world -Hello world -Hello world -``` - -*** - -### Language - -Text passages can be annotated with ISO 639-1 language codes as shown below. -SSML expects a full code including a country. While you can provide those too -SSMD will use a sensible default in case where this is omitted. -As can be seen in the first example where `en` defaults to `en-US` and -`de` defaults to `de-DE`. - -SSMD: -``` -Ich sah [Guardians of the Galaxy](en) im Kino. -Ich sah [Guardians of the Galaxy](en-GB) im Kino. -I saw ["Die Häschenschule"](de) in the cinema. -``` - -SSML: -```html -Ich sah Guardians of the Galaxy im Kino. -Ich sah Guardians of the Galaxy im Kino. -I saw "Die Häschenschule" in the cinema. -``` - -*** - -### Mark - -Sections of text can be tagged using marks. They do not effect the synthesis but -can be returned by SSML processing engines as meta information and to emit -events during processing based on these marks. - -SSMD: -``` -I always wanted a @animal cat as a pet. -``` - -SSML: -```html -I always wanted a cat as a pet. -``` - -*** - -### Paragraph - -Empty lines indicate a paragraph. - -SSMD: -``` -First prepare the ingredients. -Don't forget to wash them first. - -Lastly mix them all together. -``` - -SSML: -```html -

First prepare the ingredients. Don't forget to wash them first.

-

Lastly mix them all together.

-``` - -### Phoneme - -Sometimes the speech synthesis engine needs to be told how exactly to pronounce a word. -This can be done via phonemes. While SSML supports IPA, SSMD uses [X-SAMPA](https://en.wikipedia.org/wiki/X-SAMPA) by default. - -SSMD: -``` -The German word ["dich"](ph: dIC) does not sound like dick. -``` - -SSML: -```html -The German word "dich" does not sound like dick. -``` - -### Prosody - -The prosody or rythm depends the volume, rate and pitch of the delivered text. - -Each of those values can be defined by a number between 1 and 5 where those mean: - -| number | volume | rate | pitch | -| ------ | ------ | ---- | ----- | -| 0 | silent | | | -| 1 | x-soft | x-slow | x-low | -| 2 | soft | slow | low | -| 3 | medium | medium | medium | -| 4 | loud | fast | high | -| 5 | x-loud | x-fast | x-high | - -SSMD: -``` -Volume: - -(silent) ---extra soft-- --soft- -medium -+loud+ or LOUD -++extra loud++ - -Rate: - -<fast> ->>extra fast>> - -Pitch: - -__extra low__ -_low_ -medium -^high^ -^^extra high^^ - -++>>^^extra loud, fast and high^^>>++ or -[extra loud, fast, and high](vrp: 555) or -[extra loud, fast, and high](v: 5, r: 5, p: 5) -``` - -SSML: -```html -Volume: - -silent -extra soft -soft -medium -loud or loud -extra loud - -Rate: - -extra slow -slow -medium -fast -extra fast - -Pitch: - -extra low -low -medium -high -extra high - -extra loud, fast and high or -extra loud, fast and high or -extra loud, fast and high +```ruby +gem 'ssmd' ``` -The shortcuts are listed first. While they can be combined, sometimes it's easier and shorter to just use -the explizit form shown in the last 2 lines. All of them can be nested, too. -Moreover changes in volume (`[louder](v: +10dB)`) and pitch (`[lower](p: -4%)`) can also be given explicitly in relative values. +And then execute: -### Say-as + $ bundle -You can give the speech sythesis engine hints as to what it's supposed to read using `as`. +Or install it yourself as: -Possible values: + $ gem install ssmd -* character - spell out each single character, e.g. for KGB -* number - cardinal number, e.g. 100 -* ordinal - ordinal number, e.g. 1st -* digits - spell out each single digit, e.g. 123 as 1 - 2 - 3 -* fraction - pronounce number as fraction, e.g. 3/4 as three quarters -* unit - e.g. 1meter -* date - read content as a date, must provide format -* time - duration in minutes and seconds -* address - read as part of an address -* telephone - read content as a telephone number -* expletive - beeps out the content +## Usage -SSMD -``` -Today on [29.12.2017](as: date, format: "dd.mm.yyyy") my -telephone number is [+49 123456](as: telephone). -You can't say [fuck](as: expletive) on television. -``` +TODO: Write usage instructions here -SSML: -```html -Today on 29.12.2017 my -telephone number is +49 123456. -You can't say fuck on television. -``` +## Development -*** +After checking out the repo, run `bin/setup` to install dependencies. You can run `bin/console` for an interactive prompt that will allow you to experiment. -### Substitution +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). -Allows to substitute the pronuciation of a word, such as an acronym, with an alias. +### Tests -SSMD: -``` -I'd like to drink some [H2O](sub: water) now. -``` - -SSML: -```html -I'd like to drink some H2O now. -``` - -*** +Run `rake spec` to run the tests against a given executable. -### Extensions +This implementation and any other can be tested against the SSMD specification. +Said specification is extracted from `SPECIFICATION.md`. +It runs each SSMD snippet through the tested tool and compares it to the output of +the following SSML snippet. If they match the test passes. -It must be possible to extend SSML with constructs specific to certain speech synthesis engines. -Registered extensions must have a unique name. They can take parameters. -For instance let's a assume we registered Amazon Polly's whisper effect in some hypothetical SSMD -library API. +## Contributing -```ruby -SSMD.register "whisper", "amazon:effect", name: "whispered" -``` +Bug reports and pull requests are welcome on GitHub at https://github.com/machisuji/ssmd. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. -SSMD: -``` -If he [whispers](ext: whisper), he lies. -``` +## License -SSML: -```html -If he whispers, he lies. -``` +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..b7e9ed5 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/SPECIFICATION.md b/SPECIFICATION.md new file mode 100644 index 0000000..5ef89ba --- /dev/null +++ b/SPECIFICATION.md @@ -0,0 +1,311 @@ +# SSMD Specification + +Here we specify how Speech Synthesis Markdown (SSMD) works. + +## Syntax + +SSMD is mapped to SSML using the following rules. + +* [Text](#text) +* [Emphasis](#emphasis) +* [Break](#break) +* [Language](#language) +* [Mark](#mark) +* [Paragraph](#paragraph) +* [Phoneme](#phoneme) +* [Prosody](#prosody) +* [Say-as](#say-as) +* [Substitution](#substitution) +* [Extensions](#extensions) + +*** + +### Text + +Any text written is implicitly wrapped in a `` root element. +This will be omitted in the rest of the examples shown in this section. + +SSMD: +``` +text +``` + +SSML: +```html +text +``` + +*** + +### Emphasis + +SSMD: +``` +*word* +``` + +SSML: +```html +word +``` + +*** + +### Break + +Pauses can be indicated by using `...`. Several modifications to the duration are allowed as shown below. + +SSMD: +``` +Hello ... world (default: x-strong break like after a paragraph) +Hello - ...0 world (skip break when there would otherwise be one like after this dash) +Hello ...c world (medium break like after a comma) +Hello ...s world (strong break like after a sentence) +Hello ...p world (extra string break like after a paragraph) +Hello ...5s world (5 second break (max 10s)) +Hello ...100ms world (100 millisecond break (max 10000ms)) +Hello ...100 world (100 millisecond break (max 10000ms)) +``` + +SSML: +```html +Hello world +Hello - world +Hello world +Hello world +Hello world +Hello world +Hello world +Hello world +``` + +*** + +### Language + +Text passages can be annotated with ISO 639-1 language codes as shown below. +SSML expects a full code including a country. While you can provide those too +SSMD will use a sensible default in case where this is omitted. +As can be seen in the first example where `en` defaults to `en-US` and +`de` defaults to `de-DE`. + +SSMD: +``` +Ich sah [Guardians of the Galaxy](en) im Kino. +Ich sah [Guardians of the Galaxy](en-GB) im Kino. +I saw ["Die Häschenschule"](de) in the cinema. +``` + +SSML: +```html +Ich sah Guardians of the Galaxy im Kino. +Ich sah Guardians of the Galaxy im Kino. +I saw "Die Häschenschule" in the cinema. +``` + +*** + +### Mark + +Sections of text can be tagged using marks. They do not effect the synthesis but +can be returned by SSML processing engines as meta information and to emit +events during processing based on these marks. + +SSMD: +``` +I always wanted a @animal cat as a pet. +``` + +SSML: +```html +I always wanted a cat as a pet. +``` + +*** + +### Paragraph + +Empty lines indicate a paragraph. + +SSMD: +``` +First prepare the ingredients. +Don't forget to wash them first. + +Lastly mix them all together. +``` + +SSML: +```html +

First prepare the ingredients. Don't forget to wash them first.

+

Lastly mix them all together.

+``` + +### Phoneme + +Sometimes the speech synthesis engine needs to be told how exactly to pronounce a word. +This can be done via phonemes. While SSML supports IPA, SSMD uses [X-SAMPA](https://en.wikipedia.org/wiki/X-SAMPA) by default. + +SSMD: +``` +The German word ["dich"](ph: dIC) does not sound like dick. +``` + +SSML: +```html +The German word "dich" does not sound like dick. +``` + +### Prosody + +The prosody or rythm depends the volume, rate and pitch of the delivered text. + +Each of those values can be defined by a number between 1 and 5 where those mean: + +| number | volume | rate | pitch | +| ------ | ------ | ---- | ----- | +| 0 | silent | | | +| 1 | x-soft | x-slow | x-low | +| 2 | soft | slow | low | +| 3 | medium | medium | medium | +| 4 | loud | fast | high | +| 5 | x-loud | x-fast | x-high | + +SSMD: +``` +Volume: + +~silent~ +--extra soft-- +-soft- +medium ++loud+ or LOUD +++extra loud++ + +Rate: + +<fast> +>>extra fast>> + +Pitch: + +__extra low__ +_low_ +medium +^high^ +^^extra high^^ + +++>>^^extra loud, fast and high^^>>++ or +[extra loud, fast, and high](vrp: 555) or +[extra loud, fast, and high](v: 5, r: 5, p: 5) +``` + +SSML: +```html +Volume: + +silent +extra soft +soft +medium +loud or loud +extra loud + +Rate: + +extra slow +slow +medium +fast +extra fast + +Pitch: + +extra low +low +medium +high +extra high + +extra loud, fast and high or +extra loud, fast and high or +extra loud, fast and high +``` + +The shortcuts are listed first. While they can be combined, sometimes it's easier and shorter to just use +the explizit form shown in the last 2 lines. All of them can be nested, too. +Moreover changes in volume (`[louder](v: +10dB)`) and pitch (`[lower](p: -4%)`) can also be given explicitly in relative values. + +### Say-as + +You can give the speech sythesis engine hints as to what it's supposed to read using `as`. + +Possible values: + +* character - spell out each single character, e.g. for KGB +* number - cardinal number, e.g. 100 +* ordinal - ordinal number, e.g. 1st +* digits - spell out each single digit, e.g. 123 as 1 - 2 - 3 +* fraction - pronounce number as fraction, e.g. 3/4 as three quarters +* unit - e.g. 1meter +* date - read content as a date, must provide format +* time - duration in minutes and seconds +* address - read as part of an address +* telephone - read content as a telephone number +* expletive - beeps out the content + +SSMD +``` +Today on [29.12.2017](as: date, format: "dd.mm.yyyy") my +telephone number is [+49 123456](as: telephone). +You can't say [fuck](as: expletive) on television. +``` + +SSML: +```html +Today on 29.12.2017 my +telephone number is +49 123456. +You can't say fuck on television. +``` + +*** + +### Substitution + +Allows to substitute the pronuciation of a word, such as an acronym, with an alias. + +SSMD: +``` +I'd like to drink some [H2O](sub: water) now. +``` + +SSML: +```html +I'd like to drink some H2O now. +``` + +*** + +### Extensions + +It must be possible to extend SSML with constructs specific to certain speech synthesis engines. +Registered extensions must have a unique name. They can take parameters. +For instance let's a assume we registered Amazon Polly's whisper effect in some hypothetical SSMD +library API. + +```ruby +SSMD.register "whisper", "amazon:effect", name: "whispered" +``` + +SSMD: +``` +If he [whispers](ext: whisper), he lies. +``` + +SSML: +```html +If he whispers, he lies. +``` \ No newline at end of file diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..0491c02 --- /dev/null +++ b/bin/console @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "ssmd" + +require "pry" +Pry.start diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/lib/ssmd.rb b/lib/ssmd.rb new file mode 100644 index 0000000..f957e5c --- /dev/null +++ b/lib/ssmd.rb @@ -0,0 +1,5 @@ +require "ssmd/version" + +module SSMD + # Your code goes here... +end diff --git a/lib/ssmd/version.rb b/lib/ssmd/version.rb new file mode 100644 index 0000000..c53b7e9 --- /dev/null +++ b/lib/ssmd/version.rb @@ -0,0 +1,3 @@ +module SSMD + VERSION = "0.1.0" +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..c25223d --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,11 @@ +require "bundler/setup" +require "ssmd" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/spec/ssmd_spec.rb b/spec/ssmd_spec.rb new file mode 100644 index 0000000..ebba0d8 --- /dev/null +++ b/spec/ssmd_spec.rb @@ -0,0 +1,11 @@ +require "spec_helper" + +RSpec.describe SSMD do + it "has a version number" do + expect(SSMD::VERSION).not_to be nil + end + + it "does something useful" do + expect(false).to eq(true) + end +end diff --git a/ssmd.gemspec b/ssmd.gemspec new file mode 100644 index 0000000..8611cf9 --- /dev/null +++ b/ssmd.gemspec @@ -0,0 +1,39 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'ssmd/version' + +Gem::Specification.new do |spec| + spec.name = "ssmd" + spec.version = SSMD::VERSION + spec.authors = ["Markus Kahl"] + spec.email = ["machisuji@gmail.com"] + + spec.summary = %q{ + Speech Synthesis Markdown (SSMD) is an lightweight alternative syntax for SSML + and the corresponding tool converting SSMD to SSML. + } + spec.homepage = "https://github.com/machisuji/ssmd" + spec.license = "MIT" + + # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' + # to allow pushing to a single host or delete this section to allow pushing to any host. + if spec.respond_to?(:metadata) + spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" + else + raise "RubyGems 2.0 or newer is required to protect against " \ + "public gem pushes." + end + + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_development_dependency "bundler", "~> 1.14" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "pry", "~> 0.10.4" +end