Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit df56f1e
Showing
49 changed files
with
1,534 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# This is a basic workflow to help you get started with Actions | ||
|
||
name: CI | ||
|
||
# Controls when the action will run. Triggers the workflow on push or pull request | ||
# events but only for the master branch | ||
on: | ||
push: | ||
branches: [ master ] | ||
# pull_request: | ||
# branches: [ master ] | ||
|
||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel | ||
jobs: | ||
# This workflow contains a single job called "build" | ||
build: | ||
# The type of runner that the job will run on | ||
runs-on: ubuntu-latest | ||
|
||
# Steps represent a sequence of tasks that will be executed as part of the job | ||
steps: | ||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it | ||
- uses: actions/checkout@v2 | ||
- name: Build | ||
env: | ||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} | ||
run: | | ||
mkdir -p build | ||
cd build | ||
for txt in ../levels/*.txt ; do | ||
node ../actions/generate_audio_files.js "$txt" | ||
done | ||
cp -r ../scaffold/. ./ | ||
node ../actions/build_manifest.js ../levels/*.txt | ||
- name: Commit files | ||
run: | | ||
# bail if there are no changes | ||
# git diff-index --quiet HEAD -- && exit | ||
git config --local user.email "action@github.com" | ||
git config --local user.name "GitHub Action" | ||
git add build/ | ||
git commit -m "Generate audio and rebuild website" -a | ||
- name: Push changes | ||
uses: ad-m/github-push-action@master | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Upload artifact | ||
uses: actions/upload-artifact@v1.0.0 | ||
with: | ||
# Artifact name | ||
name: ${{ github.sha }} | ||
# Directory containing files to upload | ||
path: build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Design | ||
|
||
## Text Corpus | ||
|
||
All text used in the interface is configured by files in [`/levels`](/levels). | ||
|
||
Files should be created for `1.txt` to `12.txt` for grade level texts. | ||
These files should have lines of text to be spoken by the application at the appropriate level. E.G. with a file `1.txt`: | ||
|
||
```txt | ||
I am happy. | ||
Birds Sing. | ||
``` | ||
|
||
Would create 2 audio snippets and texts to be read at grade level 1 in the corresponding order: `I am happy.` then `Birds sing.`. | ||
|
||
Any file that is not `NUMBER.txt` will be treated as a special ad-hoc file and audio for the first line of the file will be generated. | ||
|
||
The only file used for this nature is [`help.txt`](/levels/help.txt) | ||
|
||
## Changing the user interface | ||
|
||
| File | Purpose | | ||
| ---- | ---- | | ||
| [`/scaffold/player.html`](/scaffold/index.html) | This file represents all the visual containers that the application uses. It does not contain styles. It does not contain interactivity for events such as clicking buttons. If you need to add a space to put text, add a button, or change the organization of containers you likely want to edit this file. | | ||
| [`/scaffold/interactivity.js`](/scaffold/interactivity.js) | This file represents all behaviors that respond to user interface events. If you are looking to react to some event like the grade changing, you likely want to edit this file. NOTE: this file uses IE11 compatible syntax and thus most modern features of JS are not used, intentionally. | | ||
| [`/scaffold/style.css`](/scaffold/style.css) | This file represents all styling of elements in `player.html`. If you are looking to change colors, padding, or layout you likely want to edit this file. NOTE: button images are not styled by this file, in order to change those colors use an SVG editor. | | ||
|
||
## Automation | ||
|
||
The app will be placed in [`/build`](/build) every time the repository is pushed to. | ||
This is controlled by [the github action](.github/workflows/sync.yaml). | ||
The action uses scripts in [`/actions`](/actions) to take the files from `/levels` and `/scaffold`. | ||
The scripts run commands to generate the appropriate files for the application under `/build`. | ||
|
||
Roughly: | ||
|
||
1. The texts in `/levels` are compiled into a mapping of "text snippet."->"audio_NUMBER.mp3" in [`/build/text-to-mp3s.json`](/build/text-to-mp3s.json). If audio for a text does not exist it is created as a new audio file, but if it already exists no action is taken. NOTE: Audio files are not deleted if they do not have an entry in `text-to-mp3s.json`. | ||
1. The files in `/levels` are sorted by if they are a grade level or ad-hoc text, then placed into [`/build/mp3s.json`](/build/mp3s.json). This is the file used to generate what a user sees and hears when running the application. | ||
1. The files in `/scaffold` are copied to `/build`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
This is free and unencumbered software released into the public domain. | ||
|
||
Anyone is free to copy, modify, publish, use, compile, sell, or | ||
distribute this software, either in source code form or as a compiled | ||
binary, for any purpose, commercial or non-commercial, and by any | ||
means. | ||
|
||
In jurisdictions that recognize copyright laws, the author or authors | ||
of this software dedicate any and all copyright interest in the | ||
software to the public domain. We make this dedication for the benefit | ||
of the public at large and to the detriment of our heirs and | ||
successors. We intend this dedication to be an overt act of | ||
relinquishment in perpetuity of all present and future rights to this | ||
software under copyright law. | ||
|
||
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 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. | ||
|
||
For more information, please refer to <https://unlicense.org> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# Reading Tutor | ||
|
||
## LICENSE | ||
|
||
[Unlicense](https://spdx.org/licenses/Unlicense.html) | ||
|
||
## Generating audio files to be used for the web | ||
|
||
In order to run the Reading Tutor, all lines to be read aloud must be | ||
created before the app is opened. This gives better audio results and | ||
makes it so that more web browsers are able to run Reading Tutor. | ||
|
||
Prerequisites: | ||
|
||
* The `node` CLI must be installed | ||
* The `aws` CLI must be installed | ||
|
||
In order to generate the audio files it needs to be given what text needs | ||
to be converted into audio. In order to do so, create a `.txt` where each | ||
line is a sentence to be read by Reading Tutor. E.G. a `grade1.txt` file | ||
might look like: | ||
|
||
```txt | ||
I am happy. | ||
Dogs bark. | ||
Birds sing. | ||
``` | ||
|
||
Running: | ||
|
||
```console | ||
node generate_audio_files.js grade1.txt | ||
``` | ||
|
||
Will instruct `generate_audio_files.js` to read all lines of `grade1.txt` | ||
and generate audio accordingly. | ||
|
||
## Build the web page for the application | ||
|
||
In order to run the Reading Tutor it needs to list the texts for the web | ||
page to show and in what order. This is related to but independent of the | ||
list of available audio files. | ||
|
||
In order to build the web page, the list of grades for the | ||
web page will be used when you run: | ||
|
||
```console | ||
node build_web_page.js 1=grade1.txt 2=grade2.txt | ||
``` | ||
|
||
Would generate the web page with 2 grade levels `1` and `2` with text from | ||
the 2 files `grade1.txt` and `grade2.txt`. Grades that are not whole numbers, are less than 1, or if the list has missing increments will cause errors. | ||
|
||
## Hosting the web application | ||
|
||
On a static file server, place a copy of the `build/` directory after | ||
generating the audio files and the web page. It will contain a file | ||
`index.html` that can be opened to run the Reading Tutor within a web | ||
browser. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const levelTextPaths = process.argv.slice(2); | ||
|
||
if (levelTextPaths.length === 0) { | ||
console.error('Must include at least 1 text file to process'); | ||
process.exit(1); | ||
} | ||
|
||
const existingAudio = new Map( | ||
JSON.parse( | ||
fs.readFileSync('./text-to-mp3s.json', 'utf8') | ||
) | ||
); | ||
const outPath = 'mp3s.json'; | ||
const adHoc = {}; | ||
const levels = {}; | ||
const seenAudio = new Set(); | ||
for (const levelTextPath of levelTextPaths) { | ||
const level = path.basename(levelTextPath, '.txt'); | ||
const levelText = fs.readFileSync(levelTextPath, 'utf8'); | ||
if (level === `${+level}`) { | ||
const lines = levels[level] = []; | ||
// grade level | ||
for (const line of levelText.split(/\r?\n/g)) { | ||
if (line.trim().length === 0) continue; | ||
const audioPath = existingAudio.get(line); | ||
if (audioPath) { | ||
seenAudio.add(audioPath); | ||
lines.push([line, audioPath]); | ||
} else { | ||
console.error(`couldn't find audio for ${ | ||
JSON.stringify(line)}, found in ${levelTextPath} | ||
`.replace(/\s+/, ' ')); | ||
process.exit(1); | ||
} | ||
} | ||
} else { | ||
if (level === 'levels') { | ||
console.error( | ||
'File named levels.txt is not allowed, please rename file.' | ||
); | ||
process.exit(1); | ||
} | ||
const audioPath = existingAudio.get(levelText); | ||
if (audioPath) { | ||
seenAudio.add(audioPath); | ||
adHoc[level] = [levelText, audioPath]; | ||
} else { | ||
console.error(`couldn't find audio for ${ | ||
JSON.stringify(levelText.replace(/(?:\r?\n)+$/, '')) | ||
}, be sure it was generated, if it was a multi-line file, | ||
it needs to be changed to a single line. | ||
`.replace(/\s+/, ' ')); | ||
process.exit(1); | ||
} | ||
} | ||
} | ||
fs.writeFileSync( | ||
outPath, | ||
JSON.stringify({ | ||
...adHoc, | ||
levels | ||
}), | ||
'utf8' | ||
); | ||
process.stdout.write([...seenAudio].join('\n') + '\n'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
'use strict'; | ||
/** | ||
* @author Matthew Carlson <mj.carlson801@gmail.com> | ||
*/ | ||
// | ||
// usage: node generate_audio_files.js lines.txt | ||
// | ||
// each line of the .txt file represents an audio clip to be generated | ||
// | ||
// e.g. to create a set of 3 audio files: | ||
// | ||
// ---- test.txt ---- | ||
// Test | ||
// this | ||
// out. | ||
// -------- | ||
// | ||
// it would create 3 audio files with "Test", "this", and "out." | ||
// | ||
// it will store the mapping of text to audio in a JSON file "text-to-mp3s.json" | ||
// | ||
// it will generate audio files of the name audio_NNN.mp3 where "NNN" is | ||
// replaced with a number | ||
// | ||
// it will not generate audio files if a mapping already exists | ||
// | ||
const fs = require("fs"); | ||
const { spawnSync } = require("child_process"); | ||
|
||
//Capture text file to use | ||
let fileToUse = process.argv[2]; | ||
if (typeof fileToUse !== "string") { | ||
console.error("Please pass in a file to parse"); | ||
process.exit(1); | ||
} | ||
|
||
//Read file and convert to strings | ||
let content = fs.readFileSync(fileToUse, "utf8").toString(); | ||
|
||
//create Map from file, but make an empty one if it fails | ||
let contentMap; | ||
try { | ||
let readJSON = fs.readFileSync("text-to-mp3s.json", "utf8").toString(); | ||
let parsedJSON = JSON.parse(readJSON); | ||
contentMap = new Map(parsedJSON); | ||
} catch (e) { | ||
contentMap = new Map(); | ||
} | ||
|
||
//read txt-to-mp3s.json and push to Map | ||
|
||
//create counter for filename | ||
let counter = contentMap.size + 1; | ||
|
||
//create function that checks if filename is already a value in the Map | ||
let mp3File; | ||
function createFilename() { | ||
mp3File = "audio_" + counter + ".mp3"; | ||
while (Array.from(contentMap.values()).includes(mp3File) || | ||
fs.existsSync(mp3File) === true | ||
) { | ||
counter++; | ||
mp3File = "audio_" + counter + ".mp3"; | ||
} | ||
} | ||
|
||
//populate Map | ||
for (let text of content.split(/\r?\n/)) { | ||
if (text.trim().length === 0) continue; | ||
//check if text already exists | ||
if (contentMap.has(text)) { | ||
// do nothing | ||
} else { | ||
//add to Map | ||
createFilename(); | ||
let result = spawnSync("aws", [ | ||
"--region", | ||
"us-east-1", | ||
"polly", | ||
"synthesize-speech", | ||
"--engine", | ||
"neural", | ||
"--language", | ||
"en-US", | ||
"--output-format", | ||
"mp3", | ||
"--voice-id", | ||
"Joanna", | ||
"--text-type", | ||
"text", | ||
"--text", | ||
text, | ||
mp3File | ||
]); | ||
console.log(result.stdout.toString()); | ||
console.log(result.stderr.toString()); | ||
if (result.status !== 0) { | ||
console.error("Failed to generate audio file"); | ||
process.exit(1); | ||
} | ||
contentMap.set(text, mp3File); | ||
} | ||
} | ||
|
||
//convert Map contents to JSON | ||
let data = JSON.stringify([...contentMap]); | ||
|
||
//update 'text-to-mp3s.json.' | ||
fs.writeFileSync("./text-to-mp3s.json", data, { encoding: "utf8" }); |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.