diff options
| author | Pinapelz <yukais@pinapelz.com> | 2023-11-15 00:28:35 -0800 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2023-11-15 00:28:35 -0800 |
| commit | 71b680fb9d29057b97748c54d1ad20229fe3394c (patch) | |
| tree | 1fb4dc09865857cd8705ac5b35a59fbfa951d22b /src/app | |
| parent | c789256ebd5691b805c81bd673a153a984b3039e (diff) | |
feat: add custom upload for lrc and video/audio
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/App.tsx | 151 | ||||
| -rw-r--r-- | src/app/components/KaraokePlayer.tsx | 58 | ||||
| -rw-r--r-- | src/app/data.ts | 66 |
3 files changed, 159 insertions, 116 deletions
diff --git a/src/app/App.tsx b/src/app/App.tsx index ebe2d33..871e387 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,7 +1,8 @@ -import { CSSProperties, useCallback, useRef, useEffect, useState } from "react"; -import styled, { css } from "styled-components"; -import { Lrc, LrcLine } from "react-lrc"; -import { LRC } from "./data"; +import React, { useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; +import KaraokePlayer from './components/KaraokePlayer'; +import { toast, ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; const Root = styled.div` position: absolute; @@ -9,79 +10,129 @@ const Root = styled.div` height: 100%; top: 0; left: 0; - display: flex; flex-direction: column; + align-items: center; + background-color: #f5f5f5; `; -const lrcStyle: CSSProperties = { - flex: 1, - minHeight: 0, - overflow: 'hidden !important' -}; -const Line = styled.div<{ $active: boolean; $next: boolean }>` - min-height: 10px; - padding: 14px 30px; - font-size: 40px; - font-family : "Roboto", sans-serif; - font-weight: 500; - text-align: center; - color: rgb(72,72,72); +const FileInputContainer = styled.div` + margin-bottom: 20px; + display: flex; + justify-content: center; + gap: 20px; + padding: 10px; + border-radius: 5px; + background-color: #ffffff; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +`; - background: linear-gradient(to right, rgba(0,0,0,0) 50%, rgb(200, 190, 190) 50%); - background-size: 200% 100%; - background-position: right bottom; +const FileInput = styled.input` + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ddd; + cursor: pointer; + display: none; + &:hover, &:focus { + background-color: #eaeaea; + outline: none; + } +`; - ${({ $active }) => $active && css` - color: black; - font-weight: 700; - background-position: left bottom; - color: rgb(50, 50, 50); - `} +const FileInputLabel = styled.label` + padding: 10px 15px; + border-radius: 5px; + border: 1px solid #ddd; + cursor: pointer; + &:hover, &:focus { + background-color: #eaeaea; + outline: none; + } `; + function App() { + const [currentMillisecond, setCurrentMillisecond] = useState(0); - + const [lrcContent, setLrcContent] = useState(''); + const [videoUrl, setVideoUrl] = useState(''); + const [showFileInputs, setShowFileInputs] = useState(true); const videoRef = useRef<HTMLVideoElement>(null); + const [offset, setOffset] = useState('0'); + const handleLrcFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + setLrcContent(e.target?.result as string); + if (videoUrl) setShowFileInputs(false); + }; + reader.readAsText(file); + toast.success("LRC file loaded successfully", { autoClose: 2000 }); + } + }; + + const handleVideoFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const file = event.target.files?.[0]; + if (file) { + const url = URL.createObjectURL(file); + setVideoUrl(url); + setShowFileInputs(true); + toast.success("Video file loaded successfully", { autoClose: 2000 }); + } + }; useEffect(() => { const video = videoRef.current; if (!video) return; const syncLrcWithVideo = () => { - const offset = 400; - setCurrentMillisecond((video.currentTime * 1000)+offset); + console.log(offset); + setCurrentMillisecond((video.currentTime * 1000) + parseInt(offset)); }; - video.addEventListener('timeupdate', syncLrcWithVideo); return () => { video.removeEventListener('timeupdate', syncLrcWithVideo); }; - }, [setCurrentMillisecond]); - - const lineRenderer = useCallback( - ({ active, line: { content } }: { active: boolean; line: LrcLine }) => { - const next = active && content === ''; - return <Line $active={active} $next={next}>{content}</Line> - }, - [] - ); + }); return ( <Root> + <ToastContainer /> <div style={{ display: 'flex', width: '100%', height: '100vh' }}> - <Lrc - lrc={LRC} - lineRenderer={lineRenderer} - currentMillisecond={currentMillisecond} - style={lrcStyle} - recoverAutoScrollInterval={0} - /> - <div style={{ flex: 1 }}> - <video ref={videoRef} src="https://cdn.pinapelz.com/VTuber%20Covers%20Archive/pj9yqqTYa-E.webm" controls style={{ width: '100%', height: '100%' }} /> - + <KaraokePlayer + lrc={lrcContent} + currentMillisecond={currentMillisecond} + /> + <div style={{ flex: 1, position: 'relative' }} onMouseEnter={() => setShowFileInputs(true)} onMouseLeave={() => setShowFileInputs(false)}> + {videoUrl ? <video ref={videoRef} src={videoUrl} controls style={{ width: '100%', height: '100%' }} /> :<div style={{ width: '100%', height: '100%', backgroundColor: '#ddd', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> + <p style={{fontSize: '30px', textAlign: 'center', fontFamily:'Arial', fontWeight:'bold'}}> + Please select the video and lrc (lyrics) file <br/> + Hover over me for a menu</p> + </div> + } + {showFileInputs && ( + <FileInputContainer style={{ position: 'absolute', bottom: '20px', left: 0 }}> + <FileInputLabel htmlFor="lrcUpload" style={{ cursor: 'pointer' }}>LRC</FileInputLabel> + <FileInput id="lrcUpload" type="file" accept=".lrc" onChange={handleLrcFileChange} /> + <FileInputLabel htmlFor="videoUpload" style={{ cursor: 'pointer' }}>Video</FileInputLabel> + <FileInput id="videoUpload" type="file" accept="video/*" onChange={handleVideoFileChange} /> + <FileInputLabel htmlFor="srvUpload" style={{ cursor: 'pointer' }}>SRV</FileInputLabel> + <FileInput disabled type="file" accept=".srv" /> + <div style={{ display: 'flex', flexDirection: 'column', fontFamily: 'Arial' }}> + <label>Offset (±ms) </label> + <input + type="number" + style={{ fontSize: '20px' }} + id="numberInput" + value={offset} + onChange={(e) => setOffset(e.target.value)} + step="100" + /> + </div> + </FileInputContainer> + )} </div> </div> </Root> diff --git a/src/app/components/KaraokePlayer.tsx b/src/app/components/KaraokePlayer.tsx new file mode 100644 index 0000000..5160226 --- /dev/null +++ b/src/app/components/KaraokePlayer.tsx @@ -0,0 +1,58 @@ +import React, { CSSProperties, useCallback } from 'react'; +import styled, { css } from 'styled-components'; +import { Lrc, LrcLine } from 'react-lrc'; + +const Line = styled.div<{ $active: boolean; $next: boolean }>` + min-height: 10px; + padding: 14px 30px; + + font-size: 40px; + font-family: "Roboto", sans-serif; + font-weight: 500; + text-align: center; + color: rgb(72,72,72); + + background: linear-gradient(to right, rgba(0,0,0,0) 50%, rgb(200, 190, 190) 50%); + background-size: 200% 100%; + background-position: right bottom; + transition: color 1s ease-out, background-position 1s ease-out; + + ${({ $active }) => $active && css` + color: black; + font-weight: 700; + background-position: left bottom; + color: rgb(50, 50, 50); + `} +`; +const lrcStyle: CSSProperties = { + flex: 1, + minHeight: 0, + overflow: 'hidden !important' +}; + +interface KaraokePlayerProps { + currentMillisecond: number; + lrc: string; +} + +const KaraokePlayer: React.FC<KaraokePlayerProps> = ({ currentMillisecond, lrc }) => { + const lineRenderer = useCallback( + ({ active, line: { content } }: { active: boolean; line: LrcLine }) => { + const next = active && content === ''; + return <Line $active={active} $next={next}>{content}</Line>; + }, + [] + ); + + return ( + <Lrc + lrc={lrc} + lineRenderer={lineRenderer} + currentMillisecond={currentMillisecond} + style={lrcStyle} + recoverAutoScrollInterval={0} + /> + ); +}; + +export default KaraokePlayer; diff --git a/src/app/data.ts b/src/app/data.ts deleted file mode 100644 index eff48cf..0000000 --- a/src/app/data.ts +++ /dev/null @@ -1,66 +0,0 @@ -export const LRC = `[00:11.53]Another day, I wander -[00:13.85]Without escape, I ponder -[00:15.98]A million questions -[00:17.25]I don't need to find an answer for -[00:20.59]Like, is it worth the hassle? -[00:22.78]Or, is it worth the pain? -[00:24.23]Look inside the mirror and say to myself -[00:28.17]Am I enough? -[00:30.81]Oh, am I worth it? -[00:35.570] -[00:37.670]I see the way you notice -[00:39.980]All of my fragile moments -[00:42.210]A part of me is still uncertain I should let you in -[00:46.670]But as it flows and passes -[00:48.800]The time will just confirm -[00:50.150]That when you're here with me -[00:51.940]I can just let go -[00:54.560]Now suddenly, the clouds clear out -[00:59.070]All my worries disappear -[01:01.070]And all the stars, they feel so near -[01:03.340]I could almost reach out right now -[01:07.580]Because of you, feel myself again -[01:11.980]You helped me realize what was here to do -[01:16.480]Because of you, I'm feeling real again -[01:20.840]Now I know where I should go -[01:22.940]You got me walking back to hope -[01:25.090]One step at a time -[01:34.560]I can't explain the feeling -[01:36.630]A sort of, kind of healing -[01:38.980]A different wave of love that travels -[01:41.530]Through your precious words -[01:43.370]At times, I circle back, but -[01:45.600]I guess I just forget -[01:46.980]That when I'm down and low -[01:48.940]I could count on you -[01:51.460]Now suddenly, the clouds clear out -[01:55.760]All my worries disappear -[01:57.810]And all the stars, they feel so near -[02:00.300]I could almost reach out right now -[02:05.530]Because of you, I feel myself again -[02:09.930]You helped me realize what I was here to do -[02:14.370]Because of you, I'm feeling real again -[02:18.520]Now I know where I should go -[02:20.640]You got me walking back to hope -[02:22.250]A step at a time -[02:25.940]♪ -[02:41.030]What can I say but thank you -[02:43.400]Surrounded by some angels -[02:45.190]Honestly, it's hard for me to live without -[02:49.800]Tomorrow's bringing something new -[02:53.160]I know for certain, it's worth it -[02:56.560]All thanks to you -[03:08.840]Because of you, I feel myself again -[03:13.240]You helped me realize what I was here to do -[03:17.450]Because of you, I'm feeling real again -[03:21.930]Now I know where I should go -[03:23.980]You got me walking back to hope -[03:26.390]Because of you, I feel myself again -[03:30.700]You helped me realize what I was here to do -[03:34.940]Because of you, I'm feeling real again -[03:39.310]Now I know where I should go -[03:41.570]You got me walking back to hope -[03:43.860]One step at a time -[03:51.980]A little closer -[03:55.580]Every step gets closer -[03:58.330]`; |
