kopia lustrzana https://github.com/mediacms-io/mediacms
Merge branch 'main' into feat-celery-run
commit
4a7505ac3a
14
README.md
14
README.md
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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"""
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
User-Agent: *
|
||||
Allow: /
|
||||
Sitemap: {{ FRONTEND_HOST }}/sitemap.xml
|
||||
|
|
|
@ -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>
|
Ładowanie…
Reference in New Issue