Skip to content

Commit

Permalink
Fixed some directory issues.
Browse files Browse the repository at this point in the history
Added UI download bar and API endpoint
  • Loading branch information
JeeZeh committed Aug 9, 2020
1 parent e60cfbb commit fed2c7f
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 51 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ If you've ever wanted to quickly search for something that was said in a series

# Caveats - these are important

> I **highly** recommend using a VPN to avoid being IP limited or banned.
1. You _must_ provide a playlist. Even if you just want one video, wrap it in a playlist!
1. YouTube does a lot with playlists. For example, clicking "Play all" on a channel uploads will give you a playlist link. This is how you index an entire channel.
2. The subtitles are, in most cases, provided by YouTube's auto-captions, though it does download official ones if present!
Expand Down Expand Up @@ -65,7 +67,7 @@ If you've ever wanted to quickly search for something that was said in a series
1. ~Figure out how to efficiently search for text across millions of subtitle entries~ [FlexSearch](https://github.com/nextapps-de/flexsearch)
1. ~Refresh playlist_data folder on changes so you don't need to keep restarting the UI~
1. Include playlist title in Ytd
1. Download playlists from UI
1. ~Download playlists from UI~

### UI

Expand Down
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react-feather": "^2.0.8",
"react-router-dom": "^5.2.0",
"react-spinners": "^0.9.0",
"react-toastify": "^6.0.8",
"react-window": "^1.8.5",
"styled-components": "^5.0.1",
"typescript": "^3.9.7",
Expand Down Expand Up @@ -70,7 +71,8 @@
"test": "echo \"Error: no test specified\" && exit 1",
"tsc": "tsc -p .",
"clean-dist": "rimraf dist/*",
"start": "npm run tsc && concurrently --kill-others \"webpack-dev-server --config=configs/webpack/dev.js\" \"node server.js\"",
"dev": "npm run tsc && concurrently --kill-others \"webpack-dev-server --config=configs/webpack/dev.js\" \"node server.js\"",
"start": "npm run tsc && npm run build && start http://localhost:6060 & node server.js",
"server": "npm run tsc && node server.js",
"build": "npm run clean-dist && webpack -p --config=configs/webpack/dev.js",
"ytdl": "npm run update-ytdl && npm run tsc && node ytdl -- ",
Expand Down
50 changes: 43 additions & 7 deletions server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { PlaylistJSON, YtdlPlaylistDownloader } from "./ytdl";
import express from "express";
import { PlaylistJSON } from "./ytdl";
import * as fs from "fs";
import humanizeDuration from "humanize-duration";
import cors from "cors";
import md5 from "object-hash";
const app = express();
const port = 8080; // default port to listen
const port = 6060; // default port to listen
const playlistDirectory = "./playlist_data";
const playlists = new Map<string, PlaylistJSON>();
const playlistHashes = new Map<string, string>();
const playlistMetadata = new Map<string, PlaylistMetadata>();
let ytdlSingleton = false;

app.use(cors());
app.use(cors(), express.static("dist", {}));

const generateMetadata = async (playlistData: PlaylistJSON) => {
if (Object.keys(playlistData.videos).length === 0) return false;
Expand Down Expand Up @@ -43,7 +44,7 @@ const initPlaylistData = async () => {
fs.readFileSync(playlistDirectory + "/" + filename, "utf-8")
);
metadataPromise.push(generateMetadata(playlistData));
playlistHashes.set(filename, md5(playlistData.videos));
playlistHashes.set(filename.split(".json")[0], md5(playlistData.videos));
playlists.set(filename.split(".json")[0], playlistData);
}
await Promise.all(metadataPromise);
Expand Down Expand Up @@ -75,9 +76,10 @@ const checkUpdates = async (id?: string) => {
await Promise.all(readAsync);
for (const file of playlistDir) {
let data = readAsync.find((d) => file === d.id + ".json");

if (md5(data.videos) !== playlistHashes.get(file)) {
playlists.set(file, data);
const filename = file.split(".json")[0];
const hash = playlistHashes.get(filename);
if (!hash || md5(data.videos) !== hash) {
playlists.set(filename, data);
toUpdate.push(data);
}
}
Expand Down Expand Up @@ -129,6 +131,40 @@ const initRoutes = async () => {
}
res.json(playlist);
});

app.post("/download", (req, res) => {
const id = req.query.id;
if (typeof id !== "string") {
res.status(400).send("Please provide a single ID string");
return;
}
if (ytdlSingleton)
return res.status(400).json({
success: false,
message: "Ytdl download already in progress",
});
ytdlSingleton = true;
YtdlPlaylistDownloader(id)
.then((playlistId) => {
if (playlistId) {
res.json({ success: true, message: playlistId });
} else {
res.status(500).json({
success: false,
message: "Downloading failed, check server output",
});
}
})
.catch((e) => {
console.error(e);
res.status(500).json({
success: false,
message: "Downloading failed, check server output",
error: e,
});
})
.finally(() => (ytdlSingleton = false));
});
};

initPlaylistData()
Expand Down
60 changes: 46 additions & 14 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,39 @@ import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import "./../assets/scss/App.scss";
import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
import { Playlists } from "./Playlists";
import DownloadBar from "./DownloadBar";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import Typography from "@material-ui/core/Typography";
import PlaylistExplorer from "./PlaylistExplorer";
import styled from "styled-components";
import { Button } from "@material-ui/core";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { createMuiTheme } from "@material-ui/core/styles";

const theme = createMuiTheme({
palette: {
primary: {
light: "#757ce8",
main: "#3f50b5",
dark: "#002884",
contrastText: "#fff",
},
secondary: {
light: "#ff7961",
main: "#f44336",
dark: "#ba000d",
contrastText: "#000",
},
},
});

const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,

backgroundColor: "#fafafa",
minHeight: "101vh",
},
Expand All @@ -23,6 +45,9 @@ const useStyles = makeStyles((theme: Theme) =>
header: {
color: "whitesmoke",
},
bar: {
justifyContent: "space-between",
},
})
);

Expand All @@ -44,23 +69,30 @@ const App = (props: {}) => {
return (
<div className={classes.root}>
<Router>
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
<StyledLink to={"/"} className={classes.header}>
YouTube Playlist Autosub Explorer
<AppBar position="sticky">
<Toolbar className={classes.bar}>
<div>
<Typography variant="h6" className={classes.title}>
<StyledLink to={"/"} className={classes.header}>
YouTube Playlist Autosub Explorer
</StyledLink>
</Typography>
</div>

<DownloadBar toastController={toast} />
<div>
<StyledLink to={"/"}>
<Button
variant="outlined"
style={{ color: "whitesmoke", borderColor: "whitesmoke" }}
>
Home
</Button>
</StyledLink>
</Typography>
<StyledLink to={"/"}>
<Button
variant="outlined"
style={{ color: "whitesmoke", borderColor: "whitesmoke" }}
>
Home
</Button>
</StyledLink>
</div>
</Toolbar>
</AppBar>
<ToastContainer />
<Route exact={true} path="/" component={Playlists} />
<Route path="/p/:playlistId" component={PlaylistExplorer} />
</Router>
Expand Down
94 changes: 94 additions & 0 deletions src/components/DownloadBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from "react";
import { useState } from "react";
import Typography from "@material-ui/core/Typography";
import { SubSearchResult } from "./PlaylistExplorer";
import { downloadPlaylist } from "./PlaylistApi";
import { VideoInfo } from "../../ytdl";
import { Play, Send } from "react-feather";
import {
LinearProgress,
ListItemIcon,
TextField,
Button,
Grid,
} from "@material-ui/core";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import { withStyles } from "@material-ui/core/styles";
import PropTypes from "prop-types";
import { IconButton } from "@material-ui/core/";
import { Toast } from "react-toastify/dist/components";

const styles = {
root: {
maxWidth: 400,
justifyContent: "center",
alignItems: "center",
},
input: {
width: 300,
color: "whitesmoke",
},
};

const DownloadBar = (props: any) => {
const { classes, toastController } = props;
const [inputValue, setInputValue] = useState("");

const searchPlaylist = async () => {
if (inputValue === "") {
toastController.error("Please enter a playlist ID or URL", {
progress: 0,
autoClose: false,
});
return;
}

toastController.info(
"Attempting to download playlist. This might take a while, so check the console output for progress updates",
{ progress: 0, autoClose: false }
);
const download = await downloadPlaylist(inputValue);
if (!download.success) {
toastController.error(download.message, {
progress: 1,
autoClose: false,
});
console.error("Error downloading playlist", download);
return;
}

toastController.success("Download succeeded, refresh your playlists!", {
progress: 1,
autoClose: false,
});
};

return (
<Grid container className={classes.root}>
<Grid item>
<TextField
variant="standard"
placeholder="Download a playlist"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
InputProps={{
className: classes.input,
}}
/>
</Grid>
<Grid item>
<IconButton onClick={() => searchPlaylist()}>
<Send color="whitesmoke" />
</IconButton>
</Grid>
</Grid>
);
};

DownloadBar.propTypes = {
classes: PropTypes.object.isRequired,
toastController: PropTypes.func.isRequired,
};

export default withStyles(styles)(DownloadBar);
9 changes: 8 additions & 1 deletion src/components/PlaylistApi.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { PlaylistMetadata } from "../../server";
import { PlaylistJSON } from "../../ytdl";

const host = "http://localhost:8080";
const host = "http://localhost:6060";
const PLAYLIST_METADATA = "playlist_metadata";
const PLAYLIST = "playlist";
const DOWNLOAD = "download";

export const getPlaylistMetadata = async (): Promise<PlaylistMetadata[]> => {
return await (await fetch(`${host}/${PLAYLIST_METADATA}`)).json();
Expand All @@ -12,3 +13,9 @@ export const getPlaylistMetadata = async (): Promise<PlaylistMetadata[]> => {
export const getPlaylist = async (id: string): Promise<PlaylistJSON> => {
return await (await fetch(`${host}/${PLAYLIST}/${id}`)).json();
};

export const downloadPlaylist = async (id: string): Promise<string | any> => {
return await (
await fetch(`${host}/${DOWNLOAD}?id=${id}`, { method: "POST" })
).json();
};
Loading

0 comments on commit fed2c7f

Please sign in to comment.