mirror of
https://github.com/chibicitiberiu/ytsm.git
synced 2024-02-24 05:43:31 +00:00
Allow watching of all videos in a channel, folder, or other view sequentially
This commit is contained in:
parent
794b9bd42d
commit
b3d83cd150
@ -72,7 +72,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)
|
||||||
@ -163,6 +166,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
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
]
|
@ -177,6 +177,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):
|
||||||
|
@ -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 %}
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user