aboutsummaryrefslogtreecommitdiffstats
path: root/src/app
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2023-11-15 00:28:35 -0800
committerPinapelz <yukais@pinapelz.com>2023-11-15 00:28:35 -0800
commit71b680fb9d29057b97748c54d1ad20229fe3394c (patch)
tree1fb4dc09865857cd8705ac5b35a59fbfa951d22b /src/app
parentc789256ebd5691b805c81bd673a153a984b3039e (diff)
feat: add custom upload for lrc and video/audio
Diffstat (limited to 'src/app')
-rw-r--r--src/app/App.tsx151
-rw-r--r--src/app/components/KaraokePlayer.tsx58
-rw-r--r--src/app/data.ts66
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]`;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage