mirror of
https://github.com/chibicitiberiu/ytsm.git
synced 2024-02-24 05:43:31 +00:00
Major refactor of codebase.
This commit is contained in:
6
app/YtManagerApp/models/__init__.py
Normal file
6
app/YtManagerApp/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .video_order import VIDEO_ORDER_CHOICES, VIDEO_ORDER_MAPPING
|
||||
from .subscription_folder import SubscriptionFolder
|
||||
from .subscription import Subscription
|
||||
from .video import Video
|
||||
from .jobs import JobExecution, JobMessage, JOB_STATES_MAP, JOB_MESSAGE_LEVELS_MAP
|
||||
from .video_provider import VideoProviderConfig
|
44
app/YtManagerApp/models/jobs.py
Normal file
44
app/YtManagerApp/models/jobs.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
JOB_STATES = [
|
||||
('running', 0),
|
||||
('finished', 1),
|
||||
('failed', 2),
|
||||
('interrupted', 3),
|
||||
]
|
||||
|
||||
JOB_STATES_MAP = {
|
||||
'running': 0,
|
||||
'finished': 1,
|
||||
'failed': 2,
|
||||
'interrupted': 3,
|
||||
}
|
||||
|
||||
JOB_MESSAGE_LEVELS = [
|
||||
('normal', 0),
|
||||
('warning', 1),
|
||||
('error', 2),
|
||||
]
|
||||
JOB_MESSAGE_LEVELS_MAP = {
|
||||
'normal': 0,
|
||||
'warning': 1,
|
||||
'error': 2,
|
||||
}
|
||||
|
||||
|
||||
class JobExecution(models.Model):
|
||||
start_date = models.DateTimeField(auto_now=True, null=False)
|
||||
end_date = models.DateTimeField(null=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
|
||||
description = models.CharField(max_length=250, null=False, default="")
|
||||
status = models.IntegerField(choices=JOB_STATES, null=False, default=0)
|
||||
|
||||
|
||||
class JobMessage(models.Model):
|
||||
timestamp = models.DateTimeField(auto_now=True, null=False)
|
||||
job = models.ForeignKey(JobExecution, null=False, on_delete=models.CASCADE)
|
||||
progress = models.FloatField(null=True)
|
||||
message = models.CharField(max_length=1024, null=False, default="")
|
||||
level = models.IntegerField(choices=JOB_MESSAGE_LEVELS, null=False, default=0)
|
||||
suppress_notification = models.BooleanField(null=False, default=False)
|
58
app/YtManagerApp/models/subscription.py
Normal file
58
app/YtManagerApp/models/subscription.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
from .subscription_folder import SubscriptionFolder
|
||||
from .video_order import VIDEO_ORDER_CHOICES
|
||||
|
||||
|
||||
class Subscription(models.Model):
|
||||
name = models.CharField(null=False, max_length=1024)
|
||||
parent_folder = models.ForeignKey(SubscriptionFolder, on_delete=models.CASCADE, null=True, blank=True)
|
||||
provider_id = models.CharField(max_length=128, null=False)
|
||||
playlist_id = models.CharField(null=False, max_length=128)
|
||||
description = models.TextField()
|
||||
channel_id = models.CharField(max_length=128)
|
||||
channel_name = models.CharField(max_length=1024)
|
||||
thumbnail = models.CharField(max_length=1024)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
# youtube adds videos to the 'Uploads' playlist at the top instead of the bottom
|
||||
rewrite_playlist_indices = models.BooleanField(null=False, default=False)
|
||||
|
||||
# overrides
|
||||
auto_download = models.BooleanField(null=True, blank=True)
|
||||
download_limit = models.IntegerField(null=True, blank=True)
|
||||
download_order = models.CharField(
|
||||
null=True, blank=True,
|
||||
max_length=128,
|
||||
choices=VIDEO_ORDER_CHOICES)
|
||||
automatically_delete_watched = models.BooleanField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return f'subscription {self.id}, name="{self.name}", playlist_id="{self.playlist_id}"'
|
||||
|
||||
def delete_subscription(self, keep_downloaded_videos: bool):
|
||||
self.delete()
|
||||
|
||||
def copy_from(self, other: "Subscription"):
|
||||
self.name = other.name
|
||||
self.parent_folder = other.parent_folder
|
||||
self.provider_id = other.provider_id
|
||||
self.playlist_id = other.playlist_id
|
||||
self.description = other.description
|
||||
self.channel_id = other.channel_id
|
||||
self.channel_name = other.channel_name
|
||||
self.thumbnail = other.thumbnail
|
||||
try:
|
||||
self.user = other.user
|
||||
except User.DoesNotExist:
|
||||
self.user = None
|
||||
|
||||
self.rewrite_playlist_indices = other.rewrite_playlist_indices
|
||||
|
||||
self.auto_download = other.auto_download
|
||||
self.download_limit = other.download_limit
|
||||
self.download_order = other.download_order
|
||||
self.automatically_delete_watched = other.automatically_delete_watched
|
76
app/YtManagerApp/models/subscription_folder.py
Normal file
76
app/YtManagerApp/models/subscription_folder.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import logging
|
||||
from typing import Callable, Union, Any, Optional
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models.functions import Lower
|
||||
|
||||
|
||||
class SubscriptionFolder(models.Model):
|
||||
name = models.CharField(null=False, max_length=250)
|
||||
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
class Meta:
|
||||
ordering = [Lower('parent__name'), Lower('name')]
|
||||
|
||||
def __str__(self):
|
||||
s = ""
|
||||
current = self
|
||||
while current is not None:
|
||||
s = current.name + " > " + s
|
||||
current = current.parent
|
||||
return s[:-3]
|
||||
|
||||
def __repr__(self):
|
||||
return f'folder {self.id}, name="{self.name}"'
|
||||
|
||||
def delete_folder(self, keep_subscriptions: bool):
|
||||
from .subscription import Subscription
|
||||
if keep_subscriptions:
|
||||
|
||||
def visit(node: Union["SubscriptionFolder", "Subscription"]):
|
||||
if isinstance(node, Subscription):
|
||||
node.parent_folder = None
|
||||
node.save()
|
||||
|
||||
SubscriptionFolder.traverse(self.id, self.user, visit)
|
||||
|
||||
self.delete()
|
||||
|
||||
@staticmethod
|
||||
def traverse(root_folder_id: Optional[int],
|
||||
user: User,
|
||||
visit_func: Callable[[Union["SubscriptionFolder", "Subscription"]], Any]):
|
||||
from .subscription import Subscription
|
||||
|
||||
data_collected = []
|
||||
|
||||
def collect(data):
|
||||
if data is not None:
|
||||
data_collected.append( data)
|
||||
|
||||
# Visit root
|
||||
if root_folder_id is not None:
|
||||
root_folder = SubscriptionFolder.objects.get(id=root_folder_id)
|
||||
collect(visit_func(root_folder))
|
||||
|
||||
queue = [root_folder_id]
|
||||
visited = []
|
||||
|
||||
while len(queue) > 0:
|
||||
folder_id = queue.pop()
|
||||
|
||||
if folder_id in visited:
|
||||
logging.error('Found folder tree cycle for folder id %d.', folder_id)
|
||||
continue
|
||||
visited.append(folder_id)
|
||||
|
||||
for folder in SubscriptionFolder.objects.filter(parent_id=folder_id, user=user).order_by(Lower('name')):
|
||||
collect(visit_func(folder))
|
||||
queue.append(folder.id)
|
||||
|
||||
for subscription in Subscription.objects.filter(parent_folder_id=folder_id, user=user).order_by(Lower('name')):
|
||||
collect(visit_func(subscription))
|
||||
|
||||
return data_collected
|
50
app/YtManagerApp/models/video.py
Normal file
50
app/YtManagerApp/models/video.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import mimetypes
|
||||
import os
|
||||
|
||||
from django.db import models
|
||||
|
||||
from .subscription import Subscription
|
||||
|
||||
|
||||
class Video(models.Model):
|
||||
video_id = models.CharField(null=False, max_length=12)
|
||||
name = models.TextField(null=False)
|
||||
description = models.TextField()
|
||||
watched = models.BooleanField(default=False, null=False)
|
||||
new = models.BooleanField(default=True, null=False)
|
||||
downloaded_path = models.TextField(null=True, blank=True)
|
||||
downloaded_size = models.IntegerField(null=True, blank=True)
|
||||
subscription = models.ForeignKey(Subscription, on_delete=models.CASCADE)
|
||||
playlist_index = models.IntegerField(null=False)
|
||||
publish_date = models.DateTimeField(null=False)
|
||||
last_updated_date = models.DateTimeField(null=False, auto_now=True)
|
||||
thumbnail = models.TextField()
|
||||
uploader_name = models.CharField(null=False, max_length=255)
|
||||
views = models.IntegerField(null=False, default=0)
|
||||
rating = models.FloatField(null=False, default=0.5)
|
||||
|
||||
def get_files(self):
|
||||
if self.downloaded_path is not None:
|
||||
directory, file_pattern = os.path.split(self.downloaded_path)
|
||||
for file in os.listdir(directory):
|
||||
if file.startswith(file_pattern):
|
||||
yield os.path.join(directory, file)
|
||||
|
||||
def find_video(self):
|
||||
"""
|
||||
Finds the video file from the downloaded files, and
|
||||
returns
|
||||
:return: Tuple containing file path and mime type
|
||||
"""
|
||||
for file in self.get_files():
|
||||
mime, _ = mimetypes.guess_type(file)
|
||||
if mime is not None and mime.startswith('video/'):
|
||||
return file, mime
|
||||
|
||||
return None, None
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return f'video {self.id}, video_id="{self.video_id}"'
|
17
app/YtManagerApp/models/video_order.py
Normal file
17
app/YtManagerApp/models/video_order.py
Normal file
@@ -0,0 +1,17 @@
|
||||
VIDEO_ORDER_CHOICES = [
|
||||
('newest', 'Newest'),
|
||||
('oldest', 'Oldest'),
|
||||
('playlist', 'Playlist order'),
|
||||
('playlist_reverse', 'Reverse playlist order'),
|
||||
('popularity', 'Popularity'),
|
||||
('rating', 'Top rated'),
|
||||
]
|
||||
|
||||
VIDEO_ORDER_MAPPING = {
|
||||
'newest': '-publish_date',
|
||||
'oldest': 'publish_date',
|
||||
'playlist': 'playlist_index',
|
||||
'playlist_reverse': '-playlist_index',
|
||||
'popularity': '-views',
|
||||
'rating': '-rating'
|
||||
}
|
6
app/YtManagerApp/models/video_provider.py
Normal file
6
app/YtManagerApp/models/video_provider.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class VideoProviderConfig(models.Model):
|
||||
provider_id = models.CharField(max_length=128, unique=True, help_text="Provider ID")
|
||||
settings = models.TextField(help_text="Video provider settings")
|
Reference in New Issue
Block a user