Compare commits

...

7 Commits

Author SHA1 Message Date
Markos Gogoulos
df98b65704 feat: pass version on static files (#1318) 2025-07-07 11:54:02 +03:00
Markos Gogoulos
a607996bfa feat: adds minimum resolution of 144p 2025-07-07 11:34:02 +03:00
Markos Gogoulos
79f2e2bb11 feat: replace format with fstrings 2025-07-07 11:26:08 +03:00
Markos Gogoulos
d54732040a feat: add DB connection pooling 2025-07-07 11:18:40 +03:00
Andy
e8520bc7cd fix: date picker in edit media (#1297)
By default, Django uses type=text for the date input, which respects
the locale settings. However, when changing the input type to date,
it should only accept YYYY-MM-DD format so the input field can be
properly handled.
2025-07-06 11:44:44 +03:00
Markos Gogoulos
b6e46e7b62 feat: replace login middleware (#1314) 2025-07-06 11:25:50 +03:00
Adam Stradovnik
36eab954bd feat: Adds support for Slovenian frontend translations (#1306) 2025-07-06 11:05:07 +03:00
24 changed files with 278 additions and 142 deletions

View File

@@ -38,7 +38,7 @@ A demo is available at https://demo.mediacms.io
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media - **Configurable actions**: allow download, add comments, add likes, dislikes, report media
- **Configuration options**: change logos, fonts, styling, add more pages - **Configuration options**: change logos, fonts, styling, add more pages
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options - **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9) - **Multiple transcoding profiles**: sane defaults for multiple dimensions (144p, 240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Adaptive video streaming**: possible through HLS protocol - **Adaptive video streaming**: possible through HLS protocol
- **Subtitles/CC**: support for multilingual subtitle files - **Subtitles/CC**: support for multilingual subtitle files
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers - **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
@@ -93,20 +93,14 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b). A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
## Configuration
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
## Information for developers
Check out the new section on the [Developer Experience](docs/dev_exp.md) page
## Documentation ## Documentation
* [Users documentation](docs/user_docs.md) page * [Users documentation](docs/user_docs.md) page
* [Administrators documentation](docs/admins_docs.md) page * [Administrators documentation](docs/admins_docs.md) page
* [Developers documentation](docs/developers_docs.md) page * [Developers documentation](docs/developers_docs.md) page
* [Configuration](docs/admins_docs.md#5-configuration) page
* [Transcoding](docs/transcoding.md) page
* [Developer Experience](docs/dev_exp.md) page
## Technology ## Technology

View File

@@ -186,7 +186,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
VIDEO_CHUNKS_DURATION = 60 * 4 VIDEO_CHUNKS_DURATION = 60 * 4
# always get these two, even if upscaling # always get these two, even if upscaling
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360] MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
# default settings for notifications # default settings for notifications
# not all of them are implemented # not all of them are implemented
@@ -376,16 +376,7 @@ LOGGING = {
}, },
} }
DATABASES = { DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mediacms",
"HOST": "127.0.0.1",
"PORT": "5432",
"USER": "mediacms",
"PASSWORD": "mediacms",
}
}
REDIS_LOCATION = "redis://127.0.0.1:6379/1" REDIS_LOCATION = "redis://127.0.0.1:6379/1"
@@ -466,6 +457,7 @@ LANGUAGES = [
('pt', _('Portuguese')), ('pt', _('Portuguese')),
('ru', _('Russian')), ('ru', _('Russian')),
('zh-hans', _('Simplified Chinese')), ('zh-hans', _('Simplified Chinese')),
('sl', _('Slovenian')),
('zh-hant', _('Traditional Chinese')), ('zh-hant', _('Traditional Chinese')),
('es', _('Spanish')), ('es', _('Spanish')),
('tr', _('Turkish')), ('tr', _('Turkish')),
@@ -505,6 +497,10 @@ USE_ROUNDED_CORNERS = True
ALLOW_VIDEO_TRIMMER = True ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False ALLOW_CUSTOM_MEDIA_URLS = False
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
try: try:
# keep a local_settings.py file for local overrides # keep a local_settings.py file for local overrides
from .local_settings import * # noqa from .local_settings import * # noqa
@@ -544,13 +540,5 @@ except ImportError:
if GLOBAL_LOGIN_REQUIRED: if GLOBAL_LOGIN_REQUIRED:
# this should go after the AuthenticationMiddleware middleware auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(6, "login_required.middleware.LoginRequiredMiddleware") MIDDLEWARE.insert(auth_index + 1, "django.contrib.auth.middleware.LoginRequiredMiddleware")
LOGIN_REQUIRED_IGNORE_PATHS = [
r'/accounts/login/$',
r'/accounts/logout/$',
r'/accounts/signup/$',
r'/accounts/password/.*/$',
r'/accounts/confirm-email/.*/$',
# r'/api/v[0-9]+/',
]

View File

@@ -1 +1 @@
VERSION = "6.2.0" VERSION = "6.3.0"

View File

@@ -13,6 +13,7 @@ DATABASES = {
"PORT": os.getenv('POSTGRES_PORT', '5432'), "PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'), "USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'), "PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True},
} }
} }

View File

@@ -72,7 +72,7 @@ services:
POSTGRES_DB: mediacms POSTGRES_DB: mediacms
TZ: Europe/London TZ: Europe/London
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"] test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -81,6 +81,6 @@ services:
restart: always restart: always
healthcheck: healthcheck:
test: ["CMD", "redis-cli","ping"] test: ["CMD", "redis-cli","ping"]
interval: 30s interval: 10s
timeout: 10s timeout: 5s
retries: 3 retries: 3

50
docs/transcoding.md Normal file
View File

@@ -0,0 +1,50 @@
# Transcoding in MediaCMS
MediaCMS uses FFmpeg for transcoding media files. Most of the transcoding settings and configurations are defined in `files/helpers.py`.
## Configuration Options
Several transcoding parameters can be customized in `cms/settings.py`:
### FFmpeg Preset
The default FFmpeg preset is set to "medium". This setting controls the encoding speed and compression efficiency trade-off.
```python
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
```
Available presets include:
- ultrafast
- superfast
- veryfast
- faster
- fast
- medium (default)
- slow
- slower
- veryslow
Faster presets result in larger file sizes for the same quality, while slower presets provide better compression but take longer to encode.
### Other Transcoding Settings
Additional transcoding settings in `settings.py` include:
- `FFMPEG_COMMAND`: Path to the FFmpeg executable
- `FFPROBE_COMMAND`: Path to the FFprobe executable
- `DO_NOT_TRANSCODE_VIDEO`: If set to True, only the original video is shown without transcoding
- `CHUNKIZE_VIDEO_DURATION`: For videos longer than this duration (in seconds), they get split into chunks and encoded independently
- `VIDEO_CHUNKS_DURATION`: Duration of each chunk (must be smaller than CHUNKIZE_VIDEO_DURATION)
- `MINIMUM_RESOLUTIONS_TO_ENCODE`: Always encode these resolutions, even if upscaling is required
## Advanced Configuration
For more advanced transcoding settings, you may need to modify the following in `files/helpers.py`:
- Video bitrates for different codecs and resolutions
- Audio encoders and bitrates
- CRF (Constant Rate Factor) values
- Keyframe settings
- Encoding parameters for different codecs (H.264, H.265, VP9)

View File

@@ -1,5 +1,7 @@
from django.conf import settings from django.conf import settings
from cms.version import VERSION
from .frontend_translations import get_translation, get_translation_strings from .frontend_translations import get_translation, get_translation_strings
from .methods import is_mediacms_editor, is_mediacms_manager from .methods import is_mediacms_editor, is_mediacms_manager
@@ -37,6 +39,7 @@ def stuff(request):
ret["USE_SAML"] = settings.USE_SAML ret["USE_SAML"] = settings.USE_SAML
ret["USE_RBAC"] = settings.USE_RBAC ret["USE_RBAC"] = settings.USE_RBAC
ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS
ret["VERSION"] = VERSION
if request.user.is_superuser: if request.user.is_superuser:
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL

View File

@@ -83,7 +83,7 @@ class IndexRSSFeed(Feed):
return item.edit_date return item.edit_date
def item_link(self, item): def item_link(self, item):
return reverse("get_media") + "?m={0}".format(item.friendly_token) return f"{reverse('get_media')}?m={item.friendly_token}"
def item_extra_kwargs(self, item): def item_extra_kwargs(self, item):
item = { item = {
@@ -151,7 +151,7 @@ class SearchRSSFeed(Feed):
return item.edit_date return item.edit_date
def item_link(self, item): def item_link(self, item):
return reverse("get_media") + "?m={0}".format(item.friendly_token) return f"{reverse('get_media')}?m={item.friendly_token}"
def item_extra_kwargs(self, item): def item_extra_kwargs(self, item):
item = { item = {

View File

@@ -35,7 +35,7 @@ class MediaMetadataForm(forms.ModelForm):
widgets = { widgets = {
"new_tags": MultipleSelect(), "new_tags": MultipleSelect(),
"description": forms.Textarea(attrs={'rows': 4}), "description": forms.Textarea(attrs={'rows': 4}),
"add_date": forms.DateInput(attrs={'type': 'date'}), "add_date": forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
"thumbnail_time": forms.NumberInput(attrs={'min': 0, 'step': 0.1}), "thumbnail_time": forms.NumberInput(attrs={'min': 0, 'step': 0.1}),
} }
labels = { labels = {

View File

@@ -0,0 +1,104 @@
translation_strings = {
"ABOUT": "O NAS",
"AUTOPLAY": "SAMODEJNO PREDVAJANJE",
"Add a ": "Dodaj ",
"COMMENT": "KOMENTAR",
"Categories": "Kategorije",
"Category": "Kategorija",
"Change Language": "Spremeni jezik",
"Change password": "Spremeni geslo",
"About": "O nas",
"Comment": "Komentar",
"Comments": "Komentarji",
"Comments are disabled": "Komentarji so onemogočeni",
"Contact": "Kontakt",
"DELETE MEDIA": "IZBRIŠI MEDIJ",
"DOWNLOAD": "PRENESI",
"EDIT MEDIA": "UREDI MEDIJ",
"EDIT PROFILE": "UREDI PROFIL",
"EDIT SUBTITLE": "UREDI PODNAPISE",
"Edit media": "Uredi medij",
"Edit profile": "Uredi profil",
"Edit subtitle": "Uredi podnapise",
"Featured": "Izbrani",
"Go": "Pojdi",
"History": "Zgodovina",
"Home": "Domov",
"Language": "Jezik",
"Latest": "Najnovejši",
"Liked media": "Všečkani mediji",
"Manage comments": "Upravljaj komentarje",
"Manage media": "Upravljaj medije",
"Manage users": "Upravljaj uporabnike",
"Media": "Mediji",
"Media was edited": "Medij je bil urejen",
"Members": "Člani",
"My media": "Moji mediji",
"My playlists": "Moji seznami predvajanja",
"No": "Ne",
"No comment yet": "Brez komentarja",
"No comments yet": "Brez komentarjev",
"No results for": "Ni rezultatov za",
"PLAYLISTS": "SEZNAMI PREDVAJANJA",
"Playlists": "Seznami predvajanja",
"Powered by": "Poganja",
"Published on": "Objavljeno",
"Recommended": "Priporočeno",
"Register": "Registracija",
"SAVE": "SHRANI",
"SEARCH": "ISKANJE",
"SHARE": "DELI",
"SHOW MORE": "PRIKAŽI VEČ",
"SUBMIT": "POŠLJI",
"Search": "Iskanje",
"Select": "Izberi",
"Sign in": "Prijava",
"Sign out": "Odjava",
"Subtitle was added": "Podnapisi so bili dodani",
"Tags": "Oznake",
"Terms": "Pogoji",
"UPLOAD": "NALOŽI",
"Up next": "Naslednji",
"Upload": "Naloži",
"Upload media": "Naloži medij",
"Uploads": "Naloženi",
"VIEW ALL": "PRIKAŽI VSE",
"View all": "Prikaži vse",
"comment": "komentar",
"is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media": "je moderni, popolnoma opremljen odprtokodni video in medijski CMS. Razvit je za potrebe sodobnih spletnih platform za ogled in deljenje medijev",
"media in category": "mediji v kategoriji",
"media in tag": "mediji z oznako",
"view": "ogled",
"views": "ogledi",
"yet": "še",
}
replacement_strings = {
"Apr": "Apr",
"Aug": "Avg",
"Dec": "Dec",
"Feb": "Feb",
"Jan": "Jan",
"Jul": "Jul",
"Jun": "Jun",
"Mar": "Mar",
"May": "Maj",
"Nov": "Nov",
"Oct": "Okt",
"Sep": "Sep",
"day ago": "dan nazaj",
"days ago": "dni nazaj",
"hour ago": "ura nazaj",
"hours ago": "ur nazaj",
"just now": "pravkar",
"minute ago": "minuta nazaj",
"minutes ago": "minut nazaj",
"month ago": "mesec nazaj",
"months ago": "mesecev nazaj",
"second ago": "sekunda nazaj",
"seconds ago": "sekund nazaj",
"week ago": "teden nazaj",
"weeks ago": "tednov nazaj",
"year ago": "leto nazaj",
"years ago": "let nazaj",
}

View File

@@ -34,12 +34,6 @@ BUF_SIZE_MULTIPLIER = 1.5
KEYFRAME_DISTANCE = 4 KEYFRAME_DISTANCE = 4
KEYFRAME_DISTANCE_MIN = 2 KEYFRAME_DISTANCE_MIN = 2
# speed presets
# see https://trac.ffmpeg.org/wiki/Encode/H.264
X26x_PRESET = "medium" # "medium"
X265_PRESET = "medium"
X26x_PRESET_BIG_HEIGHT = "faster"
# VP9_SPEED = 1 # between 0 and 4, lower is slower # VP9_SPEED = 1 # between 0 and 4, lower is slower
VP9_SPEED = 2 VP9_SPEED = 2
@@ -55,6 +49,7 @@ VIDEO_CRFS = {
VIDEO_BITRATES = { VIDEO_BITRATES = {
"h264": { "h264": {
25: { 25: {
144: 150,
240: 300, 240: 300,
360: 500, 360: 500,
480: 1000, 480: 1000,
@@ -67,6 +62,7 @@ VIDEO_BITRATES = {
}, },
"h265": { "h265": {
25: { 25: {
144: 75,
240: 150, 240: 150,
360: 275, 360: 275,
480: 500, 480: 500,
@@ -79,6 +75,7 @@ VIDEO_BITRATES = {
}, },
"vp9": { "vp9": {
25: { 25: {
144: 75,
240: 150, 240: 150,
360: 275, 360: 275,
480: 500, 480: 500,
@@ -173,7 +170,7 @@ def rm_dir(directory):
def url_from_path(filename): def url_from_path(filename):
# TODO: find a way to preserver http - https ... # TODO: find a way to preserver http - https ...
return "{0}{1}".format(settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, "")) return f"{settings.MEDIA_URL}{filename.replace(settings.MEDIA_ROOT, '')}"
def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY): def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY):
@@ -488,7 +485,7 @@ def show_file_size(size):
if size: if size:
size = size / 1000000 size = size / 1000000
size = round(size, 1) size = round(size, 1)
size = "{0}MB".format(str(size)) size = f"{str(size)}MB"
return size return size
@@ -596,17 +593,13 @@ def get_base_ffmpeg_command(
cmd = base_cmd[:] cmd = base_cmd[:]
# preset settings # preset settings
preset = getattr(settings, "FFMPEG_DEFAULT_PRESET", "medium")
if encoder == "libvpx-vp9": if encoder == "libvpx-vp9":
if pass_number == 1: if pass_number == 1:
speed = 4 speed = 4
else: else:
speed = VP9_SPEED speed = VP9_SPEED
elif encoder in ["libx264"]:
preset = X26x_PRESET
elif encoder in ["libx265"]:
preset = X265_PRESET
if target_height >= 720:
preset = X26x_PRESET_BIG_HEIGHT
if encoder == "libx264": if encoder == "libx264":
level = "4.2" if target_height <= 1080 else "5.2" level = "4.2" if target_height <= 1080 else "5.2"
@@ -730,7 +723,7 @@ def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_fi
return False return False
if media_info.get("video_height") < resolution: if media_info.get("video_height") < resolution:
if resolution not in [240, 360]: # always get these two if resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
return False return False
# if codec == "h264_baseline": # if codec == "h264_baseline":

View File

@@ -166,14 +166,14 @@ Media becomes private if it gets reported %s times\n
) )
if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False): if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
title = "[{}] - Media was reported".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - Media was reported"
d = {} d = {}
d["title"] = title d["title"] = title
d["msg"] = msg d["msg"] = msg
d["to"] = settings.ADMIN_EMAIL_LIST d["to"] = settings.ADMIN_EMAIL_LIST
notify_items.append(d) notify_items.append(d)
if settings.USERS_NOTIFICATIONS.get("MEDIA_REPORTED", False): if settings.USERS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
title = "[{}] - Media was reported".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - Media was reported"
d = {} d = {}
d["title"] = title d["title"] = title
d["msg"] = msg d["msg"] = msg
@@ -182,7 +182,7 @@ Media becomes private if it gets reported %s times\n
if action == "media_added" and media: if action == "media_added" and media:
if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False): if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False):
title = "[{}] - Media was added".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - Media was added"
msg = """ msg = """
Media %s was added by user %s. Media %s was added by user %s.
""" % ( """ % (
@@ -195,7 +195,7 @@ Media %s was added by user %s.
d["to"] = settings.ADMIN_EMAIL_LIST d["to"] = settings.ADMIN_EMAIL_LIST
notify_items.append(d) notify_items.append(d)
if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False): if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False):
title = "[{}] - Your media was added".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - Your media was added"
msg = """ msg = """
Your media has been added! It will be encoded and will be available soon. Your media has been added! It will be encoded and will be available soon.
URL: %s URL: %s
@@ -339,7 +339,7 @@ def notify_user_on_comment(friendly_token):
media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url() media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
if user.notification_on_comments: if user.notification_on_comments:
title = "[{}] - A comment was added".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - A comment was added"
msg = """ msg = """
A comment has been added to your media %s . A comment has been added to your media %s .
View it on %s View it on %s
@@ -363,7 +363,7 @@ def notify_user_on_mention(friendly_token, user_mentioned, cleaned_comment):
media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url() media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
if user.notification_on_comments: if user.notification_on_comments:
title = "[{}] - You were mentioned in a comment".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - You were mentioned in a comment"
msg = """ msg = """
You were mentioned in a comment on %s . You were mentioned in a comment on %s .
View it on %s View it on %s

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.1.6 on 2025-07-05 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0009_alter_media_friendly_token'),
]
operations = [
migrations.AlterField(
model_name='encodeprofile',
name='resolution',
field=models.IntegerField(blank=True, choices=[(2160, '2160'), (1440, '1440'), (1080, '1080'), (720, '720'), (480, '480'), (360, '360'), (240, '240'), (144, '144')], null=True),
),
]

View File

@@ -13,7 +13,8 @@ from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import SearchVectorField from django.contrib.postgres.search import SearchVectorField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files import File from django.core.files import File
from django.db import connection, models from django.db import models
from django.db.models import Func, Value
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.urls import reverse from django.urls import reverse
@@ -72,6 +73,7 @@ ENCODE_RESOLUTIONS = (
(480, "480"), (480, "480"),
(360, "360"), (360, "360"),
(240, "240"), (240, "240"),
(144, "144"),
) )
CODECS = ( CODECS = (
@@ -90,34 +92,34 @@ def generate_uid():
def original_media_file_path(instance, filename): def original_media_file_path(instance, filename):
"""Helper function to place original media file""" """Helper function to place original media file"""
file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename)) file_name = f"{instance.uid.hex}.{helpers.get_file_name(filename)}"
return settings.MEDIA_UPLOAD_DIR + "user/{0}/{1}".format(instance.user.username, file_name) return settings.MEDIA_UPLOAD_DIR + f"user/{instance.user.username}/{file_name}"
def encoding_media_file_path(instance, filename): def encoding_media_file_path(instance, filename):
"""Helper function to place encoded media file""" """Helper function to place encoded media file"""
file_name = "{0}.{1}".format(instance.media.uid.hex, helpers.get_file_name(filename)) file_name = f"{instance.media.uid.hex}.{helpers.get_file_name(filename)}"
return settings.MEDIA_ENCODING_DIR + "{0}/{1}/{2}".format(instance.profile.id, instance.media.user.username, file_name) return settings.MEDIA_ENCODING_DIR + f"{instance.profile.id}/{instance.media.user.username}/{file_name}"
def original_thumbnail_file_path(instance, filename): def original_thumbnail_file_path(instance, filename):
"""Helper function to place original media thumbnail file""" """Helper function to place original media thumbnail file"""
return settings.THUMBNAIL_UPLOAD_DIR + "user/{0}/{1}".format(instance.user.username, filename) return settings.THUMBNAIL_UPLOAD_DIR + f"user/{instance.user.username}/{filename}"
def subtitles_file_path(instance, filename): def subtitles_file_path(instance, filename):
"""Helper function to place subtitle file""" """Helper function to place subtitle file"""
return settings.SUBTITLES_UPLOAD_DIR + "user/{0}/{1}".format(instance.media.user.username, filename) return settings.SUBTITLES_UPLOAD_DIR + f"user/{instance.media.user.username}/{filename}"
def category_thumb_path(instance, filename): def category_thumb_path(instance, filename):
"""Helper function to place category thumbnail file""" """Helper function to place category thumbnail file"""
file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename)) file_name = f"{instance.uid}.{helpers.get_file_name(filename)}"
return settings.MEDIA_UPLOAD_DIR + "categories/{0}".format(file_name) return settings.MEDIA_UPLOAD_DIR + f"categories/{file_name}"
class Media(models.Model): class Media(models.Model):
@@ -388,8 +390,6 @@ class Media(models.Model):
search field is used to store SearchVector search field is used to store SearchVector
""" """
db_table = self._meta.db_table
# first get anything interesting out of the media # first get anything interesting out of the media
# that needs to be search able # that needs to be search able
@@ -413,19 +413,8 @@ class Media(models.Model):
text = helpers.clean_query(text) text = helpers.clean_query(text)
sql_code = """ Media.objects.filter(id=self.id).update(search=Func(Value('simple'), Value(text), function='to_tsvector'))
UPDATE {db_table} SET search = to_tsvector(
'{config}', '{text}'
) WHERE {db_table}.id = {id}
""".format(
db_table=db_table, config="simple", text=text, id=self.id
)
try:
with connection.cursor() as cursor:
cursor.execute(sql_code)
except BaseException:
pass # TODO:add log
return True return True
def media_init(self): def media_init(self):
@@ -908,7 +897,7 @@ class Media(models.Model):
""" """
res = {} res = {}
valid_resolutions = [240, 360, 480, 720, 1080, 1440, 2160] valid_resolutions = [144, 240, 360, 480, 720, 1080, 1440, 2160]
if self.hls_file: if self.hls_file:
if os.path.exists(self.hls_file): if os.path.exists(self.hls_file):
hls_file = self.hls_file hls_file = self.hls_file
@@ -925,7 +914,7 @@ class Media(models.Model):
if resolution not in valid_resolutions: if resolution not in valid_resolutions:
resolution = iframe_playlist.iframe_stream_info.resolution[0] resolution = iframe_playlist.iframe_stream_info.resolution[0]
res["{}_iframe".format(resolution)] = helpers.url_from_path(uri) res[f"{resolution}_iframe"] = helpers.url_from_path(uri)
for playlist in m3u8_obj.playlists: for playlist in m3u8_obj.playlists:
uri = os.path.join(p, playlist.uri) uri = os.path.join(p, playlist.uri)
if os.path.exists(uri): if os.path.exists(uri):
@@ -934,7 +923,8 @@ class Media(models.Model):
if resolution not in valid_resolutions: if resolution not in valid_resolutions:
resolution = playlist.stream_info.resolution[0] resolution = playlist.stream_info.resolution[0]
res["{}_playlist".format(resolution)] = helpers.url_from_path(uri) res[f"{resolution}_playlist"] = helpers.url_from_path(uri)
return res return res
@property @property
@@ -953,11 +943,11 @@ class Media(models.Model):
def get_absolute_url(self, api=False, edit=False): def get_absolute_url(self, api=False, edit=False):
if edit: if edit:
return reverse("edit_media") + "?m={0}".format(self.friendly_token) return f"{reverse('edit_media')}?m={self.friendly_token}"
if api: if api:
return reverse("api_get_media", kwargs={"friendly_token": self.friendly_token}) return reverse("api_get_media", kwargs={"friendly_token": self.friendly_token})
else: else:
return reverse("get_media") + "?m={0}".format(self.friendly_token) return f"{reverse('get_media')}?m={self.friendly_token}"
@property @property
def edit_url(self): def edit_url(self):
@@ -965,7 +955,7 @@ class Media(models.Model):
@property @property
def add_subtitle_url(self): def add_subtitle_url(self):
return "/add_subtitle?m=%s" % self.friendly_token return f"/add_subtitle?m={self.friendly_token}"
@property @property
def ratings_info(self): def ratings_info(self):
@@ -1060,7 +1050,7 @@ class Category(models.Model):
verbose_name_plural = "Categories" verbose_name_plural = "Categories"
def get_absolute_url(self): def get_absolute_url(self):
return reverse("search") + "?c={0}".format(self.title) return f"{reverse('search')}?c={self.title}"
def update_category_media(self): def update_category_media(self):
"""Set media_count""" """Set media_count"""
@@ -1122,7 +1112,7 @@ class Tag(models.Model):
ordering = ["title"] ordering = ["title"]
def get_absolute_url(self): def get_absolute_url(self):
return reverse("search") + "?t={0}".format(self.title) return f"{reverse('search')}?t={self.title}"
def update_tag_media(self): def update_tag_media(self):
self.media_count = Media.objects.filter(state="public", is_reviewed=True, tags=self).count() self.media_count = Media.objects.filter(state="public", is_reviewed=True, tags=self).count()
@@ -1261,7 +1251,7 @@ class Encoding(models.Model):
return False return False
def __str__(self): def __str__(self):
return "{0}-{1}".format(self.profile.name, self.media.title) return f"{self.profile.name}-{self.media.title}"
def get_absolute_url(self): def get_absolute_url(self):
return reverse("api_get_encoding", kwargs={"encoding_id": self.id}) return reverse("api_get_encoding", kwargs={"encoding_id": self.id})
@@ -1280,7 +1270,7 @@ class Language(models.Model):
ordering = ["id"] ordering = ["id"]
def __str__(self): def __str__(self):
return "{0}-{1}".format(self.code, self.title) return f"{self.code}-{self.title}"
class Subtitle(models.Model): class Subtitle(models.Model):
@@ -1303,7 +1293,7 @@ class Subtitle(models.Model):
ordering = ["language__title"] ordering = ["language__title"]
def __str__(self): def __str__(self):
return "{0}-{1}".format(self.media.title, self.language.title) return f"{self.media.title}-{self.language.title}"
def get_absolute_url(self): def get_absolute_url(self):
return f"{reverse('edit_subtitle')}?id={self.id}" return f"{reverse('edit_subtitle')}?id={self.id}"
@@ -1347,7 +1337,7 @@ class RatingCategory(models.Model):
verbose_name_plural = "Rating Categories" verbose_name_plural = "Rating Categories"
def __str__(self): def __str__(self):
return "{0}".format(self.title) return f"{self.title}"
def validate_rating(value): def validate_rating(value):
@@ -1376,7 +1366,7 @@ class Rating(models.Model):
unique_together = ("user", "media", "rating_category") unique_together = ("user", "media", "rating_category")
def __str__(self): def __str__(self):
return "{0}, rate for {1} for category {2}".format(self.user.username, self.media.title, self.rating_category.title) return f"{self.user.username}, rate for {self.media.title} for category {self.rating_category.title}"
class Playlist(models.Model): class Playlist(models.Model):
@@ -1488,7 +1478,7 @@ class Comment(MPTTModel):
order_insertion_by = ["add_date"] order_insertion_by = ["add_date"]
def __str__(self): def __str__(self):
return "On {0} by {1}".format(self.media.title, self.user.username) return f"On {self.media.title} by {self.user.username}"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
strip_text_items = ["text"] strip_text_items = ["text"]
@@ -1501,7 +1491,7 @@ class Comment(MPTTModel):
super(Comment, self).save(*args, **kwargs) super(Comment, self).save(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self):
return reverse("get_media") + "?m={0}".format(self.media.friendly_token) return f"{reverse('get_media')}?m={self.media.friendly_token}"
@property @property
def media_url(self): def media_url(self):
@@ -1720,10 +1710,10 @@ def encoding_file_save(sender, instance, created, **kwargs):
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir: with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
seg_file = helpers.create_temp_file(suffix=".txt", dir=temp_dir) seg_file = helpers.create_temp_file(suffix=".txt", dir=temp_dir)
tf = helpers.create_temp_file(suffix=".{0}".format(instance.profile.extension), dir=temp_dir) tf = helpers.create_temp_file(suffix=f".{instance.profile.extension}", dir=temp_dir)
with open(seg_file, "w") as ff: with open(seg_file, "w") as ff:
for f in chunks_paths: for f in chunks_paths:
ff.write("file {}\n".format(f)) ff.write(f"file {f}\n")
cmd = [ cmd = [
settings.FFMPEG_COMMAND, settings.FFMPEG_COMMAND,
"-y", "-y",
@@ -1750,7 +1740,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
progress=100, progress=100,
) )
all_logs = "\n".join([st.logs for st in chunks]) all_logs = "\n".join([st.logs for st in chunks])
encoding.logs = "{0}\n{1}\n{2}".format(chunks_paths, stdout, all_logs) encoding.logs = f"{chunks_paths}\n{stdout}\n{all_logs}"
workers = list(set([st.worker for st in chunks])) workers = list(set([st.worker for st in chunks]))
encoding.worker = json.dumps({"workers": workers}) encoding.worker = json.dumps({"workers": workers})
@@ -1761,10 +1751,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
with open(tf, "rb") as f: with open(tf, "rb") as f:
myfile = File(f) myfile = File(f)
output_name = "{0}.{1}".format( output_name = f"{helpers.get_file_name(instance.media.media_file.path)}.{instance.profile.extension}"
helpers.get_file_name(instance.media.media_file.path),
instance.profile.extension,
)
encoding.media_file.save(content=myfile, name=output_name) encoding.media_file.save(content=myfile, name=output_name)
# encoding is saved, deleting chunks # encoding is saved, deleting chunks
@@ -1803,7 +1790,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
chunks_paths = [f.media_file.path for f in chunks] chunks_paths = [f.media_file.path for f in chunks]
all_logs = "\n".join([st.logs for st in chunks]) all_logs = "\n".join([st.logs for st in chunks])
encoding.logs = "{0}\n{1}".format(chunks_paths, all_logs) encoding.logs = f"{chunks_paths}\n{all_logs}"
workers = list(set([st.worker for st in chunks])) workers = list(set([st.worker for st in chunks]))
encoding.worker = json.dumps({"workers": workers}) encoding.worker = json.dumps({"workers": workers})
start_date = min([st.add_date for st in chunks]) start_date = min([st.add_date for st in chunks])

View File

@@ -136,8 +136,8 @@ def chunkize_media(self, friendly_token, profiles, force=True):
cwd = os.path.dirname(os.path.realpath(media.media_file.path)) cwd = os.path.dirname(os.path.realpath(media.media_file.path))
file_name = media.media_file.path.split("/")[-1] file_name = media.media_file.path.split("/")[-1]
random_prefix = produce_friendly_token() random_prefix = produce_friendly_token()
file_format = "{0}_{1}".format(random_prefix, file_name) file_format = f"{random_prefix}_{file_name}"
chunks_file_name = "%02d_{0}".format(file_format) chunks_file_name = f"%02d_{file_format}"
chunks_file_name += ".mkv" chunks_file_name += ".mkv"
cmd = [ cmd = [
settings.FFMPEG_COMMAND, settings.FFMPEG_COMMAND,
@@ -162,7 +162,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
chunks.append(ch[0]) chunks.append(ch[0])
if not chunks: if not chunks:
# command completely failed to segment file.putting to normal encode # command completely failed to segment file.putting to normal encode
logger.info("Failed to break file {0} in chunks." " Putting to normal encode queue".format(friendly_token)) logger.info(f"Failed to break file {friendly_token} in chunks. Putting to normal encode queue")
for profile in profiles: for profile in profiles:
if media.video_height and media.video_height < profile.resolution: if media.video_height and media.video_height < profile.resolution:
if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE: if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
@@ -211,7 +211,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
priority=priority, priority=priority,
) )
logger.info("got {0} chunks and will encode to {1} profiles".format(len(chunks), to_profiles)) logger.info(f"got {len(chunks)} chunks and will encode to {to_profiles} profiles")
return True return True
@@ -355,8 +355,8 @@ def encode_media(
# return False # return False
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir: with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
tf = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir) tf = create_temp_file(suffix=f".{profile.extension}", dir=temp_dir)
tfpass = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir) tfpass = create_temp_file(suffix=f".{profile.extension}", dir=temp_dir)
ffmpeg_commands = produce_ffmpeg_commands( ffmpeg_commands = produce_ffmpeg_commands(
original_media_path, original_media_path,
media.media_info, media.media_info,
@@ -398,7 +398,7 @@ def encode_media(
if n_times % 60 == 0: if n_times % 60 == 0:
encoding.progress = percent encoding.progress = percent
encoding.save(update_fields=["progress", "update_date"]) encoding.save(update_fields=["progress", "update_date"])
logger.info("Saved {0}".format(round(percent, 2))) logger.info(f"Saved {round(percent, 2)}")
n_times += 1 n_times += 1
except DatabaseError: except DatabaseError:
# primary reason for this is that the encoding has been deleted, because # primary reason for this is that the encoding has been deleted, because
@@ -451,7 +451,7 @@ def encode_media(
with open(tf, "rb") as f: with open(tf, "rb") as f:
myfile = File(f) myfile = File(f)
output_name = "{0}.{1}".format(get_file_name(original_media_path), profile.extension) output_name = f"{get_file_name(original_media_path)}.{profile.extension}"
encoding.media_file.save(content=myfile, name=output_name) encoding.media_file.save(content=myfile, name=output_name)
encoding.total_run_time = (encoding.update_date - encoding.add_date).seconds encoding.total_run_time = (encoding.update_date - encoding.add_date).seconds
@@ -472,7 +472,7 @@ def produce_sprite_from_video(friendly_token):
try: try:
media = Media.objects.get(friendly_token=friendly_token) media = Media.objects.get(friendly_token=friendly_token)
except BaseException: except BaseException:
logger.info("failed to get media with friendly_token %s" % friendly_token) logger.info(f"failed to get media with friendly_token {friendly_token}")
return False return False
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname: with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
@@ -516,7 +516,7 @@ def create_hls(friendly_token):
try: try:
media = Media.objects.get(friendly_token=friendly_token) media = Media.objects.get(friendly_token=friendly_token)
except BaseException: except BaseException:
logger.info("failed to get media with friendly_token %s" % friendly_token) logger.info(f"failed to get media with friendly_token {friendly_token}")
return False return False
p = media.uid.hex p = media.uid.hex
@@ -558,7 +558,7 @@ def check_running_states():
encodings = Encoding.objects.filter(status="running") encodings = Encoding.objects.filter(status="running")
logger.info("got {0} encodings that are in state running".format(encodings.count())) logger.info(f"got {encodings.count()} encodings that are in state running")
changed = 0 changed = 0
for encoding in encodings: for encoding in encodings:
now = datetime.now(encoding.update_date.tzinfo) now = datetime.now(encoding.update_date.tzinfo)
@@ -575,7 +575,7 @@ def check_running_states():
# TODO: allign with new code + chunksize... # TODO: allign with new code + chunksize...
changed += 1 changed += 1
if changed: if changed:
logger.info("changed from running to pending on {0} items".format(changed)) logger.info(f"changed from running to pending on {changed} items")
return True return True
@@ -585,7 +585,7 @@ def check_media_states():
# check encoding status of not success media # check encoding status of not success media
media = Media.objects.filter(Q(encoding_status="running") | Q(encoding_status="fail") | Q(encoding_status="pending")) media = Media.objects.filter(Q(encoding_status="running") | Q(encoding_status="fail") | Q(encoding_status="pending"))
logger.info("got {0} media that are not in state success".format(media.count())) logger.info(f"got {media.count()} media that are not in state success")
changed = 0 changed = 0
for m in media: for m in media:
@@ -593,7 +593,7 @@ def check_media_states():
m.save(update_fields=["encoding_status"]) m.save(update_fields=["encoding_status"])
changed += 1 changed += 1
if changed: if changed:
logger.info("changed encoding status to {0} media items".format(changed)) logger.info(f"changed encoding status to {changed} media items")
return True return True
@@ -628,7 +628,7 @@ def check_pending_states():
media.encode(profiles=[profile], force=False) media.encode(profiles=[profile], force=False)
changed += 1 changed += 1
if changed: if changed:
logger.info("set to the encode queue {0} encodings that were on pending state".format(changed)) logger.info(f"set to the encode queue {changed} encodings that were on pending state")
return True return True
@@ -652,7 +652,7 @@ def check_missing_profiles():
# if they appear on the meanwhile (eg on a big queue) # if they appear on the meanwhile (eg on a big queue)
changed += 1 changed += 1
if changed: if changed:
logger.info("set to the encode queue {0} profiles".format(changed)) logger.info(f"set to the encode queue {changed} profiles")
return True return True
@@ -828,7 +828,7 @@ def update_listings_thumbnails():
object.save(update_fields=["listings_thumbnail"]) object.save(update_fields=["listings_thumbnail"])
used_media.append(media.friendly_token) used_media.append(media.friendly_token)
saved += 1 saved += 1
logger.info("updated {} categories".format(saved)) logger.info(f"updated {saved} categories")
# Tags # Tags
used_media = [] used_media = []
@@ -841,7 +841,7 @@ def update_listings_thumbnails():
object.save(update_fields=["listings_thumbnail"]) object.save(update_fields=["listings_thumbnail"])
used_media.append(media.friendly_token) used_media.append(media.friendly_token)
saved += 1 saved += 1
logger.info("updated {} tags".format(saved)) logger.info(f"updated {saved} tags")
return True return True

View File

@@ -211,7 +211,7 @@ def contact(request):
name = request.POST.get("name") name = request.POST.get("name")
message = request.POST.get("message") message = request.POST.get("message")
title = "[{}] - Contact form message received".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - Contact form message received"
msg = """ msg = """
You have received a message through the contact form\n You have received a message through the contact form\n

View File

@@ -1 +1 @@
[{"model": "files.encodeprofile", "pk": 19, "fields": {"name": "h264-2160", "extension": "mp4", "resolution": 2160, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 22, "fields": {"name": "vp9-2160", "extension": "webm", "resolution": 2160, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 16, "fields": {"name": "h265-2160", "extension": "mp4", "resolution": 2160, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 4, "fields": {"name": "h264-1440", "extension": "mp4", "resolution": 1440, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 5, "fields": {"name": "vp9-1440", "extension": "webm", "resolution": 1440, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 6, "fields": {"name": "h265-1440", "extension": "mp4", "resolution": 1440, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 7, "fields": {"name": "h264-1080", "extension": "mp4", "resolution": 1080, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 8, "fields": {"name": "vp9-1080", "extension": "webm", "resolution": 1080, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 9, "fields": {"name": "h265-1080", "extension": "mp4", "resolution": 1080, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 10, "fields": {"name": "h264-720", "extension": "mp4", "resolution": 720, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 11, "fields": {"name": "vp9-720", "extension": "webm", "resolution": 720, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 12, "fields": {"name": "h265-720", "extension": "mp4", "resolution": 720, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 13, "fields": {"name": "h264-480", "extension": "mp4", "resolution": 480, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 14, "fields": {"name": "vp9-480", "extension": "webm", "resolution": 480, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 15, "fields": {"name": "h265-480", "extension": "mp4", "resolution": 480, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 3, "fields": {"name": "h264-360", "extension": "mp4", "resolution": 360, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 17, "fields": {"name": "vp9-360", "extension": "webm", "resolution": 360, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 18, "fields": {"name": "h265-360", "extension": "mp4", "resolution": 360, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "h264-240", "extension": "mp4", "resolution": 240, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 20, "fields": {"name": "vp9-240", "extension": "webm", "resolution": 240, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 21, "fields": {"name": "h265-240", "extension": "mp4", "resolution": 240, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 1, "fields": {"name": "preview", "extension": "gif", "resolution": null, "codec": null, "description": "", "active": true}}] [{"model": "files.encodeprofile", "pk": 19, "fields": {"name": "h264-2160", "extension": "mp4", "resolution": 2160, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 22, "fields": {"name": "vp9-2160", "extension": "webm", "resolution": 2160, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 23, "fields": {"name": "h264-144", "extension": "mp4", "resolution": 144, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 16, "fields": {"name": "h265-2160", "extension": "mp4", "resolution": 2160, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 4, "fields": {"name": "h264-1440", "extension": "mp4", "resolution": 1440, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 5, "fields": {"name": "vp9-1440", "extension": "webm", "resolution": 1440, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 6, "fields": {"name": "h265-1440", "extension": "mp4", "resolution": 1440, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 7, "fields": {"name": "h264-1080", "extension": "mp4", "resolution": 1080, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 8, "fields": {"name": "vp9-1080", "extension": "webm", "resolution": 1080, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 9, "fields": {"name": "h265-1080", "extension": "mp4", "resolution": 1080, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 10, "fields": {"name": "h264-720", "extension": "mp4", "resolution": 720, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 11, "fields": {"name": "vp9-720", "extension": "webm", "resolution": 720, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 12, "fields": {"name": "h265-720", "extension": "mp4", "resolution": 720, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 13, "fields": {"name": "h264-480", "extension": "mp4", "resolution": 480, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 14, "fields": {"name": "vp9-480", "extension": "webm", "resolution": 480, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 15, "fields": {"name": "h265-480", "extension": "mp4", "resolution": 480, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 3, "fields": {"name": "h264-360", "extension": "mp4", "resolution": 360, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 17, "fields": {"name": "vp9-360", "extension": "webm", "resolution": 360, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 18, "fields": {"name": "h265-360", "extension": "mp4", "resolution": 360, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "h264-240", "extension": "mp4", "resolution": 240, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 20, "fields": {"name": "vp9-240", "extension": "webm", "resolution": 240, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 21, "fields": {"name": "h265-240", "extension": "mp4", "resolution": 240, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 1, "fields": {"name": "preview", "extension": "gif", "resolution": null, "codec": null, "description": "", "active": true}}]

View File

@@ -2,7 +2,7 @@ Django==5.1.6
djangorestframework==3.15.2 djangorestframework==3.15.2
python3-saml==1.16.0 python3-saml==1.16.0
django-allauth==65.4.1 django-allauth==65.4.1
psycopg==3.2.4 psycopg[pool]==3.2.4
uwsgi==2.0.28 uwsgi==2.0.28
django-redis==5.4.0 django-redis==5.4.0
celery==5.4.0 celery==5.4.0
@@ -18,7 +18,6 @@ requests==2.32.3
django-celery-email==3.0.0 django-celery-email==3.0.0
m3u8==6.0.0 m3u8==6.0.0
django-debug-toolbar==5.0.1 django-debug-toolbar==5.0.1
django-login-required-middleware==0.9.0
pre-commit==4.1.0 pre-commit==4.1.0
django-jazzmin==3.0.1 django-jazzmin==3.0.1
pysubs2==1.8.0 pysubs2==1.8.0

View File

@@ -1,3 +1,3 @@
{% load static %} {% load static %}
<script src="{% static "js/_commons.js" %}"></script> <script src="{% static "js/_commons.js" %}?v={{ VERSION }}"></script>

View File

@@ -22,10 +22,10 @@
<link href="{% static "lib/gfonts/gfonts.css" %}" rel="stylesheet"> <link href="{% static "lib/gfonts/gfonts.css" %}" rel="stylesheet">
{% endif %} {% endif %}
<link href="{% static "css/_commons.css" %}" rel="preload" as="style"> <link href="{% static "css/_commons.css" %}?v={{ VERSION }}" rel="preload" as="style">
<link href="{% static "css/_commons.css" %}" rel="stylesheet"> <link href="{% static "css/_commons.css" %}?v={{ VERSION }}" rel="stylesheet">
<link href="{% static "css/_extra.css" %}" rel="preload" as="style"> <link href="{% static "css/_extra.css" %}?v={{ VERSION }}" rel="preload" as="style">
<link href="{% static "css/_extra.css" %}" rel="stylesheet"> <link href="{% static "css/_extra.css" %}?v={{ VERSION }}" rel="stylesheet">
<link href="{% static "js/_commons.js" %}" rel="preload" as="script"> <link href="{% static "js/_commons.js" %}?v={{ VERSION }}" rel="preload" as="script">

View File

@@ -43,8 +43,8 @@ class TestX(TestCase):
self.assertEqual(Media.objects.filter(media_type='image').count(), 1, "Media identification failed") self.assertEqual(Media.objects.filter(media_type='image').count(), 1, "Media identification failed")
self.assertEqual(Media.objects.filter(user=self.user).count(), 3, "User assignment failed") self.assertEqual(Media.objects.filter(user=self.user).count(), 3, "User assignment failed")
medium_video = Media.objects.get(title="medium_video.mp4") medium_video = Media.objects.get(title="medium_video.mp4")
self.assertEqual(len(medium_video.hls_info), 11, "Problem with HLS info") self.assertEqual(len(medium_video.hls_info), 13, "Problem with HLS info")
# using the provided EncodeProfiles, these two files should produce 9 Encoding objects. # using the provided EncodeProfiles, these two files should produce 9 Encoding objects.
# if new EncodeProfiles are added and enabled, this will break! # if new EncodeProfiles are added and enabled, this will break!
self.assertEqual(Encoding.objects.filter(status='success').count(), 9, "Not all video transcodings finished well") self.assertEqual(Encoding.objects.filter(status='success').count(), 10, "Not all video transcodings finished well")

View File

@@ -24,12 +24,12 @@ class TestFixtures(TestCase):
profiles = EncodeProfile.objects.all() profiles = EncodeProfile.objects.all()
self.assertEqual( self.assertEqual(
profiles.count(), profiles.count(),
22, 23,
"Problem with Encode Profile fixtures", "Problem with Encode Profile fixtures",
) )
profiles = EncodeProfile.objects.filter(active=True) profiles = EncodeProfile.objects.filter(active=True)
self.assertEqual( self.assertEqual(
profiles.count(), profiles.count(),
6, 7,
"Problem with Encode Profile fixtures, not as active as expected", "Problem with Encode Profile fixtures, not as active as expected",
) )

View File

@@ -7,7 +7,7 @@ def import_class(path):
path_bits = path.split(".") path_bits = path.split(".")
if len(path_bits) < 2: if len(path_bits) < 2:
message = "'{0}' is not a complete Python path.".format(path) message = f"'{path}' is not a complete Python path."
raise ImproperlyConfigured(message) raise ImproperlyConfigured(message)
class_name = path_bits.pop() class_name = path_bits.pop()
@@ -15,7 +15,7 @@ def import_class(path):
module_itself = import_module(module_path) module_itself = import_module(module_path)
if not hasattr(module_itself, class_name): if not hasattr(module_itself, class_name):
message = "The Python module '{}' has no '{}' class.".format(module_path, class_name) message = f"The Python module '{module_path}' has no '{class_name}' class."
raise ImportError(message) raise ImportError(message)
return getattr(module_itself, class_name) return getattr(module_itself, class_name)

View File

@@ -105,7 +105,7 @@ class User(AbstractUser):
ret = {} ret = {}
results = [] results = []
ret["results"] = results ret["results"] = results
ret["user_media"] = "/api/v1/media?author={0}".format(self.username) ret["user_media"] = f"/api/v1/media?author={self.username}"
return ret return ret
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@@ -210,7 +210,7 @@ class Channel(models.Model):
super(Channel, self).save(*args, **kwargs) super(Channel, self).save(*args, **kwargs)
def __str__(self): def __str__(self):
return "{0} -{1}".format(self.user.username, self.title) return f"{self.user.username} -{self.title}"
def get_absolute_url(self, edit=False): def get_absolute_url(self, edit=False):
if edit: if edit:
@@ -230,7 +230,7 @@ def post_user_create(sender, instance, created, **kwargs):
new = Channel.objects.create(title="default", user=instance) new = Channel.objects.create(title="default", user=instance)
new.save() new.save()
if settings.ADMINS_NOTIFICATIONS.get("NEW_USER", False): if settings.ADMINS_NOTIFICATIONS.get("NEW_USER", False):
title = "[{}] - New user just registered".format(settings.PORTAL_NAME) title = f"[{settings.PORTAL_NAME}] - New user just registered"
msg = """ msg = """
User has just registered with email %s\n User has just registered with email %s\n
Visit user profile page at %s Visit user profile page at %s