Allow watching of all videos in a channel, folder, or other view sequentially

This commit is contained in:
Jacob Mansfield [root@Helix] 2019-11-22 17:36:42 +00:00
parent 794b9bd42d
commit b3d83cd150
6 changed files with 148 additions and 13 deletions

View File

@ -72,7 +72,10 @@ class SynchronizeJob(Job):
if _ENABLE_UPDATE_STATS:
batch_ids = [video.video_id for video in batch]
video_stats = {v.id: v for v in self.__api.videos(batch_ids, part='id,statistics')}
video_stats = {v.id: v for v in self.__api.videos(batch_ids, part='id,statistics,contentDetails')}
else:
batch_ids = [video.video_id for video in filter(lambda video: video.duration == 0, batch)]
video_stats = {v.id: v for v in self.__api.videos(batch_ids, part='id,statistics,contentDetails')}
for video in batch:
self.progress_advance(1, "Updating video " + video.name)
@ -163,6 +166,7 @@ class SynchronizeJob(Job):
video.rating = yt_video.n_likes / (yt_video.n_likes + yt_video.n_dislikes)
video.views = yt_video.n_views
video.duration = yt_video.duration.total_seconds()
video.save()
@staticmethod

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.5 on 2019-10-18 21:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('YtManagerApp', '0012_auto_20190819_1615'),
]
operations = [
migrations.AddField(
model_name='video',
name='duration',
field=models.IntegerField(default=0),
),
]

View File

@ -177,6 +177,7 @@ class Video(models.Model):
uploader_name = models.CharField(null=False, max_length=255)
views = models.IntegerField(null=False, default=0)
rating = models.FloatField(null=False, default=0.5)
duration = models.IntegerField(null=False, default=0)
@staticmethod
def create(playlist_item: youtube.PlaylistItem, subscription: Subscription):

View File

@ -1,6 +1,12 @@
{% load humanize %}
{% load ratings %}
{% if videos %}
<div class="row">
<a class="btn" href="{% url 'video' videos.0.id %}?next={% for video in videos|slice:"1:" %}{{video.id}}{% if not forloop.last %},{% endif %}{% endfor %}">Watch All</a>
</div>
{% endif %}
<div class="video-gallery container-fluid">
<div class="row">
{% for video in videos %}
@ -101,4 +107,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -3,32 +3,103 @@
{% load humanize %}
{% load ratings %}
{% block scripts %}
<script>
function setWatchedStatus(state) {
$("#watchButton")[0].innerHTML="<span class='typcn typcn-arrow-sync'></span>";
if(state) {
$.post("{% url 'ajax_action_mark_video_watched' object.id %}", {
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function() {
$("#watchButton")[0].innerHTML="<span class='typcn typcn-eye' style='color:lightgreen;'></span>";
$("#watchButton").attr("title", "Mark as not watched");
$("#watchButton").attr("onclick","setWatchedStatus(0)");
var urlParams = new URLSearchParams(window.location.search);
if(urlParams.has("next")) {
var videos = urlParams.get("next");
if(videos == "") {return;}
videos = videos.split(",");
var next = videos.shift();
//TODO: Don't really like the URL construction here
window.location.href = "{% url 'video' 0 %}".replace("0", next)+"?next="+videos.join(",");
}
});
} else {
$.post("{% url 'ajax_action_mark_video_unwatched' object.id %}", {
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function() {
$("#watchButton")[0].innerHTML="<span class='typcn typcn-eye-outline'></span>";
$("#watchButton").attr("title", "Mark as watched");
$("#watchButton").attr("onclick","setWatchedStatus(0)");
});
}
}
</script>
{% if video_mime is None %}
<script>
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('ytplayer', {
height: '100%',
width: '100%',
videoId: '{{ object.video_id }}',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
function onPlayerStateChange(event) {
console.log("State change: ", event);
if (event.data == YT.PlayerState.ENDED) {
console.log("Video finished!");
setWatchedStatus(1);
}
}
</script>
<script src="//www.youtube.com/iframe_api"></script>
{% endif %}
{% endblock scripts%}
{% block body %}
<div class="container">
<div class="container-fluid">
<div class="row">
<div class="col-10">
<div class="col-9">
<h2>{{ object.name }}</h2>
</div>
<div class="col-2">
<a class="btn btn-secondary" href="https://youtube.com/watch?v={{ object.video_id }}" title="Watch on YouTube"><span class="typcn typcn-social-youtube"></span></a>
<div class="col-3">
<a class="btn btn-secondary" data-toggle="tooltip" href="https://youtube.com/watch?v={{ object.video_id }}" title="Watch on YouTube"><span class="typcn typcn-social-youtube"></span></a>
{% if object.watched %}
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_mark_video_unwatched' object.id %}" title="Mark not watched">
<a id="watchButton" class="btn btn-secondary" onclick="setWatchedStatus(0)" title="Mark not watched" data-toggle="tooltip" href="#">
<span class="typcn typcn-eye" style="color:lightgreen;"></span>
</a>
{% else %}
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_mark_video_watched' object.id %}" title="Mark watched">
<a id="watchButton" class="btn btn-secondary" onclick="setWatchedStatus(1)" title="Mark watched" data-toggle="tooltip" href="#">
<span class="typcn typcn-eye-outline"></span>
</a>
{% endif %}
{% if object.downloaded_path %}
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_delete_video_files' object.id %}" title="Delete downloaded">
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_delete_video_files' object.id %}" title="Delete downloaded" data-toggle="tooltip">
<span class="typcn typcn-download" style="color:lightgreen;"></span>
</a>
{% else %}
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_download_video_files' object.id %}" title="Download">
<a class="btn btn-secondary ajax-link" href="#" data-post-url="{% url 'ajax_action_download_video_files' object.id %}" title="Download" data-toggle="tooltip">
<span class="typcn typcn-download-outline"></span>
</a>
{% endif %}
{% if up_next_count %}
<span class="btn btn-secondary ajax-link">{{ up_next_count }}, {{ up_next_duration }}</span>
{% endif %}
</div>
</div>
<div class="row">
@ -38,9 +109,37 @@
<source src="{% url 'video-src' object.id %}" type="{{ video_mime }}">
</video>
{% else %}
<iframe id="ytplayer" type="text/html" width="100%" height="600"
src="https://www.youtube-nocookie.com/embed/{{ object.video_id }}?autoplay=1"
frameborder="0"></iframe>
<div id="ytplayer" style="width: 100%; height: 80vh;"></div>
<script>
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('ytplayer', {
height: '600',
width: '100%',
videoId: '{{ object.video_id }}',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
function onPlayerStateChange(event) {
console.log("State change: ", event);
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
// {% url 'ajax_action_mark_video_unwatched' object.id %}
</script>
{% endif %}
</div>
</div>

View File

@ -4,9 +4,11 @@ from django.http import HttpRequest, StreamingHttpResponse, FileResponse
from django.urls import reverse, reverse_lazy
from django.views import View
from django.views.generic import DetailView
from django.db.models import Sum
from YtManagerApp.models import Video
import datetime
class VideoDetailView(LoginRequiredMixin, DetailView):
template_name = 'YtManagerApp/video.html'
@ -18,6 +20,11 @@ class VideoDetailView(LoginRequiredMixin, DetailView):
if video is not None:
context['video_mime'] = mime
if self.request.GET.get('next'):
up_next_videos = self.request.GET.get('next').split(',')
context['up_next_count'] = len(up_next_videos)
context['up_next_duration'] = str(datetime.timedelta(seconds=Video.objects.filter(id__in=up_next_videos).aggregate(Sum('duration'))['duration__sum']))
return context