diff --git a/app/common/static/styles/_colours.scss b/app/common/static/styles/_colours.scss
index e3cd44e..b5f4b70 100644
--- a/app/common/static/styles/_colours.scss
+++ b/app/common/static/styles/_colours.scss
@@ -36,7 +36,7 @@ $form-error-text-colour: $colour-near-white;
$form-help-text-colour: $colour-light-blue;
$form-delete-button-background-colour: $colour-red;
-$collection-no-items-text-colour: $colour-light-blue;
+$collection-no-items-text-colour: $colour-near-black;
$collection-background-hover-colour: $colour-orange;
$collection-text-hover-colour: $colour-near-white;
diff --git a/app/sync/forms.py b/app/sync/forms.py
index d0c3298..e09cc8d 100644
--- a/app/sync/forms.py
+++ b/app/sync/forms.py
@@ -1,5 +1,6 @@
from django import forms
+from django.utils.translation import gettext_lazy as _
class ValidateSourceForm(forms.Form):
@@ -10,6 +11,14 @@ class ValidateSourceForm(forms.Form):
widget=forms.HiddenInput()
)
source_url = forms.URLField(
- label='Source URL',
+ label=_('Source URL'),
required=True
)
+
+
+class ConfirmDeleteSourceForm(forms.Form):
+
+ delete_media = forms.BooleanField(
+ label=_('Also delete downloaded media'),
+ required=False
+ )
diff --git a/app/sync/models.py b/app/sync/models.py
index 5e1e29c..befeb92 100644
--- a/app/sync/models.py
+++ b/app/sync/models.py
@@ -66,7 +66,7 @@ class Source(models.Model):
}
URLS = {
- SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/{key}',
+ SOURCE_TYPE_YOUTUBE_CHANNEL: 'https://www.youtube.com/c/{key}',
SOURCE_TYPE_YOUTUBE_PLAYLIST: 'https://www.youtube.com/playlist?list={key}',
}
@@ -172,11 +172,15 @@ class Source(models.Model):
@property
def icon(self):
return self.ICONS.get(self.source_type)
-
+
+ @classmethod
+ def create_url(obj, source_type, key):
+ url = obj.URLS.get(source_type)
+ return url.format(key=key)
+
@property
def url(self):
- url = self.URLS.get(self.source_type)
- return url.format(key=self.key)
+ return Source.create_url(self.source_type, self.key)
@property
def directory_path(self):
diff --git a/app/sync/templates/sync/source-delete.html b/app/sync/templates/sync/source-delete.html
new file mode 100644
index 0000000..bdc9520
--- /dev/null
+++ b/app/sync/templates/sync/source-delete.html
@@ -0,0 +1,28 @@
+{% extends 'base.html' %}
+
+{% block headtitle %}Delete source - {{ source.name }}{% endblock %}
+
+{% block content %}
+
+
+
Delete source {{ source.name }}
+
+ Are you sure you want to delete this source? Deleting a source is permanent.
+ By default, deleting a source does not delete any saved media files. You can
+ tick the "also delete downloaded media" checkbox to also remove save
+ media when you delete the source. Deleting a source cannot be undone.
+
+
+
+
+{% endblock %}
diff --git a/app/sync/templates/sync/source.html b/app/sync/templates/sync/source.html
index 7c9c3b0..e48e8f2 100644
--- a/app/sync/templates/sync/source.html
+++ b/app/sync/templates/sync/source.html
@@ -80,7 +80,7 @@
Edit source
{% endblock %}
diff --git a/app/sync/urls.py b/app/sync/urls.py
index b6e48ec..d6ee938 100644
--- a/app/sync/urls.py
+++ b/app/sync/urls.py
@@ -1,6 +1,7 @@
from django.urls import path
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
- SourceView, UpdateSourceView, MediaView, TasksView, LogsView)
+ SourceView, UpdateSourceView, DeleteSourceView, MediaView,
+ TasksView, LogsView)
app_name = 'sync'
@@ -36,9 +37,13 @@ urlpatterns = [
UpdateSourceView.as_view(),
name='update-source'),
- # Media URLs
+ path('source-delete/',
+ DeleteSourceView.as_view(),
+ name='delete-source'),
- path('media',
+ # Media URLs (note /media/ is the static media URL, don't use that)
+
+ path('mediafiles',
MediaView.as_view(),
name='media'),
diff --git a/app/sync/views.py b/app/sync/views.py
index 13267c4..8fabbbe 100644
--- a/app/sync/views.py
+++ b/app/sync/views.py
@@ -1,15 +1,17 @@
from django.conf import settings
from django.http import Http404
from django.views.generic import TemplateView, ListView, DetailView
-from django.views.generic.edit import FormView, CreateView, UpdateView
+from django.views.generic.edit import (FormView, FormMixin, CreateView, UpdateView,
+ DeleteView)
from django.urls import reverse_lazy
from django.forms import ValidationError
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from common.utils import append_uri_params
from .models import Source
-from .forms import ValidateSourceForm
+from .forms import ValidateSourceForm, ConfirmDeleteSourceForm
from .utils import validate_url
+from . import youtube
class DashboardView(TemplateView):
@@ -155,7 +157,7 @@ class ValidateSourceView(FormView):
ValidationError(self.errors['invalid_source'])
)
source_url = form.cleaned_data['source_url']
- validation_url = self.validation_urls.get(self.source_type)
+ validation_url = self.validation_urls.get(source_type)
try:
self.key = validate_url(source_url, validation_url)
except ValidationError as e:
@@ -246,6 +248,30 @@ class UpdateSourceView(UpdateView):
return append_uri_params(url, {'message': 'source-updated'})
+class DeleteSourceView(DeleteView, FormMixin):
+ '''
+ Confirm the deletion of a source with an option to delete all the media
+ associated with the source from disk when the source is deleted.
+ '''
+
+ template_name = 'sync/source-delete.html'
+ model = Source
+ form_class = ConfirmDeleteSourceForm
+ context_object_name = 'source'
+
+ def post(self, request, *args, **kwargs):
+ delete_media_val = request.POST.get('delete_media', False)
+ delete_media = True if delete_media_val is not False else False
+ if delete_media:
+ # TODO: delete media files from disk linked to this source
+ pass
+ return super().post(request, *args, **kwargs)
+
+ def get_success_url(self):
+ url = reverse_lazy('sync:sources')
+ return append_uri_params(url, {'message': 'source-deleted'})
+
+
class MediaView(TemplateView):
'''
A bare list of media added with their states.
diff --git a/app/sync/youtube.py b/app/sync/youtube.py
new file mode 100644
index 0000000..27de315
--- /dev/null
+++ b/app/sync/youtube.py
@@ -0,0 +1,41 @@
+'''
+ Wrapper for the youtube-dl library. Used so if there are any library interface
+ updates we only need to udpate them in one place.
+'''
+
+
+from django.conf import settings
+import youtube_dl
+
+
+_defaults = getattr(settings, 'YOUTUBE_DEFAULTS', {})
+
+
+class YouTubeError(youtube_dl.utils.DownloadError):
+ '''
+ Generic wrapped error for all errors that could be raised by youtube-dl.
+ '''
+
+ pass
+
+
+def extract_info(url):
+ '''
+ Extracts information from a YouTube URL and returns it as a dict. For a channel
+ or playlist this returns a dict of all the videos on the channel or playlist
+ as well as associated metadata.
+ '''
+ opts = _defaults.update({
+ 'skip_download': True,
+ 'forcejson': True,
+ 'simulate': True,
+ 'extract_flat': 'in_playlist',
+ 'playlist_items': 1,
+ })
+ response = {}
+ with youtube_dl.YoutubeDL(opts) as y:
+ try:
+ response = y.extract_info(url, download=False)
+ except youtube_dl.utils.DownloadError as e:
+ raise YouTubeError(f'Failed to extract_info for "{url}": {e}') from e
+ return response
diff --git a/app/tubesync/settings.py b/app/tubesync/settings.py
index 0e8256c..70d65d7 100644
--- a/app/tubesync/settings.py
+++ b/app/tubesync/settings.py
@@ -119,6 +119,11 @@ DJANGO_SIMPLE_TASK_WORKERS = 2
SOURCES_PER_PAGE = 25
+YOUTUBE_DEFAULTS = {
+ 'age_limit': 99,
+}
+
+
try:
from .local_settings import *
except ImportError as e: