commit ecf1d45af8d8a1e2002b5bb8148a584371548ac1
Author: NK
Date: Thu Apr 27 14:38:02 2023 +0100
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..81ea8f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+package-lock.json
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..965a122
--- /dev/null
+++ b/README.md
@@ -0,0 +1,38 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
+
+[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
+
+The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..a843cbe
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+}
+
+module.exports = nextConfig
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..98188c4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "youtube-downloader",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@types/node": "18.16.1",
+ "@types/react": "18.2.0",
+ "@types/react-dom": "18.2.1",
+ "autoprefixer": "^10.4.14",
+ "downloadjs": "^1.4.7",
+ "next": "13.3.1",
+ "react": "18.2.0",
+ "react-dom": "18.2.0",
+ "react-icons": "^4.8.0",
+ "tailwindcss": "^3.3.2",
+ "typescript": "5.0.4",
+ "ytdl-core": "npm:@distube/ytdl-core@^4.11.9"
+ },
+ "devDependencies": {
+ "@types/downloadjs": "^1.4.3"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..12a703d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
new file mode 100644
index 0000000..d5bbcf8
--- /dev/null
+++ b/src/pages/_app.tsx
@@ -0,0 +1,7 @@
+import "tailwindcss/tailwind.css";
+
+import type { AppProps } from "next/app";
+
+export default function App({ Component, pageProps }: AppProps) {
+ return ;
+}
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
new file mode 100644
index 0000000..b2fff8b
--- /dev/null
+++ b/src/pages/_document.tsx
@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from "next/document";
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/pages/api/download.ts b/src/pages/api/download.ts
new file mode 100644
index 0000000..5c266fb
--- /dev/null
+++ b/src/pages/api/download.ts
@@ -0,0 +1,30 @@
+import type { NextApiRequest, NextApiResponse } from "next";
+const ytdl = require("ytdl-core");
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ if (req.method === "POST") {
+ try {
+ const url = req.body.url;
+ const type = req.body.type;
+ if (!ytdl.validateURL(url))
+ return res.status(400).json({ message: "Invalid URL", code: 400 });
+ if (type !== "mp3" && type !== "mp4")
+ return res.status(400).json({ message: "Invalid type", code: 400 });
+ res.setHeader(
+ "Content-Type",
+ type === "mp3" ? "audio/mpeg" : "video/mp4"
+ );
+ await ytdl(url, {
+ format: type,
+ filter: type == "mp4" ? "videoandaudio" : "audioonly",
+ }).pipe(res);
+ } catch (e) {
+ console.log(e);
+ }
+ } else {
+ res.status(405).json({ message: "Method not allowed", code: 405 });
+ }
+}
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..e3c0f1a
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,150 @@
+import React, { useState } from "react";
+import Head from "next/head";
+import { FiDownloadCloud } from "react-icons/fi";
+import download from "downloadjs";
+
+export default function Home() {
+ const [url, setUrl] = useState("");
+ const [info, setInfo] = useState("");
+
+ const getTitle = async (videoID: string) => {
+ const youtubeAPI = `https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoID}&fields=items(id%2Csnippet)&key=AIzaSyB8Fk-MWT_r8nVgG35gIZoP-DhJYpJ_tZ0`;
+ let response = await fetch(youtubeAPI);
+ const res = await response.json();
+ const title = res.items[0].snippet.title;
+ return title;
+ };
+
+ const getVideoID = (url: string) => {
+ if (url.match(/watch/)) {
+ const videoID = url.split("/")[3].split("?")[1].split("=")[1];
+ return videoID;
+ } else if (url.match(/youtu.be/)) {
+ const videoID = url != "" && url.split("/")[3];
+ return videoID;
+ }
+ };
+
+ const handleMp4 = async () => {
+ const videoID = getVideoID(url);
+ setInfo("Processing the video...");
+ if (videoID) {
+ const title = await getTitle(videoID);
+ try {
+ fetch(`/api/download`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ url, type: "mp4" }),
+ })
+ .then(res => res.blob())
+ .then(blob => {
+ const sizeInBytes = blob.size;
+ console.log("sizeInBytes: ", sizeInBytes);
+ if (sizeInBytes <= 0) {
+ setInfo(
+ "Unable to download! Maybe File size is too high. Try to download video less than 5MB"
+ );
+ } else {
+ download(blob, `${title}.mp4`, "video/mp4");
+ setInfo("Ready for download!");
+ }
+ });
+ } catch (err) {
+ setInfo(
+ "Unable to download! Maybe File size is too high. Try to download video less than 5MB"
+ );
+ }
+ } else {
+ setInfo("Invalid URL");
+ }
+ };
+
+ const handleMp3 = async () => {
+ const videoID = getVideoID(url);
+ setInfo("Processing the video...");
+ if (videoID) {
+ const title = await getTitle(videoID);
+ try {
+ const requestOptions = {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ url, type: "mp3" }),
+ };
+ fetch(`/api/download`, requestOptions)
+ .then(res => res.blob())
+ .then(blob => {
+ const sizeInBytes = blob.size;
+ console.log("sizeInBytes: ", sizeInBytes);
+ if (sizeInBytes <= 0) {
+ setInfo(
+ "Unable to download! Maybe File size is too high. Try to download video less than 5MB"
+ );
+ } else {
+ download(blob, `${title}.mp3`, "audio/mpeg");
+ setInfo("Ready for download!");
+ }
+ });
+ } catch (err) {
+ console.log("err: ", err);
+ }
+ } else {
+ setInfo("Invalid URL");
+ }
+ };
+
+ return (
+
+
+
Youtube Downlaoder
+
+
+
+
+
+
+ {" "}
+ Youtube Downlaoder{" "}
+
+
+ {" "}
+ Sample video link : https://youtu.be/videoID{" "}
+
+
+
+
+ {
+ setInfo("");
+ setUrl(e.target.value);
+ }}
+ placeholder="Paste the valid youtube link"
+ required
+ >
+
+
+
+
+
+
+
+ {info &&
{info}
}
+
+
+
+ );
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
new file mode 100644
index 0000000..76fcadc
--- /dev/null
+++ b/src/styles/globals.css
@@ -0,0 +1,3 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..e182eeb
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,11 @@
+module.exports = {
+ purge: ["./src/pages/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
+ darkMode: false, // or 'media' or 'class'
+ theme: {
+ extend: {},
+ },
+ variants: {
+ extend: {},
+ },
+ plugins: [],
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..611254a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "postcss.config.js",
+ "tailwind.config.js"
+ ],
+ "exclude": ["node_modules"]
+}