diff --git a/enjoy/src/constants.ts b/enjoy/src/constants.ts index d8dbd556..2ce6097a 100644 --- a/enjoy/src/constants.ts +++ b/enjoy/src/constants.ts @@ -376,7 +376,7 @@ export const IPA_MAPPING: { [key: string]: string } = { g: "g", q: "k", ɢ: "g", - ʔ: "", + ʔ: "t", ɡ: "g", m: "m", ɱ: "m", @@ -420,7 +420,7 @@ export const IPA_MAPPING: { [key: string]: string } = { ʈʃ: "tʃ", dʒ: "dʒ", ʋ: "v", - ɹ: "r", + ɹ: "ɹ", ɻ: "r", j: "j", ɰ: "w", @@ -444,29 +444,29 @@ export const IPA_MAPPING: { [key: string]: string } = { ɘ: "ə", ɵ: "ə", ɤ: "ɒ", - o: "ɔː", + o: "o", ə: "ə", - oː: "ɔː", - ɛ: "æ", + oː: "oː", + ɛ: "ɛ", œ: "æ", - ɜ: "əː", + ɜ: "ɜ", ɞ: "əː", ʌ: "ʌ", - ɔ: "ɔː", + ɔ: "ɔ", ɜː: "əː", uː: "uː", ɔː: "ɔː", - ɛː: "æ", + ɛː: "ɛ:", æ: "æ", - a: "ɑː", - ɶ: "ɑː", - ɐ: "ɑː", - ɑ: "ɑː", + a: "ɑ", + ɶ: "ɑ", + ɐ: "ə", + ɑ: "ɑ", ɒ: "ɒ", ɑː: "ɑː", "◌˞": "", - ɚ: "ɪə", - ɝ: "ɪə", + ɚ: "ɚ", + ɝ: "ɝ", ɹ̩: "r", eɪ: "eɪ", əʊ: "əʊ", @@ -474,20 +474,20 @@ export const IPA_MAPPING: { [key: string]: string } = { aɪ: "aɪ", ɔɪ: "ɔɪ", aʊ: "aʊ", - iə: "ɪə", - ɜr: "ɪə(r)", - ɑr: "ɑː(r)", - ɔr: "ɔː(r)", - oʊr: "əʊ(r)", - oːɹ: "ɔː(r)", - ir: "iː(r)", - ɪɹ: "ɪ(r)", - ɔːɹ: "ɔː(r)", - ɑːɹ: "ɑː(r)", - ʊɹ: "ʊ(r)", - ʊr: "ʊ(r)", - ɛr: "æ(r)", - ɛɹ: "æ(r)", + iə: "iə", + ɜr: "ɜr", + ɑr: "ɑr", + ɔr: "ɔr", + oʊr: "əʊr", + oːɹ: "ɔːɹ", + ir: "ir", + ɪɹ: "ɪɹ", + ɔːɹ: "ɔːɹ", + ɑːɹ: "ɑːɹ", + ʊɹ: "ʊɹ", + ʊr: "ʊɹ", + ɛr: "ɛr", + ɛɹ: "ɛɹ", əl: "ə", aɪɚ: "aɪ", aɪə: "aɪ", diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index b16228ba..8639fb0e 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -273,7 +273,7 @@ "editResource": "编辑资源", "deleteResource": "删除资源", "deleteResourceConfirmation": "您确定要删除资源 {{name}} 吗?", - "transcribeAudioConfirmation": "这将删除原来的语音文本,您确定要重新对 {{name}} 进行语音转文本吗?", + "transcribeMediaConfirmation": "这将删除原来的语音文本,您确定要重新对 {{name}} 进行语音转文本吗?", "localFile": "本地文件", "recentlyAdded": "最近添加", "resourcesYouAddedRecently": "最近添加的资源", diff --git a/enjoy/src/index.css b/enjoy/src/index.css index fe6a9eba..82e68e07 100644 --- a/enjoy/src/index.css +++ b/enjoy/src/index.css @@ -3,6 +3,7 @@ @import "@vidstack/react/player/styles/default/layouts/audio.css"; @import "@vidstack/react/player/styles/default/layouts/video.css"; @import "intl-tel-input/build/css/intlTelInput.css"; +@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap"); @tailwind base; @tailwind components; diff --git a/enjoy/src/renderer/components/audios/audio-player.tsx b/enjoy/src/renderer/components/audios/audio-player.tsx index d7da9e1f..ae75b6a1 100644 --- a/enjoy/src/renderer/components/audios/audio-player.tsx +++ b/enjoy/src/renderer/components/audios/audio-player.tsx @@ -6,17 +6,14 @@ import { MediaPlayerControls, MediaTabs, MediaCurrentRecording, + MediaPlayer, } from "@renderer/components"; -import { formatDuration } from "@renderer/lib/utils"; import { useAudio } from "@renderer/hooks"; export const AudioPlayer = (props: { id?: string; md5?: string }) => { const { id, md5 } = props; - const { media, currentTime, setMedia, setRef } = useContext( - MediaPlayerProviderContext - ); + const { setMedia } = useContext(MediaPlayerProviderContext); const { audio } = useAudio({ id, md5 }); - const ref = useRef(null); useEffect(() => { if (!audio) return; @@ -24,14 +21,6 @@ export const AudioPlayer = (props: { id?: string; md5?: string }) => { setMedia(audio); }, [audio]); - useEffect(() => { - setRef(ref); - - return () => { - setRef(null); - }; - }, [ref]); - return (
@@ -51,18 +40,7 @@ export const AudioPlayer = (props: { id?: string; md5?: string }) => {
-
-
-
- - {formatDuration(currentTime || 0)} - - / - - {formatDuration(media?.duration || 0)} - -
-
+
diff --git a/enjoy/src/renderer/components/medias/index.ts b/enjoy/src/renderer/components/medias/index.ts index d138b819..ba93a3a5 100644 --- a/enjoy/src/renderer/components/medias/index.ts +++ b/enjoy/src/renderer/components/medias/index.ts @@ -6,6 +6,7 @@ export * from "./media-current-recording"; export * from "./media-recorder"; export * from "./media-transcription"; export * from "./media-player"; +export * from "./media-provider"; export * from "./media-tabs"; export * from "./media-loading-modal"; export * from "./add-media-button"; diff --git a/enjoy/src/renderer/components/medias/media-caption.tsx b/enjoy/src/renderer/components/medias/media-caption.tsx index 99d02847..e99f5c03 100644 --- a/enjoy/src/renderer/components/medias/media-caption.tsx +++ b/enjoy/src/renderer/components/medias/media-caption.tsx @@ -5,7 +5,6 @@ import { Button, toast, ScrollArea, Separator } from "@renderer/components/ui"; import { t } from "i18next"; import { LanguagesIcon, SpeechIcon } from "lucide-react"; import { Timeline } from "echogarden/dist/utilities/Timeline.d.js"; -import { IPA_MAPPING } from "@/constants"; import { useAiCommand } from "@renderer/hooks"; import { LoaderIcon } from "lucide-react"; import { convertIpaToNormal } from "@/utils"; @@ -337,7 +336,7 @@ export const MediaCaption = () => {
{/* use the words splitted by caption text if it is matched with the timeline length, otherwise use the timeline */} - {caption.text.includes("-") + {caption.text.split(" ").length !== caption.timeline.length ? (caption.timeline || []).map((w, index) => (
{
{w.text}
{displayIpa && ( -
+
{w.timeline .map((t) => t.timeline @@ -379,7 +378,7 @@ export const MediaCaption = () => {
{word}
{displayIpa && ( -
+
{caption.timeline[index].timeline .map((t) => t.timeline @@ -417,7 +416,7 @@ export const MediaCaption = () => { {word.text}
- + / {word.timeline .map((t) => diff --git a/enjoy/src/renderer/components/medias/media-current-recording.tsx b/enjoy/src/renderer/components/medias/media-current-recording.tsx index 58d75af4..b4a2fa3b 100644 --- a/enjoy/src/renderer/components/medias/media-current-recording.tsx +++ b/enjoy/src/renderer/components/medias/media-current-recording.tsx @@ -37,6 +37,8 @@ import { ChevronDownIcon, MoreVerticalIcon, TextCursorInputIcon, + MicIcon, + SquareIcon, } from "lucide-react"; import { t } from "i18next"; import { formatDuration } from "@renderer/lib/utils"; @@ -46,6 +48,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { const { height = 192 } = props; const { isRecording, + setIsRecording, currentRecording, renderPitchContour: renderMediaPitchContour, regions: mediaRegions, @@ -67,6 +70,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => { const [frequencies, setFrequencies] = useState([]); const [peaks, setPeaks] = useState([]); + const [width, setWidth] = useState(); const ref = useRef(null); @@ -191,6 +195,15 @@ export const MediaCurrentRecording = (props: { height?: number }) => { }); }; + const calContainerWidth = () => { + const w = document + .querySelector(".media-recording-container") + ?.getBoundingClientRect()?.width; + if (!w) return; + + setWidth(w - 48); + }; + useEffect(() => { if (!ref.current) return; if (isRecording) return; @@ -305,9 +318,6 @@ export const MediaCurrentRecording = (props: { height?: number }) => { const scrollContainer = player.getWrapper()?.closest(".scroll"); if (!scrollContainer) return; - scrollContainer.style.width = `${ - ref.current.getBoundingClientRect().width - }px`; scrollContainer.style.scrollbarWidth = "thin"; }, [ref, player]); @@ -335,6 +345,25 @@ export const MediaCurrentRecording = (props: { height?: number }) => { mediaActiveRegion, ]); + useEffect(() => { + if (!ref?.current) return; + + ref.current.style.width = `${width}px`; + }, [width]); + + useEffect(() => { + calContainerWidth(); + window.addEventListener("resize", () => { + calContainerWidth(); + }); + + return () => { + window.removeEventListener("resize", () => { + calContainerWidth(); + }); + }; + }, []); + useHotkeys( ["Ctrl+R", "Meta+R"], (keyboardEvent, hotkeyEvent) => { @@ -354,18 +383,27 @@ export const MediaCurrentRecording = (props: { height?: number }) => { if (isRecording) return ; if (!currentRecording?.src) return ( -
-
+
+
+
+
+ +
+ +
); return ( -
+
@@ -380,7 +418,7 @@ export const MediaCurrentRecording = (props: { height?: number }) => {
-
+
+ +
); }; + +export const MediaRecordButton = (props: { + isRecording: boolean; + setIsRecording: (value: boolean) => void; +}) => { + const { isRecording, setIsRecording } = props; + + return ( + + ); +}; diff --git a/enjoy/src/renderer/components/medias/media-player-controls.tsx b/enjoy/src/renderer/components/medias/media-player-controls.tsx index 506a481e..f99c7b99 100644 --- a/enjoy/src/renderer/components/medias/media-player-controls.tsx +++ b/enjoy/src/renderer/components/medias/media-player-controls.tsx @@ -1,15 +1,6 @@ import { useEffect, useState, useContext } from "react"; import { type Region as RegionType } from "wavesurfer.js/dist/plugins/regions"; import { - AlertDialog, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogCancel, - AlertDialogAction, DropdownMenu, DropdownMenuItem, DropdownMenuTrigger, @@ -18,7 +9,6 @@ import { Popover, PopoverTrigger, PopoverContent, - toast, } from "@renderer/components/ui"; import { MediaPlayerProviderContext, @@ -31,17 +21,9 @@ import { Repeat1Icon, RepeatIcon, GaugeIcon, - ZoomInIcon, - ZoomOutIcon, - MicIcon, - MinimizeIcon, - GalleryHorizontalIcon, - SpellCheckIcon, - Share2Icon, ListRestartIcon, SkipForwardIcon, SkipBackIcon, - SquareIcon, SaveIcon, UndoIcon, TextCursorInputIcon, @@ -54,14 +36,8 @@ import debounce from "lodash/debounce"; import { AlignmentResult } from "echogarden/dist/api/API.d.js"; const PLAYBACK_RATE_OPTIONS = [0.75, 0.8, 0.9, 1.0]; -const ZOOM_RATIO_OPTIONS = [ - 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0, -]; -const MIN_ZOOM_RATIO = 0.25; -const MAX_ZOOM_RATIO = 4.0; export const MediaPlayerControls = () => { const { - media, decoded, wavesurfer, currentTime, @@ -71,7 +47,6 @@ export const MediaPlayerControls = () => { setZoomRatio, fitZoomRatio, transcription, - pitchChart, regions, activeRegion, setActiveRegion, @@ -79,14 +54,10 @@ export const MediaPlayerControls = () => { setEditingRegion, transcriptionDraft, setTranscriptionDraft, - isRecording, - setIsRecording, } = useContext(MediaPlayerProviderContext); - const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); + const { EnjoyApp } = useContext(AppSettingsProviderContext); const [playMode, setPlayMode] = useState<"loop" | "single" | "all">("single"); const [playbackRate, setPlaybackRate] = useState(1); - const [displayInlineCaption, setDisplayInlineCaption] = - useState(true); const [isSelectingRegion, setIsSelectingRegion] = useState(false); const playOrPause = () => { @@ -116,34 +87,6 @@ export const MediaPlayerControls = () => { setCurrentSegmentIndex(currentSegmentIndex + 1); }; - const onShare = async () => { - if (!media.source && !media.isUploaded) { - try { - await EnjoyApp.audios.upload(media.id); - } catch (err) { - toast.error(t("shareFailed"), { - description: err.message, - }); - return; - } - } - webApi - .createPost({ - targetType: media.mediaType, - targetId: media.id, - }) - .then(() => { - toast.success(t("sharedSuccessfully"), { - description: t("sharedAudio"), - }); - }) - .catch((err) => { - toast.error(t("shareFailed"), { - description: err.message, - }); - }); - }; - /* * Update segmentRegion when currentSegmentIndex is updated * or when editingRegion is toggled. @@ -457,228 +400,144 @@ export const MediaPlayerControls = () => { }, [regions, activeRegion]); return ( -
-
-
- {wavesurfer?.isPlaying() ? ( +
+
+ + - ) : ( + + +
{t("playbackRate")}
+
+ {PLAYBACK_RATE_OPTIONS.map((rate, i) => ( +
{ + setPlaybackRate(rate); + }} + > + {rate} +
+ ))} +
+
+
+ + + - )} -
+ + + setPlayMode("single")} + > + + {t("playSingleSegment")} + + setPlayMode("loop")} + > + + {t("playInLoop")} + + setPlayMode("all")} + > + + {t("playAllSegments")} + + + -
+ + + {wavesurfer?.isPlaying() ? ( - + ) : ( + )} - - - - - - setPlayMode("single")} - > - - {t("playSingleSegment")} - - setPlayMode("loop")} - > - - {t("playInLoop")} - - setPlayMode("all")} - > - - {t("playAllSegments")} - - - + - - - - - -
{t("playbackRate")}
-
- {PLAYBACK_RATE_OPTIONS.map((rate, i) => ( -
{ - setPlaybackRate(rate); - }} - > - {rate} -
- ))} -
-
-
- - - - - - - - - - - - + +
-
- {editingRegion && ( -
- - + -
- )} -
- -
- - - - - {media?.mediaType === "Audio" - ? t("shareAudio") - : t("shareVideo")} - - - {media?.mediaType === "Audio" - ? t("areYouSureToShareThisAudioToCommunity") - : t("areYouSureToShareThisVideoToCommunity")} - - - - {t("cancel")} - - - - - - - - - - - - +
)} - +
diff --git a/enjoy/src/renderer/components/medias/media-player.tsx b/enjoy/src/renderer/components/medias/media-player.tsx index 9d0979ad..341c9329 100644 --- a/enjoy/src/renderer/components/medias/media-player.tsx +++ b/enjoy/src/renderer/components/medias/media-player.tsx @@ -1,43 +1,266 @@ -import { useContext } from "react"; -import { MediaPlayerProviderContext } from "@renderer/context"; +import { useEffect, useContext, useRef, useState } from "react"; import { - MediaPlayer as VidstackMediaPlayer, - MediaProvider, - isAudioProvider, - isVideoProvider, - useMediaRemote, -} from "@vidstack/react"; + AppSettingsProviderContext, + MediaPlayerProviderContext, +} from "@renderer/context"; +import { formatDuration } from "@renderer/lib/utils"; +import { t } from "i18next"; import { - DefaultAudioLayout, - defaultLayoutIcons, -} from "@vidstack/react/player/layouts/default"; + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogCancel, + AlertDialogAction, + DropdownMenu, + DropdownMenuItem, + DropdownMenuTrigger, + DropdownMenuContent, + Button, + toast, +} from "@renderer/components/ui"; +import { + GalleryHorizontalIcon, + Share2Icon, + SpellCheckIcon, + MinimizeIcon, + ZoomInIcon, + ZoomOutIcon, + MoreVerticalIcon, +} from "lucide-react"; + +const ZOOM_RATIO_OPTIONS = [ + 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 3.5, 4.0, +]; +const MIN_ZOOM_RATIO = 0.25; +const MAX_ZOOM_RATIO = 4.0; export const MediaPlayer = () => { - const { media, setMediaProvider, setDecodeError } = useContext( - MediaPlayerProviderContext - ); - const mediaRemote = useMediaRemote(); - if (!media?.src) return null; + const { EnjoyApp, webApi } = useContext(AppSettingsProviderContext); + const { + media, + currentTime, + setRef, + pitchChart, + wavesurfer, + zoomRatio, + setZoomRatio, + fitZoomRatio, + } = useContext(MediaPlayerProviderContext); + const [displayInlineCaption, setDisplayInlineCaption] = + useState(true); + const [isSharing, setIsSharing] = useState(false); + const [width, setWidth] = useState(); + + const ref = useRef(null); + + const onShare = async () => { + if (!media.source && !media.isUploaded) { + try { + await EnjoyApp.audios.upload(media.id); + } catch (err) { + toast.error(t("shareFailed"), { + description: err.message, + }); + return; + } + } + webApi + .createPost({ + targetType: media.mediaType, + targetId: media.id, + }) + .then(() => { + toast.success(t("sharedSuccessfully"), { + description: t("sharedAudio"), + }); + }) + .catch((err) => { + toast.error(t("shareFailed"), { + description: err.message, + }); + }); + }; + + const calContainerWidth = () => { + const w = document + .querySelector(".media-player-container") + ?.getBoundingClientRect()?.width; + if (!w) return; + + setWidth(w - 48); + }; + + useEffect(() => { + if (ref?.current) { + setRef(ref); + } + }, [ref]); + + useEffect(() => { + if (!ref?.current) return; + + ref.current.style.width = `${width}px`; + }, [width]); + + useEffect(() => { + calContainerWidth(); + window.addEventListener("resize", () => { + calContainerWidth(); + }); + + return () => { + window.removeEventListener("resize", () => { + calContainerWidth(); + }); + }; + }, []); return ( -
- { - mediaRemote.setTarget(nativeEvent.target); - const { provider } = detail; - if (isAudioProvider(provider)) { - setMediaProvider(provider.audio); - } else if (isVideoProvider(provider)) { - setMediaProvider(provider.video); - } - }} - onError={(err) => setDecodeError(err.message)} - > - - - +
+
+
+
+ {formatDuration(currentTime || 0)} + / + + {formatDuration(media?.duration || 0)} + +
+
+
+ + + + + + + + + + + + + + + { + wavesurfer.setOptions({ + autoCenter: !wavesurfer?.options?.autoCenter, + }); + }} + > + + {t("autoCenter")} + + + setIsSharing(true)} + > + + {t("share")} + + + + + + + + + {media?.mediaType === "Audio" + ? t("shareAudio") + : t("shareVideo")} + + + {media?.mediaType === "Audio" + ? t("areYouSureToShareThisAudioToCommunity") + : t("areYouSureToShareThisVideoToCommunity")} + + + + {t("cancel")} + + + + + + +
); }; diff --git a/enjoy/src/renderer/components/medias/media-provider.tsx b/enjoy/src/renderer/components/medias/media-provider.tsx new file mode 100644 index 00000000..7e2de34e --- /dev/null +++ b/enjoy/src/renderer/components/medias/media-provider.tsx @@ -0,0 +1,43 @@ +import { useContext } from "react"; +import { MediaPlayerProviderContext } from "@renderer/context"; +import { + MediaPlayer as VidstackMediaPlayer, + MediaProvider as VidstackMediaProvider, + isAudioProvider, + isVideoProvider, + useMediaRemote, +} from "@vidstack/react"; +import { + DefaultAudioLayout, + defaultLayoutIcons, +} from "@vidstack/react/player/layouts/default"; + +export const MediaProvider = () => { + const { media, setMediaProvider, setDecodeError } = useContext( + MediaPlayerProviderContext + ); + const mediaRemote = useMediaRemote(); + if (!media?.src) return null; + + return ( +
+ { + mediaRemote.setTarget(nativeEvent.target); + const { provider } = detail; + if (isAudioProvider(provider)) { + setMediaProvider(provider.audio); + } else if (isVideoProvider(provider)) { + setMediaProvider(provider.video); + } + }} + onError={(err) => setDecodeError(err.message)} + > + + + +
+ ); +}; diff --git a/enjoy/src/renderer/components/medias/media-recorder.tsx b/enjoy/src/renderer/components/medias/media-recorder.tsx index 1026a74c..068f4566 100644 --- a/enjoy/src/renderer/components/medias/media-recorder.tsx +++ b/enjoy/src/renderer/components/medias/media-recorder.tsx @@ -8,10 +8,8 @@ import WaveSurfer from "wavesurfer.js"; import { t } from "i18next"; import { useTranscribe } from "@renderer/hooks"; import { toast } from "@renderer/components/ui"; -import { - FFMPEG_TRIM_SILENCE_OPTIONS, - FFMPEG_CONVERT_WAV_OPTIONS, -} from "@/constants"; +import { MediaRecordButton } from "@renderer/components"; +import { FFMPEG_CONVERT_WAV_OPTIONS } from "@/constants"; export const MediaRecorder = (props: { height?: number }) => { const { height = 192 } = props; @@ -74,7 +72,7 @@ export const MediaRecorder = (props: { height?: number }) => { success: t("recordingSaved"), error: (e) => t("failedToSaveRecording" + " : " + e.message), position: "bottom-right", - }, + } ); }; @@ -134,12 +132,21 @@ export const MediaRecorder = (props: { height?: number }) => { }, []); return ( -
- - {duration / 10} - / 300 - -
+
+
+ + {duration / 10} + / 300 + +
+
+ +
+ +
); }; diff --git a/enjoy/src/renderer/components/medias/media-tabs.tsx b/enjoy/src/renderer/components/medias/media-tabs.tsx index 1b4e7897..0c5f7440 100644 --- a/enjoy/src/renderer/components/medias/media-tabs.tsx +++ b/enjoy/src/renderer/components/medias/media-tabs.tsx @@ -1,7 +1,7 @@ import { useEffect, useContext, useState } from "react"; import { MediaPlayerProviderContext } from "@renderer/context"; import { - MediaPlayer, + MediaProvider, MediaTranscription, MediaInfoPanel, MediaRecordings, @@ -11,7 +11,7 @@ import { t } from "i18next"; export const MediaTabs = () => { const { media, decoded } = useContext(MediaPlayerProviderContext); - const [tab, setTab] = useState("player"); + const [tab, setTab] = useState("provider"); useEffect(() => { if (!decoded) return; @@ -27,9 +27,9 @@ export const MediaTabs = () => { {media.mediaType === "Video" && (
setTab("player")} + onClick={() => setTab("provider")} > {t("player")}
@@ -61,8 +61,8 @@ export const MediaTabs = () => {
-
- +
+
diff --git a/enjoy/src/renderer/components/sidebar.tsx b/enjoy/src/renderer/components/sidebar.tsx index 38e5d324..7f2a915a 100644 --- a/enjoy/src/renderer/components/sidebar.tsx +++ b/enjoy/src/renderer/components/sidebar.tsx @@ -55,6 +55,28 @@ export const Sidebar = () => { + + + + { {t("sidebar.mine")}
- - - - { const { id, md5 } = props; - const { media, currentTime, setMedia, setRef } = useContext( - MediaPlayerProviderContext - ); + const { setMedia } = useContext(MediaPlayerProviderContext); const { video } = useVideo({ id, md5 }); - const ref = useRef(null); useEffect(() => { if (!video) return; @@ -24,10 +21,6 @@ export const VideoPlayer = (props: { id?: string; md5?: string }) => { setMedia(video); }, [video]); - useEffect(() => { - setRef(ref); - }, [ref]); - return (
@@ -47,18 +40,7 @@ export const VideoPlayer = (props: { id?: string; md5?: string }) => {
-
-
-
- - {formatDuration(currentTime || 0)} - - / - - {formatDuration(media?.duration || 0)} - -
-
+
diff --git a/enjoy/src/renderer/hooks/use-transcriptions.tsx b/enjoy/src/renderer/hooks/use-transcriptions.tsx index 9671fead..2d07276d 100644 --- a/enjoy/src/renderer/hooks/use-transcriptions.tsx +++ b/enjoy/src/renderer/hooks/use-transcriptions.tsx @@ -90,7 +90,9 @@ export const useTranscriptions = (media: AudioType | VideoType) => { /* * Pre-process - * Some words end with period should not be a single sentence, like Mr./Ms./Dr. etc + * 1. Some words end with period should not be a single sentence, like Mr./Ms./Dr. etc + * 2. Some words connected by `-`(like scrach-off) are split into multiple words in words timeline, merge them for display; + * 3. Some numbers with `%` are split into `number + percent` in words timeline, merge thme for display; */ timeline.forEach((sentence, i) => { const nextSentence = timeline[i + 1]; @@ -107,6 +109,35 @@ export const useTranscriptions = (media: AudioType | VideoType) => { ]; nextSentence.startTime = sentence.startTime; timeline.splice(i, 1); + } else { + const words = sentence.text.split(" "); + + sentence.timeline.forEach((token, j) => { + const word = words[j]?.trim()?.toLowerCase(); + + const match = word?.match(/-|%/); + if (!match) return; + + for (let k = j + 1; k <= sentence.timeline.length - 1; k++) { + if (word.includes(sentence.timeline[k].text.toLowerCase())) { + let connector = ""; + if (match[0] === "-") { + connector = "-"; + } + token.text = [token.text, sentence.timeline[k].text].join( + connector + ); + token.timeline = [ + ...token.timeline, + ...sentence.timeline[k].timeline, + ]; + token.endTime = sentence.timeline[k].endTime; + sentence.timeline.splice(k, 1); + } else { + break; + } + } + }); } }); diff --git a/enjoy/tailwind.config.js b/enjoy/tailwind.config.js index 2302b9cd..0db1711a 100644 --- a/enjoy/tailwind.config.js +++ b/enjoy/tailwind.config.js @@ -11,6 +11,9 @@ module.exports = { }, }, extend: { + fontFamily: { + code: ['"Source Code Pro"'], + }, colors: { border: "hsl(var(--border))", input: "hsl(var(--input))",