mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-21 05:56:03 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa8a2d92dc | ||
|
|
6bbd4c2809 | ||
|
|
c4148bd504 | ||
|
|
ea8b2af26f | ||
|
|
5aa899cef0 |
38
.github/workflows/semantic-release.yaml
vendored
Normal file
38
.github/workflows/semantic-release.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
name: Semantic Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
semantic-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: dev
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "lts/*"
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm clean-install
|
||||||
|
|
||||||
|
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
|
||||||
|
run: npm audit signatures
|
||||||
|
|
||||||
|
- name: Run Semantic Release
|
||||||
|
run: npx semantic-release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -43,7 +43,6 @@ A demo is available at https://demo.mediacms.io
|
|||||||
- **REST API**: Documented through Swagger
|
- **REST API**: Documented through Swagger
|
||||||
- **Translation**: Most of the CMS is translated to a number of languages
|
- **Translation**: Most of the CMS is translated to a number of languages
|
||||||
|
|
||||||
|
|
||||||
## Example cases
|
## Example cases
|
||||||
|
|
||||||
- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
|
- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
|
||||||
@@ -68,7 +67,12 @@ Copyright Markos Gogoulos.
|
|||||||
|
|
||||||
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
|
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
|
||||||
|
|
||||||
|
### Commercial Hostings
|
||||||
|
**Elestio**
|
||||||
|
|
||||||
|
You can deploy MediaCMS on Elestio using one-click deployment. Elestio supports MediaCMS by providing revenue share so go ahead and click below to deploy and use MediaCMS.
|
||||||
|
|
||||||
|
[](https://elest.io/open-source/mediacms)
|
||||||
|
|
||||||
## Hardware considerations
|
## Hardware considerations
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ DEBUG = False
|
|||||||
# is also shown on several places as emails
|
# is also shown on several places as emails
|
||||||
PORTAL_NAME = "MediaCMS"
|
PORTAL_NAME = "MediaCMS"
|
||||||
PORTAL_DESCRIPTION = ""
|
PORTAL_DESCRIPTION = ""
|
||||||
LANGUAGE_CODE = "en-us"
|
|
||||||
TIME_ZONE = "Europe/London"
|
TIME_ZONE = "Europe/London"
|
||||||
|
|
||||||
# who can add media
|
# who can add media
|
||||||
@@ -531,3 +530,8 @@ LANGUAGES = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en' # default language
|
LANGUAGE_CODE = 'en' # default language
|
||||||
|
|
||||||
|
SPRITE_NUM_SECS = 10
|
||||||
|
# number of seconds for sprite image.
|
||||||
|
# If you plan to change this, you must also follow the instructions on admin_docs.md
|
||||||
|
# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
|
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
|
||||||
- [19. Rounded corners on videos](#19-rounded-corners)
|
- [19. Rounded corners on videos](#19-rounded-corners)
|
||||||
- [20. Translations](#20-translations)
|
- [20. Translations](#20-translations)
|
||||||
|
- [21. How to change the video frames on videos](#21-fames)
|
||||||
|
|
||||||
## 1. Welcome
|
## 1. Welcome
|
||||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||||
@@ -833,10 +834,22 @@ After the string is marked as translatable, add the string to `files/frontend-tr
|
|||||||
python manage.py process_translations
|
python manage.py process_translations
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to populate the string in all the languages. NO PR will be accepted if this procedure is not followed. You don't have to translate the string to all supported languages, but the command has to run and populate the existing dictionaries with the new strings for all languages. This ensures that there is no missing string to be translated in any language.
|
In order to populate the string in all the languages. NO PR will be accepted if this procedure is not followed. You don't have to translate the string to all supported languages, but the command has to run and populate the existing dictionaries with the new strings for all languages. This ensures that there is no missing string to be translated in any language.
|
||||||
|
|
||||||
After this command is run, translate the string to the language you want. If the string to be translated lives in Django templates, you don't have to re-build the frontend. If the change lives in the frontend, you will have to re-build in order to see the changes. The Makefile command `make build-frontend` can help with this.
|
After this command is run, translate the string to the language you want. If the string to be translated lives in Django templates, you don't have to re-build the frontend. If the change lives in the frontend, you will have to re-build in order to see the changes. The Makefile command `make build-frontend` can help with this.
|
||||||
|
|
||||||
|
|
||||||
### 20.5 Add a new language and translate
|
### 20.5 Add a new language and translate
|
||||||
To add a new language: add the language in settings.py, then add the file in `files/frontend-translations/`. Make sure you copy the initial strings by copying `files/frontend-translations/en.py` to it.
|
To add a new language: add the language in settings.py, then add the file in `files/frontend-translations/`. Make sure you copy the initial strings by copying `files/frontend-translations/en.py` to it.
|
||||||
|
|
||||||
|
|
||||||
|
## 21. How to change the video frames on videos
|
||||||
|
|
||||||
|
By default while watching a video you can hover and see the small images named sprites that are extracted every 10 seconds of a video. You can change this number to something smaller by performing the following:
|
||||||
|
|
||||||
|
* edit ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and change `seconds: 10 ` to the value you prefer, eg 2.
|
||||||
|
* edit settings.py and set the same number for value SPRITE_NUM_SECS
|
||||||
|
* now you have to re-build the frontend: the easiest way is to run `make build-frontend`, which requires Docker
|
||||||
|
|
||||||
|
After that, newly uploaded videos will have sprites generated with the new number of seconds.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "約",
|
"ABOUT": "約",
|
||||||
"AUTOPLAY": "自動再生",
|
"AUTOPLAY": "自動再生",
|
||||||
"About": "",
|
"About": "約",
|
||||||
"Add a ": "追加",
|
"Add a ": "追加",
|
||||||
"COMMENT": "コメント",
|
"COMMENT": "コメント",
|
||||||
"Categories": "カテゴリー",
|
"Categories": "カテゴリー",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "정보",
|
"ABOUT": "정보",
|
||||||
"AUTOPLAY": "자동 재생",
|
"AUTOPLAY": "자동 재생",
|
||||||
"About": "",
|
"About": "정보",
|
||||||
"Add a ": "추가",
|
"Add a ": "추가",
|
||||||
"COMMENT": "댓글",
|
"COMMENT": "댓글",
|
||||||
"Categories": "카테고리",
|
"Categories": "카테고리",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "OVER",
|
"ABOUT": "OVER",
|
||||||
"AUTOPLAY": "AUTOMATISCH AFSPELEN",
|
"AUTOPLAY": "AUTOMATISCH AFSPELEN",
|
||||||
"About": "",
|
"About": "Over",
|
||||||
"Add a ": "Voeg een ",
|
"Add a ": "Voeg een ",
|
||||||
"COMMENT": "REACTIE",
|
"COMMENT": "REACTIE",
|
||||||
"Categories": "Categorieën",
|
"Categories": "Categorieën",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "SOBRE",
|
"ABOUT": "SOBRE",
|
||||||
"AUTOPLAY": "REPRODUÇÃO AUTOMÁTICA",
|
"AUTOPLAY": "REPRODUÇÃO AUTOMÁTICA",
|
||||||
"About": "",
|
"About": "Sobre",
|
||||||
"Add a ": "Adicionar um ",
|
"Add a ": "Adicionar um ",
|
||||||
"COMMENT": "COMENTÁRIO",
|
"COMMENT": "COMENTÁRIO",
|
||||||
"Categories": "Categorias",
|
"Categories": "Categorias",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "О",
|
"ABOUT": "О",
|
||||||
"AUTOPLAY": "Автовоспроизведение",
|
"AUTOPLAY": "Автовоспроизведение",
|
||||||
"About": "",
|
"About": "О",
|
||||||
"Add a ": "Добавить ",
|
"Add a ": "Добавить ",
|
||||||
"COMMENT": "КОММЕНТАРИЙ",
|
"COMMENT": "КОММЕНТАРИЙ",
|
||||||
"Categories": "Категории",
|
"Categories": "Категории",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "HAKKINDA",
|
"ABOUT": "HAKKINDA",
|
||||||
"AUTOPLAY": "OTOMATİK OYNATMA",
|
"AUTOPLAY": "OTOMATİK OYNATMA",
|
||||||
"About": "",
|
"About": "Hakkında",
|
||||||
"Add a ": "Ekle ",
|
"Add a ": "Ekle ",
|
||||||
"COMMENT": "YORUM",
|
"COMMENT": "YORUM",
|
||||||
"Categories": "Kategoriler",
|
"Categories": "Kategoriler",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "کے بارے میں",
|
"ABOUT": "کے بارے میں",
|
||||||
"AUTOPLAY": "خودکار پلے",
|
"AUTOPLAY": "خودکار پلے",
|
||||||
"About": "",
|
"About": "کے بارے میں",
|
||||||
"Add a ": "شامل کریں",
|
"Add a ": "شامل کریں",
|
||||||
"COMMENT": "تبصرہ",
|
"COMMENT": "تبصرہ",
|
||||||
"Categories": "اقسام",
|
"Categories": "اقسام",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
translation_strings = {
|
translation_strings = {
|
||||||
"ABOUT": "关于",
|
"ABOUT": "关于",
|
||||||
"AUTOPLAY": "自动播放",
|
"AUTOPLAY": "自动播放",
|
||||||
"About": "",
|
"About": "关于",
|
||||||
"Add a ": "添加一个",
|
"Add a ": "添加一个",
|
||||||
"COMMENT": "评论",
|
"COMMENT": "评论",
|
||||||
"Categories": "分类",
|
"Categories": "分类",
|
||||||
|
|||||||
@@ -374,14 +374,28 @@ def produce_sprite_from_video(friendly_token):
|
|||||||
try:
|
try:
|
||||||
tmpdir_image_files = tmpdirname + "/img%03d.jpg"
|
tmpdir_image_files = tmpdirname + "/img%03d.jpg"
|
||||||
output_name = tmpdirname + "/sprites.jpg"
|
output_name = tmpdirname + "/sprites.jpg"
|
||||||
cmd = "{0} -i {1} -f image2 -vf 'fps=1/10, scale=160:90' {2}&&files=$(ls {3}/img*.jpg | sort -t '-' -n -k 2 | tr '\n' ' ')&&convert $files -append {4}".format(
|
|
||||||
|
fps = getattr(settings, 'SPRITE_NUM_SECS', 10)
|
||||||
|
ffmpeg_cmd = [
|
||||||
settings.FFMPEG_COMMAND,
|
settings.FFMPEG_COMMAND,
|
||||||
media.media_file.path,
|
"-i", media.media_file.path,
|
||||||
tmpdir_image_files,
|
"-f", "image2",
|
||||||
tmpdirname,
|
"-vf", f"fps=1/{fps}, scale=160:90",
|
||||||
output_name,
|
tmpdir_image_files
|
||||||
)
|
]
|
||||||
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
run_command(ffmpeg_cmd)
|
||||||
|
image_files = [f for f in os.listdir(tmpdirname) if f.startswith("img") and f.endswith(".jpg")]
|
||||||
|
image_files = sorted(image_files, key=lambda x: int(re.search(r'\d+', x).group()))
|
||||||
|
image_files = [os.path.join(tmpdirname, f) for f in image_files]
|
||||||
|
cmd_convert = [
|
||||||
|
"convert",
|
||||||
|
*image_files, # image files, unpacked into the list
|
||||||
|
"-append",
|
||||||
|
output_name
|
||||||
|
]
|
||||||
|
|
||||||
|
run_command(cmd_convert)
|
||||||
|
|
||||||
if os.path.exists(output_name) and get_file_type(output_name) == "image":
|
if os.path.exists(output_name) and get_file_type(output_name) == "image":
|
||||||
with open(output_name, "rb") as f:
|
with open(output_name, "rb") as f:
|
||||||
myfile = File(f)
|
myfile = File(f)
|
||||||
@@ -421,12 +435,19 @@ def create_hls(friendly_token):
|
|||||||
existing_output_dir = output_dir
|
existing_output_dir = output_dir
|
||||||
output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token())
|
output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token())
|
||||||
files = " ".join([f.media_file.path for f in encodings if f.media_file])
|
files = " ".join([f.media_file.path for f in encodings if f.media_file])
|
||||||
cmd = "{0} --segment-duration=4 --output-dir={1} {2}".format(settings.MP4HLS_COMMAND, output_dir, files)
|
cmd = [
|
||||||
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
settings.MP4HLS_COMMAND,
|
||||||
|
'--segment-duration=4',
|
||||||
|
f'--output-dir={output_dir}',
|
||||||
|
files
|
||||||
|
]
|
||||||
|
run_command(cmd)
|
||||||
|
|
||||||
if existing_output_dir:
|
if existing_output_dir:
|
||||||
# override content with -T !
|
# override content with -T !
|
||||||
cmd = "cp -rT {0} {1}".format(output_dir, existing_output_dir)
|
cmd = "cp -rT {0} {1}".format(output_dir, existing_output_dir)
|
||||||
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
|
run_command(cmd)
|
||||||
|
|
||||||
shutil.rmtree(output_dir)
|
shutil.rmtree(output_dir)
|
||||||
output_dir = existing_output_dir
|
output_dir = existing_output_dir
|
||||||
pp = os.path.join(output_dir, "master.m3u8")
|
pp = os.path.join(output_dir, "master.m3u8")
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ const CommentsListHeader = ({ commentsLength }) => {
|
|||||||
? commentsLength + ' ' + commentsText.ucfirstPlural
|
? commentsLength + ' ' + commentsText.ucfirstPlural
|
||||||
: commentsLength + ' ' + commentsText.ucfirstSingle
|
: commentsLength + ' ' + commentsText.ucfirstSingle
|
||||||
: MediaPageStore.get('media-data').enable_comments
|
: MediaPageStore.get('media-data').enable_comments
|
||||||
? translateString('No') + commentsText.single + translateString('yet')
|
? translateString('No') + ' ' + commentsText.single + ' ' + translateString('yet')
|
||||||
: ''}
|
: ''}
|
||||||
</h2>
|
</h2>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -10,6 +10,11 @@ from django.conf import settings
|
|||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def strip_delimiters(input_string):
|
||||||
|
delimiters = " \t\n\r'\"[]{}()<>\\|&;:*-=+"
|
||||||
|
return ''.join(char for char in input_string if char not in delimiters)
|
||||||
|
|
||||||
|
|
||||||
def is_valid_uuid_format(uuid_string):
|
def is_valid_uuid_format(uuid_string):
|
||||||
pattern = re.compile(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', re.IGNORECASE)
|
pattern = re.compile(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', re.IGNORECASE)
|
||||||
return bool(pattern.match(uuid_string))
|
return bool(pattern.match(uuid_string))
|
||||||
@@ -28,6 +33,7 @@ class BaseFineUploader(object):
|
|||||||
self.uuid = uuid.uuid4()
|
self.uuid = uuid.uuid4()
|
||||||
|
|
||||||
self.filename = os.path.basename(self.filename)
|
self.filename = os.path.basename(self.filename)
|
||||||
|
self.filename = strip_delimiters(self.filename)
|
||||||
# avoid possibility of passing a fake path here
|
# avoid possibility of passing a fake path here
|
||||||
|
|
||||||
self.file = data.get("qqfile")
|
self.file = data.get("qqfile")
|
||||||
|
|||||||
Reference in New Issue
Block a user