mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-20 21:46:04 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df98b65704 | ||
|
|
a607996bfa | ||
|
|
79f2e2bb11 | ||
|
|
d54732040a | ||
|
|
e8520bc7cd | ||
|
|
b6e46e7b62 | ||
|
|
36eab954bd |
14
README.md
14
README.md
@@ -38,7 +38,7 @@ A demo is available at https://demo.mediacms.io
|
||||
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
|
||||
- **Configuration options**: change logos, fonts, styling, add more pages
|
||||
- **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
|
||||
- **Subtitles/CC**: support for multilingual subtitle files
|
||||
- **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).
|
||||
|
||||
## 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
|
||||
|
||||
* [Users documentation](docs/user_docs.md) page
|
||||
* [Administrators documentation](docs/admins_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
|
||||
|
||||
@@ -186,7 +186,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
|
||||
VIDEO_CHUNKS_DURATION = 60 * 4
|
||||
|
||||
# always get these two, even if upscaling
|
||||
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360]
|
||||
MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
|
||||
|
||||
# default settings for notifications
|
||||
# not all of them are implemented
|
||||
@@ -376,16 +376,7 @@ LOGGING = {
|
||||
},
|
||||
}
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "mediacms",
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": "5432",
|
||||
"USER": "mediacms",
|
||||
"PASSWORD": "mediacms",
|
||||
}
|
||||
}
|
||||
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
|
||||
|
||||
|
||||
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
|
||||
@@ -466,6 +457,7 @@ LANGUAGES = [
|
||||
('pt', _('Portuguese')),
|
||||
('ru', _('Russian')),
|
||||
('zh-hans', _('Simplified Chinese')),
|
||||
('sl', _('Slovenian')),
|
||||
('zh-hant', _('Traditional Chinese')),
|
||||
('es', _('Spanish')),
|
||||
('tr', _('Turkish')),
|
||||
@@ -505,6 +497,10 @@ USE_ROUNDED_CORNERS = True
|
||||
ALLOW_VIDEO_TRIMMER = True
|
||||
|
||||
ALLOW_CUSTOM_MEDIA_URLS = False
|
||||
|
||||
# ffmpeg options
|
||||
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||
|
||||
try:
|
||||
# keep a local_settings.py file for local overrides
|
||||
from .local_settings import * # noqa
|
||||
@@ -544,13 +540,5 @@ except ImportError:
|
||||
|
||||
|
||||
if GLOBAL_LOGIN_REQUIRED:
|
||||
# this should go after the AuthenticationMiddleware middleware
|
||||
MIDDLEWARE.insert(6, "login_required.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]+/',
|
||||
]
|
||||
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
|
||||
MIDDLEWARE.insert(auth_index + 1, "django.contrib.auth.middleware.LoginRequiredMiddleware")
|
||||
|
||||
@@ -1 +1 @@
|
||||
VERSION = "6.2.0"
|
||||
VERSION = "6.3.0"
|
||||
|
||||
@@ -13,6 +13,7 @@ DATABASES = {
|
||||
"PORT": os.getenv('POSTGRES_PORT', '5432'),
|
||||
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
|
||||
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
|
||||
"OPTIONS": {'pool': True},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
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
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -81,6 +81,6 @@ services:
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli","ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
50
docs/transcoding.md
Normal file
50
docs/transcoding.md
Normal 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)
|
||||
@@ -1,5 +1,7 @@
|
||||
from django.conf import settings
|
||||
|
||||
from cms.version import VERSION
|
||||
|
||||
from .frontend_translations import get_translation, get_translation_strings
|
||||
from .methods import is_mediacms_editor, is_mediacms_manager
|
||||
|
||||
@@ -37,6 +39,7 @@ def stuff(request):
|
||||
ret["USE_SAML"] = settings.USE_SAML
|
||||
ret["USE_RBAC"] = settings.USE_RBAC
|
||||
ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS
|
||||
ret["VERSION"] = VERSION
|
||||
|
||||
if request.user.is_superuser:
|
||||
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
||||
|
||||
@@ -83,7 +83,7 @@ class IndexRSSFeed(Feed):
|
||||
return item.edit_date
|
||||
|
||||
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):
|
||||
item = {
|
||||
@@ -151,7 +151,7 @@ class SearchRSSFeed(Feed):
|
||||
return item.edit_date
|
||||
|
||||
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):
|
||||
item = {
|
||||
|
||||
@@ -35,7 +35,7 @@ class MediaMetadataForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"new_tags": MultipleSelect(),
|
||||
"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}),
|
||||
}
|
||||
labels = {
|
||||
|
||||
104
files/frontend_translations/sl.py
Normal file
104
files/frontend_translations/sl.py
Normal 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",
|
||||
}
|
||||
@@ -34,12 +34,6 @@ BUF_SIZE_MULTIPLIER = 1.5
|
||||
KEYFRAME_DISTANCE = 4
|
||||
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 = 2
|
||||
|
||||
@@ -55,6 +49,7 @@ VIDEO_CRFS = {
|
||||
VIDEO_BITRATES = {
|
||||
"h264": {
|
||||
25: {
|
||||
144: 150,
|
||||
240: 300,
|
||||
360: 500,
|
||||
480: 1000,
|
||||
@@ -67,6 +62,7 @@ VIDEO_BITRATES = {
|
||||
},
|
||||
"h265": {
|
||||
25: {
|
||||
144: 75,
|
||||
240: 150,
|
||||
360: 275,
|
||||
480: 500,
|
||||
@@ -79,6 +75,7 @@ VIDEO_BITRATES = {
|
||||
},
|
||||
"vp9": {
|
||||
25: {
|
||||
144: 75,
|
||||
240: 150,
|
||||
360: 275,
|
||||
480: 500,
|
||||
@@ -173,7 +170,7 @@ def rm_dir(directory):
|
||||
|
||||
def url_from_path(filename):
|
||||
# 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):
|
||||
@@ -488,7 +485,7 @@ def show_file_size(size):
|
||||
if size:
|
||||
size = size / 1000000
|
||||
size = round(size, 1)
|
||||
size = "{0}MB".format(str(size))
|
||||
size = f"{str(size)}MB"
|
||||
return size
|
||||
|
||||
|
||||
@@ -596,17 +593,13 @@ def get_base_ffmpeg_command(
|
||||
cmd = base_cmd[:]
|
||||
|
||||
# preset settings
|
||||
preset = getattr(settings, "FFMPEG_DEFAULT_PRESET", "medium")
|
||||
|
||||
if encoder == "libvpx-vp9":
|
||||
if pass_number == 1:
|
||||
speed = 4
|
||||
else:
|
||||
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":
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# if codec == "h264_baseline":
|
||||
|
||||
@@ -166,14 +166,14 @@ Media becomes private if it gets reported %s times\n
|
||||
)
|
||||
|
||||
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["title"] = title
|
||||
d["msg"] = msg
|
||||
d["to"] = settings.ADMIN_EMAIL_LIST
|
||||
notify_items.append(d)
|
||||
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["title"] = title
|
||||
d["msg"] = msg
|
||||
@@ -182,7 +182,7 @@ Media becomes private if it gets reported %s times\n
|
||||
|
||||
if action == "media_added" and media:
|
||||
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 = """
|
||||
Media %s was added by user %s.
|
||||
""" % (
|
||||
@@ -195,7 +195,7 @@ Media %s was added by user %s.
|
||||
d["to"] = settings.ADMIN_EMAIL_LIST
|
||||
notify_items.append(d)
|
||||
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 = """
|
||||
Your media has been added! It will be encoded and will be available soon.
|
||||
URL: %s
|
||||
@@ -339,7 +339,7 @@ def notify_user_on_comment(friendly_token):
|
||||
media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
|
||||
|
||||
if user.notification_on_comments:
|
||||
title = "[{}] - A comment was added".format(settings.PORTAL_NAME)
|
||||
title = f"[{settings.PORTAL_NAME}] - A comment was added"
|
||||
msg = """
|
||||
A comment has been added to your media %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()
|
||||
|
||||
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 = """
|
||||
You were mentioned in a comment on %s .
|
||||
View it on %s
|
||||
|
||||
17
files/migrations/0010_alter_encodeprofile_resolution.py
Normal file
17
files/migrations/0010_alter_encodeprofile_resolution.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@@ -13,7 +13,8 @@ from django.contrib.postgres.indexes import GinIndex
|
||||
from django.contrib.postgres.search import SearchVectorField
|
||||
from django.core.exceptions import ValidationError
|
||||
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.dispatch import receiver
|
||||
from django.urls import reverse
|
||||
@@ -72,6 +73,7 @@ ENCODE_RESOLUTIONS = (
|
||||
(480, "480"),
|
||||
(360, "360"),
|
||||
(240, "240"),
|
||||
(144, "144"),
|
||||
)
|
||||
|
||||
CODECS = (
|
||||
@@ -90,34 +92,34 @@ def generate_uid():
|
||||
|
||||
def original_media_file_path(instance, filename):
|
||||
"""Helper function to place original media file"""
|
||||
file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename))
|
||||
return settings.MEDIA_UPLOAD_DIR + "user/{0}/{1}".format(instance.user.username, file_name)
|
||||
file_name = f"{instance.uid.hex}.{helpers.get_file_name(filename)}"
|
||||
return settings.MEDIA_UPLOAD_DIR + f"user/{instance.user.username}/{file_name}"
|
||||
|
||||
|
||||
def encoding_media_file_path(instance, filename):
|
||||
"""Helper function to place encoded media file"""
|
||||
|
||||
file_name = "{0}.{1}".format(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)
|
||||
file_name = f"{instance.media.uid.hex}.{helpers.get_file_name(filename)}"
|
||||
return settings.MEDIA_ENCODING_DIR + f"{instance.profile.id}/{instance.media.user.username}/{file_name}"
|
||||
|
||||
|
||||
def original_thumbnail_file_path(instance, filename):
|
||||
"""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):
|
||||
"""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):
|
||||
"""Helper function to place category thumbnail file"""
|
||||
|
||||
file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename))
|
||||
return settings.MEDIA_UPLOAD_DIR + "categories/{0}".format(file_name)
|
||||
file_name = f"{instance.uid}.{helpers.get_file_name(filename)}"
|
||||
return settings.MEDIA_UPLOAD_DIR + f"categories/{file_name}"
|
||||
|
||||
|
||||
class Media(models.Model):
|
||||
@@ -388,8 +390,6 @@ class Media(models.Model):
|
||||
search field is used to store SearchVector
|
||||
"""
|
||||
|
||||
db_table = self._meta.db_table
|
||||
|
||||
# first get anything interesting out of the media
|
||||
# that needs to be search able
|
||||
|
||||
@@ -413,19 +413,8 @@ class Media(models.Model):
|
||||
|
||||
text = helpers.clean_query(text)
|
||||
|
||||
sql_code = """
|
||||
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
|
||||
)
|
||||
Media.objects.filter(id=self.id).update(search=Func(Value('simple'), Value(text), function='to_tsvector'))
|
||||
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql_code)
|
||||
except BaseException:
|
||||
pass # TODO:add log
|
||||
return True
|
||||
|
||||
def media_init(self):
|
||||
@@ -908,7 +897,7 @@ class Media(models.Model):
|
||||
"""
|
||||
|
||||
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 os.path.exists(self.hls_file):
|
||||
hls_file = self.hls_file
|
||||
@@ -925,7 +914,7 @@ class Media(models.Model):
|
||||
if resolution not in valid_resolutions:
|
||||
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:
|
||||
uri = os.path.join(p, playlist.uri)
|
||||
if os.path.exists(uri):
|
||||
@@ -934,7 +923,8 @@ class Media(models.Model):
|
||||
if resolution not in valid_resolutions:
|
||||
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
|
||||
|
||||
@property
|
||||
@@ -953,11 +943,11 @@ class Media(models.Model):
|
||||
|
||||
def get_absolute_url(self, api=False, edit=False):
|
||||
if edit:
|
||||
return reverse("edit_media") + "?m={0}".format(self.friendly_token)
|
||||
return f"{reverse('edit_media')}?m={self.friendly_token}"
|
||||
if api:
|
||||
return reverse("api_get_media", kwargs={"friendly_token": self.friendly_token})
|
||||
else:
|
||||
return reverse("get_media") + "?m={0}".format(self.friendly_token)
|
||||
return f"{reverse('get_media')}?m={self.friendly_token}"
|
||||
|
||||
@property
|
||||
def edit_url(self):
|
||||
@@ -965,7 +955,7 @@ class Media(models.Model):
|
||||
|
||||
@property
|
||||
def add_subtitle_url(self):
|
||||
return "/add_subtitle?m=%s" % self.friendly_token
|
||||
return f"/add_subtitle?m={self.friendly_token}"
|
||||
|
||||
@property
|
||||
def ratings_info(self):
|
||||
@@ -1060,7 +1050,7 @@ class Category(models.Model):
|
||||
verbose_name_plural = "Categories"
|
||||
|
||||
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):
|
||||
"""Set media_count"""
|
||||
@@ -1122,7 +1112,7 @@ class Tag(models.Model):
|
||||
ordering = ["title"]
|
||||
|
||||
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):
|
||||
self.media_count = Media.objects.filter(state="public", is_reviewed=True, tags=self).count()
|
||||
@@ -1261,7 +1251,7 @@ class Encoding(models.Model):
|
||||
return False
|
||||
|
||||
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):
|
||||
return reverse("api_get_encoding", kwargs={"encoding_id": self.id})
|
||||
@@ -1280,7 +1270,7 @@ class Language(models.Model):
|
||||
ordering = ["id"]
|
||||
|
||||
def __str__(self):
|
||||
return "{0}-{1}".format(self.code, self.title)
|
||||
return f"{self.code}-{self.title}"
|
||||
|
||||
|
||||
class Subtitle(models.Model):
|
||||
@@ -1303,7 +1293,7 @@ class Subtitle(models.Model):
|
||||
ordering = ["language__title"]
|
||||
|
||||
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):
|
||||
return f"{reverse('edit_subtitle')}?id={self.id}"
|
||||
@@ -1347,7 +1337,7 @@ class RatingCategory(models.Model):
|
||||
verbose_name_plural = "Rating Categories"
|
||||
|
||||
def __str__(self):
|
||||
return "{0}".format(self.title)
|
||||
return f"{self.title}"
|
||||
|
||||
|
||||
def validate_rating(value):
|
||||
@@ -1376,7 +1366,7 @@ class Rating(models.Model):
|
||||
unique_together = ("user", "media", "rating_category")
|
||||
|
||||
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):
|
||||
@@ -1488,7 +1478,7 @@ class Comment(MPTTModel):
|
||||
order_insertion_by = ["add_date"]
|
||||
|
||||
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):
|
||||
strip_text_items = ["text"]
|
||||
@@ -1501,7 +1491,7 @@ class Comment(MPTTModel):
|
||||
super(Comment, self).save(*args, **kwargs)
|
||||
|
||||
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
|
||||
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:
|
||||
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:
|
||||
for f in chunks_paths:
|
||||
ff.write("file {}\n".format(f))
|
||||
ff.write(f"file {f}\n")
|
||||
cmd = [
|
||||
settings.FFMPEG_COMMAND,
|
||||
"-y",
|
||||
@@ -1750,7 +1740,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
progress=100,
|
||||
)
|
||||
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]))
|
||||
encoding.worker = json.dumps({"workers": workers})
|
||||
|
||||
@@ -1761,10 +1751,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
|
||||
with open(tf, "rb") as f:
|
||||
myfile = File(f)
|
||||
output_name = "{0}.{1}".format(
|
||||
helpers.get_file_name(instance.media.media_file.path),
|
||||
instance.profile.extension,
|
||||
)
|
||||
output_name = f"{helpers.get_file_name(instance.media.media_file.path)}.{instance.profile.extension}"
|
||||
encoding.media_file.save(content=myfile, name=output_name)
|
||||
|
||||
# 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]
|
||||
|
||||
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]))
|
||||
encoding.worker = json.dumps({"workers": workers})
|
||||
start_date = min([st.add_date for st in chunks])
|
||||
|
||||
@@ -136,8 +136,8 @@ def chunkize_media(self, friendly_token, profiles, force=True):
|
||||
cwd = os.path.dirname(os.path.realpath(media.media_file.path))
|
||||
file_name = media.media_file.path.split("/")[-1]
|
||||
random_prefix = produce_friendly_token()
|
||||
file_format = "{0}_{1}".format(random_prefix, file_name)
|
||||
chunks_file_name = "%02d_{0}".format(file_format)
|
||||
file_format = f"{random_prefix}_{file_name}"
|
||||
chunks_file_name = f"%02d_{file_format}"
|
||||
chunks_file_name += ".mkv"
|
||||
cmd = [
|
||||
settings.FFMPEG_COMMAND,
|
||||
@@ -162,7 +162,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
|
||||
chunks.append(ch[0])
|
||||
if not chunks:
|
||||
# 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:
|
||||
if media.video_height and media.video_height < profile.resolution:
|
||||
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,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -355,8 +355,8 @@ def encode_media(
|
||||
# return False
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
|
||||
tf = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir)
|
||||
tfpass = 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=f".{profile.extension}", dir=temp_dir)
|
||||
ffmpeg_commands = produce_ffmpeg_commands(
|
||||
original_media_path,
|
||||
media.media_info,
|
||||
@@ -398,7 +398,7 @@ def encode_media(
|
||||
if n_times % 60 == 0:
|
||||
encoding.progress = percent
|
||||
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
|
||||
except DatabaseError:
|
||||
# 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:
|
||||
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.total_run_time = (encoding.update_date - encoding.add_date).seconds
|
||||
|
||||
@@ -472,7 +472,7 @@ def produce_sprite_from_video(friendly_token):
|
||||
try:
|
||||
media = Media.objects.get(friendly_token=friendly_token)
|
||||
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
|
||||
|
||||
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
|
||||
@@ -516,7 +516,7 @@ def create_hls(friendly_token):
|
||||
try:
|
||||
media = Media.objects.get(friendly_token=friendly_token)
|
||||
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
|
||||
|
||||
p = media.uid.hex
|
||||
@@ -558,7 +558,7 @@ def check_running_states():
|
||||
|
||||
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
|
||||
for encoding in encodings:
|
||||
now = datetime.now(encoding.update_date.tzinfo)
|
||||
@@ -575,7 +575,7 @@ def check_running_states():
|
||||
# TODO: allign with new code + chunksize...
|
||||
changed += 1
|
||||
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
|
||||
|
||||
|
||||
@@ -585,7 +585,7 @@ def check_media_states():
|
||||
# check encoding status of not success media
|
||||
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
|
||||
for m in media:
|
||||
@@ -593,7 +593,7 @@ def check_media_states():
|
||||
m.save(update_fields=["encoding_status"])
|
||||
changed += 1
|
||||
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
|
||||
|
||||
|
||||
@@ -628,7 +628,7 @@ def check_pending_states():
|
||||
media.encode(profiles=[profile], force=False)
|
||||
changed += 1
|
||||
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
|
||||
|
||||
|
||||
@@ -652,7 +652,7 @@ def check_missing_profiles():
|
||||
# if they appear on the meanwhile (eg on a big queue)
|
||||
changed += 1
|
||||
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
|
||||
|
||||
|
||||
@@ -828,7 +828,7 @@ def update_listings_thumbnails():
|
||||
object.save(update_fields=["listings_thumbnail"])
|
||||
used_media.append(media.friendly_token)
|
||||
saved += 1
|
||||
logger.info("updated {} categories".format(saved))
|
||||
logger.info(f"updated {saved} categories")
|
||||
|
||||
# Tags
|
||||
used_media = []
|
||||
@@ -841,7 +841,7 @@ def update_listings_thumbnails():
|
||||
object.save(update_fields=["listings_thumbnail"])
|
||||
used_media.append(media.friendly_token)
|
||||
saved += 1
|
||||
logger.info("updated {} tags".format(saved))
|
||||
logger.info(f"updated {saved} tags")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ def contact(request):
|
||||
name = request.POST.get("name")
|
||||
message = request.POST.get("message")
|
||||
|
||||
title = "[{}] - Contact form message received".format(settings.PORTAL_NAME)
|
||||
title = f"[{settings.PORTAL_NAME}] - Contact form message received"
|
||||
|
||||
msg = """
|
||||
You have received a message through the contact form\n
|
||||
|
||||
@@ -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}}]
|
||||
|
||||
@@ -2,7 +2,7 @@ Django==5.1.6
|
||||
djangorestframework==3.15.2
|
||||
python3-saml==1.16.0
|
||||
django-allauth==65.4.1
|
||||
psycopg==3.2.4
|
||||
psycopg[pool]==3.2.4
|
||||
uwsgi==2.0.28
|
||||
django-redis==5.4.0
|
||||
celery==5.4.0
|
||||
@@ -18,7 +18,6 @@ requests==2.32.3
|
||||
django-celery-email==3.0.0
|
||||
m3u8==6.0.0
|
||||
django-debug-toolbar==5.0.1
|
||||
django-login-required-middleware==0.9.0
|
||||
pre-commit==4.1.0
|
||||
django-jazzmin==3.0.1
|
||||
pysubs2==1.8.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{% load static %}
|
||||
|
||||
<script src="{% static "js/_commons.js" %}"></script>
|
||||
<script src="{% static "js/_commons.js" %}?v={{ VERSION }}"></script>
|
||||
@@ -22,10 +22,10 @@
|
||||
<link href="{% static "lib/gfonts/gfonts.css" %}" rel="stylesheet">
|
||||
{% endif %}
|
||||
|
||||
<link href="{% static "css/_commons.css" %}" rel="preload" as="style">
|
||||
<link href="{% static "css/_commons.css" %}" rel="stylesheet">
|
||||
<link href="{% static "css/_commons.css" %}?v={{ VERSION }}" rel="preload" as="style">
|
||||
<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" %}" rel="stylesheet">
|
||||
<link href="{% static "css/_extra.css" %}?v={{ VERSION }}" rel="preload" as="style">
|
||||
<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">
|
||||
|
||||
@@ -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(user=self.user).count(), 3, "User assignment failed")
|
||||
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.
|
||||
# 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")
|
||||
|
||||
@@ -24,12 +24,12 @@ class TestFixtures(TestCase):
|
||||
profiles = EncodeProfile.objects.all()
|
||||
self.assertEqual(
|
||||
profiles.count(),
|
||||
22,
|
||||
23,
|
||||
"Problem with Encode Profile fixtures",
|
||||
)
|
||||
profiles = EncodeProfile.objects.filter(active=True)
|
||||
self.assertEqual(
|
||||
profiles.count(),
|
||||
6,
|
||||
7,
|
||||
"Problem with Encode Profile fixtures, not as active as expected",
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ def import_class(path):
|
||||
path_bits = path.split(".")
|
||||
|
||||
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)
|
||||
|
||||
class_name = path_bits.pop()
|
||||
@@ -15,7 +15,7 @@ def import_class(path):
|
||||
module_itself = import_module(module_path)
|
||||
|
||||
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)
|
||||
|
||||
return getattr(module_itself, class_name)
|
||||
|
||||
@@ -105,7 +105,7 @@ class User(AbstractUser):
|
||||
ret = {}
|
||||
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
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -210,7 +210,7 @@ class Channel(models.Model):
|
||||
super(Channel, self).save(*args, **kwargs)
|
||||
|
||||
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):
|
||||
if edit:
|
||||
@@ -230,7 +230,7 @@ def post_user_create(sender, instance, created, **kwargs):
|
||||
new = Channel.objects.create(title="default", user=instance)
|
||||
new.save()
|
||||
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 = """
|
||||
User has just registered with email %s\n
|
||||
Visit user profile page at %s
|
||||
|
||||
Reference in New Issue
Block a user