diff --git a/1000-hours/.vitepress/config-old.mts b/1000-hours/.vitepress/config-old.mts
deleted file mode 100644
index 63764275..00000000
--- a/1000-hours/.vitepress/config-old.mts
+++ /dev/null
@@ -1,714 +0,0 @@
-import { defineConfig } from "vitepress";
-import { withMermaid } from "vitepress-plugin-mermaid";
-import footnote from "markdown-it-footnote";
-import sup from "markdown-it-sup";
-import sub from "markdown-it-sub";
-import mark from "markdown-it-mark";
-import ins from "markdown-it-ins";
-
-// import markdownit from 'markdown-it'
-
-export default withMermaid(
- // https://vitepress.dev/reference/site-config
- defineConfig({
- title: "1000 小时",
- description: "用注意力填满 1000 小时就能练成任何你需要的技能……",
- head: [
- [
- "script",
- {
- async: "",
- src: "https://www.googletagmanager.com/gtag/js?id=G-Z2QZPX3T9W",
- },
- ],
- [
- "script",
- {},
- `window.dataLayer = window.dataLayer || [];
- function gtag(){dataLayer.push(arguments);}
- gtag('js', new Date());
- gtag('config', 'G-Z2QZPX3T9W');`,
- ],
- ["link", { rel: "icon", href: "/images/clock.svg" }],
- ],
- themeConfig: {
- // https://vitepress.dev/reference/default-theme-config
- nav: [
- { text: "Home", link: "/" },
- { text: "Enjoy App", link: "/enjoy-app/", activeMatch: "/enjoy-app/" },
- ],
-
- search: {
- // provider: 'local'
- provider: "algolia",
- options: {
- appId: "KKK8CORNSR",
- apiKey: "d613ff31a535ff1e9535cf9c88ec420a",
- indexName: "1000h",
- },
- },
-
- sidebar: {
- "/": [
- {
- text: "简要说明",
- collapsed: true,
- link: "/intro",
- items: [
- { text: "1. 要不要健脑?", link: "/why" },
- { text: "2. 什么最健脑?", link: "/what" },
- ],
- },
- {
- text: "训练任务",
- collapsed: true,
- items: [
- { text: "1. 启动任务", link: "/training-tasks/kick-off" },
- { text: "2. 训练方法", link: "/training-tasks/procedures" },
- {
- text: "3. 人工智能",
- collapsed: false,
- link: "/training-tasks/ai",
- items: [
- {
- text: "3.1. 基础语言能力",
- link: "/training-tasks/language",
- },
- {
- text: "3.2. 成年人的困境",
- link: "/training-tasks/predicaments",
- },
- {
- text: "3.3. 人工智能辅助",
- link: "/training-tasks/revolution",
- },
- { text: "3.4. 任务并不高级", link: "/training-tasks/ground" },
- { text: "3.5. 效果非常惊人", link: "/training-tasks/wonder" },
- ],
- },
- { text: "4. 意料之外", link: "/training-tasks/surprise" },
- ],
- },
- {
- text: "语音塑造",
- link: "/sounds-of-american-english/1-basics",
- collapsed: true,
- items: [
- {
- text: "1. 基础",
- collapsed: true,
- link: "/sounds-of-american-english/1-basics",
- items: [
- {
- text: "1.1. 字母",
- link: "/sounds-of-american-english/1.1-alphabet",
- },
- {
- text: "1.2. 音素",
- link: "/sounds-of-american-english/1.2-phonemes",
- },
- {
- text: "1.3. 口音",
- link: "/sounds-of-american-english/1.3-accents",
- },
- {
- text: "1.4. 器官",
- link: "/sounds-of-american-english/1.4-articulators",
- },
- ],
- },
- {
- text: "2. 详解",
- collapsed: true,
- link: "/sounds-of-american-english/2-details",
- items: [
- {
- text: "2.1. 元音",
- collapsed: true,
- link: "/sounds-of-american-english/2.1-vowels",
- items: [
- {
- text: "2.1.1. 口型",
- link: "/sounds-of-american-english/2.1.1-lips",
- },
- {
- text: "2.1.2. 舌位",
- link: "/sounds-of-american-english/2.1.2-tongue",
- },
- {
- text: "2.1.3. ʌ/ɑː/ɑːr",
- link: "/sounds-of-american-english/2.1.3-ʌ",
- },
- {
- text: "2.1.4. e/æ",
- link: "/sounds-of-american-english/2.1.4-e",
- },
- {
- text: "2.1.5. ə/ɚ/ɝː",
- link: "/sounds-of-american-english/2.1.5-ə",
- },
- {
- text: "2.1.6. ɪ/i/iː",
- link: "/sounds-of-american-english/2.1.6-i",
- },
- {
- text: "2.1.7. ʊ/u/uː",
- link: "/sounds-of-american-english/2.1.7-u",
- },
- {
- text: "2.1.8. ɑː/ɔː/ɔːr",
- link: "/sounds-of-american-english/2.1.8-ɔ",
- },
- {
- text: "2.1.9. aɪ... əʊ",
- link: "/sounds-of-american-english/2.1.9-aɪ",
- },
- {
- text: "2.1.10. ɤ",
- link: "/sounds-of-american-english/2.1.10-ɤ",
- },
- ]
- },
- {
- text: "2.2. 辅音",
- collapsed: true,
- link: "/sounds-of-american-english/2.2-consonants",
- items: [
- {
- text: "2.2.1. 分类",
- link: "/sounds-of-american-english/2.2.1-categorization",
- },
- {
- text: "2.2.2. p/b",
- link: "/sounds-of-american-english/2.2.2-pb",
- },
- {
- text: "2.2.3. t/d",
- link: "/sounds-of-american-english/2.2.3-td",
- },
- {
- text: "2.2.4. k/g",
- link: "/sounds-of-american-english/2.2.4-kg",
- },
- {
- text: "2.2.5. f/v",
- link: "/sounds-of-american-english/2.2.5-fv",
- },
- {
- text: "2.2.6. s/z",
- link: "/sounds-of-american-english/2.2.6-sz",
- },
- {
- text: "2.2.7. θ/ð",
- link: "/sounds-of-american-english/2.2.7-θð",
- },
- {
- text: "2.2.8. ʃ/ʒ",
- link: "/sounds-of-american-english/2.2.8-ʃʒ",
- },
- {
- text: "2.2.9. h",
- link: "/sounds-of-american-english/2.2.9-h",
- },
- {
- text: "2.2.10. tʃ/dʒ",
- link: "/sounds-of-american-english/2.2.10-tʃdʒ",
- },
- {
- text: "2.2.11. tr/dr",
- link: "/sounds-of-american-english/2.2.11-trdr",
- },
- {
- text: "2.2.12. ts/dz",
- link: "/sounds-of-american-english/2.2.12-tsdz",
- },
- {
- text: "2.2.13. m, n, ŋ",
- link: "/sounds-of-american-english/2.2.13-mnŋ",
- },
- {
- text: "2.2.14. l, r",
- link: "/sounds-of-american-english/2.2.14-lr",
- },
- {
- text: "2.2.15. w, j",
- link: "/sounds-of-american-english/2.2.15-wj",
- },
- ]
- },
- {
- text: "2.3. 音节",
- collapsed: true,
- link: "/sounds-of-american-english/2.3-syllables",
- items: [
- {
- text: "2.3.1. 构成",
- link: "/sounds-of-american-english/2.3.1-structure",
- },
- {
- text: "2.3.2. 重音",
- link: "/sounds-of-american-english/2.3.2-stress",
- },
- ]
- },
- {
- text: "2.4. 连接",
- collapsed: true,
- link: "/sounds-of-american-english/2.4-linking",
- items: [
- {
- text: "2.4.1. 停顿",
- link: "/sounds-of-american-english/2.4.1-stop",
- },
- {
- text: "2.4.2. 辅音 + 元音",
- link: "/sounds-of-american-english/2.4.2-cv",
- },
- {
- text: "2.4.3. 辅音 + 辅音",
- link: "/sounds-of-american-english/2.4.3-cc",
- },
- {
- text: "2.4.4. 元音 + 元音",
- link: "/sounds-of-american-english/2.4.4-vv",
- },
- ]
- },
- {
- text: "2.5. 韵律",
- collapsed: true,
- link: "/sounds-of-american-english/2.5-prosody",
- items: [
- {
- text: "2.5.1. 高低",
- link: "/sounds-of-american-english/2.5.1-pitch",
- },
- {
- text: "2.5.2. 起伏",
- link: "/sounds-of-american-english/2.5.2-tone",
- },
- {
- text: "2.5.3. 轻重",
- link: "/sounds-of-american-english/2.5.3-emphasis",
- },
- {
- text: "2.5.4. 缓急",
- link: "/sounds-of-american-english/2.5.4-pace",
- },
- ]
- },
- ],
- },
- {
- text: "3. 收官",
- collapsed: true,
- link: "/sounds-of-american-english/3-wrapping-up",
- items: [
- {
- text: "3.1. 流利",
- link: "/sounds-of-american-english/3.1-fluency",
- },
- {
- text: "3.2. 情绪",
- link: "/sounds-of-american-english/3.2-emotions",
- },
- ],
- },
- {
- text: "4. 而后",
- collapsed: true,
- link: "/sounds-of-american-english/4-whats-next",
- items: [
- {
- text: "4.1. 多音拼写",
- link: "/sounds-of-american-english/4.1-multisounds",
- },
- {
- text: "4.2. 组合词汇",
- link: "/sounds-of-american-english/4.2-compounds",
- },
- {
- text: "4.3. 词根词缀",
- collapsed: false,
- link: "/sounds-of-american-english/4.3-components",
- items: [
- {
- text: "4.3.1. 常见前缀",
- link: "/sounds-of-american-english/4.3.1-prefixes",
- },
- {
- text: "4.3.2. 常见后缀",
- link: "/sounds-of-american-english/4.3.2-suffixes",
- },
- {
- text: "4.3.3. 常见词根",
- link: "/sounds-of-american-english/4.3.3-roots",
- },
- ],
- },
- ],
- },
- {
- text: "5. 总结",
- link: "/sounds-of-american-english/5-sumup",
- },
- ],
- },
- // {
- // text: "语音塑造",
- // collapsed: true,
- // items: [
- // {
- // text: "1. 基础",
- // collapsed: true,
- // link: "/sounds-of-english/0-intro",
- // items: [
- // {
- // text: "1.1. 音素",
- // collapsed: true,
- // link: "/sounds-of-english/01-phonemes",
- // items: [
- // {
- // text: "1.1.1. 元音",
- // link: "/sounds-of-english/01-1-vowels",
- // },
- // {
- // text: "1.1.2. 辅音",
- // link: "/sounds-of-english/01-2-consonants",
- // },
- // {
- // text: "1.1.3. 美式语音标注",
- // link: "/sounds-of-english/01-3-us-phonemes",
- // },
- // {
- // text: "1.1.4. 示例",
- // link: "/sounds-of-english/01-4-pangram",
- // },
- // ],
- // },
- // {
- // text: "1.2. 音节",
- // link: "/sounds-of-english/02-syllables",
- // },
- // ],
- // },
- // {
- // text: "2. 详解",
- // collapsed: true,
- // link: "/sounds-of-english/03-details",
- // items: [
- // {
- // text: "2.1. 元音",
- // collapsed: false,
- // link: "/sounds-of-english/03-vowels-overview",
- // items: [
- // {
- // text: "2.1.1. 口腔内气流共鸣位置",
- // link: "/sounds-of-english/04-vowel-positions",
- // },
- // { text: "2.1.2. ʌ/ɑː", link: "/sounds-of-english/05-Ʌ" },
- // { text: "2.1.3. e/æ", link: "/sounds-of-english/06-e" },
- // { text: "2.1.4. ə/əː", link: "/sounds-of-english/07-ə" },
- // { text: "2.1.5. ɪ/iː", link: "/sounds-of-english/08-i" },
- // { text: "2.1.6. ʊ/uː", link: "/sounds-of-english/09-u" },
- // { text: "2.1.7. ɔ/ɔː", link: "/sounds-of-english/10-ɔ" },
- // {
- // text: "2.1.8. aɪ, eɪ, ɔɪ, aʊ, əʊ, eə, ɪə, ʊə",
- // link: "/sounds-of-english/11-aɪ",
- // },
- // ],
- // },
- // {
- // text: "2.2. 辅音",
- // collapsed: false,
- // link: "/sounds-of-english/12-consonants-overview",
- // items: [
- // {
- // text: "2.2.1. p, b, m, n, f, k, g, h",
- // link: "/sounds-of-english/13-pbmnfkgh",
- // },
- // { text: "2.2.2. f, v", link: "/sounds-of-english/14-fv" },
- // {
- // text: "2.2.3. m, n, ŋ",
- // link: "/sounds-of-english/15-mn",
- // },
- // {
- // text: "2.2.4. t, d, s, z; ʃ, tʃ, dʒ",
- // link: "/sounds-of-english/16-tdsz",
- // },
- // { text: "2.2.5. t, d", link: "/sounds-of-english/17-td" },
- // {
- // text: "2.2.6. tr, dr, ts, dz",
- // link: "/sounds-of-english/18-trdr",
- // },
- // {
- // text: "2.2.7. sp, st, str, sk",
- // link: "/sounds-of-english/19-sptk",
- // },
- // { text: "2.2.8. h", link: "/sounds-of-english/20-h" },
- // { text: "2.2.9. θ, ð", link: "/sounds-of-english/21-θð" },
- // { text: "2.2.10. r", link: "/sounds-of-english/22-r" },
- // { text: "2.2.11. l", link: "/sounds-of-english/23-l" },
- // { text: "2.2.12. ʒ", link: "/sounds-of-english/24-ʒ" },
- // {
- // text: "2.2.13. j, w",
- // link: "/sounds-of-english/25-jw",
- // },
- // ],
- // },
- // {
- // text: "2.3. 连读",
- // link: "/sounds-of-english/26-catenation",
- // },
- // {
- // text: "2.4. 音标学习",
- // link: "/sounds-of-english/27-learning-phonetics",
- // },
- // {
- // text: "2.4. 英美口音选择",
- // link: "/sounds-of-english/28-choosing-accent",
- // },
- // ],
- // },
- // {
- // text: "3. 进阶",
- // collapsed: true,
- // link: "/sounds-of-english/29-advanced",
- // items: [
- // {
- // text: "3.1 什么更重要?",
- // link: "/sounds-of-english/30-more-important",
- // },
- // { text: "3.2 停顿", link: "/sounds-of-english/31-pause" },
- // { text: "3.3 高低", link: "/sounds-of-english/32-high-low" },
- // { text: "3.4 起伏", link: "/sounds-of-english/33-up-down" },
- // {
- // text: "3.5 轻重",
- // link: "/sounds-of-english/34-strong-weak",
- // },
- // { text: "3.6 缓急", link: "/sounds-of-english/35-fast-slow" },
- // {
- // text: "3.7 长短",
- // link: "/sounds-of-english/36-long-short",
- // },
- // ],
- // },
- // {
- // text: "4. 收官",
- // collapsed: true,
- // link: "/sounds-of-english/37-round-up",
- // items: [
- // { text: "4.1 流利", link: "/sounds-of-english/38-fluent" },
- // { text: "4.2 情绪", link: "/sounds-of-english/39-emotional" },
- // ],
- // },
- // ],
- // },
-
- {
- text: "大脑内部",
- collapsed: true,
- items: [
- { text: "1. 小空间大世界", link: "/in-the-brain/01-inifinite" },
- { text: "2. 一切都是连接", link: "/in-the-brain/02-links" },
- { text: "3. 一切都是体育课", link: "/in-the-brain/03-sports" },
- {
- text: "4. 一切都是语文课",
- link: "/in-the-brain/04-literature",
- },
- { text: "5. 一切都需要能量", link: "/in-the-brain/05-energy" },
- {
- text: "6. 用进废退循环利用",
- link: "/in-the-brain/06-use-or-lose",
- },
- {
- text: "7. 短时间内足量重复",
- link: "/in-the-brain/07-repitition",
- },
- {
- text: "8. 新旧网络间的竞争",
- link: "/in-the-brain/08-compitition",
- },
- {
- text: "9. 注意不到就不存在",
- link: "/in-the-brain/09-unnoticed",
- },
- {
- text: "10. 熟练就是卸载负担",
- link: "/in-the-brain/10-unloading",
- },
- { text: "11. 被关注是最大负担", link: "/in-the-brain/11-burden" },
- {
- text: "12. 有限排它不可再生",
- link: "/in-the-brain/12-unreproducible",
- },
- {
- text: "13. 一切都是化学反应",
- link: "/in-the-brain/13-chemical",
- },
- {
- text: "14. 安全阈值决定成果",
- link: "/in-the-brain/14-threshold",
- },
- ],
- },
- {
- text: "自我训练",
- collapsed: true,
- link: `/self-training/00-intro`,
- items: [
- { text: "1. 用兵打仗", link: "/self-training/01-fight" },
- { text: "2. 只能自学", link: "/self-training/02-last-resort" },
- {
- text: "3. 生学硬练",
- link: "/self-training/03-trials-and-errors",
- },
- { text: "4. 走出迷宫", link: "/self-training/04-maze" },
- { text: "5. 自我纠正", link: "/self-training/05-correction" },
- { text: "6. 自主驱动", link: "/self-training/06-motives" },
- { text: "7. 自我鼓励", link: "/self-training/07-encouraging" },
- { text: "8. 自我监督", link: "/self-training/08-supervising" },
- { text: "9. 自主计划", link: "/self-training/09-planning" },
- { text: "10. 返璞归真", link: "/self-training/10-going-back" },
- ],
- },
- {
- text: "Enjoy App",
- collapsed: true,
- link: `/enjoy-app/`,
- items: [
- {
- text: "快速开始",
- collapsed: false,
- items: [
- { text: "Enjoy 简介", link: "/enjoy-app/" },
- { text: "下载安装", link: "/enjoy-app/install" },
- { text: "软件设置", link: "/enjoy-app/settings" },
- ],
- },
- {
- text: "跟读训练",
- collapsed: false,
- items: [
- { text: "音频资源", link: "/enjoy-app/audios" },
- { text: "视频资源", link: "/enjoy-app/videos" },
- ],
- },
- {
- text: "阅读文本",
- collapsed: false,
- items: [
- { text: "在线文章", link: "/enjoy-app/webpage" },
- { text: "本地电子书", link: "/enjoy-app/ebook" },
- ],
- },
- {
- text: "智能助手",
- collapsed: false,
- items: [
- { text: "简介", link: "/enjoy-app/ai-assistant" },
- { text: "GPT 服务", link: "/enjoy-app/gpt-conversation" },
- { text: "TTS 服务", link: "/enjoy-app/tts-conversation" },
- ],
- },
- {
- text: "其他",
- collapsed: false,
- items: [
- {
- text: "常见问题",
- link: "/enjoy-app/faq",
- },
- {
- text: "利用 AI 生成训练材料",
- link: "/enjoy-app/use-case-generate-audio-resources",
- },
- ],
- },
- ]
- }
- ],
-
- "/enjoy-app/": [
- {
- text: "快速开始",
- collapsed: false,
- items: [
- { text: "Enjoy 简介", link: "/enjoy-app/" },
- { text: "下载安装", link: "/enjoy-app/install" },
- { text: "软件设置", link: "/enjoy-app/settings" },
- { text: "版本更新", link: "/enjoy-app/changelog" },
- ],
- },
- {
- text: "跟读训练",
- collapsed: false,
- items: [
- { text: "音频资源", link: "/enjoy-app/audios" },
- { text: "视频资源", link: "/enjoy-app/videos" },
- ],
- },
- {
- text: "阅读文本",
- collapsed: false,
- items: [
- { text: "在线文章", link: "/enjoy-app/webpage" },
- { text: "本地电子书", link: "/enjoy-app/ebook" },
- ],
- },
- {
- text: "智能助手",
- collapsed: false,
- items: [
- { text: "简介", link: "/enjoy-app/ai-assistant" },
- { text: "GPT 服务", link: "/enjoy-app/gpt-conversation" },
- { text: "TTS 服务", link: "/enjoy-app/tts-conversation" },
- ],
- },
- {
- text: "其他",
- collapsed: false,
- items: [
- {
- text: "常见问题",
- link: "/enjoy-app/faq",
- },
- {
- text: "利用 AI 生成训练材料",
- link: "/enjoy-app/use-case-generate-audio-resources",
- },
- ],
- },
- {
- text: "返回",
- link: "/intro",
- },
- ],
-
- },
-
- socialLinks: [
- {
- icon: "github",
- link: "https://github.com/zuodaotech/everyone-can-use-english/tree/main/1000-hours",
- },
- ],
- },
-
- markdown: {
- // https://vitepress.dev/reference/markdown
- math: true,
- config: (md) => {
- // use more markdown-it plugins!
- md.use(footnote);
- md.use(sub);
- md.use(sup);
- md.use(mark);
- md.use(ins);
- },
- // toc: {
- // level: [2, 3, 4]
- // }
- },
- })
-);
diff --git a/1000-hours/.vitepress/config.mts b/1000-hours/.vitepress/config.mts
index baeec58f..0c9b4b62 100644
--- a/1000-hours/.vitepress/config.mts
+++ b/1000-hours/.vitepress/config.mts
@@ -147,7 +147,6 @@ export default withMermaid(
collapsed: true,
link: "/sounds-of-american-english/3.2-consonants",
items: [
-
{
text: "3.2.1. p/b",
link: "/sounds-of-american-english/3.2.1-pb",
@@ -247,6 +246,29 @@ export default withMermaid(
text: "7. 从此之后",
link: "/sounds-of-american-english/7-whats-next",
},
+ {
+ text: "8. 附录",
+ collapsed: true,
+ link: "/sounds-of-american-english/8-appendix",
+ items: [
+ {
+ text: "8.1. 输入音标与特殊符号",
+ link: "/sounds-of-american-english/8.1-inputting-phonemes-and-symbols",
+ },
+ {
+ text: "8.2. 获取 CEPD 音标",
+ link: "/sounds-of-american-english/8.2-cepd-phonetics-and-sound",
+ },
+ {
+ text: "8.3. 音标练习",
+ link: "/sounds-of-american-english/8.3-phoneme-exercises",
+ },
+ {
+ text: "8.4. 每日练习语音生成",
+ link: "/sounds-of-american-english/8.4-daily-speech-exercises",
+ },
+ ],
+ },
],
},
{
diff --git a/1000-hours/public/alfred-workflows/alfred-Dictionaries.alfredworkflow b/1000-hours/public/alfred-workflows/CEPD-phonetic-transcription.alfredworkflow
similarity index 92%
rename from 1000-hours/public/alfred-workflows/alfred-Dictionaries.alfredworkflow
rename to 1000-hours/public/alfred-workflows/CEPD-phonetic-transcription.alfredworkflow
index 9c017829..2aaa4440 100644
Binary files a/1000-hours/public/alfred-workflows/alfred-Dictionaries.alfredworkflow and b/1000-hours/public/alfred-workflows/CEPD-phonetic-transcription.alfredworkflow differ
diff --git a/1000-hours/public/alfred-workflows/alfred-IPA-Phonetic-Symbols.alfredworkflow b/1000-hours/public/alfred-workflows/IPA-Phonetic-Symbols.alfredworkflow
similarity index 61%
rename from 1000-hours/public/alfred-workflows/alfred-IPA-Phonetic-Symbols.alfredworkflow
rename to 1000-hours/public/alfred-workflows/IPA-Phonetic-Symbols.alfredworkflow
index 45b4f111..7de8429f 100644
Binary files a/1000-hours/public/alfred-workflows/alfred-IPA-Phonetic-Symbols.alfredworkflow and b/1000-hours/public/alfred-workflows/IPA-Phonetic-Symbols.alfredworkflow differ
diff --git a/1000-hours/public/images/ipae.png b/1000-hours/public/images/ipae.png
new file mode 100644
index 00000000..58825a0c
Binary files /dev/null and b/1000-hours/public/images/ipae.png differ
diff --git a/1000-hours/public/images/phoneme-exercises.png b/1000-hours/public/images/phoneme-exercises.png
new file mode 100644
index 00000000..36168c79
Binary files /dev/null and b/1000-hours/public/images/phoneme-exercises.png differ
diff --git a/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.ipynb b/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.ipynb
new file mode 100644
index 00000000..a18770d1
--- /dev/null
+++ b/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.ipynb
@@ -0,0 +1,528 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Install or update openai modules \n",
+ "%pip install openai --upgrade openai \n",
+ "%pip install python-dotenv mutagen pydub"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The history saving thread hit an unexpected error (DatabaseError('database disk image is malformed')).History will not be written to the database.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from openai import OpenAI\n",
+ "import re\n",
+ "import os\n",
+ "import IPython\n",
+ "from datetime import datetime\n",
+ "from mutagen.mp3 import MP3\n",
+ "from mutagen.id3 import ID3, APIC, TPE1, TALB, TCON\n",
+ "from dotenv import load_dotenv\n",
+ "from pydub import AudioSegment\n",
+ "\n",
+ "load_dotenv()\n",
+ "client = OpenAI(\n",
+ ")\n",
+ "\n",
+ "def add_metadata(mp3_file, image_file, artist, album, genre):\n",
+ " try:\n",
+ " audio = MP3(mp3_file, ID3=ID3)\n",
+ "\n",
+ " # Add ID3 tag if it doesn't exist\n",
+ " try:\n",
+ " audio.add_tags()\n",
+ " except:\n",
+ " pass\n",
+ "\n",
+ " # Add artist name\n",
+ " audio.tags.add(TPE1(encoding=3, text=artist))\n",
+ "\n",
+ " # Add album name\n",
+ " audio.tags.add(TALB(encoding=3, text=album))\n",
+ "\n",
+ " # Add genre\n",
+ " audio.tags.add(TCON(encoding=3, text=genre))\n",
+ "\n",
+ " # Add artwork\n",
+ " with open(image_file, 'rb') as albumart:\n",
+ " audio.tags.add(APIC(\n",
+ " encoding=3, # 3 is for utf-8\n",
+ " mime='image/jpeg', # image/jpeg or image/png\n",
+ " type=3, # 3 is for the cover image\n",
+ " desc=u'Cover',\n",
+ " data=albumart.read()\n",
+ " ))\n",
+ "\n",
+ " audio.save()\n",
+ " # print(f\"\\rMetadata added to {mp3_file}\")\n",
+ "\n",
+ " except Exception as e:\n",
+ " print(f\"\\rAn error occurred: {e}\")\n",
+ "\n",
+ "def get_response(user_prompt, role_definition, model=\"gpt-4o-mini\"):\n",
+ " user_prompt += \"\\n\\n Please provide response directly without further explanation.\"\n",
+ "\n",
+ " rspd_translation = client.chat.completions.create(\n",
+ " model=model,\n",
+ " messages=[\n",
+ " {\n",
+ " \"role\": \"system\", \n",
+ " \"content\": role_definition\n",
+ " },\n",
+ " {\n",
+ " \"role\": \"user\", \n",
+ " \"content\": user_prompt\n",
+ " }\n",
+ " ])\n",
+ " return rspd_translation.choices[0].message.content\n",
+ "\n",
+ "def get_openai_tts_audio(text, file_name=\"\", performer=\"alloy\", silence_duration=1500, with_ending=False, ending_file=\"resources/ending.mp3\", progress=True):\n",
+ "\n",
+ " # check artwork.png and ending.mp3 files exist\n",
+ " if not os.path.isfile('resources/Artwork.png') or not os.path.isfile('resources/ending.mp3'):\n",
+ " print(\"\\rEither Artwork.png or ending.mp3 file not found.\")\n",
+ " return\n",
+ "\n",
+ " # split the text into lines\n",
+ " text = markdown_to_text(text).split(\"\\n\")\n",
+ " # remove empty lines\n",
+ " text = [t for t in text if t]\n",
+ "\n",
+ " for t in text:\n",
+ " speech_file_path = f'temp-{text.index(t)}.mp3'\n",
+ " \n",
+ " with client.audio.speech.with_streaming_response.create(\n",
+ " model=\"tts-1\",\n",
+ " voice=performer,\n",
+ " input=t.strip()\n",
+ " ) as response:\n",
+ " response.stream_to_file(speech_file_path)\n",
+ " \n",
+ " # output a progress percentage, keep updating within a line\n",
+ " if progress:\n",
+ " print(f\"\\rprocessing audio performed by {performer.capitalize()}: {round((text.index(t)+1)/len(text)*100)}%\", end='...')\n",
+ " if progress:\n",
+ " print(\"\\n\")\n",
+ " \n",
+ " # create an audio of silence of specified silence_duration\n",
+ " temp_audio = AudioSegment.silent(duration=silence_duration)\n",
+ " for t in text:\n",
+ " seg = AudioSegment.from_file(f'temp-{text.index(t)}.mp3')\n",
+ " temp_audio += seg + AudioSegment.silent(duration=silence_duration)\n",
+ " # delete the temp file\n",
+ " os.remove(f'temp-{text.index(t)}.mp3')\n",
+ " temp_audio.export('~temp.mp3', format='mp3')\n",
+ " speech = AudioSegment.from_file('~temp.mp3')\n",
+ " if with_ending:\n",
+ " ending = AudioSegment.from_file(ending_file)\n",
+ " combined = speech + ending\n",
+ " else:\n",
+ " combined = speech\n",
+ " os.remove('~temp.mp3')\n",
+ " if file_name:\n",
+ " # if file_name has no extension, add .mp3\n",
+ " if file_name.endswith('.mp3'):\n",
+ " speech_file_path = file_name\n",
+ " else:\n",
+ " speech_file_path = f'{file_name}.mp3' \n",
+ " else:\n",
+ " speech_file_path = f'{datetime.now().strftime(\"%Y%m%d_%H%M%S\")}_{performer}.mp3'\n",
+ " combined.export(speech_file_path, format='mp3')\n",
+ " # print(f\"\\rAudio file saved as {speech_file_path}\")\n",
+ "\n",
+ " image_file = 'resources/Artwork.png'\n",
+ " artist = 'tts'\n",
+ " album = 'Daily Speech Training'\n",
+ " genre = 'SPEECH'\n",
+ "\n",
+ " add_metadata(speech_file_path, image_file, artist, album, genre)\n",
+ " IPython.display.Audio(speech_file_path)\n",
+ "\n",
+ " return speech_file_path\n",
+ "\n",
+ "# convert markdown to plain text\n",
+ "def markdown_to_text(markdown_text):\n",
+ " # remove bold text\n",
+ " text = re.sub(r'\\*\\*(.*?)\\*\\*', r'\\1', markdown_text)\n",
+ " # remove italic text\n",
+ " text = re.sub(r'\\*(.*?)\\*', r'\\1', text)\n",
+ " # remove links\n",
+ " text = re.sub(r'\\[(.*?)\\]\\(.*?\\)', r'\\1', text)\n",
+ " # remove images\n",
+ " text = re.sub(r'\\!\\[.*?\\]\\(.*?\\)', '', text)\n",
+ " # remove code blocks\n",
+ " text = re.sub(r'```(.*?)```', r'\\1', text)\n",
+ " # remove inline code\n",
+ " text = re.sub(r'`(.*?)`', r'\\1', text)\n",
+ " # remove ordered lists\n",
+ " text = re.sub(r'\\d+\\.(.*?\\n)', r'\\1', text)\n",
+ " # remove unordered lists\n",
+ " text = re.sub(r'\\*(.*?\\n)', r'\\1', text)\n",
+ " # remove blockquotes\n",
+ " text = re.sub(r'>(.*?\\n)', r'\\1', text)\n",
+ " # remove horizontal rules\n",
+ " text = re.sub(r'-+', '', text)\n",
+ " # remove headings\n",
+ " text = re.sub(r'#+', '', text)\n",
+ " # remove extra spaces\n",
+ " text = re.sub(r' +', ' ', text)\n",
+ " text = text.strip()\n",
+ " return text\n",
+ "\n",
+ "def add_zero(number, length=2):\n",
+ " return str(number).zfill(length)\n",
+ "\n",
+ "# read text from a file\n",
+ "def read_text_from_file(file_path):\n",
+ " with open(file_path, 'r') as file:\n",
+ " return file.read()\n",
+ " \n",
+ "# write text to a file\n",
+ "def write_text_to_file(file_path, text):\n",
+ " with open(file_path, \"w\") as file:\n",
+ " file.write(text)\n",
+ "\n",
+ "# read lines from a file\n",
+ "def read_lines_from_file(file_path):\n",
+ " with open(file_path, 'r') as file:\n",
+ " return file.readlines()\n",
+ "\n",
+ "# write lines to a file\n",
+ "def write_lines_to_file(file_path, lines):\n",
+ " with open(file_path, 'w') as file:\n",
+ " file.writelines(lines)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Mastering a skill is much like honing a craft with a chisel: learning provides the foundational knowledge, but it is practice that shapes the masterpiece. In a world where knowledge is at our fingertips, the danger lies not in a lack of information, but in our failure to translate that information into action. Learning is an essential first step, akin to gathering the raw materials needed for a project; however, it is through rigorous practice that we refine those materials into something meaningful.\n",
+ "\n",
+ "Think about it this way: learning serves as our intellectual toolbox, while practice is the actual construction work. When we engage in practical application, we’re not merely reviewing what we've learned; we’re embedding those ideas into our very being, making them second nature. This transformation is what separates the novices from the experts. For instance, a musician can study music theory for years, but without consistent practice, those notes on a page remain just that — notes on a page.\n",
+ "\n",
+ "The overwhelming majority of people who claim that what they learn is not useful may simply be overlooking the fact that they haven't put in the necessary time to practice. When knowledge remains dormant, it cannot spark creativity or innovation; thus, the notion that learning is inadequate becomes a self-fulfilling prophecy. Engaging in regular, deliberate practice bridges the gap between book smarts and street smarts.\n",
+ "\n",
+ "In the end, the most profound lessons often come from doing more than just thinking. To truly embody what we've learned, we must grab life by the reins and dive headfirst into practice. In this dance between learning and practicing, it is practice that ultimately leads to mastery, making it the unsung hero in the journey toward expertise.\n",
+ "\n",
+ "Generating the audio file...\n",
+ "\n",
+ "processing audio performed by Alloy: 100%...\n",
+ "\n",
+ "Audio file saved as Discourse_learning-and_20240823_091917-alloy.mp3!\n",
+ "\n",
+ "processing audio performed by Nova: 100%...\n",
+ "\n",
+ "Audio file saved as Discourse_learning-and_20240823_091917-nova.mp3!\n",
+ "\n",
+ "All done!\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Generate a passage by ChatGPT, with topic, key words or points you specify.\n",
+ "\n",
+ "role_definition = \"\"\"\n",
+ "You're my American English guru, \n",
+ "Whatever topic or key words or points I give you,\n",
+ "You'll develop them into a 300 words American English essay.\n",
+ "Be creative, do not use any cliche to start your passage, such as \"In todays's fast-paced world\"...\n",
+ "If I send you \"random\", you'll write a random essay with recent events or trending topics.\n",
+ "Use as diverse as possible expressions or idioms Americans use in daily conversations.\n",
+ "Return the passage as pure text without any markdown or code block.\n",
+ "\"\"\"\n",
+ "\n",
+ "user_prompt = \"\"\"\n",
+ "learning and practicing\n",
+ "\n",
+ "学本身用处并不大的,练才是真正的关键。学只不过是知道,只有高密度地练才能做到。\n",
+ "\n",
+ "学是系统二的工作。练是把知识、技能、流程和习惯压缩进系统一的工作。\n",
+ "\n",
+ "如果学的重要性是 1 的话,练的重要性可能是 99 甚至 9999。\n",
+ "\n",
+ "学是为了练,练是为了把学变成自己的东西。\n",
+ "\n",
+ "学了很多很多,从来不练,才是人们最大的问题。说什么学到的东西用不上,只不过是自己没练所以才用不了而已。哪儿有什么学了没用的东西呢?\n",
+ "\"\"\"\n",
+ "\n",
+ "rspd = get_response(user_prompt, role_definition, model=\"gpt-4o-mini\")\n",
+ "rspd = rspd.replace(\"—\", \" — \")\n",
+ "\n",
+ "audio_filename_prefix = f\"Discourse_{'-'.join(user_prompt.strip().rstrip(',.!?\\\")').split(' ')[:2])}_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n",
+ "\n",
+ "print(rspd + \"\\n\\nGenerating the audio file...\\n\")\n",
+ "write_text_to_file(f'{audio_filename_prefix}.md', rspd)\n",
+ "\n",
+ "for p in [\"alloy\", \"nova\"]:\n",
+ " audio_filename = get_openai_tts_audio(rspd, file_name=f'{audio_filename_prefix}-{p}.mp3', performer=p, silence_duration=1500, with_ending=True, ending_file=\"ending.mp3\")\n",
+ " print(f\"Audio file saved as {audio_filename_prefix}-{p}.mp3!\\n\")\n",
+ "\n",
+ "print(\"All done!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Conversation 1: The Balance Between Learning and Practicing\n",
+ "Alloy: You know, I believe that practicing is way more important than just learning.\n",
+ "Nova: That's an interesting take! But can we really downplay the importance of learning?\n",
+ "Alloy: Not entirely, but I think learning is just the starting point. Practice is where it counts.\n",
+ "Nova: I get that. It's like the old saying, 'you can lead a horse to water, but you can't make it drink.'\n",
+ "Alloy: Exactly! Just sitting in a classroom won't make you a pro. You’ve got to roll up your sleeves.\n",
+ "Nova: I agree, but don’t you think some foundational knowledge is necessary before you can even practice?\n",
+ "Alloy: Sure, but too much focus on learning can lead to overthinking. You just have to dive in!\n",
+ "Nova: That's a fair point. People often get stuck in analysis paralysis. But what about the risk of learning the wrong way?\n",
+ "Alloy: That’s where good mentorship comes in. They can guide your practice and correct any mistakes.\n",
+ "Nova: Valid argument! Perhaps the blend of learning and practicing is more about ratios rather than strict hierarchy.\n",
+ "\n",
+ "Conversation 2: Understanding the Role of Learning vs. Practicing\n",
+ "Alloy: Have you ever noticed how some people just learn and never really practice? It's puzzling.\n",
+ "Nova: Oh, for sure. It’s like they think just knowing something is enough. But that's not how it works!\n",
+ "Alloy: Exactly! Knowledge without application is like having a key that doesn't fit any lock.\n",
+ "Nova: Ha! Good analogy! But what about folks who claim they learned a lot yet can't apply it?\n",
+ "Alloy: I think they’re just making excuses. If you really grasp something, practicing it should come naturally.\n",
+ "Nova: But there are always exceptions. Some people might need more time to process before taking action.\n",
+ "Alloy: That could be, but there's a line between understanding and overthinking, don’t you think?\n",
+ "Nova: Absolutely! It’s frustrating when you see someone stuck in their own head.\n",
+ "Alloy: At the end of the day, practice solidifies what you've learned, turning knowledge into skill.\n",
+ "Nova: True that! I guess it’s all about finding that sweet spot between learning and practicing.\n",
+ "\n",
+ "Generating the audio file...\n",
+ "\n",
+ "Audio file saved as Conversation_learning-and_20240823_092956.mp3!\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Generating audio from conversations with a topic you specify.\n",
+ "\n",
+ "import json\n",
+ "import random\n",
+ "\n",
+ "def conversation_audio(conversation):\n",
+ " topic = conversation['topic'].strip()\n",
+ " random_speaker = [\"Alloy\", \"Nova\"][random.randint(0, 1)].lower()\n",
+ " # 1 second silence audio\n",
+ " audio = AudioSegment.silent(duration=500)\n",
+ " topic_audio = AudioSegment.from_file(get_openai_tts_audio(topic, \"temp~~.mp3\", random_speaker, 1000, with_ending=False, progress=False))\n",
+ " audio += topic_audio\n",
+ " # remove topic.mp3 file\n",
+ " os.remove(\"temp~~.mp3\")\n",
+ " sounds = [\"resources/Basso.aiff\", \"resources/Blow.aiff\", \"resources/Bottle.aiff\", \"resources/Frog.aiff\", \"resources/Funk.aiff\", \"resources/Glass.aiff\", \"resources/Hero.aiff\", \"resources/Morse.aiff\", \"resources/Ping.aiff\", \"resources/Pop.aiff\", \"resources/Purr.aiff\", \"resources/Sosumi.aiff\", \"resources/Submarine.aiff\", \"resources/Tink.aiff\"]\n",
+ " sound = random.choice(sounds)\n",
+ " AudioSegment.from_file(sound).export(\"dot-ending.mp3\", format=\"mp3\")\n",
+ " ending_file_name = \"dot-ending.mp3\"\n",
+ "\n",
+ " for exchange in conversation['exchanges']:\n",
+ " for speaker, line in exchange.items():\n",
+ " # if last line in exchange, assign with_ending=True\n",
+ " with_ending = conversation['exchanges'].index(exchange) == len(conversation['exchanges']) - 1\n",
+ " # print(f\"\\rspeaker, line, with_ending\")\n",
+ " audio += AudioSegment.from_file(get_openai_tts_audio(line.replace(\"—\", \" — \"), \"temp~~.mp3\", speaker.lower(), 1000, with_ending, ending_file=ending_file_name, progress=False))\n",
+ " # remove temp file\n",
+ " os.remove(\"temp~~.mp3\")\n",
+ "\n",
+ " os.remove(ending_file_name)\n",
+ "\n",
+ " return audio\n",
+ "\n",
+ "# user_prompt = \"\"\"\n",
+ "# on homosexuality, Although we don't oppose homosexuality, we do have another concern—what if our child isn't naturally homosexual but is somehow influenced to become one? How should we view this situation, and how can we prevent it from happening?\n",
+ "# \"\"\"\n",
+ "\n",
+ "role_definition = \"\"\"\n",
+ "You're my American English guru, \n",
+ "Whatever topic or key words I give you,\n",
+ "You'll desing two conversations or debates with several exchanges between Alloy and Nova,\n",
+ "each of which is about 400 words long, and is completely different with each other.\n",
+ "Make sure to mix exchnages in conversation with positive, neutral, and negative reactions, which might well including specifically from: \n",
+ "agreement, enthusiasm, support, admiration, acknowledgment, curiosity, clarification, reflection, disagreement, doubt, surprise, disapproval, confusion, indifference, frustration, sarcasm, etc.\n",
+ "Be creative to start every exchanges in conversation.\n",
+ "Use as diverse as possible expressions or idioms Americans use in daily conversations.\n",
+ "Return these conversations in json format, without code block such as ```json```.\n",
+ "Check the validity of the json format before returning it.\n",
+ "\n",
+ "Example of a conversation:\n",
+ "\n",
+ "{\n",
+ " \"conversations\": [\n",
+ " {\n",
+ " \"topic\": \"The Importance of Renewable Energy\",\n",
+ " \"exchanges\": [\n",
+ " {\n",
+ " \"Alloy\": \"I really think we should invest more in renewable energy sources.\"\n",
+ " },\n",
+ " {\n",
+ " \"Nova\": \"I agree, but do you think it's feasible for all countries right now?\"\n",
+ " },\n",
+ " {\n",
+ " \"Alloy\": \"Definitely! With the right policies and technological advancements, it can be achieved.\"\n",
+ " },\n",
+ " {\n",
+ " \"Nova\": \"True, but the initial costs might be a hurdle for some developing nations.\"\n",
+ " },\n",
+ " {\n",
+ " \"Alloy\": \"That's valid. Perhaps international support could help bridge that gap.\"\n",
+ " }\n",
+ " ]\n",
+ " },\n",
+ " ],\n",
+ "}\n",
+ "\"\"\"\n",
+ "\n",
+ "responds = get_response(user_prompt, role_definition, model=\"gpt-4o-mini\")\n",
+ "\n",
+ "# load json\n",
+ "conversations = json.loads(responds)\n",
+ "\n",
+ "# extract first three words from the user prompt to use as the filename prefix\n",
+ "whole_audio_filename_prefix = f\"Conversation_{'-'.join(user_prompt.strip().rstrip(',.!?\\\")').split(' ')[:2])}_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n",
+ "\n",
+ "# dump json to a file\n",
+ "with open(f\"{whole_audio_filename_prefix}.json\", \"w\") as file:\n",
+ " json.dump(conversations, file)\n",
+ "\n",
+ "# get each conversation, topic and exchanges, and each exchange\n",
+ "with open(f\"{whole_audio_filename_prefix}.md\", \"w\") as file:\n",
+ " for conversation in conversations['conversations']:\n",
+ " topic = conversation['topic']\n",
+ " exchanges = conversation['exchanges']\n",
+ " print(f\"\\nConversation {conversations['conversations'].index(conversation)+1}: {topic}\")\n",
+ " file.write(f\"\\n## Conversation {conversations['conversations'].index(conversation)+1}: {topic}\\n\")\n",
+ " for exchange in exchanges:\n",
+ " for speaker, text in exchange.items():\n",
+ " print(f\"{speaker}: {text}\")\n",
+ " file.write(f\"\\n**{speaker}**: {text.replace(\"—\", \" — \")}\\n\")\n",
+ "\n",
+ "print(\"\\nGenerating the audio file...\")\n",
+ "whole_audio = AudioSegment.silent(duration=1000)\n",
+ "# get each conversation, topic and exchanges, and each exchange\n",
+ "for conversation in conversations['conversations']:\n",
+ " whole_audio += conversation_audio(conversation)\n",
+ "\n",
+ "whole_audio += AudioSegment.from_file(\"resources/ending.mp3\")\n",
+ "whole_audio_filename = f\"{whole_audio_filename_prefix}.mp3\"\n",
+ "whole_audio.export(whole_audio_filename, format=\"mp3\")\n",
+ " \n",
+ "add_metadata(whole_audio_filename, 'Artwork.png', 'tts', 'Daily Speech Training - Conversations', 'SPEECH')\n",
+ "print(f\"\\nAudio file saved as {whole_audio_filename}!\") \n",
+ "IPython.display.Audio(whole_audio_filename)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Generating audio from your own text.\n",
+ "\n",
+ "text = \"\"\"\n",
+ "\n",
+ "\"\"\"\n",
+ "\n",
+ "IPython.display.Audio(get_openai_tts_audio(text, mp3_file_path))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Generating audio from a text file.\n",
+ "\n",
+ "text_file_path = '/Users/joker/Desktop/Ten Lessons I Wish I Had Been Taught'\n",
+ "\n",
+ "# if file has no extension, add .mp3, if it has, replace it with .mp3\n",
+ "if text_file_path.endswith('.md'):\n",
+ " mp3_file_path = text_file_path.replace('.md', '.mp3')\n",
+ "else:\n",
+ " mp3_file_path = f'{text_file_path}.mp3'\n",
+ "\n",
+ "text = markdown_to_text(read_text_from_file(text_file_path))\n",
+ "\n",
+ "IPython.display.Audio(get_openai_tts_audio(text, mp3_file_path))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# add meta data to all mp3 in current dir\n",
+ "for file in os.listdir():\n",
+ " if file.endswith('.mp3'):\n",
+ " add_metadata(file, 'Artwork.png', 'tts', 'Daily Speech Training', 'SPEECH')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "base",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.zip b/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.zip
new file mode 100644
index 00000000..3dd68f5b
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/8.4-daily-speech-exercises.zip differ
diff --git a/1000-hours/public/jupyter-notebooks/edge-tts-valcab-pronounciation.ipynb b/1000-hours/public/jupyter-notebooks/edge-tts-valcab-pronounciation.ipynb
index 512c9ff7..412b1b5a 100644
--- a/1000-hours/public/jupyter-notebooks/edge-tts-valcab-pronounciation.ipynb
+++ b/1000-hours/public/jupyter-notebooks/edge-tts-valcab-pronounciation.ipynb
@@ -1247,7 +1247,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.12.2"
+ "version": "3.12.4"
}
},
"nbformat": 4,
diff --git a/1000-hours/public/jupyter-notebooks/phonetics-fill-in-exercise.ipynb b/1000-hours/public/jupyter-notebooks/phonetics-fill-in-exercise.ipynb
new file mode 100644
index 00000000..aa9fbbb3
--- /dev/null
+++ b/1000-hours/public/jupyter-notebooks/phonetics-fill-in-exercise.ipynb
@@ -0,0 +1,188 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%pip install python-vlc"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import requests\n",
+ "import json\n",
+ "import vlc\n",
+ "import re\n",
+ "import random\n",
+ "from IPython.display import Audio\n",
+ "\n",
+ "import json\n",
+ "import requests\n",
+ "\n",
+ "def load_json_database(source):\n",
+ " records = []\n",
+ " \n",
+ " def parse_json_lines(lines):\n",
+ " for line in lines:\n",
+ " if line:\n",
+ " try:\n",
+ " record = json.loads(line)\n",
+ " records.append(record)\n",
+ " except json.JSONDecodeError as e:\n",
+ " print(f\"Error parsing JSON: {e}\")\n",
+ "\n",
+ " try:\n",
+ " if source.startswith('http://') or source.startswith('https://'):\n",
+ " # Handle as URL\n",
+ " response = requests.get(source)\n",
+ " response.raise_for_status() # Raise an error for bad status codes\n",
+ " parse_json_lines(response.iter_lines(decode_unicode=True))\n",
+ " else:\n",
+ " # Handle as file\n",
+ " with open(source, 'r', encoding='utf-8') as file:\n",
+ " parse_json_lines(file)\n",
+ " except requests.exceptions.RequestException as e:\n",
+ " print(f\"Error fetching data from URL: {e}\")\n",
+ " except FileNotFoundError as e:\n",
+ " print(f\"Error opening file: {e}\")\n",
+ " except Exception as e:\n",
+ " print(f\"An unexpected error occurred: {e}\")\n",
+ " \n",
+ " return records\n",
+ "\n",
+ "url = \"https://raw.githubusercontent.com/zelic91/camdict/main/cam_dict.refined.json\"\n",
+ "json_database = load_json_database(url)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def search_in_json_database(database, search_word, region):\n",
+ " for record in database:\n",
+ " # 检查 word 字段是否匹配\n",
+ " if record.get('word') == search_word:\n",
+ " # 找到匹配项后,获取美式发音信息\n",
+ " pos_items = record.get('pos_items', [])\n",
+ " for pos_item in pos_items:\n",
+ " pronunciations = pos_item.get('pronunciations', [])\n",
+ " for pronunciation in pronunciations:\n",
+ " if pronunciation.get('region') == region:\n",
+ " # 找到美式发音,返回相关信息\n",
+ " return {\n",
+ " 'pronunciation': pronunciation.get('pronunciation'),\n",
+ " 'audio': pronunciation.get('audio')\n",
+ " }\n",
+ " # 如果没有找到匹配的 word 字段,返回 'not exist'\n",
+ " return 'not exist'\n",
+ "\n",
+ "def replace_with_underscores(match):\n",
+ " return '_' * len(match.group(0))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "broadband\n",
+ "https://dictionary.cambridge.org/media/english/us_pron/c/cus/cus00/cus00276.mp3\n",
+ "Fill vowels in blanks: ˈbr__d.b_nd\n",
+ "Fill in consonants in blanks: ˈ__ɑː_._æ__\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# get a random word from the database\n",
+ "\n",
+ "vowel_phonetics = re.compile(r'ɑː|ɑːr|ʌ||iː|ɪ|i|ɪr|ʊ|ʊr|uː|ʊr|e|er|æ|ə|ɚ|ɝː|ɒ|ɔː|ɔːr|ɔɪ|aɪ|aɪr|eɪ|aʊ|aʊr|oʊ|')\n",
+ "consonant_phonetics = re.compile(r'p|b|t|d|k|ɡ|f|v|θ|ð|s|z|ʃ|ʒ|tʃ|dʒ|r|h|l|t̬|j|w|ŋ|n|m|tr|dr|ts|dz|br|pr|fr|ɡr|θr|dr|ʃr|kr|bl|kl|ɡl|fl|pl|sl|sp|st|sk|sm|sn|sw|str|spr|skr|spl|sfr|skw|skr|skl|')\n",
+ "\n",
+ "# if the word is with certain enddings such as 'es, ed, ing', get another word\n",
+ "random_word = random.choice(json_database)\n",
+ "while random_word['word'].endswith(('ed', 'ing', 'es', 'ts', 'ks', 'ds', 'ps', 'bs', 'gs', 'ls', 'rs', 'ms', 'ns', 'er', 'est')):\n",
+ " random_word = random.choice(json_database)\n",
+ "\n",
+ "# get pronunciation of the random word with region 'us'\n",
+ "random_word_us = search_in_json_database(json_database, random_word['word'], 'us')\n",
+ "\n",
+ "# get the word's phonetics\n",
+ "random_word_entry = random_word['word']\n",
+ "print(random_word_entry)\n",
+ "\n",
+ "random_word_phonetics = random_word_us['pronunciation']\n",
+ "\n",
+ "# get the audio url of the word\n",
+ "random_word_us_audio_url = random_word_us['audio']\n",
+ "print(random_word_us_audio_url)\n",
+ "\n",
+ "blank_vowel_phonetics = re.sub(vowel_phonetics, replace_with_underscores, random_word_phonetics)\n",
+ "blank_consonant_phonetics = re.sub(consonant_phonetics, replace_with_underscores, random_word_phonetics)\n",
+ "\n",
+ "# fill vowels in blanks\n",
+ "print(f'Fill vowels in blanks: {blank_vowel_phonetics}')\n",
+ "\n",
+ "# fill consonants in blanks\n",
+ "print(f'Fill in consonants in blanks: {blank_consonant_phonetics}')\n",
+ "\n",
+ "# play the audio\n",
+ "player = vlc.MediaPlayer(random_word_us['audio'])\n",
+ "player.play()\n",
+ "\n",
+ "# display the audio\n",
+ "Audio(url=random_word_us_audio_url)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "base",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/1000-hours/public/jupyter-notebooks/resources/Artwork.png b/1000-hours/public/jupyter-notebooks/resources/Artwork.png
new file mode 100644
index 00000000..8ae4e581
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Artwork.png differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Basso.aiff b/1000-hours/public/jupyter-notebooks/resources/Basso.aiff
new file mode 100644
index 00000000..e2cf1cdf
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Basso.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Blow.aiff b/1000-hours/public/jupyter-notebooks/resources/Blow.aiff
new file mode 100644
index 00000000..d91a572d
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Blow.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Bottle.aiff b/1000-hours/public/jupyter-notebooks/resources/Bottle.aiff
new file mode 100644
index 00000000..b9f92afc
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Bottle.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Frog.aiff b/1000-hours/public/jupyter-notebooks/resources/Frog.aiff
new file mode 100644
index 00000000..86fb39da
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Frog.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Funk.aiff b/1000-hours/public/jupyter-notebooks/resources/Funk.aiff
new file mode 100644
index 00000000..4be05108
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Funk.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Glass.aiff b/1000-hours/public/jupyter-notebooks/resources/Glass.aiff
new file mode 100644
index 00000000..e07aeed9
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Glass.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Hero.aiff b/1000-hours/public/jupyter-notebooks/resources/Hero.aiff
new file mode 100644
index 00000000..bd371ab8
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Hero.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Morse.aiff b/1000-hours/public/jupyter-notebooks/resources/Morse.aiff
new file mode 100644
index 00000000..fa21a269
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Morse.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Ping.aiff b/1000-hours/public/jupyter-notebooks/resources/Ping.aiff
new file mode 100644
index 00000000..4d29f10e
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Ping.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Pop.aiff b/1000-hours/public/jupyter-notebooks/resources/Pop.aiff
new file mode 100644
index 00000000..9700859d
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Pop.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Purr.aiff b/1000-hours/public/jupyter-notebooks/resources/Purr.aiff
new file mode 100644
index 00000000..3f9ea306
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Purr.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Sosumi.aiff b/1000-hours/public/jupyter-notebooks/resources/Sosumi.aiff
new file mode 100644
index 00000000..5b6bf16e
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Sosumi.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Submarine.aiff b/1000-hours/public/jupyter-notebooks/resources/Submarine.aiff
new file mode 100644
index 00000000..2e2ee188
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Submarine.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/Tink.aiff b/1000-hours/public/jupyter-notebooks/resources/Tink.aiff
new file mode 100644
index 00000000..6b413aa6
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/Tink.aiff differ
diff --git a/1000-hours/public/jupyter-notebooks/resources/ending.mp3 b/1000-hours/public/jupyter-notebooks/resources/ending.mp3
new file mode 100644
index 00000000..27567f76
Binary files /dev/null and b/1000-hours/public/jupyter-notebooks/resources/ending.mp3 differ
diff --git a/1000-hours/sounds-of-american-english/1-phonemes.md b/1000-hours/sounds-of-american-english/1-phonemes.md
index 38730854..d98500a5 100644
--- a/1000-hours/sounds-of-american-english/1-phonemes.md
+++ b/1000-hours/sounds-of-american-english/1-phonemes.md
@@ -359,9 +359,9 @@
| ŋ |
-thank θæŋk |
+thank θæŋk |
ŋ |
-thank θæŋk |
+thank θæŋk |
| l |
diff --git a/1000-hours/sounds-of-american-english/3.2.11-mnŋ.md b/1000-hours/sounds-of-american-english/3.2.11-mnŋ.md
index 2d65218c..47191b86 100644
--- a/1000-hours/sounds-of-american-english/3.2.11-mnŋ.md
+++ b/1000-hours/sounds-of-american-english/3.2.11-mnŋ.md
@@ -28,9 +28,9 @@
| ŋ |
-thank θæŋk |
+thank θæŋk |
ŋ |
-thank θæŋk |
+thank θæŋk |
diff --git a/1000-hours/sounds-of-american-english/8-appendix.md b/1000-hours/sounds-of-american-english/8-appendix.md
new file mode 100644
index 00000000..b459aa1e
--- /dev/null
+++ b/1000-hours/sounds-of-american-english/8-appendix.md
@@ -0,0 +1,3 @@
+# 附录
+
+这里补充的是一些日常可以使用的桌面版工具。
\ No newline at end of file
diff --git a/1000-hours/sounds-of-american-english/8.1-inputting-phonemes-and-symbols.md b/1000-hours/sounds-of-american-english/8.1-inputting-phonemes-and-symbols.md
new file mode 100644
index 00000000..c9fe58bb
--- /dev/null
+++ b/1000-hours/sounds-of-american-english/8.1-inputting-phonemes-and-symbols.md
@@ -0,0 +1,63 @@
+# 8.1. 输入音标与特殊符号
+
+在电子文档中输入音标符号(及其其它特殊符号)从来都很麻烦。
+
+再一次,我用 Alfred 作为辅助,以下是 workflow 文件:
+
+> [IPA-Phonetic-Symbols](https:///1000h.org/public/alfred-workflows/IPA-Phonetic-Symbols.alfredworkflow)
+
+以启动关键字 `ipae` 为例 —— 呼出 Alfred:
+
+
+
+这时,就可以用 `CMD + 数字` 的方式,将对应的符号插入当前文本。比如,`CMD + 4` 就是将 ɝː 插入当前文本编辑器。
+
+以下罗列的是各个符号对应的 Alfred 关键字(Keywords):
+
+| 关键字(Keyword) | 符号(Symbol) |
+| ----- | ----- |
+| `ipaa` | ʌ |
+| `ipaaa` | ɑ |
+| `ipaae` | æ |
+| `ipae` | ə |
+| `ipaeeer` | ɝː |
+| `ipaer` | ɚ |
+| `ipaes` | ᵊ |
+| `ipai` | ɪ |
+| `ipau` | ʊ |
+| `ipao` | ɒ |
+| `ipaoo` | ɔ |
+| `ipal` | ɤ |
+| `ipatd` | t̠ |
+| `ipatg` | ʔ |
+| `ipats` | ᵗ |
+| `ipan` | ŋ |
+| `ipath` | θ |
+| `ipad` | ð |
+| `ipas` | ʃ |
+| `ipaz` | ʒ |
+| `ipaj` | ʲ |
+| `ipaw` | ʷ |
+| `ipa1` | ◌̅ flat |
+| `ipa2` | ◌́ rise |
+| `ipa3` | ◌̌ fall-rise |
+| `ipa4` | ◌̀ fall |
+| `ipa5` | ◌̂ pitch raise |
+| `ipa6` | ◌̲ long vowel |
+| `ipa7` | ◌̩ syllabic consonant |
+| `ipa8` | ◌̥ voiceless |
+| `ipa9` | ◌̚ stop |
+| `ipa0` | ◌ |
+| `ipa`: | ː long vowel symbol |
+| `ipa`" | ˈ prime stress |
+| `ipa`' | ˌ secondary stress |
+| `ipa-` | ◌‿◌ linking |
+| `ipa\|` | ‖ grouping boundary |
+| `-->` | ⭢ |
+| `<--` | ⭠ |
+| `<->` | ⭤ |
+| `irise` | ⤴ senetence intonation rise |
+| `idown` | ⤵ senetence intonation fall |
+
+
+
diff --git a/1000-hours/sounds-of-american-english/8.2-cepd-phonetics-and-sound.md b/1000-hours/sounds-of-american-english/8.2-cepd-phonetics-and-sound.md
new file mode 100644
index 00000000..87fc6707
--- /dev/null
+++ b/1000-hours/sounds-of-american-english/8.2-cepd-phonetics-and-sound.md
@@ -0,0 +1,110 @@
+# 8.2. 获取 CEPD 音标
+
+macOS 上有一个收费软件,[Alfred](https://www.alfredapp.com/),可以用来定义很多快捷流程(workflow)去完成相对复杂的任务。比如,通过设定关键字启动一个 Python 脚本,查询某个单词(甚至整个句子)在《剑桥英语发声词典》(CEPD)中的音标。
+
+> Alfred 的使用方法,参见:
+> https://github.com/xiaolai/apple-computer-literacy/blob/main/alfred.md
+
+在 Github 上有一个开源的仓库,提供了《剑桥英语发声词典》的 json 格式数据库:
+
+> https://github.com/zelic91/camdict
+
+将这个仓库里的 [cam_dict.refined.json](https://github.com/zelic91/camdict/raw/main/cam_dict.refined.json) 下载并保存到本地某个位置。
+
+我写了一个 Alfred 的 workflow,使用的是 macOS 系统自带的 python3:`/usr/bin/python3`:
+
+> [CEPD-phonetic-transcription.alfredworkflow](https:///1000h.org/public/alfred-workflows/CEPD-phonetic-transcription.alfredworkflow)
+
+下载这个文件之后,导入 Alfred。
+
+在使用之前要注意:
+
+> * 修改各个 Python 脚本内的 `cam_dict.refined.json` 的文件路径
+
+这个 workflow 可用的启动关键字分别是:
+
+> * `cams`:查询音标(美式发音)
+> * `camk`:查询音标(英式发音)
+> * `camsd`:用浏览器打开 CEPD 真人示范录音(美式发音)在线网址
+> * `camsd`:用浏览器打开 CEPD 真人示范录音(英式发音)在线网址
+> * `camw`:用浏览器打开 CEPD 查询页面
+> * `ipa`:返回 CMU(卡耐基梅隆大学)音标库中的音标
+
+以下是查询音标的 workflow(启动关键字为 `cams`)中的 python 脚本:
+
+```python
+#
+# NOTE: Python 2 is deprecated in macOS, and has been removed from macOS 12.3+
+#
+import sys
+import json
+
+# 假设你的 JSON 数据库是一个 JSON 文件,我们将从文件中加载数据
+# 如果 JSON 数据在内存中或其他格式,你可能需要修改这部分代码
+def load_json_database(file_path):
+ records = []
+ with open(file_path, 'r') as file:
+ for line in file:
+ try:
+ record = json.loads(line)
+ records.append(record)
+ except json.JSONDecodeError as e:
+ print(f"Error parsing JSON: {e}")
+ return records
+
+# 在 JSON 数据库中检索 word
+def search_in_json_database(database, search_word, region):
+ for record in database:
+ # 检查 word 字段是否匹配
+ if record.get('word') == search_word:
+ # 找到匹配项后,获取美式发音信息
+ pos_items = record.get('pos_items', [])
+ for pos_item in pos_items:
+ pronunciations = pos_item.get('pronunciations', [])
+ for pronunciation in pronunciations:
+ if pronunciation.get('region') == region:
+ # 找到美式发音,返回相关信息
+ return {
+ 'pronunciation': pronunciation.get('pronunciation'),
+ 'audio': pronunciation.get('audio')
+ }
+ # 如果没有找到匹配的 word 字段,返回 'not exist'
+ return 'not exist'
+
+# cam_dict.refined.json 的文件路径
+json_db_file_path = '/Users/joker/github/camdict/cam_dict.refined.json'
+
+# 要检索的单词
+search_word = sys.argv[1]
+
+region = "us"
+
+json_database = load_json_database(json_db_file_path)
+
+# replace punctuations in text with space
+punctuations = ",.?!;"
+for p in punctuations:
+ search_word = search_word.replace(p, " ")
+words = [word for word in search_word.split() if word.strip() != '']
+
+phonetics = []
+
+for w in words:
+ # 检索并获取结果
+ w = w.strip().lower()
+
+ if w[-1] in punctuations:
+ w = w.rstrip(",.?!;")
+ result = search_in_json_database(json_database, w, region)
+
+ if result == 'not exist':
+ phonetics.append(w+"*")
+ else:
+ phonetics.append(result['pronunciation'])
+
+returnvalue = ''
+for p in phonetics:
+ returnvalue += p + ' '
+
+sys.stdout.write(returnvalue.strip())
+```
\ No newline at end of file
diff --git a/1000-hours/sounds-of-american-english/8.3-phoneme-exercises.md b/1000-hours/sounds-of-american-english/8.3-phoneme-exercises.md
new file mode 100644
index 00000000..705017e2
--- /dev/null
+++ b/1000-hours/sounds-of-american-english/8.3-phoneme-exercises.md
@@ -0,0 +1,127 @@
+# 8.3. 音标练习
+
+这是一个 Jupyter Notebook,用来建立音标符号与声音之间的关联。
+
+每次执行,随即从《剑桥英语发声词典》中选取一个词汇,播放真人朗读语音,而后要求对元音或者辅音填空……
+
+> [phonetics-fill-in-exercise.ipynb](https://1000h.org/public/jupyter-notebooks/phonetics-fill-in-exercise.ipynb)
+
+执行结果如下:
+
+
+
+Jupyter Notebook 代码如下:
+
+``` Python
+# %%
+%pip install python-vlc
+
+# %%
+import requests
+import json
+import vlc
+import re
+import random
+from IPython.display import Audio
+
+import json
+import requests
+
+def load_json_database(source):
+ records = []
+
+ def parse_json_lines(lines):
+ for line in lines:
+ if line:
+ try:
+ record = json.loads(line)
+ records.append(record)
+ except json.JSONDecodeError as e:
+ print(f"Error parsing JSON: {e}")
+
+ try:
+ if source.startswith('http://') or source.startswith('https://'):
+ # Handle as URL
+ response = requests.get(source)
+ response.raise_for_status() # Raise an error for bad status codes
+ parse_json_lines(response.iter_lines(decode_unicode=True))
+ else:
+ # Handle as file
+ with open(source, 'r', encoding='utf-8') as file:
+ parse_json_lines(file)
+ except requests.exceptions.RequestException as e:
+ print(f"Error fetching data from URL: {e}")
+ except FileNotFoundError as e:
+ print(f"Error opening file: {e}")
+ except Exception as e:
+ print(f"An unexpected error occurred: {e}")
+
+ return records
+
+url = "https://raw.githubusercontent.com/zelic91/camdict/main/cam_dict.refined.json"
+json_database = load_json_database(url)
+
+
+# %%
+def search_in_json_database(database, search_word, region):
+ for record in database:
+ # 检查 word 字段是否匹配
+ if record.get('word') == search_word:
+ # 找到匹配项后,获取美式发音信息
+ pos_items = record.get('pos_items', [])
+ for pos_item in pos_items:
+ pronunciations = pos_item.get('pronunciations', [])
+ for pronunciation in pronunciations:
+ if pronunciation.get('region') == region:
+ # 找到美式发音,返回相关信息
+ return {
+ 'pronunciation': pronunciation.get('pronunciation'),
+ 'audio': pronunciation.get('audio')
+ }
+ # 如果没有找到匹配的 word 字段,返回 'not exist'
+ return 'not exist'
+
+def replace_with_underscores(match):
+ return '_' * len(match.group(0))
+
+# %%
+# get a random word from the database
+
+vowel_phonetics = re.compile(r'ɑː|ɑːr|ʌ||iː|ɪ|i|ɪr|ʊ|ʊr|uː|ʊr|e|er|æ|ə|ɚ|ɝː|ɒ|ɔː|ɔːr|ɔɪ|aɪ|aɪr|eɪ|aʊ|aʊr|oʊ|')
+consonant_phonetics = re.compile(r'p|b|t|d|k|ɡ|f|v|θ|ð|s|z|ʃ|ʒ|tʃ|dʒ|r|h|l|t̬|j|w|ŋ|n|m|tr|dr|ts|dz|br|pr|fr|ɡr|θr|dr|ʃr|kr|bl|kl|ɡl|fl|pl|sl|sp|st|sk|sm|sn|sw|str|spr|skr|spl|sfr|skw|skr|skl|')
+
+# if the word is with certain enddings such as 'es, ed, ing', get another word
+random_word = random.choice(json_database)
+while random_word['word'].endswith(('ed', 'ing', 'es', 'ts', 'ks', 'ds', 'ps', 'bs', 'gs', 'ls', 'rs', 'ms', 'ns', 'er', 'est')):
+ random_word = random.choice(json_database)
+
+# get pronunciation of the random word with region 'us'
+random_word_us = search_in_json_database(json_database, random_word['word'], 'us')
+
+# get the word's phonetics
+random_word_entry = random_word['word']
+print(random_word_entry)
+
+random_word_phonetics = random_word_us['pronunciation']
+
+# get the audio url of the word
+random_word_us_audio_url = random_word_us['audio']
+print(random_word_us_audio_url)
+
+blank_vowel_phonetics = re.sub(vowel_phonetics, replace_with_underscores, random_word_phonetics)
+blank_consonant_phonetics = re.sub(consonant_phonetics, replace_with_underscores, random_word_phonetics)
+
+# fill vowels in blanks
+print(f'Fill vowels in blanks: {blank_vowel_phonetics}')
+
+# fill consonants in blanks
+print(f'Fill in consonants in blanks: {blank_consonant_phonetics}')
+
+# play the audio
+player = vlc.MediaPlayer(random_word_us['audio'])
+player.play()
+
+# display the audio
+Audio(url=random_word_us_audio_url)
+```
+
diff --git a/1000-hours/sounds-of-american-english/8.4-daily-speech-exercises.md b/1000-hours/sounds-of-american-english/8.4-daily-speech-exercises.md
new file mode 100644
index 00000000..49687dc0
--- /dev/null
+++ b/1000-hours/sounds-of-american-english/8.4-daily-speech-exercises.md
@@ -0,0 +1,10 @@
+# 8.4. 每日练习语音生成
+
+这是一个 Jupyter Notebook —— 需要有自己的 OpenAI API Key。指定 `user-prompt`,而后生成
+
+> * 一个篇章及其 markdown 文件,以及由 alloy 和 nova 朗读的 mp3 文件
+> * 同一话题的两个对话,及其 mp3 文件
+
+压缩包链接:
+
+> [8.4-daily-speech-exercises.zip](https://1000h.org/public/jupyter-notebooks/8.4-daily-speech-exercises.zip)