mirror of
https://github.com/chibicitiberiu/ytsm.git
synced 2024-02-24 05:43:31 +00:00
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:
commit
bd2336dd4d
@ -74,7 +74,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)
|
||||
@ -167,6 +170,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
|
||||
|
18
app/YtManagerApp/migrations/video_duration.py
Normal file
18
app/YtManagerApp/migrations/video_duration.py
Normal 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),
|
||||
),
|
||||
]
|
@ -182,6 +182,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):
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user