diff --git a/index.html b/index.html index ff09bca..87bc77b 100644 --- a/index.html +++ b/index.html @@ -58,8 +58,8 @@

香港圍頭話及客家話文字轉語音

香港本土語言保育協會

-
@@ -70,7 +70,7 @@

-

關於

+

關於


歡迎使用香港圍頭話及客家話文字轉語音(Text-to-Speech)朗讀器!

diff --git a/src/App.tsx b/src/App.tsx index 234e97f..333aedd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -34,8 +34,21 @@ export default function App() { } }, [textArea, btnAddSentence]); + const [screenshotMode, setScreenshotMode] = useState(false); const addSentence = useCallback(() => { if (!textArea.current) return; + if (textArea.current.value === "_screenshot") { + setScreenshotMode(true); + setSentences([ + { language: "waitau", voice: "male", inferenceMode: "online", voiceSpeed: 1, syllables: segment("天地玄黃,宇宙洪荒。") }, + { language: "waitau", voice: "female", inferenceMode: "online", voiceSpeed: 1, syllables: segment("日月盈昃,辰宿列張。") }, + { language: "hakka", voice: "male", inferenceMode: "online", voiceSpeed: 1, syllables: segment("天地玄黃,宇宙洪荒。") }, + { language: "hakka", voice: "female", inferenceMode: "online", voiceSpeed: 1, syllables: segment("日月盈昃,辰宿列張。") }, + ]); + textArea.current.value = ""; + resizeElements(); + return; + } setSentences([ ...textArea.current.value.split("\n").flatMap(text => (text.trim() ? [{ language, voice, inferenceMode, voiceSpeed, syllables: segment(text) }] : [])), ...sentences, @@ -83,12 +96,12 @@ export default function App() {
聲線
- - + +
-
- {sentences.map((sentence, i) => ( - + {sentences.flatMap((sentence, i) => ( + !screenshotMode || sentence.language === language + ? [ + , + ] + : [] ))}
diff --git a/src/AudioPlayer.tsx b/src/AudioPlayer.tsx index 612900f..bfe1522 100644 --- a/src/AudioPlayer.tsx +++ b/src/AudioPlayer.tsx @@ -34,7 +34,10 @@ export default function AudioPlayer({ setDownloadState, currSettingsDialogPage, setCurrSettingsDialogPage, + screenshotMode, }: SentenceComponentState) { + const overrideSeekBar = screenshotMode && syllables[0][0] === "t"; + useEffect(() => void context.resume(), []); const [buffer, setBuffer] = useState(); const [sourceNode, setSourceNode] = useState(); @@ -84,7 +87,7 @@ export default function AudioPlayer({ useEffect(() => { async function getDownloadComponents() { - if (inferenceMode === "online" || !db || downloadVersion || currSettingsDialogPage) return; + if (screenshotMode || inferenceMode === "online" || !db || downloadVersion || currSettingsDialogPage) return; setDownloadVersion(undefined); setDownloadError(undefined); setBuffer(undefined); @@ -107,7 +110,7 @@ export default function AudioPlayer({ const [generationRetryCounter, generationRetry] = useReducer((n: number) => n + 1, 0); const text = syllables.join(" "); useEffect(() => { - if (inferenceMode !== "online" && !downloadVersion) return; + if (screenshotMode || inferenceMode !== "online" && !downloadVersion) return; async function generateAudio() { const key = `${inferenceMode}/${voiceSpeed}/${downloadVersion}/${language}/${voice}`; let textToBuffer = audioCache.get(key); @@ -225,14 +228,14 @@ export default function AudioPlayer({ onClick={isPlaying === false ? playAudio : pauseAudio} aria-label={isPlaying === false ? "播放" : "暫停"} tabIndex={buffer ? 0 : -1}> - {isPlaying === false ? : } + {!overrideSeekBar && isPlaying === false ? : } - {(error || !buffer) &&
+ {!screenshotMode && (error || !buffer) &&
{error ?
diff --git a/src/SentenceCard.tsx b/src/SentenceCard.tsx index 01f4290..d67e852 100644 --- a/src/SentenceCard.tsx +++ b/src/SentenceCard.tsx @@ -140,6 +140,7 @@ export default function SentenceCard({ setDownloadState, currSettingsDialogPage, setCurrSettingsDialogPage, + screenshotMode, }: SentenceCardProps) { const [enabledEdges, setEnabledEdges] = useState(new Set()); const edges = useMemo(() => { @@ -172,7 +173,7 @@ export default function SentenceCard({ const chars: JSX.Element[] = []; while (i < edgesInGroup.end) { chars.push( - + {syllables[i]} {flattenedProns[i].length > 1 ? replaceHakkaTones(language, hakkaToneMode, flattenedProns[i]) : "\xa0"} , @@ -235,8 +236,8 @@ export default function SentenceCard({
{TERMINOLOGY[language]} {TERMINOLOGY[voice]} - {INFERENCE_MODE_TO_LABEL[inferenceMode]} - {voiceSpeed}× + {!screenshotMode && {INFERENCE_MODE_TO_LABEL[inferenceMode]}} + {!screenshotMode && {voiceSpeed}×}
{tables}
@@ -244,7 +245,8 @@ export default function SentenceCard({ sentence={{ language, voice, inferenceMode, voiceSpeed, syllables: inferenceMode === "lightweight" ? enabledEdgesProns : flattenedProns }} setDownloadState={setDownloadState} currSettingsDialogPage={currSettingsDialogPage} - setCurrSettingsDialogPage={setCurrSettingsDialogPage} /> + setCurrSettingsDialogPage={setCurrSettingsDialogPage} + screenshotMode={screenshotMode} />
; } diff --git a/src/SettingsDialog.tsx b/src/SettingsDialog.tsx index 17105de..7d343af 100644 --- a/src/SettingsDialog.tsx +++ b/src/SettingsDialog.tsx @@ -50,10 +50,10 @@ const SettingsDialog = forwardRef(functio - {currSettingsDialogPage &&

+ {currSettingsDialogPage &&

{currSettingsDialogPage === "settings" ? <> - 設定 + 設定 : <>

客家話標調方式
- +
diff --git a/src/consts.tsx b/src/consts.tsx index 256af0d..c6548e5 100644 --- a/src/consts.tsx +++ b/src/consts.tsx @@ -7,8 +7,8 @@ export const TERMINOLOGY: Record = { hakka: "客家話", male: "男聲", female: "女聲", - digits: "數字", diacritics: "調號", + digits: "數字", }; export const VOICE_TO_ICON: Record = { @@ -39,7 +39,7 @@ export const ALL_VOICES: readonly Voice[] = ["male", "female"]; export const ALL_INFERENCE_MODES: readonly InferenceMode[] = ["online", "offline", "lightweight"]; export { ALL_MODEL_COMPONENTS } from "./inference/infer"; export const ALL_AUDIO_COMPONENTS: readonly AudioComponent[] = ["chars", "words"]; -export const ALL_HAKKA_TONE_MODES: readonly HakkaToneMode[] = ["digits", "diacritics"]; +export const ALL_HAKKA_TONE_MODES: readonly HakkaToneMode[] = ["diacritics", "digits"]; export const MODEL_COMPONENT_TO_N_CHUNKS: Record = { enc: 2, diff --git a/src/index.css b/src/index.css index b391ec7..d82274f 100644 --- a/src/index.css +++ b/src/index.css @@ -38,6 +38,9 @@ a { @apply link link-primary no-underline; } + b { + @apply font-extrabold; + } ruby { @apply inline-flex flex-col-reverse items-center gap-0.5 align-bottom; } diff --git a/src/types.ts b/src/types.ts index 1530540..bce2158 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,7 @@ export interface Sentence { export type OfflineInferenceMode = "offline" | "lightweight"; export type InferenceMode = "online" | OfflineInferenceMode; -export type HakkaToneMode = "digits" | "diacritics"; +export type HakkaToneMode = "diacritics" | "digits"; export type ModelComponent = "enc" | "emb" | "sdp" | "flow" | "dec"; @@ -163,6 +163,7 @@ export interface SettingsDialogState { export interface SentenceComponentState extends SetDownloadStatus, SettingsDialogState { sentence: Sentence; + screenshotMode: boolean; } export interface Actions { diff --git a/tailwind.config.ts b/tailwind.config.ts index 34f086d..4109bd0 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -10,7 +10,7 @@ export default { extend: { fontFamily: { serif: [ - "Chiron Sung HK", + // "Chiron Sung HK", "Chiron Sung HK WS", "Times New Roman", "Times",