Typescript remake

master
Feuerhamster 4 years ago
parent 94ba9a5a65
commit 35bcbe9568

3
.gitignore vendored

@ -0,0 +1,3 @@
node_modules
.idea
dist

@ -0,0 +1,34 @@
# Build stage
FROM node:lts-alpine AS builder
RUN mkdir /app
WORKDIR /app
# copy configs folder
COPY package*.json ./
COPY tsconfig.json ./
# copy source code to /app/src folder
COPY src src
# install dependencies (https://docs.npmjs.com/cli/v7/commands/npm-ci)
RUN npm ci
# build
RUN npm run build
# Production stage
FROM node:lts-alpine
ENV NODE_ENV=production
RUN mkdir /app
WORKDIR /app
COPY package*.json ./
COPY --from=builder /app/dist dist
RUN mkdir targets
# install production dependencies
RUN npm ci
CMD ["npm", "start"]

@ -1 +1,29 @@
# web-youtube-downloader
# Web YouTube Downloader
A simple express app which allows you to view all source urls of a YouTube video to directly download it from Google's servers.
## 📦 Dependencies
- [Express](https://expressjs.com/)
- [Typescript](https://www.typescriptlang.org/)
- [Pug](https://pugjs.org/)
- [ytdl-core](https://www.npmjs.com/package/ytdl-core)
## 💽 Installation
### Docker
```shell
git clone https://github.com/Feuerhamster/web-youtube-downloader.git
cd web-youtube-downloader
docker build -t Feuerhamster/web-youtube-downloader .
docker run Feuerhamster/web-youtube-downloader
-e PORT=3000
```
### Manually
*Requires NodeJS 14 or higher*
```shell
git clone https://github.com/Feuerhamster/web-youtube-downloader.git
cd web-youtube-downloader
npm install
npm run build
npm run start
```

@ -1,55 +0,0 @@
<?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
if(isset($_GET["vid"])){
$ytpage = file_get_contents("https://www.youtube.com/watch?v=".$_GET["vid"]);
unset($_COOKIE);
$pattern = "/ytplayer.config\ \=\ ({.+}});/";
preg_match_all($pattern, $ytpage, $matches);
$json = $matches[1][0];
//parse informations
$json = json_decode($json);
$ytdata = $json->args->player_response;
$ytdata = json_decode($ytdata);
if($ytdata->videoDetails->useCipher == false){
$data = new stdClass();
$data->title = $ytdata->videoDetails->title;
$data->channel = $ytdata->videoDetails->author;
$data->videoId = $ytdata->videoDetails->videoId;
$data->length = $ytdata->videoDetails->lengthSeconds;
$data->views = $ytdata->videoDetails->viewCount;
$data->sources = $ytdata->streamingData;
$data->thumbnails = $ytdata->videoDetails->thumbnail->thumbnails;
$send = json_encode($data);
echo $send;
}else{
$data = new stdClass();
$data->error = "cipher_video";
$send = json_encode($data);
echo $send;
}
}else{
$data = new stdClass();
$data->error = "missing_video_id";
$send = json_encode($data);
echo $send;
}
?>

@ -1,59 +0,0 @@
<h1>API for Developers</h1>
<p>You can get the data of a video very easily by using our API.</p>
<h2>Web Request structure</h2>
<span class="label"><span class="label-name" style="width: 70px;display: inline-block;">Method</span><span class="label-value label-green">GET</span></span>
<span class="label"><span class="label-name" style="width: 70px;display: inline-block;">Url</span><span class="label-value label-blue">https://feuerhamster.code-elite.net/web-youtube-downloader/api.php</span></span>
<span class="label"><span class="label-name" style="width: 70px;display: inline-block;">Params</span><span class="label-value label-purble">vid={YOUTUBE VIDEO ID}</span></span>
<span class="label"><span class="label-name" style="width: 70px;display: inline-block;">Return</span><span class="label-value label-orange">JSON</span></span>
<h2 style="margin-top: 40px;">Return values</h2>
<p>There are mulitple return values for that request. The return values are also in JSON format</p>
<div class="pre">Errors:
- error: missing_video_id //returns if you have not send the video Id in the query string
- error: cipher_video" //returns if the video is protected and can not be read by the api
</div>
<div class="pre">Video Data JSON structure:
- title
- channel
- length
- views
- sources:
- expiresInSeconds
- formats:
ARRAY()
- adaptiveFormats:
ARRAY()
- thumbnails:
ARRAY()
</div>
<h2 style="margin-top: 40px;">Examples</h2>
<p>An example url for an api call</p>
<div class="pre">https://feuerhamster.code-elite.net/web-youtube-downloader/api.php?vid=nD6_aQIJgW0
</div class="pre">
<p>An example with Ajax in JavaScript</p>
<div class="pre">var videoId = "nD6_aQIJgW0";
$.ajax({
type: 'GET',
url: 'https://feuerhamster.code-elite.net/web-youtube-downloader/api.php',
data: 'vid=' + videoId,
success: function(res){
if(!res.error){
console.log(res);
}
}
});
</div>

@ -1,31 +0,0 @@
<div class="box box-mobile">
<img id="video-thumbnail" style="max-height: 160px;">
<div>
<h1 id="video-title" style="margin: 0px 0px 0px 20px;">...</h1>
<h2 id="video-time" style="margin: 0px 0px 0px 20px; color: #9e4b8b;">...</h2>
<h2 id="video-views" style="margin: 0px 0px 0px 20px; color: #9e4b8b;">...</h2>
<h2 id="video-channel" style="margin: 0px 0px 0px 20px; color: rgb(120,120,120)">...</h2>
<h3 id="video-links" style="margin: 0px 0px 0px 20px;">...</h3>
</div>
</div>
<h1>Main Sources</h1>
<div id="video-main-source">
</div>
<h1>Adaptive Sources</h1>
<div id="video-sources">
</div>
<h1>Thumbnails</h1>
<div id="video-thumbnails">
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

@ -1,9 +0,0 @@
<div id="home-page">
<h1 style="font-size: 42px">Download YouTube videos from source!</h1>
<h2 style="margin-bottom: 50px;">All Video and Audio Tracks + Thumbnails</h2>
<input type="text" id="home-video-url" placeholder="Paste your Video URL" onchange="loadVideoURL(this.value);" />
</div>

@ -1,107 +0,0 @@
//get query string
var urlQs = document.URL.split('?');
//check if any querystring is set
if(urlQs[1]){
//parse querytring args
var qsArgs = urlQs[1].split('=');
if(qsArgs[0] == "vid"){
$('#content').load('./downloader.html');
getVideoData(qsArgs[1]);
}else if(qsArgs[0] == "page"){
if(qsArgs[1] == "infos"){
$('#content').load('./infos.html');
}else if(qsArgs[1] == "documentation"){
$('#content').load('./docs.php');
}else{
$('#content').html('<center><h1>ERROR 404: Page not found!</h1></center>');
}
}
}else{
$('#content').load('./home.html');
}
function parseMimeType(mimeType){
var regex = /(\w+)\/(\w+)\;\ codecs\=\"(.+)\"/;
var result = regex.exec(mimeType);
return { type: result[1], format: result[2], codec: result[3] };
}
function secToMin(time) {
var hr = ~~(time / 3600);
var min = ~~((time % 3600) / 60);
var sec = time % 60;
var sec_min = "";
if (hr > 0) {
sec_min += "" + hrs + ":" + (min < 10 ? "0" : "");
}
sec_min += "" + min + ":" + (sec < 10 ? "0" : "");
sec_min += "" + sec;
return sec_min+ " minutes";
}
function getVideoData(videoId){
$.ajax({
type: "GET",
url: "./api.php",
data: "vid=" + videoId,
success: function(res){
console.log(res);
if(!res.error){
$('#video-title').html(res.title);
$('#video-channel').html(res.channel);
$('#video-views').html(res.views.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,') + " views");
$('#video-time').html(secToMin(res.length));
$('#video-thumbnail').attr("src", res.thumbnails[3].url);
$('#video-links').html('<a href="https://www.youtube.com/watch?v=' + res.videoId + '" style="color: #2980b9" target="_blank">https://www.youtube.com/watch?v=' + res.videoId + '<a>');
res.sources.formats.forEach(element => {
var mainMime = parseMimeType(element.mimeType);
$('#video-main-source').append('<div class="box box-mobile"> <video src="' + element.url + '" controls height="190px"></video> <div style="padding: 10px 20px 10px 20px"> <h2 style="margin-top: 0px; margin-bottom: 10px; font-weight: 100"><b style="font-weight: 700">Quality:</b> ' +element.qualityLabel+ ' </h2> <h2 style="margin-top: 0px; margin-bottom: 10px; font-weight: 100"><b style="font-weight: 700">Format:</b> ' + mainMime.format + ' </h2> <h2 style="margin-top: 0px; margin-bottom: 10px; font-weight: 100"><b style="font-weight: 700">Codec:</b> ' + mainMime.codec + ' </h2> <a href="' + element.url + '" target="_blank" class="download-link-large"><i class="fas fa-external-link-alt"></i> Download</a> </div></div>');
});
res.sources.adaptiveFormats.forEach(element => {
var mime = parseMimeType(element.mimeType);
if(mime.type == "video"){
$('#video-sources').append('<div class="box" style="margin: 10px 0px 10px 0px;"><span class="label"><span class="label-name">Type</span><span class="label-value label-red">' + mime.type + '</span></span><span class="label"><span class="label-name">Quality</span><span class="label-value label-green">' + element.qualityLabel + '</span></span><span class="label"><span class="label-name">Format</span><span class="label-value label-orange">' + mime.format + '</span></span><span class="label"><span class="label-name">Codec</span><span class="label-value label-purble">' + mime.codec + '</span></span> <div class="spacer"></div> <a href="' + element.url + '" target="_blank" style="color: rgb(50,50,50); font-size: 26px;"><i class="fas fa-external-link-alt"></i></a> </div>');
}else{
$('#video-sources').append('<div class="box" style="margin: 10px 0px 10px 0px;"><span class="label"><span class="label-name">Type</span><span class="label-value label-blue">' + mime.type + '</span></span><span class="label"><span class="label-name">Bitrate</span><span class="label-value label-green">' + element.bitrate + '</span></span><span class="label"><span class="label-name">Format</span><span class="label-value label-orange">' + mime.format + '</span></span><span class="label"><span class="label-name">Codec</span><span class="label-value label-purble">' + mime.codec + '</span></span> <div class="spacer"></div> <a href="' + element.url + '" target="_blank" style="color: rgb(50,50,50); font-size: 26px;"><i class="fas fa-external-link-alt"></i></a> </div>');
}
});
res.thumbnails.forEach(element => {
$('#video-thumbnails').append('<div class="box box-mobile" style="margin: 10px 0px 10px 0px; flex-direction: row"><img src="' + element.url + '" style="width: 200px;" /><div style="padding: 10px 20px 10px 20px"><span class="label" style="margin: 0px;"><span class="label-name">Size</span><span class="label-value label-green">' + element.width + 'px*' + element.height + 'px</span></span><br/><a href="' + element.url + '" target="_blank" class="download-link-large" style="font-size: 20px;" download="' + element.url + '"><i class="fas fa-external-link-alt"></i> Download</a></div></div>')
});
}else{
$('#content').html('<center><h1>An error has occurred!</h1><h2>Please try another video or try again later</h2></center>');
}
}
});
}
function loadVideoURL(source){
//parse soruce and get id
var ytidRegex = /(youtu\.be\/|youtube\.com\/(watch\?(.*&)?v=|(embed|v)\/))([^\?&"'>]+)/;
var videoId = ytidRegex.exec(source)[5];
window.location.href = window.location.protocol + "//" + window.location.hostname + window.location.pathname + "?vid=" + videoId;
}

@ -1,49 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<title>Web-YTDL</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=400, initial-scale=0.8">
<meta name="description" content="Download YouTube videos from source!">
<link rel="stylesheet" href="style.css?v=2" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css" integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Muli&display=swap" rel="stylesheet">
<link rel="icon" href="./favicon.ico" type="image/x-icon">
<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon">
</head>
<body>
<div id="head-bar">
<h1 onclick="window.location.href='./';" style="cursor: pointer;"> <i class="fas fa-file-video"></i> <span id="head-text">Web-Youtube-Downloader</span></h1>
<div class="spacer"></div>
<input type="text" id="headbar-video-url" placeholder="Paste your video URL" onchange="loadVideoURL(this.value); this.value = '';" />
</div>
<div id="content">
</div>
<footer>
<a href="?page=documentation">Developer API</a> <a href="https://github.com/Feuerhamster/web-youtube-downloader" target="_blank">GitHub</a> <a href="?page=infos">Disclaimer/Informations</a> <a href="https://hamsterlabs.de" target="_blank">Visit HamsterLabs</a>
</footer>
</body>
</html>
<script src="index.js"></script>

@ -1,26 +0,0 @@
<h1>Disclaimer</h1>
<p>This Website is not a part of youtube or google</p>
<h1>Infos</h1>
<p>
<b>Contact the owner:</b> <br/><br/>
<b>Email: </b> myhamstermail@gmail.com<br/>
<b>GitHub: </b> github.com/Feuerhamster<br/>
<br/><br/>
<b>Where do we have the data of the youtube videos?</b><br/><br/>
We use the official data from the youtube.com website. All URLs and metadata come from the respective page of the corresponding Youtube video.<br/><br/>
<br/><br/>
<b>Is the downloading of youtube videos lawful?</b><br/><br/>
Everyone is allowed to download public Youtube videos for private use.<br/><br/>
<br/><br/>
<b>Does this website violate laws and regulations such as once "convert2mp3"?</b><br/><br/>
No. On "convert2mp3" the videos were converted on the servers of the provider and made available for download.<br/>
On our website, you download the videos directly from the sources on the google server.
</p>

1815
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,30 @@
{
"name": "web-youtube-downloader",
"version": "2.0.0",
"description": "Download YouTube videos from source",
"main": "dist/main.js",
"scripts": {
"start": "node dist/main.js",
"build": "tsc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Feuerhamster/web-youtube-downloader.git"
},
"author": "Feuerhamster",
"license": "ISC",
"bugs": {
"url": "https://github.com/Feuerhamster/web-youtube-downloader/issues"
},
"homepage": "https://github.com/Feuerhamster/web-youtube-downloader#readme",
"dependencies": {
"express": "^4.17.1",
"node-cache": "^5.1.2",
"pug": "^3.0.2",
"typescript": "^4.2.3",
"ytdl-core": "^4.8.2"
},
"devDependencies": {
"@types/express": "^4.17.11"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,200 @@
/*
* Main styles
*/
* {
box-sizing: border-box;
}
html, body {
min-height: 100%;
width: 100%;
margin: 0;
font-family: sans-serif;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #222;
background-color: #fafafa;
padding: 20px;
}
/*
* titlebox
*/
.title-box {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.title-box > img {
max-height: 100px;
border-radius: 6px;
margin-right: 30px;
box-shadow: 1px 1px 12px rgba(0, 0, 0, 0.2);
}
.title-box h1 {
font-size: 32px;
margin: 0 0 20px 0;
}
.title-box a {
margin: 5px;
font-size: 18px;
color: #2481e6;
text-decoration: none;
}
.title-box a:hover {
text-decoration: underline;
}
/*
* responsive element
*/
.responsive {
display: flex;
flex-direction: column;
flex-grow: 1;
justify-content: center;
align-items: stretch;
max-width: 720px;
}
.responsive > h1 {
font-size: 32px;
margin: 20px 0;
}
.responsive > h2 {
font-size: 24px;
margin: 20px 0;
}
.responsive input {
border: 2px solid #ddd;
padding: 10px 14px;
border-radius: 6px;
font-size: 18px;
outline: 0;
margin-right: 10px;
background-color: white;
}
button {
background-color: #eb6161;
cursor: pointer;
border: none;
color: white;
padding: 10px 14px;
border-radius: 6px;
font-size: 18px;
outline: 0;
}
button:hover {
background-color: #c32e2e;
}
.content {
display: flex;
flex-direction: column;
align-items: stretch;
max-width: 720px;
}
.center {
display: flex;
flex-direction: column;
align-items: center;
}
/*
* Table
*/
table {
width: 100%;
border-collapse: collapse;
overflow: auto;
}
td, th {
border-bottom: 2px solid #ddd;
padding: 10px;
text-align: left;
}
tr:last-child > td {
border: 0;
}
tr:hover td {
background-color: rgba(0, 0, 0, 0.05);
}
td a {
color: #3488ee;
text-decoration: none;
}
td a:hover {
text-decoration: underline;
}
td img {
max-height: 70px;
}
/*
* Format label
*/
.format {
color: white;
padding: 2px 5px;
border-radius: 3px;
}
.format-0 {
background-color: #43c043;
}
.format-1 {
background-color: #3488ee;
}
.format-2 {
background-color: #EE6565;
}
td.mp4 {
color: #f38a00;
font-weight: bold;
}
td.webm {
color: #3488ee;
font-weight: bold;
}
/*
* Iframe
*/
iframe {
height: 355px;
}
@media only screen and (max-width: 720px) {
.responsive {
width: 100%;
}
table.formats th:nth-child(5), table.formats td:nth-child(5) {
display: none;
}
}

@ -0,0 +1,15 @@
import express, { Application } from "express";
import defaultRoute from "./routes/default";
const app: Application = express();
const port: number = parseInt(process.env.PORT) || 4131;
app.set("views", "views");
app.set("view engine", "pug");
app.use(express.static("public"));
app.use(defaultRoute);
app.listen(port, () => {
console.log("App started on port: " + port);
});

@ -0,0 +1,29 @@
import {Request, Response, Router} from "express";
import {VideoData} from "../types/youtube";
import {YouTube} from "../services/youtube";
const router: Router = Router();
router.get("/", async (req: Request, res: Response) => {
// No video searched -> startpage
if(!req.query.url) {
res.render("index");
return;
}
let video: VideoData = await YouTube.getVideoInfo(req.query.url);
// Cant get video -> error page
if(!video) {
res.render("error");
return;
}
res.render("video", video);
});
router.get("/about", (req, res) => res.render("about"));
export default router;

@ -0,0 +1,56 @@
import ytdl, {videoInfo} from "ytdl-core";
import {FormatType, VideoData} from "../types/youtube";
import NodeCache from "node-cache";
export class YouTube {
private static cache = new NodeCache({ stdTTL: 3600 });
static async getVideoInfo(url): Promise<VideoData> {
if(this.cache.has(url)) {
return this.cache.get(url);
}
let video: videoInfo = null;
let videoData: VideoData = <VideoData>{};
// Get video info
try {
video = await ytdl.getInfo(url.toString());
} catch(e) {
return null;
}
// format info data
videoData.id = video.videoDetails.videoId;
videoData.url = video.videoDetails.video_url;
videoData.title = video.videoDetails.title;
videoData.author = video.videoDetails.author.name;
videoData.thumbnails = video.videoDetails.thumbnails.map((t) => ({
url: t.url,
size: `${t.width}x${t.height}`
}));
videoData.formats = video.formats.map((f) => ({
type: f.hasVideo && f.hasAudio ? FormatType.main : f.hasVideo ? FormatType.video : FormatType.audio,
quality: f.qualityLabel,
container: f.container,
codecs: f.codecs,
url: f.url,
bitrate: f.bitrate.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ")
}));
videoData.formats = videoData.formats.sort((a, b) => a.type < b.type ? -1 : a.type > b.type ? 1 : 0);
// Save to cache
if(!this.cache.has(url)) {
this.cache.set(url, videoData);
}
return videoData;
}
}

@ -0,0 +1,26 @@
export interface VideoData {
id: string;
title: string;
author: string;
url: string;
thumbnails: Thumbnail[];
formats: Format[]
}
export interface Thumbnail {
url: string;
size: string
}
export interface Format {
type: FormatType
quality: string;
container: string;
codecs: string;
url: string;
bitrate: string
}
export enum FormatType {
main, audio, video
}

@ -1,183 +0,0 @@
body, html{
margin: 0px;
height: 100%;
width: 100%;
font-family: 'Muli', sans-serif;
}
*{
box-sizing: border-box;
}
h1, h2, h3, h4, h5, h6, p{
color: rgb(50,50,50);
}
@media (max-width:901px){
.box-mobile{
flex-direction: column!important;
align-items: center;
}
.box{
flex-wrap: wrap;
}
#head-text{
display: none;
}
}
.pre{
background-color: rgba(240,240,240);
border: 2px solid rgba(230,230,230);
border-radius: 2px;
padding: 10px;
font-family: 'Courier New', Courier, monospace;
color: rgb(50,50,50);
overflow-x: auto;
margin-top: 20px;
margin-bottom: 20px;
white-space: pre;
}
#head-bar{
display: flex;
align-items: flex-start;
background-color: #d63030;
padding: 5px;
box-shadow: 0px 0px 5px 1px rgba(0,0,0,0.3);
position: relative;
}
#head-bar > h1{
color: white;
margin: 0px;
font-family: 'Muli', sans-serif;
font-size: 28px;
padding: 2px;
}
.spacer{
flex-grow: 1;
}
#head-bar > input{
background-color: rgba(255,255,255,0.3);
border: 0;
padding: 8px;
margin: 2px;
font-size: 16px;
color: white;
font-family: 'Muli', sans-serif;
min-width: 300px;
border-radius: 2px;
}
#head-bar > input:focus{
box-shadow: 0px 0px 5px 1px rgba(70,70,70,0.2);
}
#content{
display: flex;
width: 100%;
min-height: calc(100% - 93px);
flex-direction: column;
background-color: rgb(250,250,250);
padding: 30px;
}
#home-page{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
flex-direction: column;
}
#home-page > input{
background-color: #f04f4f;
border: 0;
padding: 10px;
margin: 5px;
font-size: 18px;
font-family: 'Muli', sans-serif;
min-width: 400px;
border-radius: 2px;
color: white;
}
#home-page > input:focus{
box-shadow: 0px 0px 2px 1px rgba(180,180,180,1);
}
footer{
padding: 10px;
display: flex;
justify-content: center;
background-color: rgb(250,250,250);
}
footer > a{
font-size: 18px;
margin-left: 10px;
margin-right: 10px;
color: rgb(100, 100, 100);
}
footer > a:hover{
color: rgb(70, 70, 70);
}
.box{
background-color: white;
padding: 10px;
box-shadow: 0px 0px 4px 1px rgba(0,0,0,0.2);
display: flex;
margin: 10px 0px 10px 0px;
flex-direction: row;
}
.label{
display: inline-block;
margin: 5px;
}
.label > .label-name{
background-color: rgb(120,120,120);
color: white;
padding: 2px 5px 2px 5px;
border-radius: 2px 0px 0px 2px;
}
.label-value{
color: white;
padding: 2px 5px 2px 5px;
border-radius: 0px 2px 2px 0px;
}
.label > .label-blue{
background-color: #3498db;
}
.label > .label-red{
background-color: #e74c3c;
}
.label > .label-green{
background-color: #27ae60;
}
.label > .label-orange{
background-color: #e49517;
}
.label > .label-purble{
background-color: #8a52a0;
}
.download-link-large{
color: white;
font-size: 24px;
text-decoration: none;
background-color: #0984e3;
padding: 5px 10px 5px 10px;
border-radius: 2px;
box-shadow: 0px 0px 4px 1px rgba(0,0,0,0.2);
margin-top: 10px;
display: inline-block;
}
.download-link-large:hover{
background-color: #0569b6;
}

@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true,
"outDir": "dist"
},
"exclude": [
"node_modules"
]
}

@ -0,0 +1,12 @@
doctype html
html(lang="en")
head
title Web YouTube Downloader
meta(name="description" content="Download YouTube videos from source!")
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1.0")
link(rel="stylesheet" href="style.css")
body
div.responsive
include partials/titlebox
block content

@ -0,0 +1,17 @@
extends _layout
block content
h1 About
h2 Disclaimer
p This Website is not a part of YouTube or Google.
h2 Where do we have the data of the youtube videos?
p We use the official data from the youtube.com website. All URLs and metadata come from the respective page of the corresponding Youtube video.
h2 Is the downloading of youtube videos lawful?
p Everyone is allowed to download public Youtube videos for private use.
h2 Does this website violate laws and regulations such as once "convert2mp3"?
p
| No. On "convert2mp3" the videos were converted on the servers of the provider and made available for download.
| On our website, you download the videos directly from the sources on the google server.

@ -0,0 +1,4 @@
extends _layout
block content
h1 Cannot fetch video

@ -0,0 +1,10 @@
extends _layout
block content
div.center
h1 Download YouTube videos from source!
h2 All Video and Audio Tracks + Thumbnails
form
input(type="url" placeholder="Paste a YouTube video url" name="url")
button Fetch

@ -0,0 +1,8 @@
div.title-box
img(src="logo.png")
div
h1 Web YouTube Downloader
nav
a(href="/") Startpage
a(href="/about") About
a(href="https://github.com/Feuerhamster/web-youtube-downloader/" target="_blank" rel="noopener") GitHub

@ -0,0 +1,48 @@
extends _layout
block content
h2 Video
iframe(src="https://www.youtube.com/embed/" + id frameborder="0" allow="autoplay; encrypted-media")
h2 Formats
table.formats
tr
th Format
th Quality
th Bitrate
th Container
th Codecs
th URL
each format in formats
tr
td
case format.type
when 0
span.format.format-0 Video
when 1
span.format.format-1 Audio
when 2
span.format.format-2 Adaptive
td= format.quality
td= format.bitrate
td(class=format.container)= format.container
td= format.codecs
td
a(href=format.url target="_blank" rel="noreferrer") Download
h2 Thumbnails
table
tr
th Image
th Size
th Download
each thumbnail in thumbnails
tr
td
img(src=thumbnail.url)
td= thumbnail.size
td
a(href=thumbnail.url target="_blank" rel="noreferrer") Download
Loading…
Cancel
Save