2 Commits

5 changed files with 35 additions and 38 deletions

View File

@@ -5,15 +5,32 @@ type props = {
dataSend: React.MouseEventHandler<HTMLButtonElement>; dataSend: React.MouseEventHandler<HTMLButtonElement>;
handleDownload: React.MouseEventHandler<HTMLButtonElement>; handleDownload: React.MouseEventHandler<HTMLButtonElement>;
downloadable: boolean; downloadable: boolean;
progress: number progress: number;
uploading: boolean;
} }
const ExportWidget = ({dataSend, handleDownload, downloadable, progress}: props) => { const Spinner = () => (
<svg
className="animate-spin h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
);
const ExportWidget = ({dataSend, handleDownload, downloadable, progress, uploading}: props) => {
return ( return (
<div className={"flex flex-col gap-3"}> <div className={"flex flex-col gap-3"}>
<BlueButton <BlueButton
onClick={dataSend}> onClick={dataSend}
Export disabled={uploading}
className={uploading ? "opacity-60 cursor-not-allowed flex items-center justify-center gap-2" : ""}
>
{uploading && <Spinner />}
{uploading ? "Uploading..." : "Export"}
</BlueButton> </BlueButton>
<div className="h-10 flex items-center"> <div className="h-10 flex items-center">
@@ -33,4 +50,4 @@ const ExportWidget = ({dataSend, handleDownload, downloadable, progress}: props)
) )
} }
export default ExportWidget; export default ExportWidget;

View File

@@ -34,6 +34,7 @@ const ClipEdit = () => {
}); });
const [progress, setProgress] = useState(0); const [progress, setProgress] = useState(0);
const [downloadable, setDownloadable] = useState(false); const [downloadable, setDownloadable] = useState(false);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
@@ -59,7 +60,10 @@ const ClipEdit = () => {
}; };
const sendData = async () => { const sendData = async () => {
if (!localFile) return; if (!localFile || uploading) return;
setUploading(true);
setError(null);
let jobId: string; let jobId: string;
try { try {
@@ -67,6 +71,7 @@ const ClipEdit = () => {
setUploadedId(jobId); setUploadedId(jobId);
} catch (err: unknown) { } catch (err: unknown) {
setError(`Failed to start compression: ${err instanceof Error ? err.message : 'Unknown error'}`); setError(`Failed to start compression: ${err instanceof Error ? err.message : 'Unknown error'}`);
setUploading(false);
return; return;
} }
@@ -81,14 +86,17 @@ const ClipEdit = () => {
if (job.state === 'FAILED') { if (job.state === 'FAILED') {
setError(`Compression failed: ${job.errorOutput ?? 'Unknown error'}`); setError(`Compression failed: ${job.errorOutput ?? 'Unknown error'}`);
clearInterval(intervalId); clearInterval(intervalId);
setUploading(false);
} else if (job.isComplete) { } else if (job.isComplete) {
clearInterval(intervalId); clearInterval(intervalId);
setDownloadable(true); setDownloadable(true);
setUploading(false);
} }
}) })
.catch((err: Error) => { .catch((err: Error) => {
setError(`Failed to fetch progress: ${err.message}`); setError(`Failed to fetch progress: ${err.message}`);
clearInterval(intervalId); clearInterval(intervalId);
setUploading(false);
}); });
}; };
@@ -166,6 +174,7 @@ const ClipEdit = () => {
handleDownload={handleDownload} handleDownload={handleDownload}
downloadable={downloadable} downloadable={downloadable}
progress={progress} progress={progress}
uploading={uploading}
/> />
</Box> </Box>
</div> </div>

View File

@@ -16,6 +16,7 @@ import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@Service @Service
@@ -214,7 +215,7 @@ public class MediaService {
return segMs < endMs && (segMs + HLS_SEGMENT_LENGTH * 1000) > startMs; return segMs < endMs && (segMs + HLS_SEGMENT_LENGTH * 1000) > startMs;
}) })
.sorted(Comparator.comparingLong(this::parseTimestampMs)) .sorted(Comparator.comparingLong(this::parseTimestampMs))
.toList(); .collect(Collectors.toList());
} }
} }

View File

@@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -76,13 +75,9 @@ public class StreamActionsService {
progressTracker.markComplete(); progressTracker.markComplete();
return CompletableFuture.completedFuture(outputFile); return CompletableFuture.completedFuture(outputFile);
} catch (IOException e) { } catch (Exception e) {
logger.error("Failed to save section to '{}'", outputFile, e); logger.error("Failed to save section to '{}'", outputFile, e);
return CompletableFuture.failedFuture(e); return CompletableFuture.failedFuture(e);
} catch (InterruptedException e) {
logger.error("Failed to save section");
Thread.currentThread().interrupt();
return CompletableFuture.failedFuture(e);
} }
} }
} }

View File

@@ -1,25 +0,0 @@
INSERT INTO users ( google_id, username, email, name)
VALUES
('google-uid-001', 'alice', 'alice@example.com', 'Alice Example'),
( 'google-uid-002', 'bob', 'bob@example.com', 'Bob Example'),
('google-uid-003', 'carol', 'carol@example.com', 'Carol Example'),
( 'google-uid-004', 'wizard42', 'gandalf@middle.earth', 'Gandalf the Grey'),
( 'google-uid-005', 'catnap', 'whiskers@meowmail.com', 'Sir Whiskers McFluff'),
( 'google-uid-006', 'robotron', 'bender@futurama.tv', 'Bender Rodriguez'),
('google-uid-007', 'unicorn', 'sparkle@rainbow.com', 'Princess Sparklehoof'),
( 'google-uid-008', 'pirate', 'blackbeard@seas.com', 'Edward Teach'),
( 'google-uid-009', 'detective', 'holmes@bakerstreet.uk', 'Sherlock Holmes'),
( 'google-uid-010', 'timey', 'docbrown@delorean.net', 'Dr. Emmett Brown');
INSERT INTO clips (user_id, title, description, width, height, fps, duration, file_size, video_path)
VALUES
(4, 'Fireworks Over Hobbiton', 'A magical display of fireworks by Gandalf.', 1920, 1080, 30, 120, 104857600, '/videos/fireworks_hobbiton.mp4'),
(5, 'Catnap Chronicles', 'Sir Whiskers McFluff naps in 12 different positions.', 1280, 720, 24, 60, 52428800, '/videos/catnap_chronicles.mp4'),
(6, 'Bite My Shiny Metal...', 'Bender shows off his new upgrades.', 1920, 1080, 60, 45, 73400320, '/videos/bender_upgrades.mp4'),
(7, 'Rainbow Dash', 'Princess Sparklehoof gallops across a double rainbow.', 1920, 1080, 30, 90, 67108864, '/videos/rainbow_dash.mp4'),
( 8, 'Pirate Karaoke Night', 'Blackbeard sings sea shanties with his crew.', 1280, 720, 25, 180, 157286400, '/videos/pirate_karaoke.mp4'),
( 9, 'The Case of the Missing Sandwich', 'Sherlock Holmes investigates a lunchtime mystery.', 1920, 1080, 30, 75, 50331648, '/videos/missing_sandwich.mp4'),
( 10, '88 Miles Per Hour', 'Doc Brown demonstrates time travel with style.', 1920, 1080, 60, 30, 41943040, '/videos/88mph.mp4'),
( 1, 'Alice in Videoland', 'Alice explores a surreal digital wonderland.', 1280, 720, 30, 150, 94371840, '/videos/alice_videoland.mp4'),
( 2, 'Bob''s Building Bonanza', 'Bob constructs a house out of cheese.', 1920, 1080, 24, 200, 209715200, '/videos/bob_cheesehouse.mp4'),
( 3, 'Carol''s Coding Catastrophe', 'Carol debugs a spaghetti codebase.', 1280, 720, 30, 100, 73400320, '/videos/carol_coding.mp4');