From 7d1b5ceb0d79ab8428750f68e7f105bf662696b0 Mon Sep 17 00:00:00 2001 From: Shubbu03 Date: Mon, 23 Jun 2025 22:10:15 +0530 Subject: Completed the getVideoInfo func according to the comment specified --- apps/web/src/lib/ffmpeg-utils.ts | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) (limited to 'apps/web/src') diff --git a/apps/web/src/lib/ffmpeg-utils.ts b/apps/web/src/lib/ffmpeg-utils.ts index 56085a21..de203fd6 100644 --- a/apps/web/src/lib/ffmpeg-utils.ts +++ b/apps/web/src/lib/ffmpeg-utils.ts @@ -102,26 +102,52 @@ export const getVideoInfo = async (videoFile: File): Promise<{ fps: number; }> => { const ffmpeg = await initFFmpeg(); - + const inputName = 'input.mp4'; - + // Write input file await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); - - // Get video info + + // Capture FFmpeg stderr output + let ffmpegOutput = ''; + const listener = (data: string) => { + ffmpegOutput += data; + }; + ffmpeg.on('log', ({ message }) => listener(message)); + + // Run ffmpeg to get info (stderr will contain the info) await ffmpeg.exec(['-i', inputName, '-f', 'null', '-']); - - // Note: In a real implementation, you'd parse the FFmpeg output - // For now, we'll return default values and enhance this later - + + // Remove listener + // (No off() method in ffmpeg.wasm, so this is a no-op, but included for clarity) + // Cleanup await ffmpeg.deleteFile(inputName); - + + // Parse output for duration, resolution, and fps + // Example: Duration: 00:00:10.00, start: 0.000000, bitrate: 1234 kb/s + // Example: Stream #0:0: Video: h264 (High), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 90k tbn, 60 tbc + + const durationMatch = ffmpegOutput.match(/Duration: (\d+):(\d+):([\d.]+)/); + let duration = 0; + if (durationMatch) { + const [, h, m, s] = durationMatch; + duration = parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s); + } + + const videoStreamMatch = ffmpegOutput.match(/Video:.* (\d+)x(\d+)[^,]*, ([\d.]+) fps/); + let width = 0, height = 0, fps = 0; + if (videoStreamMatch) { + width = parseInt(videoStreamMatch[1]); + height = parseInt(videoStreamMatch[2]); + fps = parseFloat(videoStreamMatch[3]); + } + return { - duration: 10, // Placeholder - would parse from FFmpeg output - width: 1920, // Placeholder - height: 1080, // Placeholder - fps: 30 // Placeholder + duration, + width, + height, + fps }; }; -- cgit v1.3.1 From 6a701ee8c064a4652db475c93ee89c3ffe112986 Mon Sep 17 00:00:00 2001 From: Shubbu03 Date: Mon, 23 Jun 2025 22:22:43 +0530 Subject: better error handling --- apps/web/src/lib/ffmpeg-utils.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'apps/web/src') diff --git a/apps/web/src/lib/ffmpeg-utils.ts b/apps/web/src/lib/ffmpeg-utils.ts index de203fd6..ae0c267b 100644 --- a/apps/web/src/lib/ffmpeg-utils.ts +++ b/apps/web/src/lib/ffmpeg-utils.ts @@ -108,18 +108,26 @@ export const getVideoInfo = async (videoFile: File): Promise<{ // Write input file await ffmpeg.writeFile(inputName, new Uint8Array(await videoFile.arrayBuffer())); - // Capture FFmpeg stderr output + // Capture FFmpeg stderr output with a one-time listener pattern let ffmpegOutput = ''; + let listening = true; const listener = (data: string) => { - ffmpegOutput += data; + if (listening) ffmpegOutput += data; }; ffmpeg.on('log', ({ message }) => listener(message)); // Run ffmpeg to get info (stderr will contain the info) - await ffmpeg.exec(['-i', inputName, '-f', 'null', '-']); + try { + await ffmpeg.exec(['-i', inputName, '-f', 'null', '-']); + } catch (error) { + listening = false; + await ffmpeg.deleteFile(inputName); + console.error('FFmpeg execution failed:', error); + throw new Error('Failed to extract video info. The file may be corrupted or in an unsupported format.'); + } - // Remove listener - // (No off() method in ffmpeg.wasm, so this is a no-op, but included for clarity) + // Disable listener after exec completes + listening = false; // Cleanup await ffmpeg.deleteFile(inputName); @@ -220,4 +228,4 @@ export const extractAudio = async ( await ffmpeg.deleteFile(outputName); return blob; -}; \ No newline at end of file +}; \ No newline at end of file -- cgit v1.3.1 From 8bcd226bdb4eb3468df0590dbe6d3d8d68b2acbd Mon Sep 17 00:00:00 2001 From: Deepanshu Mishra Date: Mon, 23 Jun 2025 22:51:34 +0530 Subject: Added google auth and some auth page ui optimisations --- apps/web/.env.example | 6 +- apps/web/src/app/auth/login/page.tsx | 134 ++++++++++++++++++++-------- apps/web/src/app/auth/signup/page.tsx | 163 ++++++++++++++++++++++++---------- apps/web/src/components/header.tsx | 4 +- apps/web/src/components/icons.tsx | 22 +++++ apps/web/src/lib/auth-client.ts | 2 +- apps/web/src/lib/auth.ts | 6 ++ 7 files changed, 254 insertions(+), 83 deletions(-) create mode 100644 apps/web/src/components/icons.tsx (limited to 'apps/web/src') diff --git a/apps/web/.env.example b/apps/web/.env.example index 8477646c..0b4c649b 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -7,9 +7,13 @@ DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut" BETTER_AUTH_URL=http://localhost:3000 BETTER_AUTH_SECRET=your-secret-key-here +#Google Clients +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + # Development Environment NODE_ENV=development # Redis UPSTASH_REDIS_REST_URL=http://localhost:8079 -UPSTASH_REDIS_REST_TOKEN=example_token \ No newline at end of file +UPSTASH_REDIS_REST_TOKEN=example_token diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx index 5eadb85c..ebf2eb3f 100644 --- a/apps/web/src/app/auth/login/page.tsx +++ b/apps/web/src/app/auth/login/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { useSearchParams, useRouter } from "next/navigation"; -import { authClient } from "@/lib/auth-client"; +import { useRouter } from "next/navigation"; +import { signIn } from "@/lib/auth-client"; import { Button } from "@/components/ui/button"; import { Card, @@ -13,81 +13,145 @@ import { import { Suspense, useState } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; import Link from "next/link"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { ArrowLeft, Loader2 } from "lucide-react"; +import { GoogleIcon } from "@/components/icons"; function LoginForm() { - const searchParams = useSearchParams(); const router = useRouter(); - const redirectUrl = searchParams.get("redirect"); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); + const [isEmailLoading, setIsEmailLoading] = useState(false); + const [isGoogleLoading, setIsGoogleLoading] = useState(false); const handleLogin = async () => { setError(null); - const { error } = await authClient.signIn.email({ + setIsEmailLoading(true); + + const { error } = await signIn.email({ email, password, }); if (error) { setError(error.message || "An unexpected error occurred."); + setIsEmailLoading(false); return; } - router.push(redirectUrl || "/"); + router.push("/editor"); + }; + + const handleGoogleLogin = async () => { + setError(null); + setIsGoogleLoading(true); + + try { + await signIn.social({ + provider: "google", + }); + router.push("/editor"); + } catch (error) { + setError("Failed to sign in with Google. Please try again."); + setIsGoogleLoading(false); + } }; + const isAnyLoading = isEmailLoading || isGoogleLoading; + return ( -
+
{error && ( Error {error} )} -
- - setEmail(e.target.value)} - /> + + +
+
+ +
+
+ Or continue with +
-
- - setPassword(e.target.value)} - /> +
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
-
); } export default function LoginPage() { + const router = useRouter(); + return ( -
- - - Login - - Enter your email and password to login. +
+ + + + Welcome back + + Sign in to your account to continue - - Loading...
}> + + + +
}> -
+
Don't have an account?{" "} - + Sign up
diff --git a/apps/web/src/app/auth/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx index f328851c..3d125a07 100644 --- a/apps/web/src/app/auth/signup/page.tsx +++ b/apps/web/src/app/auth/signup/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { useSearchParams, useRouter } from "next/navigation"; -import { authClient } from "@/lib/auth-client"; +import { useRouter } from "next/navigation"; +import { signUp, signIn } from "@/lib/auth-client"; import { Button } from "@/components/ui/button"; import { Card, @@ -13,21 +13,26 @@ import { import { Suspense, useState } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; import Link from "next/link"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Loader2, ArrowLeft } from "lucide-react"; +import { GoogleIcon } from "@/components/icons"; function SignUpForm() { - const searchParams = useSearchParams(); const router = useRouter(); - const redirectUrl = searchParams.get("redirect"); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(null); + const [isEmailLoading, setIsEmailLoading] = useState(false); + const [isGoogleLoading, setIsGoogleLoading] = useState(false); const handleSignUp = async () => { setError(null); - const { error } = await authClient.signUp.email({ + setIsEmailLoading(true); + + const { error } = await signUp.email({ name, email, password, @@ -35,70 +40,138 @@ function SignUpForm() { if (error) { setError(error.message || "An unexpected error occurred."); + setIsEmailLoading(false); return; } - router.push(redirectUrl || "/"); + router.push("/auth/login"); + }; + + const handleGoogleSignUp = async () => { + setError(null); + setIsGoogleLoading(true); + + try { + await signIn.social({ + provider: "google", + }); + + router.push("/editor"); + } catch (error) { + setError("Failed to sign up with Google. Please try again."); + setIsGoogleLoading(false); + } }; + const isAnyLoading = isEmailLoading || isGoogleLoading; + return ( -
+
{error && ( Error {error} )} -
- - setName(e.target.value)} - /> -
-
- - setEmail(e.target.value)} - /> + + + +
+
+ +
+
+ Or continue with +
-
- - setPassword(e.target.value)} - /> + +
+
+ + setName(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
-
); } export default function SignUpPage() { + const router = useRouter(); + return ( -
- - - Sign Up - Create an account to get started. +
+ + + + + Create your account + + Get started with your free account today + - - Loading...
}> + + + +
}> -
+
Already have an account?{" "} - - Login + + Sign in
diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 004863d9..109d022e 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -5,8 +5,10 @@ import Image from "next/image"; import { Button } from "./ui/button"; import { ArrowRight } from "lucide-react"; import { HeaderBase } from "./header-base"; +import { useSession } from "@/lib/auth-client"; export function Header() { + const { data: session } = useSession(); const leftContent = ( OpenCut Logo @@ -21,7 +23,7 @@ export function Header() { GitHub - + +
+
+ +
+
+ Or continue with +
+
+
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+ +
+
+ ); +} + +export default function LoginPage() { + const router = useRouter(); + + return ( +
+ + + + Welcome back + + Sign in to your account to continue + + + + + +
}> + + +
+ Don't have an account?{" "} + + Sign up + +
+ + +
+ ); +} diff --git a/apps/web/src/app/(auth)/signup/page.tsx b/apps/web/src/app/(auth)/signup/page.tsx new file mode 100644 index 00000000..3d125a07 --- /dev/null +++ b/apps/web/src/app/(auth)/signup/page.tsx @@ -0,0 +1,181 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { signUp, signIn } from "@/lib/auth-client"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Suspense, useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import Link from "next/link"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Loader2, ArrowLeft } from "lucide-react"; +import { GoogleIcon } from "@/components/icons"; + +function SignUpForm() { + const router = useRouter(); + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [isEmailLoading, setIsEmailLoading] = useState(false); + const [isGoogleLoading, setIsGoogleLoading] = useState(false); + + const handleSignUp = async () => { + setError(null); + setIsEmailLoading(true); + + const { error } = await signUp.email({ + name, + email, + password, + }); + + if (error) { + setError(error.message || "An unexpected error occurred."); + setIsEmailLoading(false); + return; + } + + router.push("/auth/login"); + }; + + const handleGoogleSignUp = async () => { + setError(null); + setIsGoogleLoading(true); + + try { + await signIn.social({ + provider: "google", + }); + + router.push("/editor"); + } catch (error) { + setError("Failed to sign up with Google. Please try again."); + setIsGoogleLoading(false); + } + }; + + const isAnyLoading = isEmailLoading || isGoogleLoading; + + return ( +
+ {error && ( + + Error + {error} + + )} + + + +
+
+ +
+
+ Or continue with +
+
+ +
+
+ + setName(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setEmail(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+
+ + setPassword(e.target.value)} + disabled={isAnyLoading} + className="h-11" + /> +
+ +
+
+ ); +} + +export default function SignUpPage() { + const router = useRouter(); + + return ( +
+ + + + + Create your account + + Get started with your free account today + + + + + +
}> + + +
+ Already have an account?{" "} + + Sign in + +
+ + +
+ ); +} diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx deleted file mode 100644 index ebf2eb3f..00000000 --- a/apps/web/src/app/auth/login/page.tsx +++ /dev/null @@ -1,162 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { signIn } from "@/lib/auth-client"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Suspense, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import Link from "next/link"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { ArrowLeft, Loader2 } from "lucide-react"; -import { GoogleIcon } from "@/components/icons"; - -function LoginForm() { - const router = useRouter(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [isEmailLoading, setIsEmailLoading] = useState(false); - const [isGoogleLoading, setIsGoogleLoading] = useState(false); - - const handleLogin = async () => { - setError(null); - setIsEmailLoading(true); - - const { error } = await signIn.email({ - email, - password, - }); - - if (error) { - setError(error.message || "An unexpected error occurred."); - setIsEmailLoading(false); - return; - } - - router.push("/editor"); - }; - - const handleGoogleLogin = async () => { - setError(null); - setIsGoogleLoading(true); - - try { - await signIn.social({ - provider: "google", - }); - router.push("/editor"); - } catch (error) { - setError("Failed to sign in with Google. Please try again."); - setIsGoogleLoading(false); - } - }; - - const isAnyLoading = isEmailLoading || isGoogleLoading; - - return ( -
- {error && ( - - Error - {error} - - )} - - -
-
- -
-
- Or continue with -
-
-
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
- ); -} - -export default function LoginPage() { - const router = useRouter(); - - return ( -
- - - - Welcome back - - Sign in to your account to continue - - - - - -
}> - - -
- Don't have an account?{" "} - - Sign up - -
- - -
- ); -} diff --git a/apps/web/src/app/auth/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx deleted file mode 100644 index 3d125a07..00000000 --- a/apps/web/src/app/auth/signup/page.tsx +++ /dev/null @@ -1,181 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { signUp, signIn } from "@/lib/auth-client"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Suspense, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import Link from "next/link"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Loader2, ArrowLeft } from "lucide-react"; -import { GoogleIcon } from "@/components/icons"; - -function SignUpForm() { - const router = useRouter(); - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [isEmailLoading, setIsEmailLoading] = useState(false); - const [isGoogleLoading, setIsGoogleLoading] = useState(false); - - const handleSignUp = async () => { - setError(null); - setIsEmailLoading(true); - - const { error } = await signUp.email({ - name, - email, - password, - }); - - if (error) { - setError(error.message || "An unexpected error occurred."); - setIsEmailLoading(false); - return; - } - - router.push("/auth/login"); - }; - - const handleGoogleSignUp = async () => { - setError(null); - setIsGoogleLoading(true); - - try { - await signIn.social({ - provider: "google", - }); - - router.push("/editor"); - } catch (error) { - setError("Failed to sign up with Google. Please try again."); - setIsGoogleLoading(false); - } - }; - - const isAnyLoading = isEmailLoading || isGoogleLoading; - - return ( -
- {error && ( - - Error - {error} - - )} - - - -
-
- -
-
- Or continue with -
-
- -
-
- - setName(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
- ); -} - -export default function SignUpPage() { - const router = useRouter(); - - return ( -
- - - - - Create your account - - Get started with your free account today - - - - - -
}> - - -
- Already have an account?{" "} - - Sign in - -
- - -
- ); -} -- cgit v1.3.1