Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
spurll committed Jul 3, 2019
0 parents commit 46315e8
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
download/
10 changes: 10 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from os import urandom, path


# Web Server
CSRF_ENABLED = True
SECRET_KEY = urandom(30)
PROPAGATE_EXCEPTIONS = True

basedir = path.abspath(path.dirname(__file__))
VIDEO_DIR = path.join(basedir, 'download')
70 changes: 70 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
ytdl
====

A web application for downloading audio/video from YouTube. Basically just a web wrapper
for [youtube-dl](https://ytdl-org.github.io/youtube-dl/) with a stripped-down feature set.

Usage
=====

Installation
------------

You'll need to install [youtube-dl](https://ytdl-org.github.io/youtube-dl/). It's
recomended that you keep it updated (e.g., via a cron job).

```
youtube-dl -U
```

You'll also need to install ffmpeg/avconv.

Requirements
------------

* flask
* flask-wtf
* [youtube-dl](https://ytdl-org.github.io/youtube-dl/)
* ffmpeg/avconv

Starting the Server
-------------------

Start the server with `run.py`. By default it will be accessible at `localhost:9999`. To
make the server world-accessible or for other options, see `run.py -h`.

If you're having trouble configuring your sever, I wrote a
[blog post](http://blog.spurll.com/2015/02/configuring-flask-uwsgi-and-nginx.html)
explaining how you can get Flask, uWSGI, and Nginx working together.

Bugs and Feature Requests
=========================

Feature Requests
----------------

* TODO: Fix formatting
* TODO: Delete downloads after a while
* TODO: Include recommended cron lines for deleting old downloads and updating ytdl

Known Bugs
----------

None

Thanks
======

Thanks to the team behind [youtube-dl](https://ytdl-org.github.io/youtube-dl/)!

License Information
===================

Written by Gem Newman. [Website](http://spurll.com) | [GitHub](https://github.com/spurll/) | [Twitter](https://twitter.com/spurll)

This work is licensed under Creative Commons [BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/).

JQuery included under the [MIT "Expat" License](https://opensource.org/licenses/MIT).

Remember: [GitHub is not my CV](https://blog.jcoglan.com/2013/11/15/why-github-is-not-your-cv/).

28 changes: 28 additions & 0 deletions run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env python3


from argparse import ArgumentParser

from ytdl import app


if __name__ == '__main__':
description = "Runs the Flask server."
parser = ArgumentParser(description=description)
parser.add_argument("-0", "--public", help="Makes the server world-"
"accessible by hosting at 0.0.0.0.",
action="store_true")
parser.add_argument("-p", "--port", help="Defines the port. Defaults to "
"9999.", type=int, default=9999)
parser.add_argument("-d", "--debug", help="Turns server debug mode on. "
"(Not recommended for world-accesible servers!)",
action="store_true")
parser.add_argument("-r", "--reload", help="Turns the automatic realoder "
"on. This setting restarts the server whenever a "
"change in the source is detected.",
action="store_true")
args = parser.parse_args()

app.run(host="0.0.0.0" if args.public else "localhost", port=args.port,
use_debugger=args.debug, use_reloader=args.reload)

13 changes: 13 additions & 0 deletions ytdl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from flask import Flask
import os


app = Flask(__name__)
app.config.from_object('config')


if not os.path.exists(app.config['VIDEO_DIR']):
os.makedirs(app.config['VIDEO_DIR'])


from ytdl import views
17 changes: 17 additions & 0 deletions ytdl/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask_wtf import FlaskForm
from wtforms import TextField, RadioField
from wtforms.validators import Required


class DownloadForm(FlaskForm):
url = TextField('URL', validators=[Required()])
fmt = RadioField(
'Format',
choices=[
('mp3', 'Audio (MP3)'),
('wav', 'Audio (WAV)'),
('v', 'Video')
],
default='mp3'
)

Binary file added ytdl/static/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
110 changes: 110 additions & 0 deletions ytdl/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
body {
font-family: serif;
color: #ffffff;
background-color: #222222;
overflow-y: auto;
margin: 0px;
}

p {
margin: 5px;
text-align: center;
}

table {
width: 100%;
margin-top: 5px;
margin-bottom: 10px;
border: 0px;
border-spacing: 0px;
}

th { font-variant: small-caps; }
td { font-size: 85%; padding: 3px; }
tfoot { font-weight: bold; font-variant: small-caps; }

#content {
width: 100%;
max-width: 500px;
margin: auto;
padding-top: 10px;
}

#title-buffer {
width: 100%;
height: 52px;
top: 0px;
position: static;
}

#title-bar {
width: 100%;
top: 0px;
position: fixed;
background-color: #111111;
border-bottom: solid #444444;
}

#title {
width: 60%;
margin: auto;
font-variant: small-caps;
font-size: 200%;
text-align: center;
padding-top: 10px;
padding-bottom: 10px;
}

#links {
width: calc(20% - 20px);
float: left;
text-align: left;
padding: 10px;
}

#user {
width: calc(20% - 20px);
float: right;
text-align: right;
padding: 10px;
}

.heading {
font-variant: small-caps;
text-align: center;
font-size: 150%;
padding-top: 5px;
padding-bottom: 10px;
}

.small-screen {
width: auto;
margin: auto;
float: none;
text-align: center;
padding: 2px;
}

.messages { font-style: italic; text-align: center; }
.wide { width: 70%; top: 0px; }
.narrow { width: 30%; top: 0px; }
.right { float: right; text-align: right; }
.section { padding-top: 5px; padding-bottom: 10px; }
.login { max-width: 320px; margin: auto; }

tr:nth-child(even) { background-color: #444444; border-color: #444444; }

a:link { text-decoration: none; color: #888888; }
a:visited { text-decoration: none; color: #888888; }
a:hover { text-decoration: none; color: #ffffff; }
a:active { text-decoration: none; color: #888888; }

input { color: #ffffff; background-color: transparent; border: solid 1px #444444; }
select { color: #ffffff; background-color: transparent; border: solid 1px #444444; }
select option { background-color: #222222; }
input[type="text"] { width: 100%; padding: 5px; margin: 5px; }
input[type="password"] { width: 100%; }
input[type="submit"] { width: 90%; padding: 5px; margin: 5px; }

ul { padding: 0; text-align: center; }
li { display: inline-block; margin: 10px; }
78 changes: 78 additions & 0 deletions ytdl/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<html>
<head>
<!-- Support for iOS device sizes. -->
<meta name="viewport" content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no'>

{% if title %}
<title>{{title}}</title>
{% endif %}

<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}"/>
<link rel="shortcut icon" href="{{url_for('static', filename='icon.png')}}"/>

<script>
$(document).ready(function() {
resizeContent();
resizeBuffer();
});

$(window).resize(function() {
resizeContent();
resizeBuffer();
});

$(window).on('orientationchange', function() {
resizeContent();
resizeBuffer();
});

function resizeBuffer() {
// Resizes the title buffer which pushes content down below the floating title bar.
$('#title-buffer').height($('#title-bar').outerHeight());
}

function resizeContent() {
if ($(window).width() < 700) {
// Moves and resizes the links and login information information on a smaller screen.
$('#links').addClass('small-screen');
$('#user').addClass('small-screen');
$('#title').addClass('small-screen');
}
else {
// Moves and resizes the links and login information information on a bigger (or horizontal) screen.
$('#links').removeClass('small-screen');
$('#user').removeClass('small-screen');
$('#title').removeClass('small-screen');
}
}
</script>
</head>

<body>
<div id="title-buffer"></div>
<div id="title-bar">
<div id="title">ytdl</div>
</div>

{% with messages = get_flashed_messages() %}
<div class="messages">
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% endif %}
</div>
{% endwith %}

<div id="content">
<form method="POST">
{{form.hidden_tag()}}

<div class="narrow right"><input type="submit" value="Download" /></div>
<div class="wide left">{{form.url(autofocus=True, placeholder="Video URL")}}</div>

<div>{{form.fmt}}</div>
</form>
</div>
</body>
</html>
54 changes: 54 additions & 0 deletions ytdl/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import os.path
from tempfile import mkdtemp
from subprocess import run
from flask import render_template, flash, request, send_from_directory

from ytdl import app
from ytdl.forms import DownloadForm


@app.route('/', methods=['GET', 'POST'])
def index():
"""
Download.
"""
form = DownloadForm()

if request.method == 'GET':
return render_template('index.html', form=form)

if not form.validate_on_submit():
flash('Invalid form data. Please try again.')
return render_template('index.html', form=form)

# Create temporary directory
directory = mkdtemp(dir=app.config['VIDEO_DIR'])

fmt = form.fmt.data

try:
# Download the file
process = run(
[
'youtube-dl',
form.url.data,
'-o', os.path.join(directory, '%(title)s.%(ext)s'),
'-x' if fmt != 'v' else '',
f'--audio-format={fmt}' if fmt != 'v' else ''
],
check=True
)

except Exception as e:
print(e)
flash('Error: Could not download/convert the video as requested.')
return render_template('index.html', form=form)

files = os.listdir(directory)
if not files:
flash('Error: Could not find the file specified.')
return render_template('index.html', form=form)

# Send the file to the user
return send_from_directory(directory, files[0], as_attachment=True)

0 comments on commit 46315e8

Please sign in to comment.