Merge branch 'main' into feat-celery-run

pull/860/head
Markos Gogoulos 2023-11-10 15:45:14 +02:00
commit 4a7505ac3a
11 zmienionych plików z 148 dodań i 16 usunięć

Wyświetl plik

@ -6,7 +6,7 @@
MediaCMS 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. It can be used to build a small to medium video and media portal within minutes.
MediaCMS 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. It can be used to build a small to medium video and media portal within minutes.
It is built mostly using the modern stack Django + React and includes a REST API.
@ -56,15 +56,15 @@ A demo is available at https://demo.mediacms.io
## Philosophy
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
## License
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
Copyright Markos Gogoulos and Yiannis Stergiou
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
Copyright Markos Gogoulos.
## Support and paid services
@ -75,7 +75,7 @@ We provide custom installations, development of extra functionality, migration f
## Hardware dependencies
For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram.
For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram.
In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365).
@ -127,7 +127,7 @@ If you like the project, here's a few things you can do
- Share on social media about the project
- Open issues, participate on discussions, report bugs, suggest ideas
- Star the project
- Add functionality, work on a PR, fix an issue!
- Add functionality, work on a PR, fix an issue!
## Contact

Wyświetl plik

@ -59,7 +59,7 @@ def login():
file.writelines(f'USERNAME={json.loads(response.text)["username"]}\n')
print(f"Welcome to MediaCMS [bold blue]{username}[/bold blue]. Your auth creds have been suceesfully stored in the .env file", ":v:")
else:
print(f'Error: {"non_field_errors":["User not found."]}')
print(f'Error: {"non_field_errors": ["User not found."]}')
@apis.command()
@ -73,7 +73,7 @@ def upload_media():
if os.path.isdir(path):
for filename in os.listdir(path):
files = {}
abs = os.path.abspath("{path}/{filename}")
abs = os.path.abspath(f"{path}/{filename}")
files['media_file'] = open(f'{abs}', 'rb')
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
if response.status_code == 201:

Wyświetl plik

@ -93,6 +93,9 @@ ALLOW_MENTION_IN_COMMENTS = False # allowing to mention other users with @ in t
# valid options: content, author
RELATED_MEDIA_STRATEGY = "content"
# Whether or not to generate a sitemap.xml listing the pages on the site (default: False)
GENERATE_SITEMAP = False
USE_I18N = True
USE_L10N = True
USE_TZ = True
@ -467,7 +470,7 @@ except ImportError:
if "http" not in FRONTEND_HOST:
# FRONTEND_HOST needs a http:// preffix
FRONTEND_HOST = f"http://{FRONTEND_HOST}"
FRONTEND_HOST = f"http://{FRONTEND_HOST}" # noqa
if LOCAL_INSTALL:
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
@ -486,4 +489,7 @@ if GLOBAL_LOGIN_REQUIRED:
r'/api/v[0-9]+/',
]
# if True, only show original, don't perform any action on videos
DO_NOT_TRANSCODE_VIDEO = False
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

Wyświetl plik

@ -18,7 +18,7 @@
- [15. Debugging email issues](#15-debugging-email-issues)
- [16. Frequently Asked Questions](#16-frequently-asked-questions)
- [17. Cookie consent code](#17-cookie-consent-code)
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
## 1. Welcome
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
@ -470,6 +470,14 @@ ADMINS_NOTIFICATIONS = {
- Make the portal workflow public, but at the same time set `GLOBAL_LOGIN_REQUIRED = True` so that only logged in users can see content.
- You can either set `REGISTER_ALLOWED = False` if you want to add members yourself or checkout options on "django-allauth settings" that affects registration in `cms/settings.py`. Eg set the portal invite only, or set email confirmation as mandatory, so that you control who registers.
### 5.24 Enable the sitemap
Whether or not to enable generation of a sitemap file at http://your_installation/sitemap.xml (default: False)
```
GENERATE_SITEMAP = False
```
## 6. Manage pages
to be written
@ -762,3 +770,12 @@ this will re-create the sprites for videos that the task failed.
On file `templates/components/header.html` you can find a simple cookie consent code. It is commented, so you have to remove the `{% comment %}` and `{% endcomment %}` lines in order to enable it. Or you can replace that part with your own code that handles cookie consent banners.
![Simple Cookie Consent](images/cookie_consent.png)
## 18. Disable encoding and show only original file
When videos are uploaded, they are getting encoded to multiple resolutions, a procedure called transcoding. Sometimes this is not needed and you only need to show the original file, eg when MediaCMS is running on a low capabilities server. To achieve this, edit settings.py and set
```
DO_NOT_TRANSCODE_VIDEO = True
```
This will disable the transcoding process and only the original file will be shown. Note that this will also disable the sprites file creation, so you will not have the preview thumbnails on the video player.

Wyświetl plik

@ -40,6 +40,12 @@ class MediaAdmin(admin.ModelAdmin):
def get_comments_count(self, obj):
return obj.comments.count()
@admin.action(description="Generate missing encoding(s)", permissions=["change"])
def generate_missing_encodings(modeladmin, request, queryset):
for m in queryset:
m.encode(force=False)
actions = [generate_missing_encodings]
get_comments_count.short_description = "Comments count"
@ -74,7 +80,18 @@ class SubtitleAdmin(admin.ModelAdmin):
class EncodingAdmin(admin.ModelAdmin):
pass
list_display = ["get_title", "chunk", "profile", "progress", "status", "has_file"]
list_filter = ["chunk", "profile", "status"]
def get_title(self, obj):
return str(obj)
get_title.short_description = "Encoding"
def has_file(self, obj):
return obj.media_encoding_url is not None
has_file.short_description = "Has file"
admin.site.register(EncodeProfile, EncodeProfileAdmin)

Wyświetl plik

@ -538,8 +538,8 @@ def get_base_ffmpeg_command(
target_width = round(target_height * 16 / 9)
scale_filter_opts = [
f"if(lt(iw\\,ih)\\,{target_height}\\,{target_width})",
f"if(lt(iw\\,ih)\\,{target_width}\\,{target_height})",
f"if(lt(iw\\,ih)\\,{target_height}\\,{target_width})", # noqa
f"if(lt(iw\\,ih)\\,{target_width}\\,{target_height})", # noqa
"force_original_aspect_ratio=decrease",
"force_divisible_by=2",
"flags=lanczos",

Wyświetl plik

@ -430,8 +430,13 @@ class Media(models.Model):
self.set_media_type()
if self.media_type == "video":
self.set_thumbnail(force=True)
self.produce_sprite_from_video()
self.encode()
if settings.DO_NOT_TRANSCODE_VIDEO:
self.encoding_status = "success"
self.save()
self.produce_sprite_from_video()
else:
self.produce_sprite_from_video()
self.encode()
elif self.media_type == "image":
self.set_thumbnail(force=True)
return True
@ -667,6 +672,13 @@ class Media(models.Model):
return ret
for key in ENCODE_RESOLUTIONS_KEYS:
ret[key] = {}
# if this is enabled, return original file on a way
# that video.js can consume
if settings.DO_NOT_TRANSCODE_VIDEO:
ret['0-original'] = {"h264": {"url": helpers.url_from_path(self.media_file.path), "status": "success", "progress": 100}}
return ret
for encoding in self.encodings.select_related("profile").filter(chunk=False):
if encoding.profile.extension == "gif":
continue

Wyświetl plik

@ -89,3 +89,6 @@ urlpatterns = [
re_path(r"^manage/media$", views.manage_media, name="manage_media"),
re_path(r"^manage/users$", views.manage_users, name="manage_users"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if hasattr(settings, "GENERATE_SITEMAP") and settings.GENERATE_SITEMAP:
urlpatterns.append(path("sitemap.xml", views.sitemap, name="sitemap"))

Wyświetl plik

@ -292,6 +292,16 @@ def search(request):
return render(request, "cms/search.html", context)
def sitemap(request):
"""Sitemap"""
context = {}
context["media"] = list(Media.objects.filter(Q(listable=True)).order_by("-add_date"))
context["playlists"] = list(Playlist.objects.filter().order_by("-add_date"))
context["users"] = list(User.objects.filter())
return render(request, "sitemap.xml", context, content_type="application/xml")
def tags(request):
"""List tags view"""

Wyświetl plik

@ -1,2 +1,3 @@
User-Agent: *
Allow: /
Sitemap: {{ FRONTEND_HOST }}/sitemap.xml

Wyświetl plik

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% load static %}
<url>
<loc>{{ FRONTEND_HOST }}</loc>
<changefreq>always</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/featured</loc>
<changefreq>daily</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/recommended</loc>
<changefreq>always</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/latest</loc>
<changefreq>hourly</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/members</loc>
<changefreq>daily</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/tags</loc>
<changefreq>daily</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/categories</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/history</loc>
<changefreq>always</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/liked</loc>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/about</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/tos</loc>
<changefreq>monthly</changefreq>
</url>
<url>
<loc>{{ FRONTEND_HOST }}/contact</loc>
<changefreq>never</changefreq>
</url>
{% for media_object in media %}
<url>
<loc>{{ FRONTEND_HOST}}/view?m={{ media_object.friendly_token }}</loc>
</url>
{% endfor %}
{% for playlist_object in playlists %}
<url>
<loc>{{ FRONTEND_HOST}}/playlists/{{ playlist_object.friendly_token }}</loc>
</url>
{% endfor %}
{% for user_object in users %}
<url>
<loc>{{ FRONTEND_HOST}}/user/{{ user_object.username }}/</loc>
</url>
{% endfor %}
</urlset>