Merge pull request #87 from cyberjacob/watch-all-videos

Allow watching of all videos in a channel, folder, or other view sequentially
This commit is contained in:
chibicitiberiu 2019-11-30 12:42:52 +02:00 committed by GitHub
commit bd2336dd4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 148 additions and 13 deletions

View File

@ -74,7 +74,10 @@ class SynchronizeJob(Job):
if _ENABLE_UPDATE_STATS: if _ENABLE_UPDATE_STATS:
batch_ids = [video.video_id for video in batch] 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: for video in batch:
self.progress_advance(1, "Updating video " + video.name) self.progress_advance(1, "Updating video " + video.name)
@ -167,6 +170,7 @@ class SynchronizeJob(Job):
video.rating = yt_video.n_likes / (yt_video.n_likes + yt_video.n_dislikes) video.rating = yt_video.n_likes / (yt_video.n_likes + yt_video.n_dislikes)
video.views = yt_video.n_views video.views = yt_video.n_views
video.duration = yt_video.duration.total_seconds()
video.save() video.save()
@staticmethod @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

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

View File

@ -1,6 +1,12 @@
{% load humanize %} {% load humanize %}
{% load ratings %} {% 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="video-gallery container-fluid">
<div class="row"> <div class="row">
{% for video in videos %} {% for video in videos %}
@ -101,4 +107,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,32 +3,103 @@
{% load humanize %} {% load humanize %}
{% load ratings %} {% 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 %} {% block body %}
<div class="container"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-10"> <div class="col-9">
<h2>{{ object.name }}</h2> <h2>{{ object.name }}</h2>
</div> </div>
<div class="col-2"> <div class="col-3">
<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> <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 %} {% 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> <span class="typcn typcn-eye" style="color:lightgreen;"></span>
</a> </a>
{% else %} {% 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> <span class="typcn typcn-eye-outline"></span>
</a> </a>
{% endif %} {% endif %}
{% if object.downloaded_path %} {% 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> <span class="typcn typcn-download" style="color:lightgreen;"></span>
</a> </a>
{% else %} {% 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> <span class="typcn typcn-download-outline"></span>
</a> </a>
{% endif %} {% endif %}
{% if up_next_count %}
<span class="btn btn-secondary ajax-link">{{ up_next_count }}, {{ up_next_duration }}</span>
{% endif %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -38,9 +109,37 @@
<source src="{% url 'video-src' object.id %}" type="{{ video_mime }}"> <source src="{% url 'video-src' object.id %}" type="{{ video_mime }}">
</video> </video>
{% else %} {% else %}
<iframe id="ytplayer" type="text/html" width="100%" height="600" <div id="ytplayer" style="width: 100%; height: 80vh;"></div>
src="https://www.youtube-nocookie.com/embed/{{ object.video_id }}?autoplay=1"
frameborder="0"></iframe> <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 %} {% endif %}
</div> </div>
</div> </div>

View File

@ -4,9 +4,11 @@ from django.http import HttpRequest, StreamingHttpResponse, FileResponse
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.views import View from django.views import View
from django.views.generic import DetailView from django.views.generic import DetailView
from django.db.models import Sum
from YtManagerApp.models import Video from YtManagerApp.models import Video
import datetime
class VideoDetailView(LoginRequiredMixin, DetailView): class VideoDetailView(LoginRequiredMixin, DetailView):
template_name = 'YtManagerApp/video.html' template_name = 'YtManagerApp/video.html'
@ -18,6 +20,11 @@ class VideoDetailView(LoginRequiredMixin, DetailView):
if video is not None: if video is not None:
context['video_mime'] = mime 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 return context