From 786b38c1ec0dc6e8f688aeca6379a34a1dec2762 Mon Sep 17 00:00:00 2001 From: krmanik <12841290+krmanik@users.noreply.github.com> Date: Sun, 11 Feb 2024 17:34:10 +0800 Subject: [PATCH] add msedge tts to generate audio for simplified --- package.json | 7 +- plugins/custom-docusaurus-plugin/index.js | 9 +++ src/dict/dict.ts | 2 +- src/pages/create.tsx | 79 ++++++++++++++++++-- tsconfig.json | 3 +- yarn.lock | 90 ++++++++++++++++++++++- 6 files changed, 175 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 05a8ae8..e7d126d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@docusaurus/preset-classic": "^3.1.1", "@mdx-js/react": "^1.6.22", "buffer": "^6.0.3", + "bufferutil": "^4.0.8", "chinese-s2t": "^1.0.0", "chinese-to-pinyin": "^1.3.1", "clsx": "^1.2.1", @@ -29,9 +30,11 @@ "genanki-js": "^2.0.0", "hanzi-writer": "^3.3.0", "jieba-wasm": "^0.0.2", + "msedge-tts": "^1.3.4", "path-browserify": "^1.0.1", "primereact": "^10.5.0", "prism-react-renderer": "^1.3.5", + "process": "^0.11.10", "react": "^18.1.0", "react-dom": "^18.1.0", "react-icons": "^4.4.0", @@ -40,7 +43,9 @@ "sql.js": "^1.10.2", "stream-browserify": "^3.0.0", "unzipit": "^1.4.3", - "url-loader": "^4.1.1" + "url-loader": "^4.1.1", + "utf-8-validate": "^6.0.3", + "webpack": "^5.90.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", diff --git a/plugins/custom-docusaurus-plugin/index.js b/plugins/custom-docusaurus-plugin/index.js index fb19477..5533d4e 100644 --- a/plugins/custom-docusaurus-plugin/index.js +++ b/plugins/custom-docusaurus-plugin/index.js @@ -1,3 +1,5 @@ +const webpack = require('webpack'); + module.exports = function (context, options) { return { name: 'custom-docusaurus-plugin', @@ -12,11 +14,18 @@ module.exports = function (context, options) { crypto: require.resolve("crypto-browserify"), buffer: require.resolve("buffer/"), stream: require.resolve("stream-browserify"), + process: "process/browser", }, fallback: { fs: false, + ws: false, } }, + plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser' + }) + ] }; }, }; diff --git a/src/dict/dict.ts b/src/dict/dict.ts index 7ca615f..10b109a 100644 --- a/src/dict/dict.ts +++ b/src/dict/dict.ts @@ -2,7 +2,7 @@ import { unzip } from 'unzipit'; import pinzhu from './pinyinzhuyin'; let dict; -let host = "http://localhost:3000/Anki-xiehanzi/"; +let host = "https://krmanik.github.io/Anki-xiehanzi"; // https://github.com/cschiller/zhongwen class ZhongwenDictionary { diff --git a/src/pages/create.tsx b/src/pages/create.tsx index af4ce0a..926d76e 100644 --- a/src/pages/create.tsx +++ b/src/pages/create.tsx @@ -33,6 +33,9 @@ import pinzhu from "../dict/pinyinzhuyin"; import create_styles from "./create.module.css"; import styles from "./index.module.css"; +import { MsEdgeTTS, OUTPUT_FORMAT } from "msedge-tts"; +import { ProgressBar } from "primereact/progressbar"; + export default function CreateDeck(): JSX.Element { const [words, setWords] = useState< { @@ -62,6 +65,7 @@ export default function CreateDeck(): JSX.Element { const [texAreaValue, setTexAreaValue] = React.useState(""); const [db, setDb] = useState(null); const dt = useRef(null); + const [progressbarValue, setProgressbarValue] = useState(0); const exportCSV = (selectionOnly) => { dt.current.exportCSV({ selectionOnly }); @@ -454,6 +458,26 @@ export default function CreateDeck(): JSX.Element { setWords([...words, ..._words]); } + // https://github.com/feross/stream-to-blob/blob/master/index.js + function streamToBlob(stream, mimeType): Promise { + if (mimeType != null && typeof mimeType !== "string") { + throw new Error("Invalid mimetype, expected string."); + } + return new Promise((resolve, reject) => { + const chunks = []; + stream + .on("data", (chunk) => chunks.push(chunk)) + .once("end", () => { + const blob = + mimeType != null + ? new Blob(chunks, { type: mimeType }) + : new Blob(chunks); + resolve(blob); + }) + .once("error", reject); + }); + } + async function generateDeck(e) { let flds = []; let req = []; @@ -582,11 +606,11 @@ for (var _hide of hideList) { }); } - const modelId = Math.floor(Math.random() * (1 << 30) + (1 << 30)); + // const modelId = Math.floor(Math.random() * (1 << 30) + (1 << 30)); const m = new Model({ name: "Basic - (Anki-xiehanzi)", - id: modelId.toString(), + id: "1969669503", flds: flds, css: CONSTANTS.DECK_CSS, req: req, @@ -688,22 +712,59 @@ for (var _hide of hideList) { if (!response.ok) { return null; } + + progress += 1; + setProgressbarValue((progress / total) * 100); + return response.blob(); }; - Promise.all(mediaFiles.map(fetchFile)) + const wordFiles = words.map((word) => word.Simplified); + + let progress = 0; + let total = wordFiles.length + mediaFiles.length; + + const fetchAudio = async (word) => { + const tts = new MsEdgeTTS(); + await tts.setMetadata( + "zh-CN-XiaoxiaoNeural", + OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3 + ); + const readable = tts.toStream(word); + const blob = await streamToBlob(readable, "audio/mp3"); + + progress += 1; + setProgressbarValue((progress / total) * 100); + + return blob; + }; + + // edge tts mp3 audio + Promise.all(wordFiles.map(fetchAudio)) .then((blobs) => { blobs.forEach((blob, index) => { - if (blob) { - p.addMedia(blob, mediaFiles[index]); - } + p.addMedia(blob, `cmn-${wordFiles[index]}.mp3`); }); }) .catch((error) => { console.error("Error fetching or adding media:", error); }) - .finally(() => { - p.writeToFile(`${deckName}.apkg`); + .finally(async () => { + // sidebar icons + Promise.all(mediaFiles.map(fetchFile)) + .then((blobs) => { + blobs.forEach((blob, index) => { + if (blob) { + p.addMedia(blob, mediaFiles[index]); + } + }); + }) + .catch((error) => { + console.error("Error fetching or adding media:", error); + }) + .finally(async () => { + p.writeToFile(`${deckName}.apkg`); + }); }); } @@ -905,6 +966,8 @@ for (var _hide of hideList) { )} + +