Download of Videos is now possible. Minor styling changes.
This commit is contained in:
25
backend.py
25
backend.py
@@ -1,6 +1,5 @@
|
|||||||
import threading
|
import threading
|
||||||
import yt_dlp as ydl
|
import yt_dlp as ydl
|
||||||
from yt_dlp import DownloadError
|
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -13,15 +12,15 @@ from file_cache import *
|
|||||||
|
|
||||||
|
|
||||||
# adds thread to queue and starts it
|
# adds thread to queue and starts it
|
||||||
def enqueue_download(url, update=False):
|
def enqueue_download(url, update=False, ext='mp3'):
|
||||||
# download and processing is happening in background / another thread
|
# download and processing is happening in background / another thread
|
||||||
t = Thread(target=process_general, args=(url,update))
|
t = Thread(target=process_general, args=(url, ext, update))
|
||||||
thread_queue.append(t)
|
thread_queue.append(t)
|
||||||
t.start()
|
t.start()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def process_general(url, update=False):
|
def process_general(url, ext, update=False):
|
||||||
# get current time and put in list to be displayed on /index
|
# get current time and put in list to be displayed on /index
|
||||||
current_time = datetime.now().time()
|
current_time = datetime.now().time()
|
||||||
running_downloads.append([url, str(current_time.hour) + ':' + str(current_time.minute)])
|
running_downloads.append([url, str(current_time.hour) + ':' + str(current_time.minute)])
|
||||||
@@ -43,7 +42,7 @@ def process_general(url, update=False):
|
|||||||
if update:
|
if update:
|
||||||
process_update(parent, query, current_thread)
|
process_update(parent, query, current_thread)
|
||||||
else:
|
else:
|
||||||
process_download(url, parent, query, current_thread)
|
process_download(url, ext, parent, query, current_thread)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
running_downloads.pop(0)
|
running_downloads.pop(0)
|
||||||
@@ -54,7 +53,7 @@ def process_general(url, update=False):
|
|||||||
|
|
||||||
|
|
||||||
# this is the 'controller' for the download process
|
# this is the 'controller' for the download process
|
||||||
def process_download(url, parent, query, current_thread):
|
def process_download(url, ext, parent, query, current_thread):
|
||||||
# one of the three cases does not throw an exception
|
# one of the three cases does not throw an exception
|
||||||
# and therefore gets to download and return
|
# and therefore gets to download and return
|
||||||
# kinda hacky but whatever
|
# kinda hacky but whatever
|
||||||
@@ -76,12 +75,12 @@ def process_download(url, parent, query, current_thread):
|
|||||||
urls.append('https://www.youtube.com/watch?v=' + video['id'])
|
urls.append('https://www.youtube.com/watch?v=' + video['id'])
|
||||||
|
|
||||||
# start download
|
# start download
|
||||||
download_all(url, parent)
|
download_all(url, ext, parent)
|
||||||
thread_queue.remove(current_thread)
|
thread_queue.remove(current_thread)
|
||||||
return
|
return
|
||||||
|
|
||||||
# when downloading: channel: DownloadError, single file: KeyError
|
# when downloading: channel: DownloadError, single file: KeyError
|
||||||
except (DownloadError, KeyError):
|
except (ydl.DownloadError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# if downloading channel
|
# if downloading channel
|
||||||
@@ -102,7 +101,7 @@ def process_download(url, parent, query, current_thread):
|
|||||||
urls.append('https://www.youtube.com/watch?v=' + video['id'])
|
urls.append('https://www.youtube.com/watch?v=' + video['id'])
|
||||||
|
|
||||||
# start download
|
# start download
|
||||||
download_all(url, parent)
|
download_all(url, ext, parent)
|
||||||
thread_queue.remove(current_thread)
|
thread_queue.remove(current_thread)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ def process_download(url, parent, query, current_thread):
|
|||||||
urls.append('https://www.youtube.com/watch?v=' + query['id'])
|
urls.append('https://www.youtube.com/watch?v=' + query['id'])
|
||||||
|
|
||||||
# start download
|
# start download
|
||||||
download_all(url)
|
download_all(url, ext=ext)
|
||||||
|
|
||||||
# this is broad on purpose; there has been no exception thrown here _yet_
|
# this is broad on purpose; there has been no exception thrown here _yet_
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -156,7 +155,7 @@ def process_update(parent, query, current_thread):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# when downloading: channel: DownloadError, single file: KeyError
|
# when downloading: channel: DownloadError, single file: KeyError
|
||||||
except (DownloadError, KeyError):
|
except (ydl.DownloadError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# if downloading channel
|
# if downloading channel
|
||||||
@@ -196,7 +195,7 @@ def check_already_exists(video_id) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def download_all(url, parent=None, ext='mp3'):
|
def download_all(url, ext, parent=None):
|
||||||
# if no new files to download, there's nothing to do here
|
# if no new files to download, there's nothing to do here
|
||||||
if not len(urls) > 0: return
|
if not len(urls) > 0: return
|
||||||
|
|
||||||
@@ -311,7 +310,7 @@ def yt_download(location, ext='mp3'):
|
|||||||
# try to download all new files
|
# try to download all new files
|
||||||
try:
|
try:
|
||||||
ydl.YoutubeDL(opts).download(urls)
|
ydl.YoutubeDL(opts).download(urls)
|
||||||
except DownloadError:
|
except ydl.DownloadError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import URLField, SubmitField
|
from wtforms import URLField, SelectField, SubmitField
|
||||||
from wtforms.validators import URL, DataRequired
|
from wtforms.validators import URL, DataRequired, AnyOf
|
||||||
|
|
||||||
|
|
||||||
class DownloadForm(FlaskForm):
|
class DownloadForm(FlaskForm):
|
||||||
url = URLField('Video, Channel or Playlist', validators=[DataRequired(), URL()], render_kw={'placeholder': 'YouTube Link'})
|
url = URLField('Video, Channel or Playlist', validators=[DataRequired(), URL()], render_kw={'placeholder': 'YouTube Link'})
|
||||||
|
ext = SelectField('Type', choices=[('mp3', 'Audio'), ('mp4', 'Video')], validators=[AnyOf(('mp3', 'mp4'))])
|
||||||
submit = SubmitField('Go')
|
submit = SubmitField('Go')
|
||||||
|
|||||||
26
frontend.py
26
frontend.py
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from flask import Blueprint, request, render_template, flash, send_from_directory, current_app
|
from flask import Blueprint, request, render_template, flash, send_from_directory
|
||||||
from flask_nav3 import Nav
|
from flask_nav3 import Nav
|
||||||
from flask_nav3.elements import Navbar, View
|
from flask_nav3.elements import Navbar, View
|
||||||
|
|
||||||
@@ -14,10 +14,9 @@ frontend = Blueprint('frontend', __name__)
|
|||||||
nav = Nav()
|
nav = Nav()
|
||||||
nav.register_element('frontend_top', Navbar(
|
nav.register_element('frontend_top', Navbar(
|
||||||
View('ytm-ls', '.index'),
|
View('ytm-ls', '.index'),
|
||||||
View('downloader', '.downloader'),
|
View('Downloader', '.downloader'),
|
||||||
View('updater', '.updater'),
|
View('Library', '.library')
|
||||||
View('library', '.library')
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,11 +35,12 @@ def downloader():
|
|||||||
|
|
||||||
# get url out of form
|
# get url out of form
|
||||||
url = str(form.url.data)
|
url = str(form.url.data)
|
||||||
|
ext = str(form.ext.data)
|
||||||
|
|
||||||
# check if valid link
|
# check if valid link
|
||||||
# this tool should technically work with other platforms but that is not tested
|
# this tool should technically work with other platforms but that is not tested
|
||||||
# since KeyErrors are to be expected in backend.process_download(url), it's blocked here
|
# since KeyErrors are to be expected in backend.process_download(url), it's blocked here
|
||||||
# you are invited to test and adjust the code for other platforms and open a merge request
|
# you are invited to test and adjust the code for other platforms
|
||||||
valid_link = True if 'youtube.com' in url or 'youtu.be' in url else False
|
valid_link = True if 'youtube.com' in url or 'youtu.be' in url else False
|
||||||
|
|
||||||
# if there has been a problem with the form (empty or error) or the link is not valid
|
# if there has been a problem with the form (empty or error) or the link is not valid
|
||||||
@@ -49,7 +49,7 @@ def downloader():
|
|||||||
return render_template('downloader.html', form=form, ytLink=valid_link, amount=len(urls))
|
return render_template('downloader.html', form=form, ytLink=valid_link, amount=len(urls))
|
||||||
|
|
||||||
# kick off download process
|
# kick off download process
|
||||||
enqueue_download(url)
|
enqueue_download(url, ext=ext)
|
||||||
|
|
||||||
# show download start confirmation
|
# show download start confirmation
|
||||||
flash('Download enqueued and will finish in background.')
|
flash('Download enqueued and will finish in background.')
|
||||||
@@ -75,21 +75,13 @@ def download(file_path):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@frontend.route('/update', methods=['GET', 'POST'])
|
@frontend.route('/update/<int:url_rowid>', methods=['GET'])
|
||||||
def updater():
|
|
||||||
downloads = query_db('SELECT name, ROWID FROM playlist')
|
|
||||||
if not downloads:
|
|
||||||
flash('Library has no playlists yet. Try downloading some!')
|
|
||||||
return render_template('updater.html', downloads=downloads)
|
|
||||||
|
|
||||||
|
|
||||||
@frontend.route('/update/<int:url_rowid>')
|
|
||||||
def update(url_rowid):
|
def update(url_rowid):
|
||||||
url = query_db('SELECT url FROM playlist WHERE ROWID = :url_rowid',
|
url = query_db('SELECT url FROM playlist WHERE ROWID = :url_rowid',
|
||||||
{'url_rowid': url_rowid})[0][0]
|
{'url_rowid': url_rowid})[0][0]
|
||||||
|
|
||||||
# kick off download process
|
# kick off download process
|
||||||
enqueue_download(url, True)
|
enqueue_download(url, update=True)
|
||||||
|
|
||||||
# show download start confirmation
|
# show download start confirmation
|
||||||
flash('Update enqueued and will finish in background.')
|
flash('Update enqueued and will finish in background.')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<div class="container" style="width: 50%">
|
<div class="container-fluid" style="width: fit-content(105%)">
|
||||||
<table id="videos" class="table">
|
<table id="videos" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
{% for video in videos %}
|
{% for video in videos %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ video['name'] }}</td>
|
<td class="text-center">{{ video['name'] }}</td>
|
||||||
<td class="text-center"><a href="/download/{{ video['path'] + video['name'] + video['ext'] }}" download>Link</a></td>
|
<td class="text-center"><a class="btn" href="/download/{{ video['path'] + video['name'] + video['ext'] }}" download>Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -2,18 +2,15 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<div class="container">
|
<div class="container-fluid" style="width: fit-content; align-self: flex-start">
|
||||||
<div class="card" style="width: fit-content">
|
<div class="table-bordered" style="padding: .5cm">
|
||||||
<ul class="list-group list-group-flush">
|
<form method="POST" class="form-group">
|
||||||
<li class="list-group-item">
|
{{ form.csrf_token }}
|
||||||
<form method="POST">
|
{{ form.url.label(class_="form-text") }} <br>
|
||||||
{{ form.csrf_token }}
|
{{ form.url(class_="form-control") }} <br>
|
||||||
{{ form.url.label }} <br>
|
{{ form.ext(class_="form-control") }} <br>
|
||||||
{{ form.url }}
|
{{ form.submit(class_="btn primary") }}
|
||||||
{{ form.submit }}
|
</form>
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<div class="container" style="width: fit-content(105%)">
|
<div class="container-fluid" style="width: fit-content(105%)">
|
||||||
{% if playlists %}
|
{% if playlists %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
@@ -12,12 +12,14 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="text-center">Playlists</th>
|
<th scope="col" class="text-center">Playlists</th>
|
||||||
|
<th scope="col" class="text-center">Update</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for playlist in playlists %}
|
{% for playlist in playlists %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center"> <a href="/library-playlist?playlist={{ playlist['ROWID'] }}">{{ playlist['name'] }}</a></td>
|
<td class="text-center"><a class="btn" href="/library-playlist?playlist={{ playlist['ROWID'] }}">{{ playlist['name'] }}</a></td>
|
||||||
|
<td class="text-center"><a class="btn" href="/update/{{ playlist[1] }}">Start</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -46,7 +48,7 @@
|
|||||||
{% for video in videos %}
|
{% for video in videos %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ video['name'] }}</td>
|
<td class="text-center">{{ video['name'] }}</td>
|
||||||
<td class="text-center"><a href="/download/{{ video['path'] + video['name'] + video['ext'] }}" download>Link</a></td>
|
<td class="text-center"><a class="btn" href="/download/{{ video['path'] + video['name'] + video['ext'] }}" download>Link</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
{%- extends "base.html" %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{{ super() }}
|
|
||||||
<div class="container" style="width: fit-content(105%)">
|
|
||||||
{% if downloads %}
|
|
||||||
<div class="card">
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<table id="videos" class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="text-center">Title</th>
|
|
||||||
<th scope="col" class="text-center">Update</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for d in downloads %}
|
|
||||||
<tr>
|
|
||||||
<td class="text-center">{{ d[0] }}</td>
|
|
||||||
<td class="text-center"><a href="/update/{{ d[1] }}">Start</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{%- endblock %}
|
|
||||||
Reference in New Issue
Block a user