├── .dockerignore ├── screenshot.png ├── frontend ├── logo.png ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-150x150.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── index.js ├── browserconfig.xml ├── site.webmanifest ├── package.json ├── index.html ├── lib │ ├── WebAudioRecorderWav.min.js │ └── WebAudioRecorder.min.js ├── style.css └── components │ └── App.vue ├── .gitignore ├── public ├── site.webmanifest └── index.html ├── Cargo.toml ├── src ├── speech.rs ├── main.rs ├── data.rs ├── state.rs └── api.rs ├── Dockerfile ├── LICENSE ├── README.md ├── Cargo.lock └── data └── examples.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .parcel-cache 2 | node_modules 3 | .idea 4 | target -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/screenshot.png -------------------------------------------------------------------------------- /frontend/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/logo.png -------------------------------------------------------------------------------- /frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/favicon.ico -------------------------------------------------------------------------------- /frontend/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/mstile-150x150.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .parcel-cache 2 | node_modules 3 | fly.toml 4 | model 5 | .DS_Store 6 | target 7 | public 8 | deps -------------------------------------------------------------------------------- /frontend/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huytd/speech/HEAD/frontend/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import './style.css'; 3 | import App from './components/App.vue'; 4 | 5 | createApp(App).mount('#app') -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.8ca598aa.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.022f9e0e.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /frontend/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "speech-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | axum = { version = "0.6.20", features = ["multipart"] } 10 | rand = "0.8.5" 11 | serde = { version = "1.0.188", features = ["derive"] } 12 | serde_json = "1.0.105" 13 | tokio = { version = "1.32.0", features = ["full"] } 14 | tower = { version = "0.4.13", features = ["util"] } 15 | tower-http = { version = "0.4.4", features = ["fs"] } 16 | vosk = "0.2.0" 17 | wav = "1.0.0" 18 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "parcel build ./index.html --dist-dir ../public", 7 | "dev": "parcel watch ./index.html --dist-dir ../public", 8 | "build:docker": "parcel build ./index.html" 9 | }, 10 | "author": "Huy Tran", 11 | "license": "BSD-3-Clause", 12 | "dependencies": { 13 | "parcel": "^2.9.3", 14 | "vue": "^3.3.4" 15 | }, 16 | "devDependencies": { 17 | "@parcel/packager-raw-url": "^2.9.3", 18 | "@parcel/transformer-vue": "^2.9.3", 19 | "@parcel/transformer-webmanifest": "^2.9.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/speech.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | use vosk::{Model, Recognizer}; 3 | 4 | pub static SPEECH_MODEL: OnceLock = OnceLock::new(); 5 | 6 | pub struct SpeechEngine; 7 | 8 | impl SpeechEngine { 9 | pub fn initialize_speech_model() { 10 | let model = Model::new("./model").expect("Could not initialize Vosk model!"); 11 | SPEECH_MODEL.set(model).ok(); 12 | } 13 | 14 | pub fn create_recognizer(sample_rate: f32) -> Option { 15 | let model = SPEECH_MODEL.get()?; 16 | let mut recognizer = Recognizer::new(model, sample_rate)?; 17 | recognizer.set_max_alternatives(0); 18 | recognizer.set_words(true); 19 | Some(recognizer) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use crate::speech::SpeechEngine; 2 | use axum::Router; 3 | use tower_http::services::ServeDir; 4 | 5 | use crate::state::ServerState; 6 | 7 | mod api; 8 | mod data; 9 | mod speech; 10 | mod state; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | SpeechEngine::initialize_speech_model(); 15 | 16 | let app = Router::new() 17 | .nest_service("/", ServeDir::new("./public")) 18 | .nest("/api", api::router()) 19 | .with_state(ServerState::new()); 20 | 21 | let port = std::env::var("PORT").unwrap_or("3000".to_string()); 22 | axum::Server::bind(&format!("0.0.0.0:{port}").parse().unwrap()) 23 | .serve(app.into_make_service()) 24 | .await 25 | .unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | speech
-------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest as frontend 2 | WORKDIR /usr/src/app/ 3 | COPY ./frontend . 4 | RUN rm -rf .parcel-cache && rm -rf ./dist 5 | RUN npm install && npm run build:docker && cp -r ./lib ./dist 6 | 7 | FROM rust:latest as backend 8 | COPY ./deps/libvosk.so /usr/local/lib/libvosk.so 9 | 10 | WORKDIR /usr/src/app 11 | COPY . . 12 | # Will build and cache the binary and dependent crates in release mode 13 | RUN --mount=type=cache,target=/usr/local/cargo,from=rust:latest,source=/usr/local/cargo \ 14 | --mount=type=cache,target=target \ 15 | cargo build --release && mv ./target/release/speech-rs ./speech-rs 16 | 17 | # Runtime image 18 | FROM rust:latest 19 | 20 | # Run as "app" user 21 | RUN useradd -ms /bin/bash app 22 | 23 | USER app 24 | WORKDIR /app 25 | 26 | # Get compiled binaries from builder's cargo install directory 27 | COPY --from=backend /usr/src/app/speech-rs /app/speech-rs 28 | COPY --from=backend /usr/src/app/model /app/model 29 | COPY --from=backend /usr/local/lib/libvosk.so /usr/local/lib/libvosk.so 30 | COPY --from=frontend /usr/src/app/dist /app/public 31 | 32 | ENV LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH 33 | 34 | # Run the app 35 | EXPOSE 3000 36 | CMD ./speech-rs -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | speech 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use vosk::CompleteResultSingle; 3 | 4 | pub struct DataUtils; 5 | 6 | impl DataUtils { 7 | pub fn clean_up_word(word: &str) -> String { 8 | word.chars() 9 | .filter(|c| c.is_alphanumeric() || *c == '\'') 10 | .collect() 11 | } 12 | } 13 | 14 | #[derive(Serialize)] 15 | pub struct RandomExampleResponse { 16 | text: &'static str, 17 | pronounce: Vec<&'static str>, 18 | } 19 | 20 | impl RandomExampleResponse { 21 | pub fn new(text: &'static str, pronounce: Vec<&'static str>) -> Self { 22 | Self { text, pronounce } 23 | } 24 | } 25 | 26 | #[derive(Serialize)] 27 | pub struct Word { 28 | pub text: String, 29 | pub score: f32, 30 | pub start: f32, 31 | pub end: f32, 32 | } 33 | 34 | #[derive(Serialize)] 35 | pub struct SpeechAnalyzeResult { 36 | pub text: String, 37 | pub words: Vec, 38 | } 39 | 40 | impl SpeechAnalyzeResult { 41 | pub fn from(single: CompleteResultSingle) -> Self { 42 | let text = single.text.to_owned(); 43 | let words = single 44 | .result 45 | .iter() 46 | .map(|w| Word { 47 | text: w.word.to_owned(), 48 | score: w.conf, 49 | start: w.start, 50 | end: w.end, 51 | }) 52 | .collect(); 53 | Self { text, words } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Huy 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Speech** is an online voice recorder that checks and helps you improve your pronunciation. 2 | 3 | Try it online at: https://speech.sege.dev 4 | 5 | ![](screenshot.png) 6 | 7 | ## Why did I build this? 8 | 9 | As a non-native English speaker, I struggle with my pronunciation a lot. This is a tool I wished I had but could not find it anywhere, so I decided to build it myself. 10 | 11 | Or maybe it's just a good excuse for yet another side project. 12 | 13 | ## How it works? 14 | 15 | Under the hood, **Speech** uses [Vosk](https://alphacephei.com/vosk/) – the speech recognition toolkit, to check your voice recording and figure out what you are trying to say. 16 | 17 | The audio recording is done using the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API), and encoded into WAV format using the [WebAudioRecorder.js](https://github.com/higuma/web-audio-recorder-js) library. 18 | 19 | ## How to run it locally? 20 | 21 | First, you need to download a Vosk model at https://alphacephei.com/vosk/models and extract it to the `model` folder in the source directory. 22 | 23 | Also, you will need to have `libvosk.so` or `libvosk.dylib` in your computer's library path. You can download it from the [vosk-api](https://github.com/alphacep/vosk-api/releases/tag/v0.3.42) repository, and copy the library file to somewhere like `/usr/local/lib`. 24 | 25 | Then you are ready to run the project: 26 | 27 | ``` 28 | $ cargo run 29 | ``` 30 | 31 | The frontend will be built automatically using [Parcel](https://parceljs.org/), and the server will be started at `http://localhost:3000`. 32 | 33 | ## Acknowledge 34 | 35 | The speech score provided by this tool is based on the confidence score of each word provided by Vosk's model. 36 | 37 | The pronunciation data is provided by [The CMU Pronouncing Dictionary](http://www.speech.cs.cmu.edu/cgi-bin/cmudict). 38 | 39 | The speech examples are collected from the [Random Sentence Generator](https://randomwordgenerator.com/sentence.php) website. -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use crate::data::{DataUtils, RandomExampleResponse}; 2 | use rand::Rng; 3 | use std::collections::HashMap; 4 | 5 | const PRONUNCIATION_DATA: &str = include_str!("../data/pronunciation.txt"); 6 | const EXAMPLE_LIST: &str = include_str!("../data/examples.txt"); 7 | 8 | #[derive(Clone)] 9 | pub struct ServerState { 10 | pronounce_dict: HashMap<&'static str, &'static str>, 11 | example_list: Vec<&'static str>, 12 | } 13 | 14 | impl ServerState { 15 | pub fn new() -> Self { 16 | let mut state = ServerState { 17 | pronounce_dict: HashMap::new(), 18 | example_list: vec![], 19 | }; 20 | state.load_pronounce_dictionary(); 21 | state.load_example_list(); 22 | state 23 | } 24 | 25 | fn load_pronounce_dictionary(&mut self) { 26 | PRONUNCIATION_DATA 27 | .lines() 28 | .into_iter() 29 | .filter(|&line| !line.starts_with(&";;;") && !line.is_empty()) 30 | .for_each(|line| { 31 | if let Some((word, pronounce)) = line.split_once(" ") { 32 | self.pronounce_dict.insert(word, pronounce); 33 | } 34 | }); 35 | } 36 | 37 | fn load_example_list(&mut self) { 38 | self.example_list = EXAMPLE_LIST.lines().collect(); 39 | } 40 | 41 | pub fn lookup_pronounce(&self, word: &str) -> &'static str { 42 | let word = DataUtils::clean_up_word(word).to_uppercase(); 43 | let result = self.pronounce_dict.get(word.as_str()).unwrap_or(&""); 44 | *result 45 | } 46 | 47 | pub fn get_random_example(&self) -> RandomExampleResponse { 48 | let index = rand::thread_rng().gen_range(0..self.example_list.len()); 49 | let selected_example = self.example_list[index]; 50 | let pronounce = selected_example 51 | .split_whitespace() 52 | .map(|word| self.lookup_pronounce(word)) 53 | .collect(); 54 | RandomExampleResponse::new(selected_example, pronounce) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use crate::data::SpeechAnalyzeResult; 2 | use crate::speech::SpeechEngine; 3 | use crate::state::ServerState; 4 | use axum::body::Body; 5 | use axum::extract::{Multipart, State}; 6 | use axum::http::StatusCode; 7 | use axum::response::IntoResponse; 8 | use axum::routing::{get, post}; 9 | use axum::{Json, Router}; 10 | 11 | async fn pronounce_lookup_handler( 12 | state: State, 13 | Json(words): Json>, 14 | ) -> impl IntoResponse { 15 | let result = words 16 | .iter() 17 | .map(|word| state.lookup_pronounce(word)) 18 | .collect::>(); 19 | Json(result) 20 | } 21 | 22 | async fn get_random_example_handler(state: State) -> impl IntoResponse { 23 | Json(state.get_random_example()) 24 | } 25 | 26 | async fn speech_recognition_handler( 27 | mut multipart: Multipart, 28 | ) -> Result { 29 | if let Some(field) = multipart 30 | .next_field() 31 | .await 32 | .expect("Could not read form data") 33 | { 34 | if field.content_type().eq(&Some("audio/wav")) 35 | || field.content_type().eq(&Some("audio/x-wav")) 36 | { 37 | let buffer = field.bytes().await.expect("Could not read audio data!"); 38 | let mut cursor = std::io::Cursor::new(buffer); 39 | let reader = wav::read(&mut cursor).unwrap(); 40 | let samples = reader.1.as_sixteen().unwrap(); 41 | if let Some(mut rec) = SpeechEngine::create_recognizer(reader.0.sampling_rate as f32) { 42 | for chunk in samples.chunks(100) { 43 | rec.accept_waveform(chunk); 44 | } 45 | if let Some(single) = rec.final_result().single() { 46 | return Ok(Json(SpeechAnalyzeResult::from(single))); 47 | } 48 | } 49 | } 50 | } 51 | Err(StatusCode::INTERNAL_SERVER_ERROR) 52 | } 53 | 54 | pub fn router() -> Router { 55 | Router::new() 56 | .route("/pronounce", post(pronounce_lookup_handler)) 57 | .route("/example", get(get_random_example_handler)) 58 | .route("/analyze", post(speech_recognition_handler)) 59 | } 60 | -------------------------------------------------------------------------------- /frontend/lib/WebAudioRecorderWav.min.js: -------------------------------------------------------------------------------- 1 | (function(t){var s=Math.min,r=Math.max;var n=function(t,s,n){var r=n.length;for(var e=0;e0){encoder.encode(recBuffers.shift());var n=Date.now();if(n>e){postProgress((bufferCount-recBuffers.length)/bufferCount);e=n+options.progressInterval}}postProgress(1)}self.postMessage({command:"complete",blob:encoder.finish(options.wav.mimeType)});cleanup()}function cleanup(){encoder=recBuffers=undefined;bufferCount=0}self.onmessage=function(n){var e=n.data;switch(e.command){case"init":init(e);break;case"options":setOptions(e.options);break;case"start":start(e.bufferSize);break;case"record":record(e.buffer);break;case"finish":finish();break;case"cancel":cleanup()}};self.postMessage({command:"loaded"}); 2 | -------------------------------------------------------------------------------- /frontend/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #f7f1e3; 3 | padding: 0; 4 | margin: 0; 5 | font-size: 14px; 6 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 7 | color: #f7f1e3; 8 | display: flex; 9 | flex-direction: column; 10 | min-height: 100vh; 11 | } 12 | 13 | a { 14 | text-decoration: none; 15 | color: #474787; 16 | } 17 | 18 | a:hover { 19 | text-decoration: underline; 20 | } 21 | 22 | h2 { 23 | font-variant: small-caps; 24 | font-weight: bold; 25 | font-size: 14px; 26 | color: #f2f2ff; 27 | text-align: left; 28 | } 29 | 30 | #app { 31 | flex: 1; 32 | } 33 | 34 | .content-section { 35 | width: 100%; 36 | background: #f7f1e3; 37 | color: #84817a; 38 | } 39 | 40 | .content-section.highlight { 41 | background: #fcfcfb; 42 | } 43 | 44 | .content-section h2 { 45 | color: #84817a; 46 | } 47 | 48 | .header-section { 49 | width: 100%; 50 | background: #474787; 51 | } 52 | 53 | .content { 54 | padding: 20px; 55 | margin: 0 auto; 56 | max-width: 640px; 57 | } 58 | 59 | .header-section h1 { 60 | margin: 0; 61 | padding: 0; 62 | } 63 | 64 | .app-section { 65 | width: 100%; 66 | background: #706fd3; 67 | padding: 20px 0 0 0; 68 | } 69 | 70 | .btn { 71 | display: flex; 72 | align-items: center; 73 | justify-content: center; 74 | padding: 8px 16px; 75 | border: none; 76 | border-radius: 6px; 77 | font-size: 14px; 78 | background: #40407a; 79 | border-bottom: 2px solid #2c2c54; 80 | color: #f7f1e3; 81 | cursor: pointer; 82 | } 83 | 84 | .btn.inline { 85 | display: inline-block; 86 | padding: 4px 8px; 87 | } 88 | 89 | .btn:hover { 90 | background: #474787; 91 | border-color: #40407a; 92 | color: #fff; 93 | } 94 | 95 | .btn svg { 96 | margin-right: 4px; 97 | } 98 | 99 | .controllers { 100 | display: flex; 101 | align-content: center; 102 | justify-content: center; 103 | gap: 10px; 104 | padding: 10px; 105 | margin-top: 10px; 106 | } 107 | 108 | .content-box { 109 | padding: 20px 16px; 110 | display: flex; 111 | flex-wrap: wrap; 112 | background: #f2f2ff; 113 | justify-content: center; 114 | border-radius: 6px; 115 | color: #2c2c54; 116 | } 117 | 118 | .word { 119 | padding: 4px 8px; 120 | border: none; 121 | border-bottom: 1px solid #FFF; 122 | border-radius: 6px; 123 | color: #FFF; 124 | margin-right: 3px; 125 | margin-bottom: 3px; 126 | cursor: pointer; 127 | font-size: 16px; 128 | } 129 | 130 | .word .score { 131 | font-size: 0.75em; 132 | opacity: 0.55; 133 | } 134 | 135 | .word:hover { 136 | opacity: 0.9; 137 | } 138 | 139 | .word.good { 140 | background: #20bf6b; 141 | } 142 | 143 | .word.average { 144 | background: #f7b731; 145 | } 146 | 147 | .word.poor { 148 | background: #fa8231; 149 | } 150 | 151 | .word.bad { 152 | background: #eb3b5a; 153 | } 154 | 155 | .word-lookup input { 156 | border: 1px solid #84817a; 157 | border-radius: 6px; 158 | margin-right: 6px; 159 | padding: 4px 8px; 160 | } 161 | 162 | .word-lookup .result { 163 | color: #ff793f; 164 | padding-top: 4px; 165 | } 166 | 167 | .footer { 168 | background: #d1ccc0; 169 | color: #84817a; 170 | text-align: center; 171 | } 172 | -------------------------------------------------------------------------------- /frontend/lib/WebAudioRecorder.min.js: -------------------------------------------------------------------------------- 1 | (function(n){var e=function(){var i=arguments[0],t=[].slice.call(arguments,1);for(var n=0;n 2 | import { ref, reactive } from 'vue'; 3 | 4 | let stream = null; 5 | let recorder = null; 6 | let audioContext = null; 7 | let input = null; 8 | 9 | async function getRandomQuote() { 10 | const res = await fetch("/api/example"); 11 | return await res.json(); 12 | } 13 | 14 | const isRecording = ref(false); 15 | const isAnalyzing = ref(false); 16 | const words = ref([]); 17 | const currentAudioBlob = ref(null); 18 | const randomQuote = reactive({ 19 | text: "", 20 | pronounce: "" 21 | }); 22 | const score = ref(0); 23 | const lookupWord = ref(""); 24 | const lookupWordResult = ref(""); 25 | const improvementList = ref([]); 26 | 27 | (async () => { 28 | let { text, pronounce } = await getRandomQuote(); 29 | randomQuote.text = text; 30 | randomQuote.pronounce = pronounce.join(" ¦ "); 31 | })(); 32 | 33 | async function startRecord() { 34 | stream = await navigator.mediaDevices.getUserMedia({ 35 | video: false, 36 | audio: { 37 | channelCount: 1, 38 | sampleRate: 48000 39 | } 40 | }); 41 | 42 | audioContext = new (window.AudioContext || window.webkitAudioContext)(); 43 | input = audioContext.createMediaStreamSource(stream); 44 | 45 | recorder = new WebAudioRecorder(input, { 46 | workerDir: "/lib/", // location of the WebAudioRecorderWorker.js file 47 | encoding: "wav", // record in WAV format 48 | numChannels: 1, // stereo recording 49 | }); 50 | 51 | /** 52 | * @param recorder 53 | * @param blob {Blob} 54 | * @returns {Promise} 55 | */ 56 | recorder.onComplete = async function(recorder, blob) { 57 | recorder = null; 58 | stream = null; 59 | 60 | currentAudioBlob.value = blob; 61 | isAnalyzing.value = true; 62 | 63 | const formData = new FormData(); 64 | formData.append('audio', blob); 65 | 66 | const res = await fetch('/api/analyze', { 67 | method: 'POST', 68 | body: formData 69 | }); 70 | 71 | const result = await res.json(); 72 | const allWords = result.words ?? []; 73 | // TODO: error handling 74 | words.value = allWords; 75 | score.value = allWords.reduce((total, word) => total + word.score, 0) * 100 / allWords.length; 76 | isAnalyzing.value = false; 77 | 78 | const poorWords = [...new Set(allWords.filter(word => word.score < 0.8).map(word => word.text))]; 79 | if (poorWords.length) { 80 | const res = await fetch("/api/pronounce", { 81 | method: "POST", 82 | headers: { 83 | "content-type": "application/json" 84 | }, 85 | body: JSON.stringify(poorWords) 86 | }); 87 | const response = await res.json(); 88 | improvementList.value = response.map((pron, i) => ({ 89 | word: poorWords[i], 90 | pronounce: pron 91 | })); 92 | } 93 | } 94 | 95 | recorder.startRecording(); 96 | isRecording.value = true; 97 | } 98 | 99 | async function stopRecord() { 100 | stream.getAudioTracks()[0].stop(); 101 | recorder.finishRecording(); 102 | isRecording.value = false; 103 | } 104 | 105 | function playWord(word) { 106 | const audioUrl = window.URL.createObjectURL(currentAudioBlob.value); 107 | const audio = new Audio(audioUrl + `#t=${word.start},${word.end}`); 108 | audio.play(); 109 | } 110 | 111 | function playFullAudio() { 112 | const audioUrl = window.URL.createObjectURL(currentAudioBlob.value); 113 | const audio = new Audio(audioUrl); 114 | audio.play(); 115 | } 116 | 117 | async function startLookup() { 118 | const res = await fetch("/api/pronounce", { 119 | method: "POST", 120 | headers: { 121 | "content-type": "application/json" 122 | }, 123 | body: JSON.stringify([lookupWord.value]) 124 | }); 125 | const response = await res.json(); 126 | lookupWordResult.value = response[0]; 127 | } 128 | 129 | 130 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async-trait" 22 | version = "0.1.73" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "axum" 39 | version = "0.6.20" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" 42 | dependencies = [ 43 | "async-trait", 44 | "axum-core", 45 | "bitflags 1.3.2", 46 | "bytes", 47 | "futures-util", 48 | "http", 49 | "http-body", 50 | "hyper", 51 | "itoa", 52 | "matchit", 53 | "memchr", 54 | "mime", 55 | "multer", 56 | "percent-encoding", 57 | "pin-project-lite", 58 | "rustversion", 59 | "serde", 60 | "serde_json", 61 | "serde_path_to_error", 62 | "serde_urlencoded", 63 | "sync_wrapper", 64 | "tokio", 65 | "tower", 66 | "tower-layer", 67 | "tower-service", 68 | ] 69 | 70 | [[package]] 71 | name = "axum-core" 72 | version = "0.3.4" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" 75 | dependencies = [ 76 | "async-trait", 77 | "bytes", 78 | "futures-util", 79 | "http", 80 | "http-body", 81 | "mime", 82 | "rustversion", 83 | "tower-layer", 84 | "tower-service", 85 | ] 86 | 87 | [[package]] 88 | name = "backtrace" 89 | version = "0.3.69" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 92 | dependencies = [ 93 | "addr2line", 94 | "cc", 95 | "cfg-if", 96 | "libc", 97 | "miniz_oxide", 98 | "object", 99 | "rustc-demangle", 100 | ] 101 | 102 | [[package]] 103 | name = "bitflags" 104 | version = "1.3.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 107 | 108 | [[package]] 109 | name = "bitflags" 110 | version = "2.4.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 113 | 114 | [[package]] 115 | name = "bytes" 116 | version = "1.5.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 119 | 120 | [[package]] 121 | name = "cc" 122 | version = "1.0.83" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 125 | dependencies = [ 126 | "libc", 127 | ] 128 | 129 | [[package]] 130 | name = "cfg-if" 131 | version = "1.0.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 134 | 135 | [[package]] 136 | name = "encoding_rs" 137 | version = "0.8.33" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 140 | dependencies = [ 141 | "cfg-if", 142 | ] 143 | 144 | [[package]] 145 | name = "fnv" 146 | version = "1.0.7" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 149 | 150 | [[package]] 151 | name = "form_urlencoded" 152 | version = "1.2.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 155 | dependencies = [ 156 | "percent-encoding", 157 | ] 158 | 159 | [[package]] 160 | name = "futures-channel" 161 | version = "0.3.28" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 164 | dependencies = [ 165 | "futures-core", 166 | ] 167 | 168 | [[package]] 169 | name = "futures-core" 170 | version = "0.3.28" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 173 | 174 | [[package]] 175 | name = "futures-sink" 176 | version = "0.3.28" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 179 | 180 | [[package]] 181 | name = "futures-task" 182 | version = "0.3.28" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 185 | 186 | [[package]] 187 | name = "futures-util" 188 | version = "0.3.28" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 191 | dependencies = [ 192 | "futures-core", 193 | "futures-task", 194 | "pin-project-lite", 195 | "pin-utils", 196 | ] 197 | 198 | [[package]] 199 | name = "getrandom" 200 | version = "0.2.10" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 203 | dependencies = [ 204 | "cfg-if", 205 | "libc", 206 | "wasi", 207 | ] 208 | 209 | [[package]] 210 | name = "gimli" 211 | version = "0.28.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 214 | 215 | [[package]] 216 | name = "hermit-abi" 217 | version = "0.3.2" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 220 | 221 | [[package]] 222 | name = "http" 223 | version = "0.2.9" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 226 | dependencies = [ 227 | "bytes", 228 | "fnv", 229 | "itoa", 230 | ] 231 | 232 | [[package]] 233 | name = "http-body" 234 | version = "0.4.5" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 237 | dependencies = [ 238 | "bytes", 239 | "http", 240 | "pin-project-lite", 241 | ] 242 | 243 | [[package]] 244 | name = "http-range-header" 245 | version = "0.3.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" 248 | 249 | [[package]] 250 | name = "httparse" 251 | version = "1.8.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 254 | 255 | [[package]] 256 | name = "httpdate" 257 | version = "1.0.3" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 260 | 261 | [[package]] 262 | name = "hyper" 263 | version = "0.14.27" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 266 | dependencies = [ 267 | "bytes", 268 | "futures-channel", 269 | "futures-core", 270 | "futures-util", 271 | "http", 272 | "http-body", 273 | "httparse", 274 | "httpdate", 275 | "itoa", 276 | "pin-project-lite", 277 | "socket2 0.4.9", 278 | "tokio", 279 | "tower-service", 280 | "tracing", 281 | "want", 282 | ] 283 | 284 | [[package]] 285 | name = "itoa" 286 | version = "1.0.9" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 289 | 290 | [[package]] 291 | name = "libc" 292 | version = "0.2.147" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 295 | 296 | [[package]] 297 | name = "lock_api" 298 | version = "0.4.10" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 301 | dependencies = [ 302 | "autocfg", 303 | "scopeguard", 304 | ] 305 | 306 | [[package]] 307 | name = "log" 308 | version = "0.4.20" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 311 | 312 | [[package]] 313 | name = "matchit" 314 | version = "0.7.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" 317 | 318 | [[package]] 319 | name = "memchr" 320 | version = "2.6.3" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 323 | 324 | [[package]] 325 | name = "mime" 326 | version = "0.3.17" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 329 | 330 | [[package]] 331 | name = "mime_guess" 332 | version = "2.0.4" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 335 | dependencies = [ 336 | "mime", 337 | "unicase", 338 | ] 339 | 340 | [[package]] 341 | name = "miniz_oxide" 342 | version = "0.7.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 345 | dependencies = [ 346 | "adler", 347 | ] 348 | 349 | [[package]] 350 | name = "mio" 351 | version = "0.8.8" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 354 | dependencies = [ 355 | "libc", 356 | "wasi", 357 | "windows-sys", 358 | ] 359 | 360 | [[package]] 361 | name = "multer" 362 | version = "2.1.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 365 | dependencies = [ 366 | "bytes", 367 | "encoding_rs", 368 | "futures-util", 369 | "http", 370 | "httparse", 371 | "log", 372 | "memchr", 373 | "mime", 374 | "spin", 375 | "version_check", 376 | ] 377 | 378 | [[package]] 379 | name = "num_cpus" 380 | version = "1.16.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 383 | dependencies = [ 384 | "hermit-abi", 385 | "libc", 386 | ] 387 | 388 | [[package]] 389 | name = "object" 390 | version = "0.32.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 393 | dependencies = [ 394 | "memchr", 395 | ] 396 | 397 | [[package]] 398 | name = "once_cell" 399 | version = "1.18.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 402 | 403 | [[package]] 404 | name = "parking_lot" 405 | version = "0.12.1" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 408 | dependencies = [ 409 | "lock_api", 410 | "parking_lot_core", 411 | ] 412 | 413 | [[package]] 414 | name = "parking_lot_core" 415 | version = "0.9.8" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 418 | dependencies = [ 419 | "cfg-if", 420 | "libc", 421 | "redox_syscall", 422 | "smallvec", 423 | "windows-targets", 424 | ] 425 | 426 | [[package]] 427 | name = "percent-encoding" 428 | version = "2.3.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 431 | 432 | [[package]] 433 | name = "pin-project" 434 | version = "1.1.3" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 437 | dependencies = [ 438 | "pin-project-internal", 439 | ] 440 | 441 | [[package]] 442 | name = "pin-project-internal" 443 | version = "1.1.3" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 446 | dependencies = [ 447 | "proc-macro2", 448 | "quote", 449 | "syn", 450 | ] 451 | 452 | [[package]] 453 | name = "pin-project-lite" 454 | version = "0.2.13" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 457 | 458 | [[package]] 459 | name = "pin-utils" 460 | version = "0.1.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 463 | 464 | [[package]] 465 | name = "ppv-lite86" 466 | version = "0.2.17" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 469 | 470 | [[package]] 471 | name = "proc-macro2" 472 | version = "1.0.66" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 475 | dependencies = [ 476 | "unicode-ident", 477 | ] 478 | 479 | [[package]] 480 | name = "quote" 481 | version = "1.0.33" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 484 | dependencies = [ 485 | "proc-macro2", 486 | ] 487 | 488 | [[package]] 489 | name = "rand" 490 | version = "0.8.5" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 493 | dependencies = [ 494 | "libc", 495 | "rand_chacha", 496 | "rand_core", 497 | ] 498 | 499 | [[package]] 500 | name = "rand_chacha" 501 | version = "0.3.1" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 504 | dependencies = [ 505 | "ppv-lite86", 506 | "rand_core", 507 | ] 508 | 509 | [[package]] 510 | name = "rand_core" 511 | version = "0.6.4" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 514 | dependencies = [ 515 | "getrandom", 516 | ] 517 | 518 | [[package]] 519 | name = "redox_syscall" 520 | version = "0.3.5" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 523 | dependencies = [ 524 | "bitflags 1.3.2", 525 | ] 526 | 527 | [[package]] 528 | name = "riff" 529 | version = "1.0.1" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" 532 | 533 | [[package]] 534 | name = "rustc-demangle" 535 | version = "0.1.23" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 538 | 539 | [[package]] 540 | name = "rustversion" 541 | version = "1.0.14" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 544 | 545 | [[package]] 546 | name = "ryu" 547 | version = "1.0.15" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 550 | 551 | [[package]] 552 | name = "scopeguard" 553 | version = "1.2.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 556 | 557 | [[package]] 558 | name = "serde" 559 | version = "1.0.188" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 562 | dependencies = [ 563 | "serde_derive", 564 | ] 565 | 566 | [[package]] 567 | name = "serde_derive" 568 | version = "1.0.188" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 571 | dependencies = [ 572 | "proc-macro2", 573 | "quote", 574 | "syn", 575 | ] 576 | 577 | [[package]] 578 | name = "serde_json" 579 | version = "1.0.105" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 582 | dependencies = [ 583 | "itoa", 584 | "ryu", 585 | "serde", 586 | ] 587 | 588 | [[package]] 589 | name = "serde_path_to_error" 590 | version = "0.1.14" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" 593 | dependencies = [ 594 | "itoa", 595 | "serde", 596 | ] 597 | 598 | [[package]] 599 | name = "serde_urlencoded" 600 | version = "0.7.1" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 603 | dependencies = [ 604 | "form_urlencoded", 605 | "itoa", 606 | "ryu", 607 | "serde", 608 | ] 609 | 610 | [[package]] 611 | name = "signal-hook-registry" 612 | version = "1.4.1" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 615 | dependencies = [ 616 | "libc", 617 | ] 618 | 619 | [[package]] 620 | name = "smallvec" 621 | version = "1.11.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 624 | 625 | [[package]] 626 | name = "socket2" 627 | version = "0.4.9" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 630 | dependencies = [ 631 | "libc", 632 | "winapi", 633 | ] 634 | 635 | [[package]] 636 | name = "socket2" 637 | version = "0.5.3" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" 640 | dependencies = [ 641 | "libc", 642 | "windows-sys", 643 | ] 644 | 645 | [[package]] 646 | name = "speech-rs" 647 | version = "0.1.0" 648 | dependencies = [ 649 | "axum", 650 | "rand", 651 | "serde", 652 | "serde_json", 653 | "tokio", 654 | "tower", 655 | "tower-http", 656 | "vosk", 657 | "wav", 658 | ] 659 | 660 | [[package]] 661 | name = "spin" 662 | version = "0.9.8" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 665 | 666 | [[package]] 667 | name = "syn" 668 | version = "2.0.31" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" 671 | dependencies = [ 672 | "proc-macro2", 673 | "quote", 674 | "unicode-ident", 675 | ] 676 | 677 | [[package]] 678 | name = "sync_wrapper" 679 | version = "0.1.2" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 682 | 683 | [[package]] 684 | name = "tokio" 685 | version = "1.32.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 688 | dependencies = [ 689 | "backtrace", 690 | "bytes", 691 | "libc", 692 | "mio", 693 | "num_cpus", 694 | "parking_lot", 695 | "pin-project-lite", 696 | "signal-hook-registry", 697 | "socket2 0.5.3", 698 | "tokio-macros", 699 | "windows-sys", 700 | ] 701 | 702 | [[package]] 703 | name = "tokio-macros" 704 | version = "2.1.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 707 | dependencies = [ 708 | "proc-macro2", 709 | "quote", 710 | "syn", 711 | ] 712 | 713 | [[package]] 714 | name = "tokio-util" 715 | version = "0.7.8" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 718 | dependencies = [ 719 | "bytes", 720 | "futures-core", 721 | "futures-sink", 722 | "pin-project-lite", 723 | "tokio", 724 | ] 725 | 726 | [[package]] 727 | name = "tower" 728 | version = "0.4.13" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 731 | dependencies = [ 732 | "futures-core", 733 | "futures-util", 734 | "pin-project", 735 | "pin-project-lite", 736 | "tokio", 737 | "tower-layer", 738 | "tower-service", 739 | "tracing", 740 | ] 741 | 742 | [[package]] 743 | name = "tower-http" 744 | version = "0.4.4" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" 747 | dependencies = [ 748 | "bitflags 2.4.0", 749 | "bytes", 750 | "futures-core", 751 | "futures-util", 752 | "http", 753 | "http-body", 754 | "http-range-header", 755 | "httpdate", 756 | "mime", 757 | "mime_guess", 758 | "percent-encoding", 759 | "pin-project-lite", 760 | "tokio", 761 | "tokio-util", 762 | "tower-layer", 763 | "tower-service", 764 | "tracing", 765 | ] 766 | 767 | [[package]] 768 | name = "tower-layer" 769 | version = "0.3.2" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 772 | 773 | [[package]] 774 | name = "tower-service" 775 | version = "0.3.2" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 778 | 779 | [[package]] 780 | name = "tracing" 781 | version = "0.1.37" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 784 | dependencies = [ 785 | "cfg-if", 786 | "log", 787 | "pin-project-lite", 788 | "tracing-core", 789 | ] 790 | 791 | [[package]] 792 | name = "tracing-core" 793 | version = "0.1.31" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 796 | dependencies = [ 797 | "once_cell", 798 | ] 799 | 800 | [[package]] 801 | name = "try-lock" 802 | version = "0.2.4" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 805 | 806 | [[package]] 807 | name = "unicase" 808 | version = "2.7.0" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 811 | dependencies = [ 812 | "version_check", 813 | ] 814 | 815 | [[package]] 816 | name = "unicode-ident" 817 | version = "1.0.11" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 820 | 821 | [[package]] 822 | name = "version_check" 823 | version = "0.9.4" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 826 | 827 | [[package]] 828 | name = "vosk" 829 | version = "0.2.0" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "3e24b8dd868df76aea46929b460640896b170d64eefffcd773084fdd4fcd67ec" 832 | dependencies = [ 833 | "serde", 834 | "serde_json", 835 | "vosk-sys", 836 | ] 837 | 838 | [[package]] 839 | name = "vosk-sys" 840 | version = "0.1.1" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "4119af93ca109482d71c6c01511a88c93131fcfdcf4ab4f5f1d8e4e4f5099d29" 843 | 844 | [[package]] 845 | name = "want" 846 | version = "0.3.1" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 849 | dependencies = [ 850 | "try-lock", 851 | ] 852 | 853 | [[package]] 854 | name = "wasi" 855 | version = "0.11.0+wasi-snapshot-preview1" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 858 | 859 | [[package]] 860 | name = "wav" 861 | version = "1.0.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" 864 | dependencies = [ 865 | "riff", 866 | ] 867 | 868 | [[package]] 869 | name = "winapi" 870 | version = "0.3.9" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 873 | dependencies = [ 874 | "winapi-i686-pc-windows-gnu", 875 | "winapi-x86_64-pc-windows-gnu", 876 | ] 877 | 878 | [[package]] 879 | name = "winapi-i686-pc-windows-gnu" 880 | version = "0.4.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 883 | 884 | [[package]] 885 | name = "winapi-x86_64-pc-windows-gnu" 886 | version = "0.4.0" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 889 | 890 | [[package]] 891 | name = "windows-sys" 892 | version = "0.48.0" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 895 | dependencies = [ 896 | "windows-targets", 897 | ] 898 | 899 | [[package]] 900 | name = "windows-targets" 901 | version = "0.48.5" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 904 | dependencies = [ 905 | "windows_aarch64_gnullvm", 906 | "windows_aarch64_msvc", 907 | "windows_i686_gnu", 908 | "windows_i686_msvc", 909 | "windows_x86_64_gnu", 910 | "windows_x86_64_gnullvm", 911 | "windows_x86_64_msvc", 912 | ] 913 | 914 | [[package]] 915 | name = "windows_aarch64_gnullvm" 916 | version = "0.48.5" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 919 | 920 | [[package]] 921 | name = "windows_aarch64_msvc" 922 | version = "0.48.5" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 925 | 926 | [[package]] 927 | name = "windows_i686_gnu" 928 | version = "0.48.5" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 931 | 932 | [[package]] 933 | name = "windows_i686_msvc" 934 | version = "0.48.5" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 937 | 938 | [[package]] 939 | name = "windows_x86_64_gnu" 940 | version = "0.48.5" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 943 | 944 | [[package]] 945 | name = "windows_x86_64_gnullvm" 946 | version = "0.48.5" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 949 | 950 | [[package]] 951 | name = "windows_x86_64_msvc" 952 | version = "0.48.5" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 955 | -------------------------------------------------------------------------------- /data/examples.txt: -------------------------------------------------------------------------------- 1 | The quick brown fox jumps over the lazy dog. 2 | My Mum tries to be cool by saying that she likes all the same things that I do. 3 | A purple pig and a green donkey flew a kite in the middle of the night and ended up sunburnt. 4 | Last Friday I saw a spotted striped blue worm shake hands with a legless lizard. 5 | A song can make or ruin a person's day if they let it get to them. 6 | Sometimes it is better to just walk away from things and go back to them later when you're in a better frame of mind. 7 | Writing a list of random sentences is harder than I initially thought it would be. 8 | Lets all be unique together until we realise we are all the same. 9 | If I don't like something, I'll stay away from it. 10 | I love eating toasted cheese and tuna sandwiches. 11 | If you like tuna and tomato sauce, try combining the two, it's really not as bad as it sounds. 12 | Someone I know recently combined Maple Syrup & buttered Popcorn thinking it would taste like caramel popcorn. It didn't and they don't recommend anyone else do it either. 13 | Sometimes, all you need to do is completely make an ass of yourself and laugh it off to realise that life isn't so bad after all. 14 | When I was little I had a car door slammed shut on my hand and I still remember it quite vividly. 15 | The clock within this blog and the clock on my laptop are 1 hour different from each other. 16 | I want to buy a onesie… but know it won't suit me. 17 | I was very proud of my nickname throughout high school but today- I couldn't be any different to what my nickname was. 18 | I currently have 4 windows open up… and I don't know why. 19 | I often see the time 11:11 or 12:34 on clocks. 20 | This is the last random sentence I will be writing and I am going to stop mid-sent 21 | We need to rent a room for our party. 22 | Yeah, I think it's a good environment for learning English. 23 | The lake is a long way from here. 24 | This is a Japanese doll. 25 | I hear that Nancy is very pretty. 26 | She was too short to see over the fence. 27 | He told us a very exciting adventure story. 28 | She always speaks to him in a loud voice. 29 | I want more detailed information. 30 | I checked to make sure that he was still alive. 31 | I'd rather be a bird than a fish. 32 | Mary plays the piano. 33 | She did her best to help him. 34 | She borrowed the book from him many years ago and hasn't yet returned it. 35 | She wrote him a long letter, but he didn't read it. 36 | Please wait outside of the house. 37 | Two seats were vacant. 38 | Tom got a small piece of pie. 39 | She folded her handkerchief neatly. 40 | We have a lot of rain in June. 41 | I am never at home on Sundays. 42 | Don't step on the broken glass. 43 | She advised him to come back at once. 44 | Let me help you with your baggage. 45 | The book is in front of the table. 46 | The mysterious diary records the voice. 47 | The stranger officiates the meal. 48 | The shooter says goodbye to his love. 49 | A glittering gem is not enough. 50 | The memory we used to share is no longer coherent. 51 | The old apple revels in its authority. 52 | Rock music approaches at high velocity. 53 | Sixty-Four comes asking for bread. 54 | Abstraction is often one floor above you. 55 | The river stole the gods. 56 | Joe made the sugar cookies; Susan decorated them. 57 | The sky is clear; the stars are twinkling. 58 | The waves were crashing on the shore; it was a lovely sight. 59 | There were white out conditions in the town; subsequently, the roads were impassable. 60 | Check back tomorrow; I will see if the book has arrived. 61 | He said he was not there yesterday; however, many people saw him there. 62 | I am happy to take your donation; any amount will be greatly appreciated. 63 | She only paints with bold colors; she does not like pastels. 64 | She works two jobs to make ends meet; at least, that was her reason for not having time to join us. 65 | Malls are great places to shop; I can find everything I need under one roof. 66 | Italy is my favorite country; in fact, I plan to spend two weeks there next year. 67 | He turned in the research paper on Friday; otherwise, he would have not passed the class. 68 | She did not cheat on the test, for it was not the right thing to do. 69 | I think I will buy the red car, or I will lease the blue one. 70 | I really want to go to work, but I am too sick to drive. 71 | I am counting my calories, yet I really want dessert. 72 | He ran out of money, so he had to stop playing poker. 73 | They got there early, and they got really good seats. 74 | There was no ice cream in the freezer, nor did they have money to go to the store. 75 | Everyone was busy, so I went to the movie alone. 76 | I would have gotten the promotion, but my attendance wasn't good enough. 77 | It was getting dark, and we weren't there yet. 78 | Cats are good pets, for they are clean and are not noisy. 79 | We have never been to Asia, nor have we visited Africa. 80 | He didn't want to go to the dentist, yet he went anyway. 81 | Sometimes I stare at a door or a wall and I wonder what is this reality, why am I alive, and what is this all about? 82 | All you need to do is pick up the pen and begin. 83 | Had he known what was going to happen, he would have never stepped into the shower. 84 | She hadn't had her cup of coffee, and that made things all the worse. 85 | All she wanted was the answer, but she had no idea how much she would hate it. 86 | It must be five o'clock somewhere. 87 | He went back to the video to see what had been recorded and was shocked at what he saw. 88 | Behind the window was a reflection that only instilled fear. 89 | That was how he came to win $1 million. 90 | She could hear him in the shower singing with a joy she hoped he'd retain after she delivered the news. 91 | As he looked out the window, he saw a clown walk by. 92 | There should have been a time and a place, but this wasn't it. 93 | It was the best sandcastle he had ever seen. 94 | There's an art to getting your way, and spitting olive pits across the table isn't it. 95 | They say that dogs are man's best friend, but this cat was setting out to sabotage that theory. 96 | Of course, she loves her pink bunny slippers. 97 | Don't put peanut butter on the dog's nose. 98 | She can live her life however she wants as long as she listens to what I have to say. 99 | There are few things better in life than a slice of pie. 100 | Not all people who wander are lost. 101 | The knives were out and she was sharpening hers. 102 | She felt that chill that makes the hairs on the back of your neck when he walked into the room. 103 | Don't piss in my garden and tell me you're trying to help my plants grow. 104 | He found the end of the rainbow and was surprised at what he found there. 105 | Two more days and all his problems would be solved. 106 | Love is not like pizza. 107 | There's a reason that roses have thorns. 108 | He looked behind the door and didn't like what he saw. 109 | He fumbled in the darkness looking for the light switch, but when he finally found it there was someone already there. 110 | He barked orders at his daughters but they just stared back with amusement. 111 | He had a hidden stash underneath the floorboards in the back room of the house. 112 | Nobody loves a pig wearing lipstick. 113 | They called out her name time and again, but were met with nothing but silence. 114 | He had accidentally hacked into his company's server. 115 | You can't compare apples and oranges, but what about bananas and plantains? 116 | It doesn't sound like that will ever be on my travel list. 117 | He swore he just saw his sushi move. 118 | He had reached the point where he was paranoid about being paranoid. 119 | The sun had set and so had his dreams. 120 | He learned the important lesson that a picnic at the beach on a windy day is a bad idea. 121 | Be careful with that butter knife. 122 | He didn't heed the warning and it had turned out surprisingly well. 123 | It was a slippery slope and he was willing to slide all the way to the deepest depths. 124 | He would only survive if he kept the fire going and he could hear thunder in the distance. 125 | His confidence would have bee admirable if it wasn't for his stupidity. 126 | She let the balloon float up into the air with her hopes and dreams. 127 | his seven-layer cake only had six layers. 128 | There was coal in his stocking and he was thrilled. 129 | The rusty nail stood erect, angled at a 45-degree angle, just waiting for the perfect barefoot to come along. 130 | So long and thanks for the fish. 131 | The random sentence generator generated a random sentence about a random sentence. 132 | She says she has the ability to hear the soundtrack of your life. 133 | It turns out you don't need all that stuff you insisted you did. 134 | The irony of the situation wasn't lost on anyone in the room. 135 | The truth is that you pay for your lifestyle in hours. 136 | The light in his life was actually a fire burning all around him. 137 | Three generations with six decades of life experience. 138 | Nothing seemed out of place except the washing machine in the bar. 139 | He decided to live his life by the big beats manifesto. 140 | The secret ingredient to his wonderful life was crime. 141 | He was sitting in a trash can with high street class. 142 | The fact that there's a stairway to heaven and a highway to hell explains life well. 143 | I met an interesting turtle while the song on the radio blasted away. 144 | At that moment he wasn't listening to music, he was living an experience. 145 | This made him feel like an old-style rootbeer float smells. 146 | In the end, he realized he could see sound and hear words. 147 | The fish listened intently to what the frogs had to say. 148 | He stepped gingerly onto the bridge knowing that enchantment awaited on the other side. 149 | He learned the hardest lesson of his life and had the scars, both physical and mental, to prove it. 150 | At that moment she realized she had a sixth sense. 151 | He wondered why at 18 he was old enough to go to war, but not old enough to buy cigarettes. 152 | She saw no irony asking me to change but wanting me to accept her for who she is. 153 | The fox in the tophat whispered into the ear of the rabbit. 154 | There can never be too many cherries on an ice cream sundae. 155 | The minute she landed she understood the reason this was a fly-over state. 156 | I just wanted to tell you I could see the love you have for your child by the way you look at her. 157 | Everyone says they love nature until they realize how dangerous she can be. 158 | He was willing to find the depths of the rabbit hole in order to be with her. 159 | She wore green lipstick like a fashion icon. 160 | Never underestimate the willingness of the greedy to throw you under the bus. 161 | She finally understood that grief was her love with no place for it to go. 162 | Each person who knows you has a different perception of who you are. 163 | The beauty of the African sunset disguised the danger lurking nearby. 164 | They ran around the corner to find that they had traveled back in time. 165 | 25 years later, she still regretted that specific moment. 166 | In that instant, everything changed. 167 | He hated that he loved what she hated about hate. 168 | Random words in front of other random words create a random sentence. 169 | The body piercing didn't go exactly as he expected. 170 | The pigs were insulted that they were named hamburgers. 171 | It caught him off guard that space smelled of seared steak. 172 | As he waited for the shower to warm, he noticed that he could hear water change temperature. 173 | He took one look at what was under the table and noped the hell out of there. 174 | She found his complete dullness interesting. 175 | The efficiency we have at removing trash has made creating trash more acceptable. 176 | The opportunity of a lifetime passed before him as he tried to decide between a cone or a cup. 177 | One small action would change her life, but whether it would be for better or for worse was yet to be determined. 178 | It was the scarcity that fueled his creativity. 179 | Flesh-colored yoga pants were far worse than even he feared. 180 | He was 100% into fasting with her until he understood that meant he couldn't eat. 181 | Tuesdays are free if you bring a gnome costume. 182 | If any cop asks you where you were, just say you were visiting Kansas. 183 | The crowd yells and screams for more memes. 184 | They throw cabbage that turns your brain into emotional baggage. 185 | Potato wedges probably are not best for relationships. 186 | It would have been a better night if the guys next to us weren't in the splash zone. 187 | I caught my squirrel rustling through my gym bag. 188 | You've been eyeing me all day and waiting for your move like a lion stalking a gazelle in a savannah. 189 | It's not often you find a soggy banana on the street. 190 | As you consider all the possible ways to improve yourself and the world, you notice John Travolta seems fairly unhappy. 191 | I purchased a baby clown from the Russian terrorist black market. 192 | You realize you're not alone as you sit in your bedroom massaging your calves after a long day of playing tug-of-war with Grandpa Joe in the hospital. 193 | You're unsure whether or not to trust him, but very thankful that you wore a turtle neck. 194 | Your girlfriend bought your favorite cookie crisp cereal but forgot to get milk. 195 | He is good at eating pickles and telling women about his emotional problems. 196 | If my calculator had a history, it would be more embarrassing than my browser history. 197 | I ate a sock because people on the Internet told me to. 198 | Stop waiting for exceptional things to just happen. 199 | Choosing to do nothing is still a choice, after all. 200 | He was so preoccupied with whether or not he could that he failed to stop to consider if he should. 201 | Red is greener than purple, for sure. 202 | On a scale from one to ten, what's your favorite flavor of random grammar? 203 | He's in a boy band which doesn't make much sense for a snake. 204 | He waited for the stop sign to turn to a go sign. 205 | It's much more difficult to play tennis with a bowling ball than it is to bowl with a tennis ball. 206 | She had some amazing news to share but nobody to share it with. 207 | The green tea and avocado smoothie turned out exactly as would be expected. 208 | When nobody is around, the trees gossip about the people who have walked under them. 209 | She wanted a pet platypus but ended up getting a duck and a ferret instead. 210 | It was at that moment that he learned there are certain parts of the body that you should never Nair. 211 | He realized there had been several deaths on this road, but his concern rose when he saw the exact number. 212 | The thunderous roar of the jet overhead confirmed her worst fears. 213 | The best key lime pie is still up for debate. 214 | Various sea birds are elegant, but nothing is as elegant as a gliding pelican. 215 | Peanut butter and jelly caused the elderly lady to think about her past. 216 | He set out for a short walk, but now all he could see were mangroves and water were for miles. 217 | Everyone was curious about the large white blimp that appeared overnight. 218 | The Tsunami wave crashed against the raised houses and broke the pilings as if they were toothpicks. 219 | They wandered into a strange Tiki bar on the edge of the small beach town. 220 | The tour bus was packed with teenage girls heading toward their next adventure. 221 | Her hair was windswept as she rode in the black convertible. 222 | Lightning Paradise was the local hangout joint where the group usually ended up spending the night. 223 | The thick foliage and intertwined vines made the hike nearly impossible. 224 | As the rental car rolled to a stop on the dark road, her fear increased by the moment. 225 | The newly planted trees were held up by wooden frames in hopes they could survive the next storm. 226 | The gruff old man sat in the back of the bait shop grumbling to himself as he scooped out a handful of worms. 227 | All they could see was the blue water surrounding their sailboat. 228 | The urgent care center was flooded with patients after the news of a new deadly virus was made public. 229 | The sign said there was road work ahead so he decided to speed up. 230 | The shark-infested South Pine channel was the only way in or out. 231 | He decided water-skiing on a frozen lake wasn't a good idea. 232 | It was always dangerous to drive with him since he insisted the safety cones were a slalom course. 233 | She was the type of girl who wanted to live in a pink house. 234 | Her life in the confines of the house became her new normal. 235 | The near-death experience brought new ideas to light. 236 | They were excited to see their first sloth. 237 | The wake behind the boat told of the past while the open sea for told life in the unknown future. 238 | Even though he thought the world was flat he didn't see the irony of wanting to travel around the world. 239 | He wondered if it could be called a beach if there was no sand. 240 | Thigh-high in the water, the fisherman's hope for dinner soon turned to despair. 241 | Combines are no longer just for farms. 242 | The small white buoys marked the location of hundreds of crab pots. 243 | The snow-covered path was no help in finding his way out of the back-country. 244 | The waitress was not amused when he ordered green eggs and ham. 245 | Buried deep in the snow, he hoped his batteries were fresh in his avalanche beacon. 246 | Courage and stupidity were all he had. 247 | I don't respect anybody who can't tell the difference between Pepsi and Coke. 248 | As the years pass by, we all know owners look more and more like their dogs. 249 | In hopes of finding out the truth, he entered the one-room library. 250 | As he entered the church he could hear the soft voice of someone whispering into a cell phone. 251 | He quietly entered the museum as the super bowl started. 252 | He had concluded that pigs must be able to fly in Hog Heaven. 253 | The rain pelted the windshield as the darkness engulfed us. 254 | Beach-combing replaced wine tasting as his new obsession. 255 | The blinking lights of the antenna tower came into focus just as I heard a loud snap. 256 | People generally approve of dogs eating cat food but not cats eating dog food. 257 | Car safety systems have come a long way, but he was out to prove they could be outsmarted. 258 | They looked up at the sky and saw a million stars. 259 | She opened up her third bottle of wine of the night. 260 | With a single flip of the coin, his life changed forever. 261 | The tart lemonade quenched her thirst, but not her longing. 262 | He was the type of guy who liked Christmas lights on his house in the middle of July. 263 | Toddlers feeding raccoons surprised even the seasoned park ranger. 264 | She was disgusted he couldn't tell the difference between lemonade and limeade. 265 | Whenever he saw a red flag warning at the beach he grabbed his surfboard. 266 | Twin 4-month-olds slept in the shade of the palm tree while the mother tanned in the sun. 267 | He always wore his sunglasses at night. 268 | The three-year-old girl ran down the beach as the kite flew behind her. 269 | While on the first date he accidentally hit his head on the beam. 270 | Her scream silenced the rowdy teenagers. 271 | After exploring the abandoned building, he started to believe in ghosts. 272 | The view from the lighthouse excited even the most seasoned traveler. 273 | He wondered if she would appreciate his toenail collection. 274 | Some bathing suits just shouldn't be worn by some people. 275 | His son quipped that power bars were nothing more than adult candy bars. 276 | Pantyhose and heels are an interesting choice of attire for the beach. 277 | She did a happy dance because all of the socks from the dryer matched. 278 | She wasn't sure whether to be impressed or concerned that he folded underwear in neat little packages. 279 | When she didn't like a guy who was trying to pick her up, she started using sign language. 280 | The delicious aroma from the kitchen was ruined by cigarette smoke. 281 | Swim at your own risk was taken as a challenge for the group of Kansas City college students. 282 | She wondered what his eyes were saying beneath his mirrored sunglasses. 283 | The ants enjoyed the barbecue more than the family. 284 | They did nothing as the raccoon attacked the lady's bag of food. 285 | When he had to picnic on the beach, he purposely put sand in other people's food. 286 | The trick to getting kids to eat anything is to put catchup on it. 287 | The beauty of the sunset was obscured by the industrial cranes. 288 | He stomped on his fruit loops and thus became a cereal killer. 289 | He walked into the basement with the horror movie from the night before playing in his head. 290 | She saw the brake lights, but not in time. 291 | No matter how beautiful the sunset, it saddened her knowing she was one day older. 292 | She lived on Monkey Jungle Road and that seemed to explain all of her strangeness. 293 | When motorists sped in and out of traffic, all she could think of was those in need of a transplant. 294 | The tortoise jumped into the lake with dreams of becoming a sea turtle. 295 | For the 216th time, he said he would quit drinking soda after this last Coke. 296 | Lucifer was surprised at the amount of life at Death Valley. 297 | It had been sixteen days since the zombies first attacked. 298 | Charles ate the french fries knowing they would be his last meal. 299 | She had the gift of being able to paint songs. 300 | Carol drank the blood as if she were a vampire. 301 | He drank life before spitting it out. 302 | He dreamed of eating green apples with worms. 303 | Dolores wouldn't have eaten the meal if she had known what it actually was. 304 | Greetings from the real universe. 305 | There's a message for you if you look up. 306 | He found the chocolate covered roaches quite tasty. 307 | It dawned on her that others could make her happier, but only she could make herself happy. 308 | She tilted her head back and let whip cream stream into her mouth while taking a bath. 309 | Jeanne wished she has chosen the red button. 310 | It was her first experience training a rainbow unicorn. 311 | He uses onomatopoeia as a weapon of mental destruction. 312 | Weather is not trivial - it's especially important when you're standing in it. 313 | I may struggle with geography, but I'm sure I'm somewhere around here. 314 | It's a skateboarding penguin with a sunhat! 315 | If you don't like toenails, you probably shouldn't look at your feet. 316 | We will not allow you to bring your pet armadillo along. 317 | When he encountered maize for the first time, he thought it incredibly corny. 318 | He used to get confused between soldiers and shoulders, but as a military man, he now soldiers responsibility. 319 | Just go ahead and press that button. 320 | Going from child, to childish, to childlike is only a matter of time. 321 | My dentist tells me that chewing bricks is very bad for your teeth. 322 | He was disappointed when he found the beach to be so sandy and the sun so sunny. 323 | She is never happy until she finds something to be unhappy about; then, she is overjoyed. 324 | I would be delighted if the sea were full of cucumber juice. 325 | The busker hoped that the people passing by would throw money, but they threw tomatoes instead, so he exchanged his hat for a juicer. 326 | I love bacon, beer, birds, and baboons. 327 | I come from a tribe of head-hunters, so I will never need a shrink. 328 | You're good at English when you know the difference between a man eating chicken and a man-eating chicken. 329 | I'm confused: when people ask me what's up, and I point, they groan. 330 | The pet shop stocks everything you need to keep your anaconda happy. 331 | This book is sure to liquefy your brain. 332 | People who insist on picking their teeth with their elbows are so annoying! 333 | He enjoys practicing his ballet in the bathroom. 334 | Separation anxiety is what happens when you can't find your phone. 335 | Improve your goldfish's physical fitness by getting him a bicycle. 336 | Nudist colonies shun fig-leaf couture. 337 | A kangaroo is really just a rabbit on steroids. 338 | When transplanting seedlings, candied teapots will make the task easier. 339 | Before he moved to the inner city, he had always believed that security complexes were psychological. 340 | Pair your designer cowboy hat with scuba gear for a memorable occasion. 341 | For oil spots on the floor, nothing beats parking a motorbike in the lounge. 342 | Standing on one's head at job interviews forms a lasting impression. 343 | Every manager should be able to recite at least ten nursery rhymes backward. 344 | Truth in advertising and dinosaurs with skateboards have much in common. 345 | Nothing is as cautiously cuddly as a pet porcupine. 346 | Nobody has encountered an explosive daisy and lived to tell the tale. 347 | Iron pyrite is the most foolish of all minerals. 348 | A suit of armor provides excellent sun protection on hot days. 349 | I am my aunt's sister's daughter. 350 | Wisdom is easily acquired when hiding under the bed with a saucepan on your head. 351 | Everybody should read Chaucer to improve their everyday vocabulary. 352 | He is no James Bond; his name is Roger Moore. 353 | I like to leave work after my eight-hour tea-break. 354 | Getting up at dawn is for the birds. 355 | If eating three-egg omelets causes weight-gain, budgie eggs are a good substitute. 356 | Shakespeare was a famous 17th-century diesel mechanic. 357 | Hit me with your pet shark! 358 | The Japanese yen for commerce is still well-known. 359 | The Guinea fowl flies through the air with all the grace of a turtle. 360 | Fluffy pink unicorns are a popular status symbol among macho men. 361 | She cried diamonds. 362 | Dan took the deep dive down the rabbit hole. 363 | Erin accidentally created a new universe. 364 | The door slammed on the watermelon. 365 | Eating eggs on Thursday for choir practice was recommended. 366 | Pink horses galloped across the sea. 367 | Flying fish flew by the space station. 368 | He colored deep space a soft yellow. 369 | Dan ate the clouds like cotton candy. 370 | He found a leprechaun in his walnut shell. 371 | Bill ran from the giraffe toward the dolphin. 372 | It didn't make sense unless you had the power to eat colors. 373 | Patricia loves the sound of nails strongly pressed against the chalkboard. 374 | He didn't understand why the bird wanted to ride the bicycle. 375 | Garlic ice-cream was her favorite. 376 | Smoky the Bear secretly started the fires. 377 | Pat ordered a ghost pepper pie. 378 | You have every right to be angry, but that doesn't give you the right to be mean. 379 | Andy loved to sleep on a bed of nails. 380 | Her daily goal was to improve on yesterday. 381 | She had a habit of taking showers in lemonade. 382 | The elephant didn't want to talk about the person in the room. 383 | The skeleton had skeletons of his own in the closet. 384 | Even with the snow falling outside, she felt it appropriate to wear her bikini. 385 | He was sure the Devil created red sparkly glitter. 386 | He wore the surgical mask in public not to keep from catching a virus, but to keep people away from him. 387 | Doris enjoyed tapping her nails on the table to annoy everyone. 388 | Nobody questions who built the pyramids in Mexico. 389 | He figured a few sticks of dynamite were easier than a fishing pole to catch fish. 390 | When money was tight, he'd get his lunch money from the local wishing well. 391 | The father died during childbirth. 392 | Plans for this weekend include turning wine into water. 393 | Iguanas were falling out of the trees. 394 | I covered my friend in baby oil. 395 | Art doesn't have to be intentional. 396 | Now I need to ponder my existence and ask myself if I'm truly real 397 | We should play with legos at camp. 398 | I'm a living furnace. 399 | Please tell me you don't work in a morgue. 400 | We have young kids who often walk into our room at night for various reasons including clowns in the closet. 401 | 8% of 25 is the same as 25% of 8 and one of them is much easier to do in your head. 402 | You bite up because of your lower jaw. 403 | Most shark attacks occur about 10 feet from the beach since that's where the people are. 404 | I'm working on a sweet potato farm. 405 | David subscribes to the \"stuff your tent into the bag\" strategy over nicely folding it. 406 | He invested some skill points in Charisma and Strength. 407 | I'm a great listener, really good with empathy vs sympathy and all that, but I hate people. 408 | He excelled at firing people nicely. 409 | When I cook spaghetti, I like to boil it a few minutes past al dente so the noodles are super slippery. 410 | Joyce enjoyed eating pancakes with ketchup. 411 | She was sad to hear that fireflies are facing extinction due to artificial light, habitat loss, and pesticides. 412 | He decided that the time had come to be stronger than any of the excuses he'd used until then. 413 | The door swung open to reveal pink giraffes and red elephants. 414 | She traveled because it cost the same as therapy and was a lot more enjoyable. 415 | The paintbrush was angry at the color the artist chose to use. 416 | The bees decided to have a mutiny against their queen. 417 | His ultimate dream fantasy consisted of being content and sleeping eight hours in a row. 418 | She looked into the mirror and saw another person. 419 | There aren't enough towels in the world to stop the sewage flowing from his mouth. 420 | He had a vague sense that trees gave birth to dinosaurs. 421 | The doll spun around in circles in hopes of coming alive. 422 | Grape jelly was leaking out the hole in the roof. 423 | Chocolate covered crickets were his favorite snack. 424 | The secret code they created made no sense, even to them. 425 | Three years later, the coffin was still full of Jello. 426 | His mind was blown that there was nothing in space except space itself. 427 | They're playing the piano while flying in the plane. 428 | The hand sanitizer was actually clear glue. 429 | The lyrics of the song sounded like fingernails on a chalkboard. 430 | It took him a month to finish the meal. 431 | Seek success, but always be prepared for random cats. 432 | Having no hair made him look even hairier. 433 | He poured rocks in the dungeon of his mind. 434 | The blue parrot drove by the hitchhiking mongoose. 435 | Happiness can be found in the depths of chocolate pudding. 436 | There are no heroes in a punk rock band. 437 | He shaved the peach to prove a point. 438 | Peanuts don't grow on trees, but cashews do. 439 | It's difficult to understand the lengths he'd go to remain short. 440 | He created a pig burger out of beef. 441 | His thought process was on so many levels that he gave himself a phobia of heights. 442 | He liked to play with words in the bathtub. 443 | There were three sphered rocks congregating in a cubed room. 444 | There was no telling what thoughts would come from the machine. 445 | He put heat on the wound to see what would grow. 446 | She used her own hair in the soup to give it more flavor. 447 | Sometimes you have to just give up and win by cheating. 448 | The toy brought back fond memories of being lost in the rain forest. 449 | She couldn't decide of the glass was half empty or half full so she drank it. 450 | He wasn't bitter that she had moved on but from the radish. 451 | He had decided to accept his fate of accepting his fate. 452 | The clouds formed beautiful animals in the sky that eventually created a tornado to wreak havoc. 453 | The swirled lollipop had issues with the pop rock candy. 454 | He picked up trash in his spare time to dump in his neighbor's yard. 455 | The fish dreamed of escaping the fishbowl and into the toilet where he saw his friend go. 456 | It was a really good Monday for being a Saturday. 457 | Greetings from the galaxy MACS0647-JD, or what we call home. 458 | He was surprised that his immense laziness was inspirational to others. 459 | He had unknowingly taken up sleepwalking as a nighttime hobby. 460 | He spiked his hair green to support his iguana. 461 | The fifty mannequin heads floating in the pool kind of freaked them out. 462 | Although it wasn't a pot of gold, Nancy was still enthralled at what she found at the end of the rainbow. 463 | After fighting off the alligator, Brian still had to face the anaconda. 464 | It didn't take long for Gary to detect the robbers were amateurs. 465 | He embraced his new life as an eggplant. 466 | Karen realized the only way she was getting into heaven was to cheat. 467 | He loved eating his bananas in hot dog buns. 468 | Thirty years later, she still thought it was okay to put the toilet paper roll under rather than over. 469 | Facing his greatest fear, he ate his first marshmallow. 470 | He found his art never progressed when he literally used his sweat and tears. 471 | Nancy was proud that she ran a tight shipwreck. 472 | It took him a while to realize that everything he decided not to change, he was actually choosing. 473 | It was difficult for Mary to admit that most of her workout consisted of exercising poor judgment. 474 | Jason lived his life by the motto, \"Anything worth doing is worth doing poorly. 475 | David proudly graduated from high school top of his class at age 97. 476 | The murder hornet was disappointed by the preconceived ideas people had of him. 477 | The two walked down the slot canyon oblivious to the sound of thunder in the distance. 478 | She was amazed by the large chunks of ice washing up on the beach. 479 | Gary didn't understand why Doug went upstairs to get one dollar bills when he invited him to go cow tipping. 480 | The beach was crowded with snow leopards. 481 | As the asteroid hurtled toward earth, Becky was upset her dentist appointment had been canceled. 482 | She looked at the masterpiece hanging in the museum but all she could think is that her five-year-old could do better. 483 | The white water rafting trip was suddenly halted by the unexpected brick wall. 484 | She always had an interesting perspective on why the world must be flat. 485 | The sudden rainstorm washed crocodiles into the ocean. 486 | Harrold felt confident that nobody would ever suspect his spy pigeon. 487 | He knew it was going to be a bad day when he saw mountain lions roaming the streets. 488 | He decided to fake his disappearance to avoid jail. 489 | He ended up burning his fingers poking someone else's fire. 490 | She had a difficult time owning up to her own crazy self. 491 | She was too busy always talking about what she wanted to do to actually do any of it. 492 | She had that tint of craziness in her soul that made her believe she could actually make a difference. 493 | He kept telling himself that one day it would all somehow make sense. 494 | She moved forward only because she trusted that the ending she now was going through must be followed by a new beginning. 495 | While all her friends were positive that Mary had a sixth sense, she knew she actually had a seventh sense. 496 | He had a wall full of masks so she could wear a different face every day. 497 | The group quickly understood that toxic waste was the most effective barrier to use against the zombies. 498 | There have been days when I wished to be separated from my body, but today wasn't one of those days. 499 | To the surprise of everyone, the Rapture happened yesterday but it didn't quite go as expected. 500 | The bullet pierced the window shattering it before missing Danny's head by mere millimeters. 501 | The complicated school homework left the parents trying to help their kids quite confused. 502 | Too many prisons have become early coffins. 503 | The external scars tell only part of the story. 504 | Excitement replaced fear until the final moment. 505 | The tattered work gloves speak of the many hours of hard labor he endured throughout his life. 506 | The hummingbird's wings blurred while it eagerly sipped the sugar water from the feeder. 507 | Poison ivy grew through the fence they said was impenetrable. 508 | Watching the geriatric men's softball team brought back memories of 3 yr olds playing t-ball. 509 | The teens wondered what was kept in the red shed on the far edge of the school grounds. 510 | He strives to keep the best lawn in the neighborhood. 511 | The spa attendant applied the deep cleaning mask to the gentleman's back. 512 | Shingle color was not something the couple had ever talked about. 513 | The sunblock was handed to the girl before practice, but the burned skin was proof she did not apply it. 514 | The virus had powers none of us knew existed. 515 | A quiet house is nice until you are ordered to stay in it for months. 516 | As time wore on, simple dog commands turned into full paragraphs explaining why the dog couldn't do something. 517 | Mothers spend months of their lives waiting on their children. 518 | The team members were hard to tell apart since they all wore their hair in a ponytail. 519 | She learned that water bottles are no longer just to hold liquid, but they're also status symbols. 520 | Their argument could be heard across the parking lot. 521 | Giving directions that the mountains are to the west only works when you can see them. 522 | Traveling became almost extinct during the pandemic. 523 | It was obvious she was hot, sweaty, and tired. 524 | The Great Dane looked more like a horse than a dog. 525 | He played the game as if his life depended on it and the truth was that it did. 526 | It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die. 527 | He went on a whiskey diet and immediately lost three days. 528 | He watched the dancing piglets with panda bear tummies in the swimming pool. 529 | Martha came to the conclusion that shake weights are a great gift for any occasion. 530 | She had convinced her kids that any mushroom found on the ground would kill them if they touched it. 531 | As she walked along the street and looked in the gutter, she realized facemasks had become the new cigarette butts. 532 | Mary realized if her calculator had a history, it would be more embarrassing than her computer browser history. 533 | The tree fell unexpectedly short. 534 | Tomorrow will bring something new, so leave today as a memory. 535 | Today I dressed my unicorn in preparation for the race. 536 | Today is the day I'll finally know what brick tastes like. 537 | Today we gathered moss for my uncle's wedding. 538 | Today I heard something new and unmemorable. 539 | Today arrived with a crash of my car through the garage door. 540 | He found rain fascinating yet unpleasant. 541 | He appeared to be confusingly perplexed. 542 | She thought there'd be sufficient time if she hid her watch. 543 | You'll see the rainbow bridge after it rains cats and dogs. 544 | A dead duck doesn't fly backward. 545 | I thought red would have felt warmer in summer but I didn't think about the equator. 546 | Green should have smelled more tranquil, but somehow it just tasted rotten. 547 | Blue sounded too cold at the time and yet it seemed to work for gin. 548 | Warm beer on a cold day isn't my idea of fun. 549 | Honestly, I didn't care much for the first season, so I didn't bother with the second. 550 | The light that burns twice as bright burns half as long. 551 | I liked their first two albums but changed my mind after that charity gig. 552 | They improved dramatically once the lead singer left. 553 | They desperately needed another drummer since the current one only knew how to play bongos. 554 | People keep telling me \"orange\" but I still prefer \"pink\". 555 | Hang on, my kittens are scratching at the bathtub and they'll upset by the lack of biscuits. 556 | The tears of a clown make my lipstick run, but my shower cap is still intact. 557 | The gloves protect my feet from excess work. 558 | The fog was so dense even a laser decided it wasn't worth the effort. 559 | He was an introvert that extroverts seemed to love. 560 | The golden retriever loved the fireworks each Fourth of July. 561 | I want a giraffe, but I'm a turtle eating waffles. 562 | Henry couldn't decide if he was an auto mechanic or a priest. 563 | 100 years old is such a young age if you happen to be a bristlecone pine. 564 | When he asked her favorite number, she answered without hesitation that it was diamonds. 565 | My biggest joy is roasting almonds while stalking prey. 566 | Edith could decide if she should paint her teeth or brush her nails. 567 | It was the first time he had ever seen someone cook dinner on an elephant. 568 | Purple is the best city in the forest. 569 | There is no better feeling than staring at a wall with closed eyes. 570 | She couldn't understand why nobody else could see that the sky is full of cotton candy. 571 | He decided to count all the sand on the beach as a hobby. 572 | My uncle's favorite pastime was building cars out of noodles. 573 | The best part of marriage is animal crackers with peanut butter. 574 | There are over 500 starfish in the bathroom drawer. 575 | She insisted that cleaning out your closet was the key to good driving. 576 | Baby wipes are made of chocolate stardust. 577 | Best friends are like old tomatoes and shoelaces. 578 | Situps are a terrible way to end your day. 579 | Cursive writing is the best way to build a race track. 580 | Waffles are always better without fire ants and fleas. 581 | The bird had a belief that it was really a groundhog. 582 | Jenny made the announcement that her baby was an alien. 583 | He felt that dining on the bridge brought romance to his relationship with his cat. 584 | She wanted to be rescued, but only if it was Tuesday and raining. 585 | She was the type of girl that always burnt sugar to show she cared. 586 | I was offended by the suggestion that my baby brother was a jewel thief. 587 | Just because the water is red doesn't mean you can't drink it. 588 | Today I bought a raincoat and wore it on a sunny day. 589 | Everyone pretends to like wheat until you mention barley. 590 | Please put on these earmuffs because I can't you hear. 591 | That is an appealing treasure map that I can't read. 592 | Jerry liked to look at paintings while eating garlic ice cream. 593 | I've always wanted to go to Tajikistan, but my cat would miss me. 594 | If you spin around three times, you'll start to feel melancholy. 595 | I used to live in my neighbor's fishpond, but the aesthetic wasn't to my taste. 596 | Imagine his surprise when he discovered that the safe was full of pudding. 597 | I only enjoy window shopping when the windows are transparent. 598 | He was the only member of the club who didn't like plum pudding. 599 | You should never take advice from someone who thinks red paint dries quicker than blue paint. 600 | I was fishing for compliments and accidentally caught a trout. 601 | It isn't true that my mattress is made of cotton candy. 602 | I received a heavy fine but it failed to crush my spirit. 603 | Mr. Montoya knows the way to the bakery even though he's never been there. 604 | I never knew what hardship looked like until it started raining bowling balls. 605 | It isn't difficult to do a handstand if you just stand on your hands. 606 | Strawberries must be the one food that doesn't go well with this brand of paint. 607 | I'd always thought lightning was something only I could see. 608 | We're careful about orange ping pong balls because people might think they're fruit. 609 | The worst thing about being at the top of the career ladder is that there's a long way to fall. 610 | Always bring cinnamon buns on a deep-sea diving expedition. 611 | There's no reason a hula hoop can't also be a circus ring. 612 | It took me too long to realize that the ceiling hadn't been painted to look like the sky. 613 | Sarah ran from the serial killer holding a jug of milk. 614 | Gwen had her best sleep ever on her new bed of nails. 615 | He never understood why what, when, and where left out who. 616 | Douglas figured the best way to succeed was to do the opposite of what he'd been doing all his life. 617 | Patricia found the meaning of life in a bowl of Cheerios. 618 | I'll have you know I've written over fifty novels 619 | That must be the tenth time I've been arrested for selling deep-fried cigars. 620 | Being unacquainted with the chief raccoon was harming his prospects for promotion. 621 | The sight of his goatee made me want to run and hide under my sister-in-law's bed. 622 | The most exciting eureka moment I've had was when I realized that the instructions on food packets were just guidelines. 623 | It must be easy to commit crimes as a snake because you don't have to worry about leaving fingerprints. 624 | I've traveled all around Africa and still haven't found the gnu who stole my scarf. 625 | At that moment I was the most fearsome weasel in the entire swamp. 626 | The chic gangster liked to start the day with a pink scarf. 627 | She was only made the society president because she can whistle with her toes. 628 | The changing of down comforters to cotton bedspreads always meant the squirrels had returned. 629 | Written warnings in instruction manuals are worthless since rabbits can't read. 630 | Barking dogs and screaming toddlers have the unique ability to turn friendly neighbors into cranky enemies. 631 | The furnace repairman indicated the heating system was acting as an air conditioner. 632 | The water flowing down the river didn't look that powerful from the car 633 | The bread dough reminded her of Santa Clause's belly. 634 | Little Red Riding Hood decided to wear orange today. 635 | The stench from the feedlot permeated the car despite having the air conditioning on recycled air. 636 | Joe discovered that traffic cones make excellent megaphones. 637 | Nancy decided to make the porta-potty her home. 638 | Acres of almond trees lined the interstate highway which complimented the crazy driving nuts. 639 | They decided to plant an orchard of cotton candy. 640 | Tomatoes make great weapons when water balloons aren't available. 641 | The tumbleweed refused to tumble but was more than willing to prance. 642 | Nancy thought the best way to create a welcoming home was to line it with barbed wire. 643 | The underground bunker was filled with chips and candy. 644 | The dead trees waited to be ignited by the smallest spark and seek their revenge. 645 | The overpass went under the highway and into a secret world. 646 | His get rich quick scheme was to grow a cactus farm. 647 | Siri became confused when we reused to follow her directions. 648 | With the high wind warning, nobody could go out and play. 649 | She found it strange that people use their cellphones to actually talk to one another. 650 | The reservoir water level continued to lower while we enjoyed our long shower. 651 | Peter found road kill an excellent way to save money on dinner. 652 | Trash covered the landscape like sprinkles do a birthday cake. 653 | The ice-cream trucks bring back bad memories for all of us. 654 | Karen believed all traffic laws should be obeyed by all except herself. 655 | They finished building the road they knew no one would ever use. 656 | When confronted with a rotary dial phone the teenager was perplexed. 657 | The glacier came alive as the climbers hiked closer. 658 | Normal activities took extraordinary amounts of concentration at the high altitude. 659 | Boulders lined the side of the road foretelling what could come next. 660 | The fence was confused about whether it was supposed to keep things in or keep things out. 661 | Homesickness became contagious in the young campers' cabin. 662 | The toddler's endless tantrum caused the entire plane anxiety. 663 | The manager of the fruit stand always sat and only sold vegetables. 664 | The hawk didn't understand why the ground squirrels didn't want to be his friend. 665 | Jim liked driving around town with his hazard lights on. 666 | Mom didn't understand why no one else wanted a hot tub full of jello. 667 | Instead of going to the party, he went home and slept. 668 | The family's excitement over going to Disneyland was crazier than she anticipated. 669 | The old rusted farm equipment surrounded the house predicting its demise. 670 | Her fragrance of choice was fresh garlic. 671 | Jason didn't understand why his parents wouldn't let him sell his little sister at the garage sale. 672 | The father handed each child a roadmap at the beginning of the 2-day road trip and explained it was so they could find their way home. 673 | More RVs were seen in the storage lot than at the campground. 674 | As he dangled from the rope deep inside the crevasse 675 | On each full moon, the night sky is as bright as the day 676 | The elderly neighborhood became enraged over the coyotes who had been blamed for the poodle's disappearance. 677 | The wooden spoon couldn't cut but left emotional scars. 678 | Kevin embraced his ability to be at the wrong place at the wrong time. 679 | She discovered van life is difficult with 2 cats and a dog. 680 | He dreamed of leaving his law firm to open a portable dog wash. 681 | Despite multiple complications and her near-death experience 682 | The teenage boy was accused of breaking his arm simply to get out of the test. 683 | The bug was having an excellent day until he hit the windshield. 684 | He was all business when he wore his clown suit. 685 | The miniature pet elephant became the envy of the neighborhood. 686 | The child's favorite Christmas gift was the large box her father's lawnmower came in. 687 | Flash photography is best used in full sunlight. 688 | The llama couldn't resist trying the lemonade. 689 | I used to practice weaving with spaghetti three hours a day but stopped because I didn't want to die alone. 690 | I cheated while playing the darts tournament by using a longbow. 691 | I had a friend in high school named Rick Shaw, but he was fairly useless as a mode of transport. 692 | The balloons floated away along with all my hopes and dreams. 693 | There were a lot of paintings of monkeys waving bamboo sticks in the gallery. 694 | The anaconda was the greatest criminal mastermind in this part of the neighborhood. 695 | They say people remember important moments in their life well, yet no one even remembers their own birth. 696 | I was starting to worry that my pet turtle could tell what I was thinking. 697 | I always dreamed about being stranded on a desert island until it actually happened. 698 | It's important to remember to be aware of rampaging grizzly bears. 699 | The estate agent quickly marked out his territory on the dance floor. 700 | If you really strain your ears, you can just about hear the sound of no one giving a damn. 701 | I honestly find her about as intimidating as a basket of kittens. 702 | It's never comforting to know that your fate depends on something as unpredictable as the popping of corn. 703 | Despite what your teacher may have told you, there is a wrong way to wield a lasso. 704 | I made myself a peanut butter sandwich as I didn't want to subsist on veggie crackers. 705 | A good example of a useful vegetable is medicinal rhubarb. 706 | I became paranoid that the school of jellyfish was spying on me. 707 | It's always a good idea to seek shelter from the evil gaze of the sun. 708 | I know many children ask for a pony, but I wanted a bicycle with rockets strapped to it. 709 | For some unfathomable reason, the response team didn't consider a lack of milk for my cereal as a proper emergency. 710 | After coating myself in vegetable oil I found my success rate skyrocketed. 711 | The efficiency with which he paired the socks in the drawer was quite admirable. 712 | I trust everything that's written in purple ink. 713 | I'm not a party animal, but I do like animal parties. 714 | I've never seen a more beautiful brandy glass filled with wine. 715 | Everything was going so well until I was accosted by a purple giraffe. 716 | There's a growing trend among teenagers of using frisbees as go-cart wheels. 717 | You have no right to call yourself creative until you look at a trowel and think that it would make a great lockpick. 718 | I can't believe this is the eighth time I'm smashing open my piggy bank on the same day! 719 | There's probably enough glass in my cupboard to build an undersea aquarium. 720 | I'm worried by the fact that my daughter looks to the local carpet seller as a role model. 721 | My secretary is the only person who truly understands my stamp-collecting obsession. 722 | It's never been my responsibility to glaze the donuts. --------------------------------------------------------------------------------