Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b9a203123 | ||
|
|
5cbd815496 | ||
|
|
3a8cacc847 | ||
|
|
5402ee7bc5 | ||
|
|
a6a2b50c8d | ||
|
|
23e48a8bb7 | ||
|
|
313cd9cbc6 | ||
|
|
0392dbe1ed | ||
|
|
a7562c244e | ||
|
|
d2ee12087c | ||
|
|
6db01932e1 | ||
|
|
53d8215346 | ||
|
|
1b960b28f8 | ||
|
|
02d9188aa1 | ||
|
|
8d9a4618f0 | ||
|
|
cf93a77802 | ||
|
|
5a1e4f25ed | ||
|
|
9fc7597e73 | ||
|
|
9b3e0250d4 | ||
|
|
1384471745 | ||
|
|
29b362c8ce | ||
|
|
b8ee2e9fb8 | ||
|
|
99be0f07dd | ||
|
|
27d1660192 | ||
|
|
98adb22205 | ||
|
|
673ddeb5bd | ||
|
|
aa8a2d92dc | ||
|
|
6bbd4c2809 | ||
|
|
c4148bd504 | ||
|
|
ea8b2af26f | ||
|
|
5aa899cef0 | ||
|
|
4992cc425c | ||
|
|
ef4067cbdd | ||
|
|
8cc3513a8a | ||
|
|
90e593946d | ||
|
|
f7136e2a11 | ||
|
|
0151e834a1 | ||
|
|
5fe4d3a9fc | ||
|
|
94c646fdb8 | ||
|
|
d665058b80 | ||
|
|
986c7d1074 | ||
|
|
1adee8c156 | ||
|
|
ffd7a52863 | ||
|
|
c5047d8df8 | ||
|
|
dcbfaca91c | ||
|
|
918df010f5 | ||
|
|
e9739bab45 | ||
|
|
e7ce9ef5c0 | ||
|
|
4829adf110 | ||
|
|
fdff0811a1 | ||
|
|
92c0ff579a | ||
|
|
847cff2b5c | ||
|
|
e8d3ff25be | ||
|
|
15d217453b | ||
|
|
029665145e | ||
|
|
487e098b96 | ||
|
|
fe7427a1f2 | ||
|
|
4bf41fe80e | ||
|
|
1fd04ca947 | ||
|
|
a1962d4b32 | ||
|
|
6e9c9ed81f | ||
|
|
51186e3253 | ||
|
|
150967b342 | ||
|
|
bb6244d862 | ||
|
|
a002422b77 | ||
|
|
24167b9624 | ||
|
|
b9db1a5e2e | ||
|
|
296aeac567 | ||
|
|
10c386f886 | ||
|
|
367faaddd1 | ||
|
|
3d59b87f09 | ||
|
|
5dee41de39 | ||
|
|
08bba5fc05 | ||
|
|
102414b514 | ||
|
|
c866fdd6ba | ||
|
|
5b601698a4 | ||
|
|
f040f73f51 | ||
|
|
b7a70d92fa | ||
|
|
2f43cef8da | ||
|
|
ad633e6fdf | ||
|
|
cd8d0ea49a | ||
|
|
a3997bfb1c | ||
|
|
4b0718c43f | ||
|
|
91d8179fa0 | ||
|
|
6532b19849 | ||
|
|
6ea8fd12a3 | ||
|
|
d971bb955f | ||
|
|
b52b008f89 | ||
|
|
30cf5d7176 | ||
|
|
6fd9a7d37f | ||
|
|
9c6d13559b | ||
|
|
8ec97a8219 | ||
|
|
de8f9ca718 | ||
|
|
a4bedca4db | ||
|
|
da565b3bfc | ||
|
|
239ff6cb60 | ||
|
|
da840b156d | ||
|
|
b08d493823 | ||
|
|
25eaa35758 | ||
|
|
cba2ed75ed | ||
|
|
de5bd07a90 | ||
|
|
34c1a4e33e | ||
|
|
80be0d06e5 | ||
|
|
a6322fae6d | ||
|
|
3d4dd27220 | ||
|
|
f6a78dd0b4 | ||
|
|
cbc9633fe2 | ||
|
|
3e7b106482 | ||
|
|
0f182c2b15 | ||
|
|
1ddfae7c95 | ||
|
|
f1969e4637 | ||
|
|
9e7a7a7482 | ||
|
|
6e478e6e82 | ||
|
|
e06deed3b8 | ||
|
|
f8376c5c58 | ||
|
|
e7ae2833d9 | ||
|
|
fb0f3ee739 | ||
|
|
c0701de047 | ||
|
|
0d4918a715 | ||
|
|
8093c4ccb5 | ||
|
|
2dbd97cb22 | ||
|
|
6b6662420f | ||
|
|
f1a1e342db | ||
|
|
738247c32b | ||
|
|
f974d78270 | ||
|
|
28031f07e5 | ||
|
|
4480fa7de1 | ||
|
|
32e07035f3 | ||
|
|
2ce8dba163 | ||
|
|
48838ac406 | ||
|
|
062e4be6c2 | ||
|
|
fb00f94bfa | ||
|
|
755df50c41 | ||
|
|
ba94989e6a | ||
|
|
86cc0442d8 | ||
|
|
2dde4257f7 | ||
|
|
3afff52ebf | ||
|
|
c27e3caff6 | ||
|
|
16e2c32d17 | ||
|
|
f4f6fa5962 | ||
|
|
b9e35c66a3 | ||
|
|
360a647eb5 | ||
|
|
7237040777 | ||
|
|
aa6520daac | ||
|
|
060bb45725 | ||
|
|
1f0cc4ff87 | ||
|
|
c28a39fa47 | ||
|
|
d17b3b4153 | ||
|
|
950adcdd9d | ||
|
|
235efbe151 | ||
|
|
8145ba0914 | ||
|
|
f74d3c4b57 | ||
|
|
9b9a718a18 | ||
|
|
26804dce40 | ||
|
|
de30fe68f1 | ||
|
|
4f72c00598 | ||
|
|
d3a3934ce7 | ||
|
|
ddbaa51285 | ||
|
|
7fae5992e7 | ||
|
|
46384a3c49 | ||
|
|
cdcf148b72 | ||
|
|
09e565e99b | ||
|
|
7bda0acd8b | ||
|
|
748d4bae4b | ||
|
|
171b9f84d7 | ||
|
|
853b28130d |
4
.coveragerc
Normal file
@@ -0,0 +1,4 @@
|
||||
[run]
|
||||
omit =
|
||||
*bento4*
|
||||
*/migrations/*
|
||||
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
20
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: "CI"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**/README.md'
|
||||
jobs:
|
||||
pre-commit:
|
||||
uses: ./.github/workflows/pre-commit.yml
|
||||
test:
|
||||
uses: ./.github/workflows/python.yml
|
||||
needs: [pre-commit]
|
||||
release:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
secrets: inherit # pass all secrets
|
||||
needs: [test]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
52
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Docker build and push
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build & release to DockerHub
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
# List of Docker images to use as base name for tags
|
||||
images: |
|
||||
mediacms/mediacms
|
||||
# Generate Docker tags based on the following events/attributes
|
||||
# Set latest tag for default branch
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=MediaCMS
|
||||
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API.
|
||||
org.opencontainers.image.vendor=MediaCMS
|
||||
org.opencontainers.image.url=https://mediacms.io/
|
||||
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
|
||||
org.opencontainers.image.licenses=AGPL-3.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
15
.github/workflows/lint_test.yml
vendored
@@ -1,15 +0,0 @@
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: pre-commit/action@v2.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
15
.github/workflows/pre-commit.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: pre-commit
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Pre-Commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
35
.github/workflows/python.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Python Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build & test via docker-compose
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Build the Stack
|
||||
run: docker compose -f docker-compose-dev.yaml build
|
||||
|
||||
- name: Start containers
|
||||
run: docker compose -f docker-compose-dev.yaml up -d
|
||||
|
||||
- name: List containers
|
||||
run: docker ps
|
||||
|
||||
- name: Sleep for 60 seconds
|
||||
run: sleep 60s
|
||||
shell: bash
|
||||
|
||||
- name: Run Django Tests
|
||||
run: docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
||||
|
||||
# Run with coverage, saves report on htmlcov dir
|
||||
# run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest --cov --cov-report=html --cov-config=.coveragerc
|
||||
|
||||
- name: Tear down the Stack
|
||||
run: docker compose -f docker-compose-dev.yaml down
|
||||
5
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
cli-tool/.env
|
||||
frontend/package-lock.json
|
||||
media_files/encoded/
|
||||
media_files/original/
|
||||
media_files/hls/
|
||||
@@ -12,5 +14,6 @@ static/ckeditor/
|
||||
static/debug_toolbar/
|
||||
static/mptt/
|
||||
static/rest_framework/
|
||||
static/drf-yasg
|
||||
cms/local_settings.py
|
||||
deploy/docker/local_settings.py
|
||||
deploy/docker/local_settings.py
|
||||
@@ -1,15 +1,16 @@
|
||||
repos:
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.9
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.5.4
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 20.8b1
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
language_version: python3
|
||||
additional_dependencies: [ 'click==8.0.4' ]
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
Wordgames.gr - https://www.wordgames.gr
|
||||
Yiannis Stergiou - ys.stergiou@gmail.com
|
||||
Markos Gogoulos - mgogoulos@gmail.com
|
||||
|
||||
Contributors
|
||||
|
||||
Swift Ugandan - swiftugandan@gmail.com
|
||||
|
||||
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!
|
||||
130
Dockerfile
@@ -1,66 +1,88 @@
|
||||
FROM python:3.8-buster AS compile-image
|
||||
FROM python:3.13-bookworm AS build-image
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
# Set up virtualenv
|
||||
ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs,pids} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
|
||||
RUN wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
|
||||
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d ../bento4 && \
|
||||
mv ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* ../bento4/ && \
|
||||
rm -rf ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
|
||||
rm -rf ../bento4/docs && \
|
||||
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||
|
||||
############ RUNTIME IMAGE ############
|
||||
FROM python:3.8-slim-buster as runtime-image
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV ADMIN_USER='admin'
|
||||
ENV ADMIN_PASSWORD='mediacms'
|
||||
ENV ADMIN_EMAIL='admin@localhost'
|
||||
|
||||
# See: https://github.com/celery/celery/issues/6285#issuecomment-715316219
|
||||
ENV CELERY_APP='cms'
|
||||
|
||||
# Use these to toggle which processes supervisord should run
|
||||
ENV ENABLE_UWSGI='yes'
|
||||
ENV ENABLE_NGINX='yes'
|
||||
ENV ENABLE_CELERY_BEAT='yes'
|
||||
ENV ENABLE_CELERY_SHORT='yes'
|
||||
ENV ENABLE_CELERY_LONG='yes'
|
||||
ENV ENABLE_MIGRATIONS='yes'
|
||||
|
||||
# Set up virtualenv
|
||||
ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
COPY --chown=www-data:www-data --from=compile-image /home/mediacms.io /home/mediacms.io
|
||||
|
||||
RUN apt-get update -y && apt-get -y upgrade && apt-get install --no-install-recommends \
|
||||
supervisor nginx ffmpeg imagemagick procps -y && \
|
||||
# Install system dependencies needed for downloading and extracting
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y --no-install-recommends wget xz-utils unzip && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get purge --auto-remove && \
|
||||
apt-get clean
|
||||
|
||||
# Install ffmpeg
|
||||
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
||||
mkdir -p ffmpeg-tmp && \
|
||||
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
|
||||
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
|
||||
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
|
||||
|
||||
# Install Bento4 in the specified location
|
||||
RUN mkdir -p /home/mediacms.io/bento4 && \
|
||||
wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
|
||||
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d /home/mediacms.io/bento4 && \
|
||||
mv /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* /home/mediacms.io/bento4/ && \
|
||||
rm -rf /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
|
||||
rm -rf /home/mediacms.io/bento4/docs && \
|
||||
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||
|
||||
############ RUNTIME IMAGE ############
|
||||
FROM python:3.13-bookworm AS runtime_image
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV CELERY_APP='cms'
|
||||
ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
# Install runtime system dependencies
|
||||
RUN apt-get update -y && \
|
||||
apt-get -y upgrade && \
|
||||
apt-get install --no-install-recommends supervisor nginx imagemagick procps -y && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get purge --auto-remove && \
|
||||
apt-get clean
|
||||
|
||||
# Copy ffmpeg and Bento4 from build image
|
||||
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
|
||||
COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
|
||||
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
|
||||
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
|
||||
|
||||
# Set up virtualenv
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
|
||||
cd /home/mediacms.io && \
|
||||
python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt requirements-dev.txt ./
|
||||
|
||||
ARG DEVELOPMENT_MODE=False
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt && \
|
||||
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
|
||||
echo "Installing development dependencies..." && \
|
||||
pip install --no-cache-dir -r requirements-dev.txt; \
|
||||
fi
|
||||
|
||||
# Copy application files
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
|
||||
# required for sprite thumbnail generation for large video files
|
||||
|
||||
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
|
||||
|
||||
# Set process control environment variables
|
||||
ENV ENABLE_UWSGI='yes' \
|
||||
ENABLE_NGINX='yes' \
|
||||
ENABLE_CELERY_BEAT='yes' \
|
||||
ENABLE_CELERY_SHORT='yes' \
|
||||
ENABLE_CELERY_LONG='yes' \
|
||||
ENABLE_MIGRATIONS='yes'
|
||||
|
||||
EXPOSE 9000 80
|
||||
|
||||
RUN chmod +x ./deploy/docker/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
|
||||
|
||||
CMD ["./deploy/docker/start.sh"]
|
||||
CMD ["./deploy/docker/start.sh"]
|
||||
23
HISTORY.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# History
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Features
|
||||
- Updates Python/Django requirements and Dockerfile to use latest 3.11 Python - https://github.com/mediacms-io/mediacms/pull/826/files. This update requires some manual steps, for existing (not new) installations. Check the update section under the [Admin docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#2-server-installation), either for single server or for Docker Compose installations
|
||||
- Upgrade postgres on Docker Compose - https://github.com/mediacms-io/mediacms/pull/749
|
||||
|
||||
### Fixes
|
||||
- video player options for HLS - https://github.com/mediacms-io/mediacms/pull/832
|
||||
- AVI videos not correctly recognised as videos - https://github.com/mediacms-io/mediacms/pull/833
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Fixes
|
||||
- Increase uwsgi buffer-size parameter. This prevents an error by uwsgi with large headers - [#5b60](https://github.com/mediacms-io/mediacms/commit/5b601698a41ad97f08c1830e14b1c18f73ab8315)
|
||||
- Fix issues with comments. These were not reported on the tracker but it is certain that they would not show comments on media files (non videos but also videos). Unfortunately this reverts work done with Timestamps on comments + Mentions on comments, more on PR [#802](https://github.com/mediacms-io/mediacms/pull/802)
|
||||
|
||||
### Features
|
||||
- Allow tags to contains other characters too, not only English alphabet ones [#801](https://github.com/mediacms-io/mediacms/pull/801)
|
||||
- Add simple cookie consent code [#799](https://github.com/mediacms-io/mediacms/pull/799)
|
||||
- Allow password reset & email verify pages on global login required [#790](https://github.com/mediacms-io/mediacms/pull/790)
|
||||
- Add api_url field to search api [#692](https://github.com/mediacms-io/mediacms/pull/692)
|
||||
19
Makefile
Normal file
@@ -0,0 +1,19 @@
|
||||
.PHONY: admin-shell build-frontend
|
||||
|
||||
admin-shell:
|
||||
@container_id=$$(docker-compose ps -q web); \
|
||||
if [ -z "$$container_id" ]; then \
|
||||
echo "Web container not found"; \
|
||||
exit 1; \
|
||||
else \
|
||||
docker exec -it $$container_id /bin/bash; \
|
||||
fi
|
||||
|
||||
build-frontend:
|
||||
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
|
||||
cp -r frontend/dist/static/* static/
|
||||
docker compose -f docker-compose-dev.yaml restart web
|
||||
|
||||
test:
|
||||
docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
||||
|
||||
133
README.md
@@ -1,6 +1,12 @@
|
||||

|
||||
# MediaCMS
|
||||
|
||||
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.
|
||||
[](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
|
||||
[](https://github.com/mediacms-io/mediacms/releases/)
|
||||
[](https://hub.docker.com/r/mediacms/mediacms)
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -34,135 +40,82 @@ A demo is available at https://demo.mediacms.io
|
||||
- **Subtitles/CC**: support for multilingual subtitle files
|
||||
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
|
||||
- **Chunked file uploads**: for pausable/resumable upload of content
|
||||
|
||||
- **REST API**: Documented through Swagger
|
||||
- **Translation**: Most of the CMS is translated to a number of languages
|
||||
|
||||
## 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.
|
||||
|
||||
- **Organization sensitive content.** In cases where content is sensitive and cannot be uploaded to external sites.
|
||||
|
||||
- **Build a great community.** MediaCMS can be customized (URLs, logos, fonts, aesthetics) so that you create a highly customized video portal for your community!
|
||||
|
||||
- **Personal portal.** Organize, categorize and host your content the way you prefer.
|
||||
|
||||
|
||||
## 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 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 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.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
## Hardware dependencies
|
||||
[](https://elest.io/open-source/mediacms)
|
||||
|
||||
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.
|
||||
## Hardware considerations
|
||||
|
||||
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).
|
||||
|
||||
|
||||
## Installation
|
||||
## Installation / Maintanance
|
||||
|
||||
There are two ways to run MediaCMS, through Docker Compose and through installing it on a server via an automation script that installs and configures all needed services.
|
||||
There are two ways to run MediaCMS, through Docker Compose and through installing it on a server via an automation script that installs and configures all needed services. Find the related pages:
|
||||
|
||||
### Docker Compose installation
|
||||
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
- [Single Server](docs/admins_docs.md#2-server-installation) page
|
||||
- [Docker Compose](docs/admins_docs.md#3-docker-installation) page
|
||||
|
||||
Run as root
|
||||
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).
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd mediacms
|
||||
```
|
||||
## Configuration
|
||||
|
||||
The default option is to serve MediaCMS on all ips available of the server (including localhost).
|
||||
|
||||
Now run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
|
||||
|
||||
For more instructions, checkout the docs on the [Docker deployment](docs/Docker_deployment.md) page. Docker Compose support has been contributed by @swiftugandan.
|
||||
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
|
||||
|
||||
|
||||
### Single server installation
|
||||
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
|
||||
|
||||
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
|
||||
Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
|
||||
|
||||
```bash
|
||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd /home/mediacms.io/mediacms/ && bash ./install.sh
|
||||
```
|
||||
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
## Information for developers
|
||||
Check out the new section on the [Developer Experience](docs/dev_exp.md) page
|
||||
|
||||
|
||||
## Update
|
||||
## Documentation
|
||||
|
||||
If you've used the above way to install MediaCMS, update with the following:
|
||||
|
||||
```bash
|
||||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||
source /home/mediacms.io/bin/activate # use virtualenv
|
||||
git pull # update code
|
||||
python manage.py migrate # run Django migrations
|
||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
||||
```
|
||||
|
||||
|
||||
## Configure
|
||||
|
||||
Several options are available on cms/settings.py, most of the things that are allowed or should be disallowed are described there. It is advisable to override any of them by adding it to cms/local_settings.py. All configuration options will be documented gradually on the [Configuration](docs/Configuration.md) page.
|
||||
|
||||
## Authors
|
||||
MediaCMS is developed by Yiannis Stergiou and Markos Gogoulos. We are Wordgames - https://wordgames.gr.
|
||||
* [Users documentation](docs/user_docs.md) page
|
||||
* [Administrators documentation](docs/admins_docs.md) page
|
||||
* [Developers documentation](docs/developers_docs.md) page
|
||||
|
||||
|
||||
## Technology
|
||||
This software uses the following list of awesome technologies:
|
||||
- Python
|
||||
- Django
|
||||
- Django Rest Framework
|
||||
- Celery
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Nginx
|
||||
- uWSGI
|
||||
- React
|
||||
- Fine Uploader
|
||||
- video.js
|
||||
- FFMPEG
|
||||
- Bento4
|
||||
|
||||
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, uWSGI, React, Fine Uploader, video.js, FFMPEG, Bento4
|
||||
|
||||
|
||||
## Who is using it
|
||||
|
||||
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
||||
|
||||
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org
|
||||
|
||||
- **Heritales** International Heritage Film Festival - https://stage.heritales.org
|
||||
- **American Association of Gynecologic Laparoscopists** - https://surgeryu.org/
|
||||
|
||||
|
||||
## How to contribute
|
||||
@@ -172,16 +125,12 @@ If you like the project, here's a few things you can do
|
||||
- Suggest us to others that are interested to hire us
|
||||
- Write a blog post/article about MediaCMS
|
||||
- Share on social media about the project
|
||||
- Open issues, participate on discussions, report bugs, suggest ideas
|
||||
- Open issues, participate on [discussions](https://github.com/mediacms-io/mediacms/discussions), report bugs, suggest ideas
|
||||
- [Show and tell](https://github.com/mediacms-io/mediacms/discussions/categories/show-and-tell) how you are using the project
|
||||
- Star the project
|
||||
- Add functionality, work on a PR, fix an issue!
|
||||
- Add functionality, work on a PR, fix an issue!
|
||||
|
||||
## Developers info
|
||||
|
||||
- API documentation available under /swagger URL (example https://demo.mediacms.io/swagger/)
|
||||
- We're working on proper documentation for users, managers and developers, until then checkout what's available on the docs/ folder of this repository
|
||||
- Before you send a PR, make sure your code is properly formatted. For that, use `pre-commit install` to install a pre-commit hook and run `pre-commit run --all` and fix everything before you commit. This pre-commit will check for your code lint everytime you commit a code.
|
||||
- Checkout the [Code of conduct page](CODE_OF_CONDUCT.md) if you want to contribute to this repository
|
||||
|
||||
## Contact
|
||||
|
||||
info@mediacms.io
|
||||
|
||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
||||
10
cli-tool/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## MediaCMS CLI Tool
|
||||
This is the CLI tool to interact with the API of your installation/instance of MediaCMS.
|
||||
|
||||
### How to configure and use the tools
|
||||
- Make sure that you have all the required installations (`cli-tool/requirements.txt`)installed. To install it -
|
||||
- Create a new virtualenv using any python virtualenv manager.
|
||||
- Then activate the virtualenv and enter `pip install -r requirements.txt`.
|
||||
- Create an .env file in this folder (`mediacms/cli-tool/`)
|
||||
- Run the cli tool using the command `python cli.py login`. This will authenticate you and store necessary creds for further authentications.
|
||||
- To check the credentials and necessary setup, run `python cli.py whoami`. This will show your details.
|
||||
167
cli-tool/cli.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import click
|
||||
import requests
|
||||
from decouple import config
|
||||
from rich import print
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
console = Console()
|
||||
|
||||
print("Welcome to the CLI Tool of [bold blue]MediaCMS![/bold blue]", ":thumbs_up:")
|
||||
|
||||
|
||||
BASE_URL = 'https://demo.mediacms.io/api/v1'
|
||||
AUTH_KEY = ''
|
||||
USERNAME = ''
|
||||
EMAIL = ''
|
||||
|
||||
|
||||
def set_envs():
|
||||
with open('.env', 'r') as file:
|
||||
if not file.read(1):
|
||||
print("Use the Login command to set your credential environment variables")
|
||||
else:
|
||||
global AUTH_KEY, USERNAME, EMAIL
|
||||
AUTH_KEY = config('AUTH_KEY')
|
||||
USERNAME = config('USERNAME')
|
||||
EMAIL = config('EMAIL')
|
||||
|
||||
|
||||
set_envs()
|
||||
|
||||
|
||||
@click.group()
|
||||
def apis():
|
||||
"""A CLI wrapper for the MediaCMS API endpoints."""
|
||||
|
||||
|
||||
@apis.command()
|
||||
def login():
|
||||
"""Login to your account."""
|
||||
|
||||
email = input('Enter your email address: ')
|
||||
password = input('Enter your password: ')
|
||||
|
||||
data = {
|
||||
"email": f"{email}",
|
||||
"password": f"{password}",
|
||||
}
|
||||
|
||||
response = requests.post(url=f'{BASE_URL}/login', data=data)
|
||||
if response.status_code == 200:
|
||||
username = json.loads(response.text)["username"]
|
||||
with open(".env", "w") as file:
|
||||
file.writelines(f'AUTH_KEY={json.loads(response.text)["token"]}\n')
|
||||
file.writelines(f'EMAIL={json.loads(response.text)["email"]}\n')
|
||||
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."]}')
|
||||
|
||||
|
||||
@apis.command()
|
||||
def upload_media():
|
||||
"""Upload media to the server"""
|
||||
|
||||
headers = {'authorization': f'Token {AUTH_KEY}'}
|
||||
|
||||
path = input('Enter the location of the file or directory where multiple files are present: ')
|
||||
|
||||
if os.path.isdir(path):
|
||||
for filename in os.listdir(path):
|
||||
files = {}
|
||||
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:
|
||||
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
|
||||
else:
|
||||
print(f'Error: {response.text}')
|
||||
|
||||
else:
|
||||
files = {}
|
||||
files['media_file'] = open(f'{os.path.abspath(path)}', 'rb')
|
||||
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
||||
if response.status_code == 201:
|
||||
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
|
||||
else:
|
||||
print(f'Error: {response.text}')
|
||||
|
||||
|
||||
@apis.command()
|
||||
def my_media():
|
||||
"""List all my media"""
|
||||
|
||||
headers = {'authorization': f'Token {AUTH_KEY}'}
|
||||
response = requests.get(url=f'{BASE_URL}/media?author={USERNAME}', headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data_json = json.loads(response.text)
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Name of the media")
|
||||
table.add_column("Media Type")
|
||||
table.add_column("State")
|
||||
|
||||
for data in data_json['results']:
|
||||
table.add_row(data['title'], data['media_type'], data['state'])
|
||||
console.print(table)
|
||||
|
||||
else:
|
||||
print(f'Could not get the media: {response.text}')
|
||||
|
||||
|
||||
@apis.command()
|
||||
def whoami():
|
||||
"""Shows the details of the authorized user"""
|
||||
headers = {'authorization': f'Token {AUTH_KEY}'}
|
||||
response = requests.get(url=f'{BASE_URL}/whoami', headers=headers)
|
||||
for data, value in json.loads(response.text).items():
|
||||
print(data, ' : ', value)
|
||||
|
||||
|
||||
@apis.command()
|
||||
def categories():
|
||||
"""List all categories."""
|
||||
response = requests.get(url=f'{BASE_URL}/categories')
|
||||
if response.status_code == 200:
|
||||
data_json = json.loads(response.text)
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Category")
|
||||
table.add_column("Description")
|
||||
|
||||
for data in data_json:
|
||||
table.add_row(data['title'], data['description'])
|
||||
|
||||
console.print(table)
|
||||
else:
|
||||
print(f'Could not get the categories: {response.text}')
|
||||
|
||||
|
||||
@apis.command()
|
||||
def encodings():
|
||||
"""List all encoding profiles"""
|
||||
response = requests.get(url=f'{BASE_URL}/encode_profiles/')
|
||||
if response.status_code == 200:
|
||||
data_json = json.loads(response.text)
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Name")
|
||||
table.add_column("Extension")
|
||||
table.add_column("Resolution")
|
||||
table.add_column("Codec")
|
||||
table.add_column("Description")
|
||||
|
||||
for data in data_json:
|
||||
table.add_row(data['name'], data['extension'], str(data['resolution']), data['codec'], data['description'])
|
||||
console.print(table)
|
||||
else:
|
||||
print(f'Could not get the encodings: {response.text}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
apis()
|
||||
4
cli-tool/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
click
|
||||
python-decouple
|
||||
requests
|
||||
rich
|
||||
@@ -3,6 +3,7 @@ from __future__ import absolute_import
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
from django.conf import settings
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings")
|
||||
app = Celery("cms")
|
||||
@@ -14,5 +15,8 @@ app.conf.beat_schedule = app.conf.CELERY_BEAT_SCHEDULE
|
||||
app.conf.broker_transport_options = {"visibility_timeout": 60 * 60 * 24} # 1 day
|
||||
# http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#redis-caveats
|
||||
|
||||
# setting this to settings.py file only is not respected. Setting here too
|
||||
app.conf.task_always_eager = settings.CELERY_TASK_ALWAYS_EAGER
|
||||
|
||||
|
||||
app.conf.worker_prefetch_multiplier = 1
|
||||
|
||||
@@ -18,7 +18,6 @@ class FastPaginationWithoutCount(PageNumberPagination):
|
||||
django_paginator_class = FasterDjangoPaginator
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
||||
return Response(
|
||||
OrderedDict(
|
||||
[
|
||||
|
||||
50
cms/dev_settings.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# Development settings, used in docker-compose-dev.yaml
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'imagekit',
|
||||
'files.apps.FilesConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'actions.apps.ActionsConfig',
|
||||
'debug_toolbar',
|
||||
'mptt',
|
||||
'crispy_forms',
|
||||
"crispy_bootstrap5",
|
||||
'uploader.apps.UploaderConfig',
|
||||
'djcelery_email',
|
||||
'drf_yasg',
|
||||
'corsheaders',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
]
|
||||
|
||||
DEBUG = True
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static_collected')
|
||||
@@ -11,6 +11,13 @@ class IsAuthorizedToAdd(permissions.BasePermission):
|
||||
return user_allowed_to_upload(request)
|
||||
|
||||
|
||||
class IsAuthorizedToAddComment(permissions.BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
if request.method in permissions.SAFE_METHODS:
|
||||
return True
|
||||
return user_allowed_to_comment(request)
|
||||
|
||||
|
||||
class IsUserOrManager(permissions.BasePermission):
|
||||
"""To be used in cases where request.user is either the
|
||||
object owner, or anyone amongst MediaCMS managers
|
||||
@@ -66,3 +73,24 @@ def user_allowed_to_upload(request):
|
||||
if request.user.advancedUser:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def user_allowed_to_comment(request):
|
||||
"""Any custom logic for whether a user is allowed
|
||||
to comment lives here
|
||||
"""
|
||||
if request.user.is_anonymous:
|
||||
return False
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
|
||||
# Default is "all"
|
||||
if not hasattr(settings, "CAN_COMMENT") or settings.CAN_COMMENT == "all":
|
||||
return True
|
||||
elif settings.CAN_COMMENT == "email_verified":
|
||||
if request.user.email_is_verified:
|
||||
return True
|
||||
elif settings.CAN_COMMENT == "advancedUser":
|
||||
if request.user.advancedUser:
|
||||
return True
|
||||
return False
|
||||
|
||||
170
cms/settings.py
@@ -1,19 +1,24 @@
|
||||
import os
|
||||
|
||||
from celery.schedules import crontab
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
DEBUG = False
|
||||
|
||||
# PORTAL NAME, this is the portal title and
|
||||
# is also shown on several places as emails
|
||||
PORTAL_NAME = "MediaCMS"
|
||||
LANGUAGE_CODE = "en-us"
|
||||
PORTAL_DESCRIPTION = ""
|
||||
TIME_ZONE = "Europe/London"
|
||||
|
||||
# who can add media
|
||||
# valid options include 'all', 'email_verified', 'advancedUser'
|
||||
CAN_ADD_MEDIA = "all"
|
||||
|
||||
# who can comment
|
||||
# valid options include 'all', 'email_verified', 'advancedUser'
|
||||
CAN_COMMENT = "all"
|
||||
|
||||
# valid choices here are 'public', 'private', 'unlisted
|
||||
PORTAL_WORKFLOW = "public"
|
||||
|
||||
@@ -86,10 +91,15 @@ MAX_MEDIA_PER_PLAYLIST = 70
|
||||
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
||||
|
||||
MAX_CHARS_FOR_COMMENT = 10000 # so that it doesn't end up huge
|
||||
TIMESTAMP_IN_TIMEBAR = False # shows timestamped comments in the timebar for videos
|
||||
ALLOW_MENTION_IN_COMMENTS = False # allowing to mention other users with @ in the comments
|
||||
|
||||
# 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
|
||||
@@ -101,7 +111,7 @@ TIME_TO_ACTION_ANONYMOUS = 10 * 60
|
||||
|
||||
# django-allauth settings
|
||||
ACCOUNT_SESSION_REMEMBER = True
|
||||
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
|
||||
ACCOUNT_LOGIN_METHODS = {"username", "email"}
|
||||
ACCOUNT_EMAIL_REQUIRED = True # new users need to specify email
|
||||
ACCOUNT_EMAIL_VERIFICATION = "optional" # 'mandatory' 'none'
|
||||
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
||||
@@ -113,13 +123,15 @@ ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
||||
ACCOUNT_USERNAME_REQUIRED = True
|
||||
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
|
||||
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
|
||||
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20
|
||||
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5
|
||||
# registration won't be open, might also consider to remove links for register
|
||||
USERS_CAN_SELF_REGISTER = True
|
||||
|
||||
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
|
||||
|
||||
# Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"]
|
||||
# Empty list disables.
|
||||
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = []
|
||||
|
||||
# django rest settings
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
@@ -146,10 +158,13 @@ STATIC_ROOT = BASE_DIR + "/static/"
|
||||
# where uploaded + encoded media are stored
|
||||
MEDIA_ROOT = BASE_DIR + "/media_files/"
|
||||
|
||||
MEDIA_UPLOAD_DIR = os.path.join(MEDIA_ROOT, "original/")
|
||||
MEDIA_ENCODING_DIR = os.path.join(MEDIA_ROOT, "encoded/")
|
||||
THUMBNAIL_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "thumbnails/")
|
||||
SUBTITLES_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "subtitles/")
|
||||
# these used to be os.path.join(MEDIA_ROOT, "folder/") but update to
|
||||
# Django 3.1.9 requires not absolute paths to be utilized...
|
||||
|
||||
MEDIA_UPLOAD_DIR = "original/"
|
||||
MEDIA_ENCODING_DIR = "encoded/"
|
||||
THUMBNAIL_UPLOAD_DIR = f"{MEDIA_UPLOAD_DIR}/thumbnails/"
|
||||
SUBTITLES_UPLOAD_DIR = f"{MEDIA_UPLOAD_DIR}/subtitles/"
|
||||
HLS_DIR = os.path.join(MEDIA_ROOT, "hls/")
|
||||
|
||||
FFMPEG_COMMAND = "ffmpeg" # this is the path
|
||||
@@ -213,11 +228,11 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = ""
|
||||
|
||||
CANNOT_ADD_MEDIA_MESSAGE = ""
|
||||
|
||||
# mp4hls command, part of Bendo4
|
||||
# mp4hls command, part of Bento4
|
||||
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
|
||||
|
||||
# highly experimental, related with remote workers
|
||||
ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
||||
ADMIN_TOKEN = ""
|
||||
# this is used by remote workers to push
|
||||
# encodings once they are done
|
||||
# USE_BASIC_HTTP = True
|
||||
@@ -232,35 +247,6 @@ ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
||||
# uncomment the two lines related to htpasswd
|
||||
|
||||
|
||||
CKEDITOR_CONFIGS = {
|
||||
"default": {
|
||||
"toolbar": "Custom",
|
||||
"width": "100%",
|
||||
"toolbar_Custom": [
|
||||
["Styles"],
|
||||
["Format"],
|
||||
["Bold", "Italic", "Underline"],
|
||||
["HorizontalRule"],
|
||||
[
|
||||
"NumberedList",
|
||||
"BulletedList",
|
||||
"-",
|
||||
"Outdent",
|
||||
"Indent",
|
||||
"-",
|
||||
"JustifyLeft",
|
||||
"JustifyCenter",
|
||||
"JustifyRight",
|
||||
"JustifyBlock",
|
||||
],
|
||||
["Link", "Unlink"],
|
||||
["Image"],
|
||||
["RemoveFormat", "Source"],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AUTH_USER_MODEL = "users.User"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
||||
@@ -289,21 +275,23 @@ INSTALLED_APPS = [
|
||||
"debug_toolbar",
|
||||
"mptt",
|
||||
"crispy_forms",
|
||||
"crispy_bootstrap5",
|
||||
"uploader.apps.UploaderConfig",
|
||||
"djcelery_email",
|
||||
"ckeditor",
|
||||
"drf_yasg",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
"allauth.account.middleware.AccountMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "cms.urls"
|
||||
@@ -331,11 +319,15 @@ WSGI_APPLICATION = "cms.wsgi.application"
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
"OPTIONS": {
|
||||
"user_attributes": ("username", "email", "first_name", "last_name"),
|
||||
"max_similarity": 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
"OPTIONS": {
|
||||
"min_length": 5,
|
||||
"min_length": 7,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -349,6 +341,16 @@ FILE_UPLOAD_HANDLERS = [
|
||||
|
||||
LOGS_DIR = os.path.join(BASE_DIR, "logs")
|
||||
|
||||
error_filename = os.path.join(LOGS_DIR, "debug.log")
|
||||
if not os.path.exists(LOGS_DIR):
|
||||
try:
|
||||
os.mkdir(LOGS_DIR)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
if not os.path.isfile(error_filename):
|
||||
open(error_filename, 'a').close()
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
@@ -356,7 +358,7 @@ LOGGING = {
|
||||
"file": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.FileHandler",
|
||||
"filename": os.path.join(LOGS_DIR, "debug.log"),
|
||||
"filename": error_filename,
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
@@ -427,9 +429,20 @@ CELERY_BEAT_SCHEDULE = {
|
||||
|
||||
LOCAL_INSTALL = False
|
||||
|
||||
# this is an option to make the whole portal available to logged in users only
|
||||
# it is placed here so it can be overrided on local_settings.py
|
||||
GLOBAL_LOGIN_REQUIRED = False
|
||||
|
||||
# TODO: separate settings on production/development more properly, for now
|
||||
# this should be ok
|
||||
CELERY_TASK_ALWAYS_EAGER = False
|
||||
if os.environ.get("TESTING"):
|
||||
CELERY_TASK_ALWAYS_EAGER = True
|
||||
|
||||
|
||||
try:
|
||||
# keep a local_settings.py file for local overrides
|
||||
from .local_settings import *
|
||||
from .local_settings import * # noqa
|
||||
|
||||
# ALLOWED_HOSTS needs a url/ip
|
||||
ALLOWED_HOSTS.append(FRONTEND_HOST.replace("http://", "").replace("https://", ""))
|
||||
@@ -440,9 +453,78 @@ 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")
|
||||
else:
|
||||
SSL_FRONTEND_HOST = FRONTEND_HOST
|
||||
|
||||
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]+/',
|
||||
]
|
||||
|
||||
# if True, only show original, don't perform any action on videos
|
||||
DO_NOT_TRANSCODE_VIDEO = False
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
# the following is related to local development using docker
|
||||
# and docker-compose-dev.yaml
|
||||
try:
|
||||
DEVELOPMENT_MODE = os.environ.get("DEVELOPMENT_MODE")
|
||||
if DEVELOPMENT_MODE:
|
||||
# keep a dev_settings.py file for local overrides
|
||||
from .dev_settings import * # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
LANGUAGES = [
|
||||
('ar', _('Arabic')),
|
||||
('bn', _('Bengali')),
|
||||
('nl', _('Dutch')),
|
||||
('en', _('English')),
|
||||
('fr', _('French')),
|
||||
('de', _('German')),
|
||||
('hi', _('Hindi')),
|
||||
('id', _('Indonesian')),
|
||||
('ja', _('Japanese')),
|
||||
('ko', _('Korean')),
|
||||
('pt', _('Portuguese')),
|
||||
('ru', _('Russian')),
|
||||
('zh-hans', _('Simplified Chinese')),
|
||||
('es', _('Spanish')),
|
||||
('tr', _('Turkish')),
|
||||
('el', _('Greek')),
|
||||
('ur', _('Urdu')),
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
# how many images will be shown on the slideshow
|
||||
SLIDESHOW_ITEMS = 30
|
||||
# this calculation is redundant most probably, setting as an option
|
||||
CALCULATE_MD5SUM = False
|
||||
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
|
||||
# allow option to override the default admin url
|
||||
# keep the trailing slash
|
||||
DJANGO_ADMIN_URL = "admin/"
|
||||
|
||||
# CSRF_COOKIE_SECURE = True
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
|
||||
21
cms/urls.py
@@ -1,7 +1,9 @@
|
||||
import debug_toolbar
|
||||
from django.conf.urls import include, url
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path
|
||||
from django.views.generic.base import TemplateView
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework.permissions import AllowAny
|
||||
@@ -12,14 +14,19 @@ schema_view = get_schema_view(
|
||||
permission_classes=(AllowAny,),
|
||||
)
|
||||
|
||||
# refactor seriously
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^__debug__/", include(debug_toolbar.urls)),
|
||||
url(r"^", include("files.urls")),
|
||||
url(r"^", include("users.urls")),
|
||||
url(r"^accounts/", include("allauth.urls")),
|
||||
url(r"^api-auth/", include("rest_framework.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
re_path(r"^__debug__/", include(debug_toolbar.urls)),
|
||||
path(
|
||||
"robots.txt",
|
||||
TemplateView.as_view(template_name="robots.txt", content_type="text/plain"),
|
||||
),
|
||||
re_path(r"^", include("files.urls")),
|
||||
re_path(r"^", include("users.urls")),
|
||||
re_path(r"^accounts/", include("allauth.urls")),
|
||||
re_path(r"^api-auth/", include("rest_framework.urls")),
|
||||
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
|
||||
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
|
||||
5
conftest.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pytest_factoryboy import register
|
||||
|
||||
from tests.users.factories import UserFactory
|
||||
|
||||
register(UserFactory)
|
||||
@@ -7,11 +7,12 @@ ln -sf /dev/stdout /var/log/nginx/mediacms.io.access.log && ln -sf /dev/stderr /
|
||||
|
||||
cp /home/mediacms.io/mediacms/deploy/docker/local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
|
||||
|
||||
mkdir -p /home/mediacms.io/mediacms/{logs,pids,media_files/hls}
|
||||
|
||||
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
|
||||
touch /home/mediacms.io/mediacms/logs/debug.log
|
||||
|
||||
# Remove any dangling pids
|
||||
rm -rf /home/mediacms.io/mediacms/pids/*
|
||||
mkdir -p /var/run/mediacms
|
||||
chown www-data:www-data /var/run/mediacms
|
||||
|
||||
TARGET_GID=$(stat -c "%g" /home/mediacms.io/mediacms/)
|
||||
|
||||
@@ -28,7 +29,8 @@ else
|
||||
fi
|
||||
|
||||
# We should do this only for folders that have a different owner, since it is an expensive operation
|
||||
find /home/mediacms.io/ ! \( -user www-data -group $TARGET_GID \) -exec chown www-data:$TARGET_GID {} +
|
||||
# Also ignoring .git folder to fix this issue https://github.com/mediacms-io/mediacms/issues/934
|
||||
find /home/mediacms.io/mediacms ! \( -path "*.git*" \) -exec chown www-data:$TARGET_GID {} +
|
||||
|
||||
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
FRONTEND_HOST = 'http://localhost'
|
||||
PORTAL_NAME = 'MediaCMS'
|
||||
SECRET_KEY = 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2'
|
||||
POSTGRES_HOST = 'db'
|
||||
REDIS_LOCATION = "redis://redis:6379/1"
|
||||
import os
|
||||
|
||||
FRONTEND_HOST = os.getenv('FRONTEND_HOST', 'http://localhost')
|
||||
PORTAL_NAME = os.getenv('PORTAL_NAME', 'MediaCMS')
|
||||
SECRET_KEY = os.getenv('SECRET_KEY', 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2')
|
||||
REDIS_LOCATION = os.getenv('REDIS_LOCATION', 'redis://redis:6379/1')
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": "mediacms",
|
||||
"HOST": POSTGRES_HOST,
|
||||
"PORT": "5432",
|
||||
"USER": "mediacms",
|
||||
"PASSWORD": "mediacms",
|
||||
"NAME": os.getenv('POSTGRES_NAME', 'mediacms'),
|
||||
"HOST": os.getenv('POSTGRES_HOST', 'db'),
|
||||
"PORT": os.getenv('POSTGRES_PORT', '5432'),
|
||||
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
|
||||
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +32,4 @@ CELERY_RESULT_BACKEND = BROKER_URL
|
||||
|
||||
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
|
||||
|
||||
DEBUG = False
|
||||
DEBUG = os.getenv('DEBUG', 'False') == 'True'
|
||||
|
||||
@@ -16,6 +16,10 @@ server {
|
||||
|
||||
location /media {
|
||||
alias /home/mediacms.io/mediacms/media_files ;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
}
|
||||
|
||||
location / {
|
||||
|
||||
99
deploy/docker/policy.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policymap [
|
||||
<!ELEMENT policymap (policy)*>
|
||||
<!ATTLIST policymap xmlns CDATA #FIXED ''>
|
||||
<!ELEMENT policy EMPTY>
|
||||
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
|
||||
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
|
||||
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
|
||||
]>
|
||||
<!--
|
||||
Configure ImageMagick policies.
|
||||
|
||||
Domains include system, delegate, coder, filter, path, or resource.
|
||||
|
||||
Rights include none, read, write, execute and all. Use | to combine them,
|
||||
for example: "read | write" to permit read from, or write to, a path.
|
||||
|
||||
Use a glob expression as a pattern.
|
||||
|
||||
Suppose we do not want users to process MPEG video images:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
|
||||
|
||||
Here we do not want users reading images from HTTP:
|
||||
|
||||
<policy domain="coder" rights="none" pattern="HTTP" />
|
||||
|
||||
The /repository file system is restricted to read only. We use a glob
|
||||
expression to match all paths that start with /repository:
|
||||
|
||||
<policy domain="path" rights="read" pattern="/repository/*" />
|
||||
|
||||
Lets prevent users from executing any image filters:
|
||||
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
|
||||
Any large image is cached to disk rather than memory:
|
||||
|
||||
<policy domain="resource" name="area" value="1GP"/>
|
||||
|
||||
Use the default system font unless overwridden by the application:
|
||||
|
||||
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
|
||||
|
||||
Define arguments for the memory, map, area, width, height and disk resources
|
||||
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
|
||||
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
|
||||
exceeds policy maximum so memory limit is 1GB).
|
||||
|
||||
Rules are processed in order. Here we want to restrict ImageMagick to only
|
||||
read or write a small subset of proven web-safe image types:
|
||||
|
||||
<policy domain="delegate" rights="none" pattern="*" />
|
||||
<policy domain="filter" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="none" pattern="*" />
|
||||
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
|
||||
-->
|
||||
<policymap>
|
||||
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
|
||||
<policy domain="resource" name="memory" value="1GiB"/>
|
||||
<policy domain="resource" name="map" value="30GiB"/>
|
||||
<policy domain="resource" name="width" value="16MP"/>
|
||||
<policy domain="resource" name="height" value="16MP"/>
|
||||
<!-- <policy domain="resource" name="list-length" value="128"/> -->
|
||||
<policy domain="resource" name="area" value="40GP"/>
|
||||
<policy domain="resource" name="disk" value="100GiB"/>
|
||||
<!-- <policy domain="resource" name="file" value="768"/> -->
|
||||
<!-- <policy domain="resource" name="thread" value="4"/> -->
|
||||
<!-- <policy domain="resource" name="throttle" value="0"/> -->
|
||||
<!-- <policy domain="resource" name="time" value="3600"/> -->
|
||||
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
|
||||
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
|
||||
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
|
||||
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
|
||||
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
|
||||
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/>
|
||||
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- <policy domain="system" name="font" value="/path/to/font.ttf"/> -->
|
||||
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
|
||||
<!-- <policy domain="system" name="shred" value="2"/> -->
|
||||
<!-- <policy domain="system" name="precision" value="6"/> -->
|
||||
<!-- not needed due to the need to use explicitly by mvg: -->
|
||||
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
|
||||
<!-- use curl -->
|
||||
<policy domain="delegate" rights="none" pattern="URL" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTPS" />
|
||||
<policy domain="delegate" rights="none" pattern="HTTP" />
|
||||
<!-- in order to avoid to get image with password text -->
|
||||
<policy domain="path" rights="none" pattern="@*"/>
|
||||
<!-- disable ghostscript format types -->
|
||||
<policy domain="coder" rights="none" pattern="PS" />
|
||||
<policy domain="coder" rights="none" pattern="PS2" />
|
||||
<policy domain="coder" rights="none" pattern="PS3" />
|
||||
<policy domain="coder" rights="none" pattern="EPS" />
|
||||
<policy domain="coder" rights="none" pattern="PDF" />
|
||||
<policy domain="coder" rights="none" pattern="XPS" />
|
||||
</policymap>
|
||||
@@ -4,20 +4,29 @@ RANDOM_ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz
|
||||
ADMIN_PASSWORD=${ADMIN_PASSWORD:-$RANDOM_ADMIN_PASS}
|
||||
|
||||
if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
|
||||
echo "Running migrations service"
|
||||
python manage.py migrate
|
||||
python manage.py loaddata fixtures/encoding_profiles.json
|
||||
python manage.py loaddata fixtures/categories.json
|
||||
EXISTING_INSTALLATION=`echo "from users.models import User; print(User.objects.exists())" |python manage.py shell`
|
||||
if [ "$EXISTING_INSTALLATION" = "True" ]; then
|
||||
echo "Loaddata has already run"
|
||||
else
|
||||
echo "Running loaddata and creating admin user"
|
||||
python manage.py loaddata fixtures/encoding_profiles.json
|
||||
python manage.py loaddata fixtures/categories.json
|
||||
|
||||
# post_save, needs redis to succeed (ie. migrate depends on redis)
|
||||
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
|
||||
--no-input \
|
||||
--username=$ADMIN_USER \
|
||||
--email=$ADMIN_EMAIL \
|
||||
--database=default || true
|
||||
echo "Created admin user with password: $ADMIN_PASSWORD"
|
||||
|
||||
fi
|
||||
echo "RUNNING COLLECTSTATIC"
|
||||
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
echo "Admin Password: $ADMIN_PASSWORD"
|
||||
|
||||
# post_save, needs redis to succeed (ie. migrate depends on redis)
|
||||
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
|
||||
--no-input \
|
||||
--username=$ADMIN_USER \
|
||||
--email=$ADMIN_EMAIL \
|
||||
--database=default || true
|
||||
|
||||
# echo "Updating hostname ..."
|
||||
# TODO: Get the FRONTEND_HOST from cms/local_settings.py
|
||||
# echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
|
||||
@@ -58,4 +67,5 @@ fi
|
||||
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
|
||||
echo "Enabling celery-long task worker"
|
||||
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
|
||||
rm /var/run/mediacms/* -f # remove any stale id, so that on forced restarts of celery workers there are no stale processes that prevent new ones
|
||||
fi
|
||||
|
||||
@@ -1 +1 @@
|
||||
client_max_body_size 1g;
|
||||
client_max_body_size 5800M;
|
||||
|
||||
@@ -9,4 +9,4 @@ user=www-data
|
||||
directory=/home/mediacms.io/mediacms
|
||||
priority=300
|
||||
startinorder=true
|
||||
command=/home/mediacms.io/bin/celery beat --pidfile=/home/mediacms.io/mediacms/pids/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log
|
||||
command=/home/mediacms.io/bin/celery beat --pidfile=/var/run/mediacms/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log
|
||||
|
||||
@@ -10,4 +10,4 @@ directory=/home/mediacms.io/mediacms
|
||||
priority=500
|
||||
startinorder=true
|
||||
startsecs=0
|
||||
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/home/mediacms.io/mediacms/pids/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks
|
||||
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks
|
||||
|
||||
@@ -9,4 +9,4 @@ user=www-data
|
||||
directory=/home/mediacms.io/mediacms
|
||||
priority=400
|
||||
startinorder=true
|
||||
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/home/mediacms.io/mediacms/pids/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks
|
||||
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks
|
||||
|
||||
@@ -21,3 +21,4 @@ vacuum = true
|
||||
hook-master-start = unix_signal:15 gracefully_kill_them_all
|
||||
need-app = true
|
||||
die-on-term = true
|
||||
buffer-size=32768
|
||||
|
||||
@@ -8,15 +8,13 @@ User=www-data
|
||||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
Environment=CELERY_APP="cms"
|
||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} beat -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms beat --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
|
||||
ExecStop=/bin/kill -s TERM $MAINPID
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -8,23 +8,21 @@ User=www-data
|
||||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERYD_NODES="long1"
|
||||
Environment=CELERY_QUEUE="long_tasks"
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
Environment=CELERY_APP="cms"
|
||||
Environment=CELERYD_MULTI="multi"
|
||||
Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1"
|
||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -8,14 +8,13 @@ User=www-data
|
||||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERYD_NODES="short1 short2"
|
||||
Environment=CELERY_QUEUE="short_tasks"
|
||||
# Absolute or relative path to the 'celery' command:
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
# App instance to use
|
||||
# comment out this line if you don't use an app
|
||||
Environment=CELERY_APP="cms"
|
||||
# or fully qualified:
|
||||
#CELERY_APP="proj.tasks:app"
|
||||
# How to call manage.py
|
||||
@@ -28,13 +27,12 @@ Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
|
||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
13
deploy/local_install/dhparams.pem
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIICCAKCAgEAo3MMiEY/fNbu+usIM0cDi6x8G3JBApv0Lswta4kiyedWT1WN51iQ
|
||||
9zhOFpmcu6517f/fR9MUdyhVKHxxSqWQTcmTEFtz4P3VLTS/W1N5VbKE2VEMLpIi
|
||||
wr350aGvV1Er0ujcp5n4O4h0I1tn4/fNyDe7+pHCdwM+hxe8hJ3T0/tKtad4fnIs
|
||||
WHDjl4f7m7KuFfheiK7Efb8MsT64HDDAYXn+INjtDZrbE5XPw20BqyWkrf07FcPx
|
||||
8o9GW50Ox7/FYq7jVMI/skEu0BRc8u6uUD9+UOuWUQpdeHeFcvLOgW53Z03XwWuX
|
||||
RXosUKzBPuGtUDAaKD/HsGW6xmGr2W9yRmu27jKpfYLUb/eWbbnRJwCw04LdzPqv
|
||||
jmtq02Gioo3lf5H5wYV9IYF6M8+q/slpbttsAcKERimD1273FBRt5VhSugkXWKjr
|
||||
XDhoXu6vZgj8Opei38qPa8pI1RUFoXHFlCe6WpZQmU8efL8gAMrJr9jUIY8eea1n
|
||||
u20t5B9ueb9JMjrNafcq6QkKhZLi6fRDDTUyeDvc0dN9R/3Yts97SXfdi1/lX7HS
|
||||
Ht4zXd5hEkvjo8GcnjsfZpAC39QfHWkDaeUGEqsl3jXjVMfkvoVY51OuokPWZzrJ
|
||||
M5+wyXNpfGbH67dPk7iHgN7VJvgX0SYscDPTtms50Vk7RwEzLeGuSHMCAQI=
|
||||
-----END DH PARAMETERS-----
|
||||
@@ -46,6 +46,12 @@ server {
|
||||
|
||||
ssl_certificate_key /etc/letsencrypt/live/localhost/privkey.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/localhost/fullchain.pem;
|
||||
ssl_dhparam /etc/nginx/dhparams/dhparams.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_ecdh_curve secp521r1:secp384r1;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
gzip on;
|
||||
access_log /var/log/nginx/mediacms.io.access.log;
|
||||
|
||||
@@ -19,10 +19,7 @@ http {
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
|
||||
34
deploy/local_install/selinux-mediacms.te
Normal file
@@ -0,0 +1,34 @@
|
||||
module selinux-mediacms 1.0;
|
||||
|
||||
require {
|
||||
type init_t;
|
||||
type var_t;
|
||||
type redis_port_t;
|
||||
type postgresql_port_t;
|
||||
type httpd_t;
|
||||
type httpd_sys_content_t;
|
||||
type httpd_sys_rw_content_t;
|
||||
class file { append create execute execute_no_trans getattr ioctl lock open read rename setattr unlink write };
|
||||
class dir { add_name remove_name rmdir };
|
||||
class tcp_socket name_connect;
|
||||
class lnk_file read;
|
||||
}
|
||||
|
||||
#============= httpd_t ==============
|
||||
|
||||
allow httpd_t var_t:file { getattr open read };
|
||||
|
||||
#============= init_t ==============
|
||||
allow init_t postgresql_port_t:tcp_socket name_connect;
|
||||
|
||||
allow init_t redis_port_t:tcp_socket name_connect;
|
||||
|
||||
allow init_t httpd_sys_content_t:dir rmdir;
|
||||
|
||||
allow init_t httpd_sys_content_t:file { append create execute execute_no_trans ioctl lock open read rename setattr unlink write };
|
||||
|
||||
allow init_t httpd_sys_content_t:lnk_file read;
|
||||
|
||||
allow init_t httpd_sys_rw_content_t:dir { add_name remove_name rmdir };
|
||||
|
||||
allow init_t httpd_sys_rw_content_t:file { create ioctl lock open read setattr unlink write };
|
||||
@@ -24,4 +24,4 @@ vacuum = true
|
||||
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
|
||||
|
||||
disable-logging = true
|
||||
|
||||
buffer-size=32768
|
||||
|
||||
89
docker-compose-dev.yaml
Normal file
@@ -0,0 +1,89 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
migrations:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
args:
|
||||
- DEVELOPMENT_MODE=True
|
||||
image: mediacms/mediacms-dev:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: True
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
ADMIN_PASSWORD: 'admin'
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
frontend:
|
||||
image: node:14
|
||||
volumes:
|
||||
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
|
||||
working_dir: /home/mediacms.io/mediacms/frontend/
|
||||
command: bash -c "npm install && npm run start"
|
||||
env_file:
|
||||
- ${PWD}/frontend/.env
|
||||
ports:
|
||||
- "8088:8088"
|
||||
depends_on:
|
||||
- web
|
||||
web:
|
||||
image: mediacms/mediacms-dev:latest
|
||||
command: "python manage.py runserver 0.0.0.0:80"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: True
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
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"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
celery_worker:
|
||||
image: mediacms/mediacms-dev:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
DEVELOPMENT_MODE: True
|
||||
depends_on:
|
||||
- web
|
||||
@@ -18,6 +18,11 @@ services:
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
@@ -63,7 +68,7 @@ services:
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data/:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
@@ -71,8 +76,9 @@ services:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -20,6 +20,8 @@ services:
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
@@ -36,6 +38,9 @@ services:
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||
VIRTUAL_HOST: localhost
|
||||
depends_on:
|
||||
- migrations
|
||||
@@ -65,7 +70,7 @@ services:
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data/:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
@@ -73,8 +78,9 @@ services:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
121
docker-compose-letsencrypt.yaml
Normal file
@@ -0,0 +1,121 @@
|
||||
version: "3"
|
||||
|
||||
# Uses https://github.com/nginx-proxy/acme-companion
|
||||
|
||||
services:
|
||||
nginx-proxy:
|
||||
image: nginxproxy/nginx-proxy
|
||||
container_name: nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- dhparam:/etc/nginx/dhparam
|
||||
- certs:/etc/nginx/certs:ro
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./deploy/docker/reverse_proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
|
||||
|
||||
acme-companion:
|
||||
image: nginxproxy/acme-companion
|
||||
container_name: nginx-proxy-acme
|
||||
volumes_from:
|
||||
- nginx-proxy
|
||||
volumes:
|
||||
- certs:/etc/nginx/certs:rw
|
||||
- acme:/etc/acme.sh
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
migrations:
|
||||
image: mediacms/mediacms:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
web:
|
||||
image: mediacms/mediacms:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
VIRTUAL_HOST: 'mediacms.52.209.5.113.nip.io'
|
||||
LETSENCRYPT_HOST: 'mediacms.52.209.5.113.nip.io'
|
||||
LETSENCRYPT_EMAIL: 'email@example.com'
|
||||
depends_on:
|
||||
- migrations
|
||||
celery_beat:
|
||||
image: mediacms/mediacms:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- redis
|
||||
celery_worker:
|
||||
image: mediacms/mediacms:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
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"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli","ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
volumes:
|
||||
conf:
|
||||
vhost:
|
||||
html:
|
||||
dhparam:
|
||||
certs:
|
||||
acme:
|
||||
@@ -11,6 +11,11 @@ services:
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
@@ -61,7 +66,7 @@ services:
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
@@ -69,8 +74,9 @@ services:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
@@ -11,6 +11,11 @@ services:
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||
command: "./deploy/docker/prestart.sh"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
@@ -57,7 +62,7 @@ services:
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres
|
||||
image: postgres:17.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
@@ -65,8 +70,9 @@ services:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
## Configuration
|
||||
|
||||
A number of options are available on `cms/settings.py`.
|
||||
|
||||
It is advisable to override any of them by adding it to `local_settings.py` .
|
||||
|
||||
In case of a the single server installation, add to `cms/local_settings.py` .
|
||||
|
||||
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
|
||||
|
||||
Any change needs restart of MediaCMS in order to take effect. So edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
|
||||
```
|
||||
#systemctl restart mediacms
|
||||
```
|
||||
|
||||
|
||||
### change portal logo
|
||||
|
||||
Set a new svg file for the white theme (`static/images/logo_dark.svg`) or the dark theme (`static/images/logo_light.svg`)
|
||||
|
||||
### set global portal title
|
||||
|
||||
set `PORTAL_NAME`, eg
|
||||
|
||||
```
|
||||
PORTAL_NAME = 'my awesome portal'
|
||||
```
|
||||
|
||||
### who can add media
|
||||
|
||||
By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are:
|
||||
|
||||
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
|
||||
|
||||
- **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
|
||||
|
||||
### what is the portal workflow
|
||||
|
||||
The `PORTAL_WORKFLOW` variable specifies what happens to newly uploaded media, whether they appear on listings (as the index page, or search)
|
||||
|
||||
- **public** is the default option and means that a media can appear on listings. If media type is video, it will appear once at least a task that produces an encoded version of the file has finished succesfully. For other type of files, as image/audio they appear instantly
|
||||
|
||||
- **private** means that newly uploaded content is private - only users can see it or MediaCMS editors, managers and admins. Those can also set the status to public or unlisted
|
||||
|
||||
- **unlisted** means that items are unlisted. However if a user visits the url of an unlisted media, it will be shown (as opposed to private)
|
||||
|
||||
|
||||
### show/hide the Sign in button
|
||||
|
||||
to show button:
|
||||
```
|
||||
LOGIN_ALLOWED = True
|
||||
```
|
||||
|
||||
to hide button:
|
||||
|
||||
```
|
||||
LOGIN_ALLOWED = False
|
||||
```
|
||||
|
||||
### show/hide the Register button
|
||||
|
||||
to show button:
|
||||
```
|
||||
REGISTER_ALLOWED = True
|
||||
```
|
||||
|
||||
to hide button:
|
||||
|
||||
```
|
||||
REGISTER_ALLOWED = False
|
||||
```
|
||||
|
||||
|
||||
### show/hide the upload media button
|
||||
|
||||
To show:
|
||||
|
||||
```
|
||||
UPLOAD_MEDIA_ALLOWED = True
|
||||
```
|
||||
|
||||
To hide:
|
||||
|
||||
```
|
||||
UPLOAD_MEDIA_ALLOWED = False
|
||||
```
|
||||
|
||||
### show/hide the actions buttons (like/dislike/report)
|
||||
|
||||
Make changes (True/False) to any of the following:
|
||||
|
||||
```
|
||||
- CAN_LIKE_MEDIA = True # whether the like media appears
|
||||
- CAN_DISLIKE_MEDIA = True # whether the dislike media appears
|
||||
- CAN_REPORT_MEDIA = True # whether the report media appears
|
||||
- CAN_SHARE_MEDIA = True # whether the share media appears
|
||||
```
|
||||
|
||||
### show/hide the download option on a media
|
||||
|
||||
Edit `templates/config/installation/features.html` and set
|
||||
|
||||
```
|
||||
download: false
|
||||
```
|
||||
|
||||
### automatically hide media upon being reported
|
||||
|
||||
set a low number for variable `REPORTED_TIMES_THRESHOLD`
|
||||
eg
|
||||
|
||||
```
|
||||
REPORTED_TIMES_THRESHOLD = 2
|
||||
```
|
||||
|
||||
once the limit is reached, media goes to private state and an email is sent to admins
|
||||
|
||||
### set a custom message on the media upload page
|
||||
|
||||
this message will appear below the media drag and drop form
|
||||
|
||||
```
|
||||
PRE_UPLOAD_MEDIA_MESSAGE = 'custom message'
|
||||
```
|
||||
|
||||
### set email settings
|
||||
|
||||
Set correct settings per provider
|
||||
|
||||
```
|
||||
DEFAULT_FROM_EMAIL = 'info@mediacms.io'
|
||||
EMAIL_HOST_PASSWORD = 'xyz'
|
||||
EMAIL_HOST_USER = 'info@mediacms.io'
|
||||
EMAIL_USE_TLS = True
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL
|
||||
EMAIL_HOST = 'mediacms.io'
|
||||
EMAIL_PORT = 587
|
||||
ADMIN_EMAIL_LIST = ['info@mediacms.io']
|
||||
```
|
||||
|
||||
### disallow user registrations from specific domains
|
||||
|
||||
set domains that are not valid for registration via this variable:
|
||||
|
||||
```
|
||||
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
|
||||
'xxx.com', 'emaildomainwhatever.com']
|
||||
```
|
||||
|
||||
### require a review by MediaCMS editors/managers/admins
|
||||
|
||||
set value
|
||||
|
||||
```
|
||||
MEDIA_IS_REVIEWED = False
|
||||
```
|
||||
|
||||
any uploaded media now needs to be reviewed before it can appear to the listings.
|
||||
MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed
|
||||
|
||||
### specify maximum number of media for a playlist
|
||||
|
||||
set a different threshold on variable `MAX_MEDIA_PER_PLAYLIST`
|
||||
|
||||
eg
|
||||
|
||||
```
|
||||
MAX_MEDIA_PER_PLAYLIST = 14
|
||||
```
|
||||
|
||||
### specify maximum size of a media that can be uploaded
|
||||
|
||||
change `UPLOAD_MAX_SIZE`.
|
||||
|
||||
default is 4GB
|
||||
|
||||
```
|
||||
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
||||
```
|
||||
|
||||
### specify maximum size of comments
|
||||
|
||||
change `MAX_CHARS_FOR_COMMENT`
|
||||
|
||||
default:
|
||||
|
||||
```
|
||||
MAX_CHARS_FOR_COMMENT = 10000
|
||||
```
|
||||
|
||||
### how many files to upload in parallel
|
||||
|
||||
set a different threshold for `UPLOAD_MAX_FILES_NUMBER`
|
||||
default:
|
||||
|
||||
```
|
||||
UPLOAD_MAX_FILES_NUMBER = 100
|
||||
```
|
||||
|
||||
### force users confirm their email upon registrations
|
||||
|
||||
default option for email confirmation is optional. Set this to mandatory in order to force users confirm their email before they can login
|
||||
|
||||
```
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'optional'
|
||||
```
|
||||
|
||||
### rate limit account login attempts
|
||||
|
||||
after this number is reached
|
||||
|
||||
```
|
||||
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20
|
||||
```
|
||||
|
||||
sets a timeout (in seconds)
|
||||
|
||||
```
|
||||
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5
|
||||
```
|
||||
|
||||
### disallow user registration
|
||||
|
||||
set the following variable to False
|
||||
|
||||
```
|
||||
USERS_CAN_SELF_REGISTER = True
|
||||
```
|
||||
|
||||
### configure notifications
|
||||
|
||||
Global notifications that are implemented are controlled by the following options:
|
||||
|
||||
```
|
||||
USERS_NOTIFICATIONS = {
|
||||
'MEDIA_ADDED': True,
|
||||
}
|
||||
```
|
||||
|
||||
If you want to disable notification for new media, set to False
|
||||
|
||||
Admins also receive notifications on different events, set any of the following to False to disable
|
||||
|
||||
```
|
||||
ADMINS_NOTIFICATIONS = {
|
||||
'NEW_USER': True,
|
||||
'MEDIA_ADDED': True,
|
||||
'MEDIA_REPORTED': True,
|
||||
}
|
||||
```
|
||||
|
||||
- NEW_USER: a new user is added
|
||||
- MEDIA_ADDED: a media is added
|
||||
- MEDIA_REPORTED: the report for a media was hit
|
||||
@@ -1,40 +0,0 @@
|
||||
# MediaCMS on Docker
|
||||
|
||||
The mediacms image is built to use supervisord as the main process, which manages one or more services required to run mediacms. We can toggle which services are run in a given container by setting the environment variables below to `yes` or `no`:
|
||||
|
||||
* ENABLE_UWSGI
|
||||
* ENABLE_NGINX
|
||||
* ENABLE_CELERY_BEAT
|
||||
* ENABLE_CELERY_SHORT
|
||||
* ENABLE_CELERY_LONG
|
||||
* ENABLE_MIGRATIONS
|
||||
|
||||
By default, all these services are enabled, but in order to create a scaleable deployment, some of them can be disabled, splitting the service up into smaller services.
|
||||
|
||||
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
|
||||
|
||||
See example deployments in the sections below. These example deployments have been tested on `docker-compose version 1.27.4` running on `Docker version 19.03.13`
|
||||
|
||||
To run, update the configs above if necessary, build the image by running `docker-compose build`, then run `docker-compose run`
|
||||
|
||||
## Simple Deployment, accessed as http://localhost
|
||||
|
||||
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
|
||||
|
||||
## Advanced Deployment, accessed as http://localhost:8000
|
||||
|
||||
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
|
||||
|
||||
## Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
|
||||
|
||||
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
## Advanced Deployment, with reverse proxy, accessed as https://localhost
|
||||
|
||||
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
## A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
|
||||
|
||||
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
|
||||
|
||||

|
||||
@@ -1,20 +0,0 @@
|
||||
## User scenarios to test
|
||||
|
||||
|
||||
## test video media + image
|
||||
try uploading a video + image, make sure they get encoded well and check they appear on index/search/category/author page
|
||||
try editing/setting metadata, confirm action is performed, also that are searchable
|
||||
try adding custom poster, confirm it loads well on video page/listings
|
||||
try specifying different thumbnail time, confirm an automatic screenshot is taken
|
||||
|
||||
|
||||
## portal workflow
|
||||
change workflow to unlisted, check they don't appear on index/search/category/author page
|
||||
|
||||
## users management
|
||||
create an admin, a MediaCMS editor and MediaCMS manager. All should see edit/delete on a media and also comments, and action should work.
|
||||
For users edit and delete, only MediaCMS manager and admin should see edit/delete and these actions should work.
|
||||
|
||||
## test subtitle
|
||||
add language and test subtitling
|
||||
|
||||
863
docs/admins_docs.md
Normal file
@@ -0,0 +1,863 @@
|
||||
# Administrators documentation
|
||||
|
||||
## Table of contents
|
||||
- [1. Welcome](#1-welcome)
|
||||
- [2. Server Installaton](#2-server-installation)
|
||||
- [3. Docker Installation](#3-docker-installation)
|
||||
- [4. Docker Deployment options](#4-docker-deployment-options)
|
||||
- [5. Configuration](#5-configuration)
|
||||
- [6. Manage pages](#6-manage-pages)
|
||||
- [7. Django admin dashboard](#7-django-admin-dashboard)
|
||||
- [8. On portal workflow](#8-on-portal-workflow)
|
||||
- [9. On user roles](#9-on-user-roles)
|
||||
- [10. Adding languages for Captions and subtitles](#10-adding-languages-for-captions-and-subtitles)
|
||||
- [11. Add/delete categories and tags](#11-adddelete-categories-and-tags)
|
||||
- [12. Video transcoding](#12-video-transcoding)
|
||||
- [13. How To Add A Static Page To The Sidebar](#13-how-to-add-a-static-page-to-the-sidebar)
|
||||
- [14. Add Google Analytics](#14-add-google-analytics)
|
||||
- [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)
|
||||
- [19. Rounded corners on videos](#19-rounded-corners)
|
||||
- [20. Translations](#20-translations)
|
||||
- [21. How to change the video frames on videos](#21-fames)
|
||||
|
||||
## 1. Welcome
|
||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||
|
||||
## 2. Server Installation
|
||||
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu (tested on versions 20, 22).
|
||||
|
||||
Installation on an Ubuntu system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
|
||||
Automated script - tested on Ubuntu 20, Ubuntu 22 and Debian Buster
|
||||
|
||||
```bash
|
||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd /home/mediacms.io/mediacms/ && bash ./install.sh
|
||||
```
|
||||
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
|
||||
|
||||
### Update
|
||||
|
||||
If you've used the above way to install MediaCMS, update with the following:
|
||||
|
||||
```bash
|
||||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||
source /home/mediacms.io/bin/activate # use virtualenv
|
||||
git pull # update code
|
||||
pip install -r requirements.txt -U # run pip install to update
|
||||
python manage.py migrate # run Django migrations
|
||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
||||
```
|
||||
|
||||
### Update from version 2 to version 3
|
||||
Version 3 is using Django 4 and Celery 5, and needs a recent Python 3.x version. If you are updating from an older version, make sure Python is updated first. Version 2 could run on Python 3.6, but version 3 needs Python3.8 and higher.
|
||||
The syntax for starting Celery has also changed, so you have to copy the celery related systemctl files and restart
|
||||
|
||||
```
|
||||
# cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
|
||||
# cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
|
||||
# cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
|
||||
# systemctl daemon-reload
|
||||
# systemctl start celery_long celery_short celery_beat
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Configuration
|
||||
Checkout the configuration section here.
|
||||
|
||||
|
||||
### Maintenance
|
||||
Database can be backed up with pg_dump and media_files on /home/mediacms.io/mediacms/media_files include original files and encoded/transcoded versions
|
||||
|
||||
|
||||
## 3. Docker Installation
|
||||
|
||||
## Installation
|
||||
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
For Ubuntu 20/22 systems this is:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
```
|
||||
|
||||
Then run as root
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd mediacms
|
||||
```
|
||||
|
||||
The default option is to serve MediaCMS on all ips available of the server (including localhost).
|
||||
If you want to explore more options (including setup of https with letsencrypt certificate) checkout [Docker deployment](/docs/admins_docs.md#4-docker-deployment-options) section for different docker-compose setups to use.
|
||||
|
||||
Run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
|
||||
|
||||
A user admin has been created with random password, you should be able to see it at the end of migrations container, eg
|
||||
|
||||
```
|
||||
migrations_1 | Created admin user with password: gwg1clfkwf
|
||||
```
|
||||
|
||||
or if you have set the ADMIN_PASSWORD variable on docker-compose file you have used (example `docker-compose.yaml`), that variable will be set as the admin user's password
|
||||
|
||||
### Update
|
||||
|
||||
Get latest MediaCMS image and stop/start containers
|
||||
|
||||
```bash
|
||||
cd /path/to/mediacms/installation
|
||||
docker pull mediacms/mediacms
|
||||
docker-compose down
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
### Update from version 2 to version 3
|
||||
Version 3 is using Python 3.11 and PostgreSQL 15. If you are updating from an older version, that was using PostgreSQL 13, the automatic update will not work, as you will receive the following message when the PostgreSQL container starts:
|
||||
|
||||
```
|
||||
db_1 | 2023-06-27 11:07:42.959 UTC [1] FATAL: database files are incompatible with server
|
||||
db_1 | 2023-06-27 11:07:42.959 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 13, which is not compatible with this version 15.2.
|
||||
```
|
||||
|
||||
At this point there are two options: either edit the Docker Compose file and make use of the existing postgres:13 image, or otherwise you have to perform the migration from postgresql 13 to version 15. More notes on https://github.com/mediacms-io/mediacms/pull/749
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
Checkout the configuration docs here.
|
||||
|
||||
|
||||
### Maintenance
|
||||
Database is stored on ../postgres_data/ and media_files on media_files/
|
||||
|
||||
|
||||
## 4. Docker Deployment options
|
||||
|
||||
The mediacms image is built to use supervisord as the main process, which manages one or more services required to run mediacms. We can toggle which services are run in a given container by setting the environment variables below to `yes` or `no`:
|
||||
|
||||
* ENABLE_UWSGI
|
||||
* ENABLE_NGINX
|
||||
* ENABLE_CELERY_BEAT
|
||||
* ENABLE_CELERY_SHORT
|
||||
* ENABLE_CELERY_LONG
|
||||
* ENABLE_MIGRATIONS
|
||||
|
||||
By default, all these services are enabled, but in order to create a scaleable deployment, some of them can be disabled, splitting the service up into smaller services.
|
||||
|
||||
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
|
||||
|
||||
See example deployments in the sections below. These example deployments have been tested on `docker-compose version 1.27.4` running on `Docker version 19.03.13`
|
||||
|
||||
To run, update the configs above if necessary, build the image by running `docker-compose build`, then run `docker-compose run`
|
||||
|
||||
### Simple Deployment, accessed as http://localhost
|
||||
|
||||
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database.
|
||||
|
||||
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
|
||||
|
||||
### Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
|
||||
Before trying this out make sure the ip points to my_domain.com.
|
||||
|
||||
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
|
||||
|
||||
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
|
||||
|
||||
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
|
||||
|
||||
Now run docker-compose -f docker-compose-letsencrypt.yaml up, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
|
||||
|
||||
### Advanced Deployment, accessed as http://localhost:8000
|
||||
|
||||
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
|
||||
|
||||
### Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
|
||||
|
||||
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
### Advanced Deployment, with reverse proxy, accessed as https://localhost
|
||||
|
||||
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
### A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
|
||||
|
||||
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
|
||||
|
||||

|
||||
|
||||
|
||||
## 5. Configuration
|
||||
Several options are available on `cms/settings.py`, most of the things that are allowed or should be disallowed are described there.
|
||||
|
||||
It is advisable to override any of them by adding it to `local_settings.py` .
|
||||
|
||||
In case of a the single server installation, add to `cms/local_settings.py` .
|
||||
|
||||
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
|
||||
|
||||
Any change needs restart of MediaCMS in order to take effect.
|
||||
|
||||
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
|
||||
```bash
|
||||
#systemctl restart mediacms
|
||||
```
|
||||
|
||||
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
|
||||
|
||||
```bash
|
||||
#docker-compose restart web celery_worker celery_beat
|
||||
```
|
||||
|
||||
### 5.1 Change portal logo
|
||||
|
||||
Set a new svg file for the white theme (`static/images/logo_dark.svg`) or the dark theme (`static/images/logo_light.svg`)
|
||||
|
||||
### 5.2 Set global portal title
|
||||
|
||||
set `PORTAL_NAME`, eg
|
||||
|
||||
```
|
||||
PORTAL_NAME = 'my awesome portal'
|
||||
```
|
||||
|
||||
### 5.3 Control who can add media
|
||||
|
||||
By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are:
|
||||
|
||||
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
|
||||
|
||||
- **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
|
||||
|
||||
### 5.4 What is the portal workflow
|
||||
|
||||
The `PORTAL_WORKFLOW` variable specifies what happens to newly uploaded media, whether they appear on listings (as the index page, or search)
|
||||
|
||||
- **public** is the default option and means that a media can appear on listings. If media type is video, it will appear once at least a task that produces an encoded version of the file has finished succesfully. For other type of files, as image/audio they appear instantly
|
||||
|
||||
- **private** means that newly uploaded content is private - only users can see it or MediaCMS editors, managers and admins. Those can also set the status to public or unlisted
|
||||
|
||||
- **unlisted** means that items are unlisted. However if a user visits the url of an unlisted media, it will be shown (as opposed to private)
|
||||
|
||||
|
||||
### 5.5 Show or hide the Sign in button
|
||||
|
||||
to show button:
|
||||
```
|
||||
LOGIN_ALLOWED = True
|
||||
```
|
||||
|
||||
to hide button:
|
||||
|
||||
```
|
||||
LOGIN_ALLOWED = False
|
||||
```
|
||||
|
||||
### 5.6 Show or hide the Register button
|
||||
|
||||
to show button:
|
||||
```
|
||||
REGISTER_ALLOWED = True
|
||||
```
|
||||
|
||||
to hide button:
|
||||
|
||||
```
|
||||
REGISTER_ALLOWED = False
|
||||
```
|
||||
|
||||
|
||||
### 5.7 Show or hide the upload media button
|
||||
|
||||
To show:
|
||||
|
||||
```
|
||||
UPLOAD_MEDIA_ALLOWED = True
|
||||
```
|
||||
|
||||
To hide:
|
||||
|
||||
```
|
||||
UPLOAD_MEDIA_ALLOWED = False
|
||||
```
|
||||
|
||||
### 5.8 Show or hide the actions buttons (like/dislike/report)
|
||||
|
||||
Make changes (True/False) to any of the following:
|
||||
|
||||
```
|
||||
- CAN_LIKE_MEDIA = True # whether the like media appears
|
||||
- CAN_DISLIKE_MEDIA = True # whether the dislike media appears
|
||||
- CAN_REPORT_MEDIA = True # whether the report media appears
|
||||
- CAN_SHARE_MEDIA = True # whether the share media appears
|
||||
```
|
||||
|
||||
### 5.9 Show or hide the download option on a media
|
||||
|
||||
Edit `templates/config/installation/features.html` and set
|
||||
|
||||
```
|
||||
download: false
|
||||
```
|
||||
|
||||
### 5.10 Automatically hide media upon being reported
|
||||
|
||||
set a low number for variable `REPORTED_TIMES_THRESHOLD`
|
||||
eg
|
||||
|
||||
```
|
||||
REPORTED_TIMES_THRESHOLD = 2
|
||||
```
|
||||
|
||||
once the limit is reached, media goes to private state and an email is sent to admins
|
||||
|
||||
### 5.11 Set a custom message on the media upload page
|
||||
|
||||
this message will appear below the media drag and drop form
|
||||
|
||||
```
|
||||
PRE_UPLOAD_MEDIA_MESSAGE = 'custom message'
|
||||
```
|
||||
|
||||
### 5.12 Set email settings
|
||||
|
||||
Set correct settings per provider
|
||||
|
||||
```
|
||||
DEFAULT_FROM_EMAIL = 'info@mediacms.io'
|
||||
EMAIL_HOST_PASSWORD = 'xyz'
|
||||
EMAIL_HOST_USER = 'info@mediacms.io'
|
||||
EMAIL_USE_TLS = True
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL
|
||||
EMAIL_HOST = 'mediacms.io'
|
||||
EMAIL_PORT = 587
|
||||
ADMIN_EMAIL_LIST = ['info@mediacms.io']
|
||||
```
|
||||
|
||||
### 5.13 Disallow user registrations from specific domains
|
||||
|
||||
Set domains that are not valid for registration via this variable:
|
||||
|
||||
```
|
||||
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
|
||||
'xxx.com', 'emaildomainwhatever.com']
|
||||
```
|
||||
|
||||
Alternatively, allow only permitted domains to register. This can be useful if you're using mediacms as a private service within an organization, and want to give free registration for those in the org, but deny registration from all other domains. Setting this option bans all domains NOT in the list from registering. Default is a blank list, which is ignored. To disable, set to a blank list.
|
||||
```
|
||||
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = [
|
||||
"private.com",
|
||||
"vod.private.com",
|
||||
"my.favorite.domain",
|
||||
"test.private.com"]
|
||||
```
|
||||
|
||||
### 5.14 Require a review by MediaCMS editors/managers/admins
|
||||
|
||||
set value
|
||||
|
||||
```
|
||||
MEDIA_IS_REVIEWED = False
|
||||
```
|
||||
|
||||
any uploaded media now needs to be reviewed before it can appear to the listings.
|
||||
MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed
|
||||
|
||||
### 5.15 Specify maximum number of media for a playlist
|
||||
|
||||
set a different threshold on variable `MAX_MEDIA_PER_PLAYLIST`
|
||||
|
||||
eg
|
||||
|
||||
```
|
||||
MAX_MEDIA_PER_PLAYLIST = 14
|
||||
```
|
||||
|
||||
### 5.16 Specify maximum size of a media that can be uploaded
|
||||
|
||||
change `UPLOAD_MAX_SIZE`.
|
||||
|
||||
default is 4GB
|
||||
|
||||
```
|
||||
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
||||
```
|
||||
|
||||
### 5.17 Specify maximum size of comments
|
||||
|
||||
change `MAX_CHARS_FOR_COMMENT`
|
||||
|
||||
default:
|
||||
|
||||
```
|
||||
MAX_CHARS_FOR_COMMENT = 10000
|
||||
```
|
||||
|
||||
### 5.18 How many files to upload in parallel
|
||||
|
||||
set a different threshold for `UPLOAD_MAX_FILES_NUMBER`
|
||||
default:
|
||||
|
||||
```
|
||||
UPLOAD_MAX_FILES_NUMBER = 100
|
||||
```
|
||||
|
||||
### 5.18 force users confirm their email upon registrations
|
||||
|
||||
default option for email confirmation is optional. Set this to mandatory in order to force users confirm their email before they can login
|
||||
|
||||
```
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'optional'
|
||||
```
|
||||
|
||||
### 5.20 Rate limit account login attempts
|
||||
|
||||
after this number is reached
|
||||
|
||||
```
|
||||
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20
|
||||
```
|
||||
|
||||
sets a timeout (in seconds)
|
||||
|
||||
```
|
||||
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5
|
||||
```
|
||||
|
||||
### 5.21 Disallow user registration
|
||||
|
||||
set the following variable to False
|
||||
|
||||
```
|
||||
USERS_CAN_SELF_REGISTER = True
|
||||
```
|
||||
|
||||
### 5.22 Configure notifications
|
||||
|
||||
Global notifications that are implemented are controlled by the following options:
|
||||
|
||||
```
|
||||
USERS_NOTIFICATIONS = {
|
||||
'MEDIA_ADDED': True,
|
||||
}
|
||||
```
|
||||
|
||||
If you want to disable notification for new media, set to False
|
||||
|
||||
Admins also receive notifications on different events, set any of the following to False to disable
|
||||
|
||||
```
|
||||
ADMINS_NOTIFICATIONS = {
|
||||
'NEW_USER': True,
|
||||
'MEDIA_ADDED': True,
|
||||
'MEDIA_REPORTED': True,
|
||||
}
|
||||
```
|
||||
|
||||
- NEW_USER: a new user is added
|
||||
- MEDIA_ADDED: a media is added
|
||||
- MEDIA_REPORTED: the report for a media was hit
|
||||
|
||||
### 5.23 Configure only member access to media
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
|
||||
### 5.25 Control who can add comments
|
||||
|
||||
By default `CAN_COMMENT = "all"` means that all registered users can add comment. Other valid options are:
|
||||
|
||||
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
|
||||
|
||||
- **advancedUser**, only users that are marked as advanced users can add comment. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
|
||||
|
||||
|
||||
## 6. Manage pages
|
||||
to be written
|
||||
|
||||
## 7. Django admin dashboard
|
||||
|
||||
## 8. On portal workflow
|
||||
Who can publish content, how content appears on public listings.Difference between statuses (private, unlisted, public)
|
||||
|
||||
## 9. On user roles
|
||||
Differences over MediaCMS manager, MediaCMS editor, logged in user
|
||||
|
||||
## 10. Adding languages for Captions and subtitles
|
||||
to be written
|
||||
|
||||
## 11. Add/delete categories and tags
|
||||
Through the admin section - http://your_installation/admin/
|
||||
|
||||
## 12. Video transcoding
|
||||
Add / remove resolutions and profiles by modifying the database table of `Encode profiles` through https://your_installation/admin/files/encodeprofile/
|
||||
|
||||
For example, the `Active` state of any profile can be toggled to enable or disable it.
|
||||
|
||||
## 13. How To Add A Static Page To The Sidebar
|
||||
|
||||
### 1. Create your html page in templates/cms/
|
||||
e.g. duplicate and rename about.html
|
||||
```
|
||||
sudo cp templates/cms/about.html templates/cms/volunteer.html
|
||||
```
|
||||
|
||||
### 2. Create your css file in static/css/
|
||||
```
|
||||
touch static/css/volunteer.css
|
||||
```
|
||||
|
||||
### 3. In your html file, update block headermeta to reflect your new page
|
||||
```
|
||||
{% block headermeta %}
|
||||
<meta property="og:title" content="Volunteer - {{PORTAL_NAME}}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "{{PORTAL_NAME}}",
|
||||
"item": {
|
||||
"@type": "WebPage",
|
||||
"@id": "{{FRONTEND_HOST}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Volunteer",
|
||||
"item": {
|
||||
"@type": "VolunteerPage",
|
||||
"@id": "{{FRONTEND_HOST}}/volunteer"
|
||||
}
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
<link href="{% static "css/volunteer.css" %}" rel="stylesheet"/>
|
||||
{% endblock headermeta %}
|
||||
```
|
||||
|
||||
### 4. In your html file, update block innercontent to reflect your actual content
|
||||
Write whatever you like.
|
||||
|
||||
### 5. In your css file, write matching styles for you html file.
|
||||
Write whatever you like.
|
||||
|
||||
### 6. Add your view to files/views.py
|
||||
```
|
||||
def volunteer(request):
|
||||
"""Volunteer view"""
|
||||
context = {}
|
||||
return render(request, "cms/volunteer.html", context)
|
||||
```
|
||||
|
||||
### 7. Add your url pattern to files/urls.py
|
||||
```
|
||||
urlpatterns = [
|
||||
url(r"^$", views.index),
|
||||
url(r"^about", views.about, name="about"),
|
||||
url(r"^volunteer", views.volunteer, name="volunteer"),
|
||||
```
|
||||
|
||||
### 8. Add your page to the left sidebar
|
||||
To add a link to your page as a menu item in the left sidebar,
|
||||
add the following code after the last line in _commons.js
|
||||
```
|
||||
/* Checks that a given selector has loaded. */
|
||||
const checkElement = async selector => {
|
||||
while ( document.querySelector(selector) === null) {
|
||||
await new Promise( resolve => requestAnimationFrame(resolve) )
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
};
|
||||
|
||||
/* Checks that sidebar nav menu has loaded, then adds menu item. */
|
||||
checkElement('.nav-menu')
|
||||
.then((element) => {
|
||||
(function(){
|
||||
var a = document.createElement('a');
|
||||
a.href = "/volunteer";
|
||||
a.title = "Volunteer";
|
||||
|
||||
var s = document.createElement('span');
|
||||
s.className = "menu-item-icon";
|
||||
|
||||
var icon = document.createElement('i');
|
||||
icon.className = "material-icons";
|
||||
icon.setAttribute("data-icon", "people");
|
||||
|
||||
s.appendChild(icon);
|
||||
a.appendChild(s);
|
||||
|
||||
var linkText = document.createTextNode("Volunteer");
|
||||
var t = document.createElement('span');
|
||||
|
||||
t.appendChild(linkText);
|
||||
a.appendChild(t);
|
||||
|
||||
var listItem = document.createElement('li');
|
||||
listItem.className = "link-item";
|
||||
listItem.appendChild(a);
|
||||
|
||||
//if signed out use 3rd nav-menu
|
||||
var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
|
||||
var loc = elem.innerText;
|
||||
if (loc.includes("About")){
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
} else { //if signed in use 4th nav-menu
|
||||
elem = document.querySelector(".nav-menu:nth-child(4) nav ul");
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
}
|
||||
})();
|
||||
});
|
||||
```
|
||||
|
||||
### 9. Restart the mediacms web server
|
||||
On docker:
|
||||
```
|
||||
sudo docker stop mediacms_web_1 && sudo docker start mediacms_web_1
|
||||
```
|
||||
|
||||
Otherwise
|
||||
```
|
||||
sudo systemctl restart mediacms
|
||||
```
|
||||
|
||||
|
||||
## 14. Add Google Analytics
|
||||
Instructions contributed by @alberto98fx
|
||||
|
||||
1. Create a file:
|
||||
|
||||
``` touch $DIR/mediacms/templates/tracking.html ```
|
||||
|
||||
2. Add the Gtag/Analytics script
|
||||
|
||||
3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
|
||||
|
||||
```
|
||||
<head>
|
||||
{% block head %}
|
||||
|
||||
<title>{% block headtitle %}{{PORTAL_NAME}}{% endblock headtitle %}</title>
|
||||
|
||||
{% include "common/head-meta.html" %}
|
||||
|
||||
{% block headermeta %}
|
||||
|
||||
<meta property="og:title" content="{{PORTAL_NAME}}">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
{%endblock headermeta %}
|
||||
|
||||
{% block externallinks %}{% endblock externallinks %}
|
||||
|
||||
{% include "common/head-links.html" %}
|
||||
|
||||
{% block topimports %}{%endblock topimports %}
|
||||
|
||||
{% include "config/index.html" %}
|
||||
|
||||
{% endblock head %}
|
||||
|
||||
</head>
|
||||
```
|
||||
|
||||
4. Add ``` {% include "tracking.html" %} ``` at the end inside the section ```<head>```
|
||||
|
||||
5. If you are using Docker and didn't mount the entire dir you need to bind a new volume:
|
||||
```
|
||||
|
||||
web:
|
||||
image: mediacms/mediacms:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./templates/root.html:/home/mediacms.io/mediacms/templates/root.html
|
||||
- ./templates/tracking.html://home/mediacms.io/mediacms/templates/tracking.html
|
||||
|
||||
```
|
||||
|
||||
## 15. Debugging email issues
|
||||
On the [Configuration](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#5-configuration) section of this guide we've see how to edit the email settings.
|
||||
In case we are yet unable to receive email from MediaCMS, the following may help us debug the issue - in most cases it is an issue of setting the correct username, password or TLS option
|
||||
|
||||
Enter the Django shell, example if you're using the Single Server installation:
|
||||
|
||||
```bash
|
||||
source /home/mediacms.io/bin/activate
|
||||
python manage.py shell
|
||||
```
|
||||
|
||||
and inside the shell
|
||||
|
||||
```bash
|
||||
from django.core.mail import EmailMessage
|
||||
from django.conf import settings
|
||||
|
||||
settings.EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
|
||||
email = EmailMessage(
|
||||
'title',
|
||||
'msg',
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
['recipient@email.com'],
|
||||
)
|
||||
email.send(fail_silently=False)
|
||||
```
|
||||
|
||||
You have the chance to either receive the email (in this case it will be sent to recipient@email.com) otherwise you will see the error.
|
||||
For example, while specifying wrong password for my Gmail account I get
|
||||
|
||||
```
|
||||
SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted. Learn more at\n5.7.8 https://support.google.com/mail/?p=BadCredentials d4sm12687785wrc.34 - gsmtp')
|
||||
```
|
||||
|
||||
## 16. Frequently Asked Questions
|
||||
Video is playing but preview thumbnails are not showing for large video files
|
||||
|
||||
Chances are that the sprites file was not created correctly.
|
||||
The output of files.tasks.produce_sprite_from_video() function in this case is something like this
|
||||
|
||||
```
|
||||
convert-im6.q16: width or height exceeds limit `/tmp/img001.jpg' @ error/cache.c/OpenPixelCache/3912.
|
||||
```
|
||||
|
||||
Solution: edit file `/etc/ImageMagick-6/policy.xml` and set bigger values for the lines that contain width and height. For example
|
||||
|
||||
```
|
||||
<policy domain="resource" name="height" value="16000KP"/>
|
||||
<policy domain="resource" name="width" value="16000KP"/>
|
||||
```
|
||||
|
||||
Newly added video files now will be able to produce the sprites file needed for thumbnail previews. To re-run that task on existing videos, enter the Django shell
|
||||
|
||||
|
||||
```
|
||||
root@8433f923ccf5:/home/mediacms.io/mediacms# source /home/mediacms.io/bin/activate
|
||||
root@8433f923ccf5:/home/mediacms.io/mediacms# python manage.py shell
|
||||
Python 3.8.14 (default, Sep 13 2022, 02:23:58)
|
||||
```
|
||||
|
||||
and run
|
||||
|
||||
```
|
||||
In [1]: from files.models import Media
|
||||
In [2]: from files.tasks import produce_sprite_from_video
|
||||
|
||||
In [3]: for media in Media.objects.filter(media_type='video', sprites=''):
|
||||
...: produce_sprite_from_video(media.friendly_token)
|
||||
```
|
||||
|
||||
this will re-create the sprites for videos that the task failed.
|
||||
|
||||
|
||||
## 17. Cookie consent code
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
## 19. Rounded corners on videos
|
||||
|
||||
By default the video player and media items are now having rounded corners, on larger screens (not in mobile). If you don't like this change, remove the `border-radius` added on the following files:
|
||||
|
||||
```
|
||||
frontend/src/static/css/_extra.css
|
||||
frontend/src/static/js/components/list-item/Item.scss
|
||||
frontend/src/static/js/components/media-page/MediaPage.scss
|
||||
```
|
||||
you now have to re-run the frontend build in order to see the changes (check docs/dev_exp.md)
|
||||
|
||||
|
||||
## 20. Translations
|
||||
|
||||
### 20.1 Set a default language
|
||||
|
||||
By default MediaCMS is available in a number of languages. To set the default language, edit `settings.py` and set LANGUAGE_CODE to the code of one of the languages.
|
||||
|
||||
### 20.2 Remove existing languages
|
||||
To limit the number of languages that are shown as available, remove them from the LANGUAGES list in `settings.py` or comment them. Only what is there is shown.
|
||||
|
||||
### 20.3 Improve existing translation
|
||||
To make improvements in existing translated content, in a language that is already translated, check the language by the code name in `files/frontend-translations/` and edit the corresponding file.
|
||||
|
||||
### 20.4 Add more content to existing translation
|
||||
Not all text is translated, so at any time you may find strings missing that need to be added to the translation. The idea here is that
|
||||
|
||||
a) you made the text as translatable, in the code
|
||||
b) you add the translated string
|
||||
|
||||
For a), you have to see if the string to be translated lives in the frontend directory (React app) or on the Django templates. There are examples for both.
|
||||
1. the Django templates, which is found in templates/ dir. Have a look on `templates/cms/about.html` to see an example of how it is done
|
||||
2. the frontend code (React), have a look how `translateString` is used in `frontend`
|
||||
|
||||
|
||||
After the string is marked as translatable, add the string to `files/frontend-translations/en.py` first, and then run
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
89
docs/dev_exp.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Developer Experience
|
||||
There is ongoing effort to provide a better developer experience and document it.
|
||||
|
||||
## How to develop locally with Docker
|
||||
First install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
Then run `docker-compose -f docker-compose-dev.yaml up`
|
||||
|
||||
```
|
||||
user@user:~/mediacms$ docker-compose -f docker-compose-dev.yaml up
|
||||
```
|
||||
|
||||
In a few minutes the app will be available at http://localhost . Login via admin/admin
|
||||
|
||||
### What does docker-compose-dev.yaml do?
|
||||
It build the two images used for backend and frontend.
|
||||
|
||||
* Backend: `mediacms/mediacms-dev:latest`
|
||||
* Frontend: `frontend`
|
||||
|
||||
and will start all services required for MediaCMS, as Celery/Redis for asynchronous tasks, PostgreSQL database, Django and React
|
||||
|
||||
For Django, the changes from the image produced by docker-compose.yaml are these:
|
||||
|
||||
* Django runs in debug mode, with `python manage.py runserver`
|
||||
* uwsgi and nginx are not run
|
||||
* Django runs in Debug mode, with Debug Toolbar
|
||||
* Static files (js/css) are loaded from static/ folder
|
||||
* corsheaders is installed and configured to allow all origins
|
||||
|
||||
For React, it will run `npm start` in the frontend folder, which will start the development server.
|
||||
Check it on http://localhost:8088/
|
||||
|
||||
### How to develop in Django
|
||||
Django starts at http://localhost and is reloading automatically. Making any change to the python code should refresh Django.
|
||||
|
||||
If Django breaks due to an error (eg SyntaxError, while editing the code), you might have to restart it
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose-dev.yaml restart web
|
||||
```
|
||||
|
||||
|
||||
|
||||
### How to develop in React
|
||||
React is started on http://localhost:8088/ , code is located in frontend/ , so making changes there should have instant effect on the page. Keep in mind that React is loading data from Django, and that it has to be built so that Django can serve it.
|
||||
|
||||
### Making changes to the frontend
|
||||
|
||||
The way React is added is more complicated than the usual SPA project and this is because React is used as a library loaded by Django Templates, so it is not a standalone project and is not handling routes etc.
|
||||
|
||||
The two directories to consider are:
|
||||
* frontend/src , for the React files
|
||||
* templates/, for the Django templates.
|
||||
|
||||
Django is using a highly intuitive hierarchical templating system (https://docs.djangoproject.com/en/4.2/ref/templates/), where the base template is templates/root.html and all other templates are extending it.
|
||||
|
||||
React is called through the Django templates, eg templates/cms/media.html is loading js/media.js
|
||||
|
||||
In order to make changes to React code, edit code on frontend/src and check it's effect on http://localhost:8088/ . Once ready, build it and copy it to the Django static folder, so that it is served by Django.
|
||||
|
||||
### Development workflow with the frontend
|
||||
1. Edit frontend/src/ files
|
||||
2. Check changes on http://localhost:8088/
|
||||
3. Build frontend with `docker-compose -f docker-compose-dev.yaml exec frontend npm run dist`
|
||||
4. Copy static files to Django static folder with`cp -r frontend/dist/static/* static/`
|
||||
5. Restart Django - `docker-compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
|
||||
6. Commit the changes
|
||||
|
||||
### Helper commands
|
||||
There is ongoing effort to provide helper commands, check the Makefile for what it supports. Eg
|
||||
|
||||
Bash into the web container:
|
||||
|
||||
```
|
||||
user@user:~/mediacms$ make admin-shell
|
||||
root@ca8c1096726b:/home/mediacms.io/mediacms# ./manage.py shell
|
||||
```
|
||||
|
||||
Build the frontend:
|
||||
|
||||
```
|
||||
user@user:~/mediacms$ make build-frontend
|
||||
docker-compose -f docker-compose-dev.yaml exec frontend npm run dist
|
||||
|
||||
> mediacms-frontend@0.9.1 dist /home/mediacms.io/mediacms/frontend
|
||||
> mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist
|
||||
...
|
||||
```
|
||||
155
docs/developers_docs.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Developers documentation
|
||||
|
||||
## Table of contents
|
||||
- [1. Welcome](#1-welcome)
|
||||
- [2. System architecture](#2-system-architecture)
|
||||
- [3. API documentation](#3-api-documentation)
|
||||
- [4. How to contribute](#4-how-to-contribute)
|
||||
- [5. Working with Docker tips](#5-working-with-docker-tips)
|
||||
- [6. Working with the automated tests](#6-working-with-the-automated-tests)
|
||||
- [7. How video is transcoded](#7-how-video-is-transcoded)
|
||||
|
||||
## 1. Welcome
|
||||
This page is created for MediaCMS developers and contains related information.
|
||||
|
||||
## 2. System architecture
|
||||
to be written
|
||||
|
||||
## 3. API documentation
|
||||
API is documented using Swagger - checkout ot http://your_installation/swagger - example https://demo.mediacms.io/swagger/
|
||||
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
|
||||
|
||||
|
||||
An example of working with Python requests library:
|
||||
|
||||
```
|
||||
import requests
|
||||
|
||||
auth = ('user' ,'password')
|
||||
upload_url = "https://domain/api/v1/media"
|
||||
title = 'x title'
|
||||
description = 'x description'
|
||||
media_file = '/tmp/file.mp4'
|
||||
|
||||
requests.post(
|
||||
url=upload_url,
|
||||
files={'media_file': open(media_file,'rb')},
|
||||
data={'title': title, 'description': description},
|
||||
auth=auth
|
||||
)
|
||||
```
|
||||
|
||||
## 4. How to contribute
|
||||
Before you send a PR, make sure your code is properly formatted. For that, use `pre-commit install` to install a pre-commit hook and run `pre-commit run --all` and fix everything before you commit. This pre-commit will check for your code lint everytime you commit a code.
|
||||
|
||||
Checkout the [Code of conduct page](../CODE_OF_CONDUCT.md) if you want to contribute to this repository
|
||||
|
||||
|
||||
## 5. Working with Docker tips
|
||||
|
||||
To perform the Docker installation, follow instructions to install Docker + Docker compose (docs/Docker_Compose.md) and then build/start docker-compose-dev.yaml . This will run the frontend application on port 8088 on top of all other containers (including the Django web application on port 80)
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose-dev.yaml build
|
||||
docker-compose -f docker-compose-dev.yaml up
|
||||
```
|
||||
|
||||
An `admin` user is created during the installation process. Its attributes are defined in `docker-compose-dev.yaml`:
|
||||
```
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_PASSWORD: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
```
|
||||
|
||||
### Frontend application changes
|
||||
Eg change `frontend/src/static/js/pages/HomePage.tsx` , dev application refreshes in a number of seconds (hot reloading) and I see the changes, once I'm happy I can run
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose-dev.yaml exec -T frontend npm run dist
|
||||
```
|
||||
|
||||
And then in order for the changes to be visible on the application while served through nginx,
|
||||
|
||||
```
|
||||
cp -r frontend/dist/static/* static/
|
||||
```
|
||||
|
||||
POST calls: cannot be performed through the dev server, you have to make through the normal application (port 80) and then see changes on the dev application on port 8088.
|
||||
Make sure the urls are set on `frontend/.env` if different than localhost
|
||||
|
||||
|
||||
Media page: need to upload content through the main application (nginx/port 80), and then use an id for page media.html, for example `http://localhost:8088/media.html?m=nc9rotyWP`
|
||||
|
||||
There are some issues with CORS too to resolve, in order for some pages to function, eg the manage comments page
|
||||
|
||||
```
|
||||
http://localhost:8088/manage-media.html manage_media
|
||||
```
|
||||
|
||||
### Backend application changes
|
||||
After I make changes to the django application (eg make a change on `files/forms.py`) in order to see the changes I have to restart the web container
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose-dev.yaml restart web
|
||||
```
|
||||
|
||||
## How video is transcoded
|
||||
|
||||
Original files get uploaded to the application server, and they get stored there as FileFields.
|
||||
|
||||
If files are videos and the duration is greater than a number (defined on settings, I think 4minutes), they are also broken in chunks, so one Encode object per chunk, for all enabled EncodeProfiles.
|
||||
|
||||
Then the workers start picking Encode objects and they transcode the chunks, so if a chunk gets transcoded correctly, the original file (the small chunk) gets replaced by the transcoded file, and the Encode object status is marked as 'success'.
|
||||
|
||||
|
||||
original.mp4 (1G, 720px)--> Encode1 (100MB, 240px, chunk=True), Encode2 (100MB, 240px, chunk=True)...EncodeXX (100MB, 720px, chunk=True) ---> when all Encode objects are success, for a resolution, they get concatenated to the original_resolution.mp4 file and this gets stored as Encode object (chunk=False). This is what is available for download.
|
||||
|
||||
Apparently the Encode object is used to store Encoded files that are served eventually (chunk=False, status='success'), but also files while they are on their way to get transcoded (chunk=True, status='pending/etc')
|
||||
|
||||
(Parenthesis opening)
|
||||
there is also an experimental small service (not commited to the repo currently) that speaks only through API and a) gets tasks to run, b) returns results. So it makes a request and receives an ffmpeg command, plus a file, it runs the ffmpeg command, and returns the result.I've used this mechanism on a number of installations to migrate existing videos through more servers/cpu and has worked with only one problem, some temporary files needed to be removed from the servers (through a periodic task, not so big problem)
|
||||
(Parenthesis closing)
|
||||
|
||||
|
||||
When the Encode object is marked as success and chunk=False, and thus is available for download/stream, there is a task that gets started and saves an HLS version of the file (1 mp4-->x number of small .ts chunks). This would be FILES_C
|
||||
|
||||
This mechanism allows for workers that have access on the same filesystem (either localhost, or through a shared network filesystem, eg NFS/EFS) to work on the same time and produce results.
|
||||
|
||||
## 6. Working with the automated tests
|
||||
|
||||
This instructions assume that you're using the docker installation
|
||||
|
||||
1. start docker-compose
|
||||
|
||||
```
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
2. Install the requirements on `requirements-dev.txt ` on web container (we'll use the web container for this)
|
||||
|
||||
```
|
||||
docker-compose exec -T web pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
3. Now you can run the existing tests
|
||||
|
||||
```
|
||||
docker-compose exec --env TESTING=True -T web pytest
|
||||
```
|
||||
|
||||
The `TESTING=True` is passed for Django to be aware this is a testing environment (so that it runs Celery tasks as functions for example and not as background tasks, since Celery is not started in the case of pytest)
|
||||
|
||||
|
||||
4. You may try a single test, by specifying the path, for example
|
||||
|
||||
```
|
||||
docker-compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
|
||||
```
|
||||
|
||||
5. You can also see the coverage
|
||||
|
||||
```
|
||||
docker-compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
|
||||
```
|
||||
|
||||
and of course...you are very welcome to help us increase it ;)
|
||||
BIN
docs/images/CC-display.png
Executable file
|
After Width: | Height: | Size: 567 KiB |
BIN
docs/images/Click-ADD-button.png
Executable file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/images/Click-Browse-button.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/Click-Download-Button.png
Executable file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
docs/images/Click-EDIT-SUBTITLE.png
Executable file
|
After Width: | Height: | Size: 509 KiB |
BIN
docs/images/Click-Edit-Media-button.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/Click-Subtitle-Language-Menu.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/images/Click-Upload-Media-button.png
Executable file
|
After Width: | Height: | Size: 502 KiB |
BIN
docs/images/Click-View-media-button.png
Executable file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
docs/images/Click-version-download.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/Continue-button.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/images/Demo1.png
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
docs/images/Demo2.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
docs/images/Demo3.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
docs/images/Edit-Media-Metadata-1.png
Executable file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/images/Edit-Media-Metadata-2.png
Executable file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/images/Mention1.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
docs/images/Mention2.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
docs/images/Mention3.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
docs/images/Mention4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/Pause-button.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/images/Processing.png
Executable file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
docs/images/Save-File.png
Executable file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/images/Select-Media-File-Click-Open.png
Executable file
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/images/Subtitles-captions1.png
Executable file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/images/Subtitles-captions2.png
Executable file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/images/Subtitles-captions3.png
Executable file
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/images/TimebarComments_Hit.png
Normal file
|
After Width: | Height: | Size: 750 KiB |
BIN
docs/images/TimebarComments_Hover.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
docs/images/Uploading.png
Executable file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
docs/images/cookie_consent.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
259
docs/user_docs.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Users documentation
|
||||
|
||||
## Table of contents
|
||||
- [Uploading media](#uploading-media)
|
||||
- [Downloading media](#downloading-media)
|
||||
- [Adding captions/subtitles](#adding-captionssubtitles)
|
||||
- [Search media](#search-media)
|
||||
- [Using Timestamps for sharing](#using-timestamps-for-sharing)
|
||||
- [Mentionning users in comments](#Mentionning-users-in-comments)
|
||||
- [Show comments in the Timebar](#Show-comments-in-the-Timebar)
|
||||
- [Share media](#share-media)
|
||||
- [Embed media](#embed-media)
|
||||
- [Customize my profile options](#customize-my-profile-options)
|
||||
|
||||
## Uploading media
|
||||
|
||||
### How to Upload Media
|
||||
|
||||
Uploading media is as simple as clicking the _Upload Media_ button, waiting for media to upload, and then clicking the media to add metadata (title, description etc.) by filling out a form.
|
||||
|
||||
#### Click Upload Button
|
||||
|
||||
Click the _Upload Media_ button from the right-side of the screen at the top:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-Upload-Media-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Upload Page
|
||||
|
||||
Clicking the _Upload Media_ button takes you to the upload page at a URL like this:
|
||||
|
||||
https://demo.mediacms.io/upload
|
||||
|
||||
#### Click Browse Button
|
||||
|
||||
Here you should click the _"Browse your files"_ button (or drag and drop a file from your desktop):
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-Browse-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Select media file and click Open button
|
||||
|
||||
Select the media file that you want to upload and click the _"Open"_ button:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Select-Media-File-Click-Open.png"/>
|
||||
</p>
|
||||
|
||||
#### Wait for file to upload
|
||||
|
||||
Wait for the file to finish uploading:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Uploading.png"/>
|
||||
</p>
|
||||
|
||||
#### Pause uploading
|
||||
|
||||
If you want you can pause upload by clicking _Pause button_:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Pause-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Continue uploading
|
||||
|
||||
Continue upload by clicking _Continue button_:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Continue-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Wait for media to finish Processing
|
||||
|
||||
Wait for the media file to finish Processing:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Processing.png"/>
|
||||
</p>
|
||||
|
||||
#### Click View media button
|
||||
|
||||
Click the View media button to open the media page:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-View-media-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Media will be in the encoding queue
|
||||
|
||||
The media will take some time to finish encoding (MediaCMS will transcode the file to several formats and resolutions). Meanwhile you can edit the media file to add metadata.
|
||||
|
||||
#### Click Edit Media button
|
||||
|
||||
Click the EDIT MEDIA button to add metadata and configure the poster image:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-Edit-Media-button.png"/>
|
||||
</p>
|
||||
|
||||
#### Add Metadata (Summary, Description etc.)
|
||||
|
||||
Make sure you fill in all the required fields, and try to complete as many of the non-required fields as possible. This ensures the database is populated with useful meta-data to help others access useful information about you and your video.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Edit-Media-Metadata-1.png"/>
|
||||
</p>
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Edit-Media-Metadata-2.png"/>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## Downloading media
|
||||
|
||||
MediaCMS offers a configurable option whereby users can make their media files available for download. Downloads are available for transcoded files, and the original file.
|
||||
|
||||
#### How To Enable Download
|
||||
|
||||
Visit the media view page and choose the EDIT MEDIA button.
|
||||
|
||||
Select the checkbox for "Allow Downloads"
|
||||
|
||||
#### How To Download Media
|
||||
|
||||
Visit the media view page and click the DOWNLOAD button below the video player.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-Download-Button.png">
|
||||
</p>
|
||||
|
||||
Choose the version you wish to download - a transcoded file or the original file:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-version-download.png">
|
||||
</p>
|
||||
|
||||
Choose Save File and click the OK button.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Save-File.png">
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## Adding captions/subtitles
|
||||
|
||||
With MediaCMS you can add subtitles/captions to your video by uploading a subtitles file in the popular .vtt format.
|
||||
|
||||
### Visit Media Page & Click EDIT SUBTITLE Button
|
||||
|
||||
Visit the "single media page" for the media you wish to add subtitles/captions to and click the EDIT SUBTITLES button:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-EDIT-SUBTITLE.png"/>
|
||||
</p>
|
||||
|
||||
### Upload Subtitles in .vtt Format
|
||||
|
||||
Click the Language menu to select the correct language for your Subtitles/Captions file:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-Subtitle-Language-Menu.png"/>
|
||||
</p>
|
||||
|
||||
Choose the correct Language for your file:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Subtitles-captions1.png"/>
|
||||
</p>
|
||||
|
||||
Click Browse to find a subtitles/captions file on your computer (if your file is not in the .vtt format, you may find a conversion service on the Internet):
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Subtitles-captions2.png"/>
|
||||
</p>
|
||||
|
||||
Choose a .vtt subtitles/captions file from your computer:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Subtitles-captions3.png"/>
|
||||
</p>
|
||||
|
||||
Click the Add button to add the file to your media:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Click-ADD-button.png"/>
|
||||
</p>
|
||||
|
||||
### View Subtitles/Captions in Video Player
|
||||
|
||||
You can now watch the captions/subtitles play back in the video player - and toggle display on/off by clicking the CC button:
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/CC-display.png"/>
|
||||
</p>
|
||||
|
||||
## Using Timestamps for sharing
|
||||
|
||||
### Using Timestamp in the URL
|
||||
|
||||
An additional GET parameter 't' can be added in video URL's to start the video at the given time. The starting time has to be given in seconds.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Demo1.png"/>
|
||||
</p>
|
||||
|
||||
Additionally the share button has an option to generate the URL with the timestamp at current second the video is.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Demo2.png"/>
|
||||
</p>
|
||||
|
||||
### Using Timestamp in the comments
|
||||
|
||||
Comments can also include timestamps. They are automatically detected upon posting the comment, and will be in the form of an hyperlink link in the comment. The timestamps in the comments have to follow the format HH:MM:SS or MM:SS
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Demo3.png"/>
|
||||
</p>
|
||||
|
||||
## Mentionning users in comments
|
||||
|
||||
Comments can also mention other users by tagging with '@'. This will open suggestion box showing usernames, and the selection will refine as the user continues typing.
|
||||
|
||||
Comments send with mentions will contain a link to the user page, and can be setup to send a mail to the mentionned user.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/Mention1.png"/>
|
||||
<img src="./images/Mention2.png"/>
|
||||
<img src="./images/Mention3.png"/>
|
||||
<img src="./images/Mention4.png"/>
|
||||
</p>
|
||||
|
||||
## Show comments in the Timebar
|
||||
|
||||
When enabled, comments including a timestamp will also be displayed in the current video Timebar as a little colorful dot. The comment can be previewed by hovering the dot (left image) and it will be displayed on top of the video when reaching the correct time (right image).
|
||||
|
||||
Only comments with correct timestamps formats (HH:MM:SS or MM:SS) will be picked up and appear in the Timebar.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/TimebarComments_Hover.png" height="180" alt="Comment preview on hover"/>
|
||||
<img src="./images/TimebarComments_Hit.png" height="180" alt="Comment shown when the timestamp is reached "/>
|
||||
</p>
|
||||
|
||||
## Search media
|
||||
How search can be used
|
||||
|
||||
## Share media
|
||||
How to share media
|
||||
|
||||
## Embed media
|
||||
How to use the embed media option
|
||||
|
||||
## Customize my profile options
|
||||
Customize profile and channel
|
||||
@@ -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)
|
||||
|
||||
@@ -15,7 +15,7 @@ class VideoEncodingError(Exception):
|
||||
|
||||
|
||||
RE_TIMECODE = re.compile(r"time=(\d+:\d+:\d+.\d+)")
|
||||
console_encoding = locale.getdefaultlocale()[1] or "UTF-8"
|
||||
console_encoding = locale.getlocale()[1] or "UTF-8"
|
||||
|
||||
|
||||
class FFmpegBackend(object):
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
from django.conf import settings
|
||||
|
||||
from .frontend_translations import get_translation, get_translation_strings
|
||||
from .methods import is_mediacms_editor, is_mediacms_manager
|
||||
|
||||
|
||||
def stuff(request):
|
||||
"""Pass settings to the frontend"""
|
||||
ret = {}
|
||||
ret["FRONTEND_HOST"] = request.build_absolute_uri('/')
|
||||
ret["FRONTEND_HOST"] = request.build_absolute_uri('/').rstrip('/')
|
||||
ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
|
||||
ret["PORTAL_NAME"] = settings.PORTAL_NAME
|
||||
ret["PORTAL_DESCRIPTION"] = settings.PORTAL_DESCRIPTION
|
||||
ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
|
||||
ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
|
||||
ret["CAN_REGISTER"] = settings.REGISTER_ALLOWED
|
||||
ret["CAN_UPLOAD_MEDIA"] = settings.UPLOAD_MEDIA_ALLOWED
|
||||
ret["TIMESTAMP_IN_TIMEBAR"] = settings.TIMESTAMP_IN_TIMEBAR
|
||||
ret["CAN_MENTION_IN_COMMENTS"] = settings.ALLOW_MENTION_IN_COMMENTS
|
||||
ret["CAN_LIKE_MEDIA"] = settings.CAN_LIKE_MEDIA
|
||||
ret["CAN_DISLIKE_MEDIA"] = settings.CAN_DISLIKE_MEDIA
|
||||
ret["CAN_REPORT_MEDIA"] = settings.CAN_REPORT_MEDIA
|
||||
@@ -28,4 +32,9 @@ def stuff(request):
|
||||
ret["ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY"] = settings.ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY
|
||||
ret["VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE"] = settings.VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE
|
||||
ret["RSS_URL"] = "/rss"
|
||||
ret["TRANSLATION"] = get_translation(request.LANGUAGE_CODE)
|
||||
ret["REPLACEMENTS"] = get_translation_strings(request.LANGUAGE_CODE)
|
||||
if request.user.is_superuser:
|
||||
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
||||
|
||||
return ret
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.search import SearchQuery
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
from django.utils.feedgenerator import Rss201rev2Feed
|
||||
|
||||
from . import helpers
|
||||
from .models import Category, Media
|
||||
from .models import Media
|
||||
from .stop_words import STOP_WORDS
|
||||
|
||||
|
||||
@@ -103,7 +102,7 @@ class SearchRSSFeed(Feed):
|
||||
description = "Latest Media RSS feed"
|
||||
|
||||
def link(self, obj):
|
||||
return f"/rss/search"
|
||||
return "/rss/search"
|
||||
|
||||
def get_object(self, request):
|
||||
category = request.GET.get("c", "")
|
||||
|
||||
60
files/frontend_translations/__init__.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
files = os.listdir(current_dir)
|
||||
translation_strings = {}
|
||||
replacement_strings = {}
|
||||
|
||||
|
||||
def check_language_code(language_code):
|
||||
# helper function
|
||||
if language_code not in [pair[0] for pair in settings.LANGUAGES]:
|
||||
return False
|
||||
if language_code in ['en', 'en-us', 'en-gb']:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
for translation_file in files:
|
||||
# the language code is zh-hans but the file is zh_hans.py
|
||||
|
||||
language_code_file = translation_file.split('.')[0]
|
||||
language_code = language_code_file.replace('_', '-')
|
||||
if not check_language_code(language_code):
|
||||
continue
|
||||
|
||||
module_name = f"files.frontend_translations.{language_code_file}"
|
||||
tr_module = importlib.import_module(module_name)
|
||||
translation_strings[language_code] = tr_module.translation_strings
|
||||
replacement_strings[language_code] = tr_module.replacement_strings
|
||||
|
||||
|
||||
def get_translation(language_code):
|
||||
# get list of translations per language
|
||||
if not check_language_code(language_code):
|
||||
return {}
|
||||
|
||||
translation = translation_strings[language_code]
|
||||
|
||||
return translation
|
||||
|
||||
|
||||
def get_translation_strings(language_code):
|
||||
# get list of replacement strings per language
|
||||
if not check_language_code(language_code):
|
||||
return {}
|
||||
|
||||
translation = replacement_strings[language_code]
|
||||
|
||||
return translation
|
||||
|
||||
|
||||
def translate_string(language_code, string):
|
||||
# translate a string to the given language
|
||||
if not check_language_code(language_code):
|
||||
return string
|
||||
|
||||
return translation_strings[language_code].get(string, string)
|
||||
104
files/frontend_translations/ar.py
Normal file
@@ -0,0 +1,104 @@
|
||||
translation_strings = {
|
||||
"ABOUT": "حول",
|
||||
"AUTOPLAY": "تشغيل تلقائي",
|
||||
"About": "حول",
|
||||
"Add a ": "أضف ",
|
||||
"COMMENT": "تعليق",
|
||||
"Categories": "الفئات",
|
||||
"Category": "الفئة",
|
||||
"Change Language": "تغيير اللغة",
|
||||
"Change password": "تغيير كلمة المرور",
|
||||
"Comment": "تعليق",
|
||||
"Comments": "تعليقات",
|
||||
"Comments are disabled": "التعليقات معطلة",
|
||||
"Contact": "اتصل",
|
||||
"DELETE MEDIA": "حذف الوسائط",
|
||||
"DOWNLOAD": "تحميل",
|
||||
"EDIT MEDIA": "تعديل الوسائط",
|
||||
"EDIT PROFILE": "تعديل الملف الشخصي",
|
||||
"EDIT SUBTITLE": "تعديل الترجمة",
|
||||
"Edit media": "تعديل الوسائط",
|
||||
"Edit profile": "تعديل الملف الشخصي",
|
||||
"Edit subtitle": "تعديل الترجمة",
|
||||
"Featured": "مميز",
|
||||
"Go": "اذهب",
|
||||
"History": "التاريخ",
|
||||
"Home": "الرئيسية",
|
||||
"Language": "اللغة",
|
||||
"Latest": "الأحدث",
|
||||
"Liked media": "الوسائط المفضلة",
|
||||
"Manage comments": "إدارة التعليقات",
|
||||
"Manage media": "إدارة الوسائط",
|
||||
"Manage users": "إدارة المستخدمين",
|
||||
"Media": "وسائط",
|
||||
"Media was edited": "تم تعديل الوسائط",
|
||||
"Members": "الأعضاء",
|
||||
"My media": "وسائطي",
|
||||
"My playlists": "قوائم التشغيل الخاصة بي",
|
||||
"No": "لا",
|
||||
"No comment yet": "لا يوجد تعليق بعد",
|
||||
"No comments yet": "لا توجد تعليقات بعد",
|
||||
"No results for": "لا توجد نتائج لـ",
|
||||
"PLAYLISTS": "قوائم التشغيل",
|
||||
"Playlists": "قوائم التشغيل",
|
||||
"Powered by": "مدعوم من",
|
||||
"Published on": "نشر في",
|
||||
"Recommended": "موصى به",
|
||||
"Register": "تسجيل",
|
||||
"SAVE": "حفظ",
|
||||
"SEARCH": "بحث",
|
||||
"SHARE": "مشاركة",
|
||||
"SHOW MORE": "عرض المزيد",
|
||||
"SUBMIT": "إرسال",
|
||||
"Search": "بحث",
|
||||
"Select": "اختر",
|
||||
"Sign in": "تسجيل الدخول",
|
||||
"Sign out": "تسجيل الخروج",
|
||||
"Subtitle was added": "تمت إضافة الترجمة",
|
||||
"Tags": "العلامات",
|
||||
"Terms": "الشروط",
|
||||
"UPLOAD": "رفع",
|
||||
"Up next": "التالي",
|
||||
"Upload": "رفع",
|
||||
"Upload media": "رفع الوسائط",
|
||||
"Uploads": "التحميلات",
|
||||
"VIEW ALL": "عرض الكل",
|
||||
"View all": "عرض الكل",
|
||||
"comment": "تعليق",
|
||||
"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": "هو نظام إدارة محتوى فيديو ووسائط مفتوح المصدر وحديث ومتكامل. تم تطويره لتلبية احتياجات المنصات الويب الحديثة لمشاهدة ومشاركة الوسائط",
|
||||
"media in category": "وسائط في الفئة",
|
||||
"media in tag": "وسائط في العلامة",
|
||||
"view": "عرض",
|
||||
"views": "مشاهدات",
|
||||
"yet": "بعد",
|
||||
}
|
||||
|
||||
replacement_strings = {
|
||||
"Apr": "أبريل",
|
||||
"Aug": "أغسطس",
|
||||
"Dec": "ديسمبر",
|
||||
"Feb": "فبراير",
|
||||
"Jan": "يناير",
|
||||
"Jul": "يوليو",
|
||||
"Jun": "يونيو",
|
||||
"Mar": "مارس",
|
||||
"May": "مايو",
|
||||
"Nov": "نوفمبر",
|
||||
"Oct": "أكتوبر",
|
||||
"Sep": "سبتمبر",
|
||||
"day ago": "منذ يوم",
|
||||
"days ago": "منذ أيام",
|
||||
"hour ago": "منذ ساعة",
|
||||
"hours ago": "منذ ساعات",
|
||||
"just now": "الآن",
|
||||
"minute ago": "منذ دقيقة",
|
||||
"minutes ago": "منذ دقائق",
|
||||
"month ago": "منذ شهر",
|
||||
"months ago": "منذ شهور",
|
||||
"second ago": "منذ ثانية",
|
||||
"seconds ago": "منذ ثوان",
|
||||
"week ago": "منذ أسبوع",
|
||||
"weeks ago": "منذ أسابيع",
|
||||
"year ago": "منذ سنة",
|
||||
"years ago": "منذ سنوات",
|
||||
}
|
||||
104
files/frontend_translations/bn.py
Normal file
@@ -0,0 +1,104 @@
|
||||
translation_strings = {
|
||||
"ABOUT": "সম্পর্কে",
|
||||
"AUTOPLAY": "স্বয়ংক্রিয় প্লে",
|
||||
"About": "সম্পর্কে",
|
||||
"Add a ": "যোগ করুন",
|
||||
"COMMENT": "মন্তব্য",
|
||||
"Categories": "বিভাগসমূহ",
|
||||
"Category": "বিভাগ",
|
||||
"Change Language": "ভাষা পরিবর্তন করুন",
|
||||
"Change password": "পাসওয়ার্ড পরিবর্তন করুন",
|
||||
"Comment": "মন্তব্য",
|
||||
"Comments": "মন্তব্যসমূহ",
|
||||
"Comments are disabled": "মন্তব্য নিষ্ক্রিয় করা হয়েছে",
|
||||
"Contact": "যোগাযোগ",
|
||||
"DELETE MEDIA": "মিডিয়া মুছুন",
|
||||
"DOWNLOAD": "ডাউনলোড",
|
||||
"EDIT MEDIA": "মিডিয়া সম্পাদনা করুন",
|
||||
"EDIT PROFILE": "প্রোফাইল সম্পাদনা করুন",
|
||||
"EDIT SUBTITLE": "সাবটাইটেল সম্পাদনা করুন",
|
||||
"Edit media": "মিডিয়া সম্পাদনা করুন",
|
||||
"Edit profile": "প্রোফাইল সম্পাদনা করুন",
|
||||
"Edit subtitle": "সাবটাইটেল সম্পাদনা করুন",
|
||||
"Featured": "বৈশিষ্ট্যযুক্ত",
|
||||
"Go": "যাও",
|
||||
"History": "ইতিহাস",
|
||||
"Home": "বাড়ি",
|
||||
"Language": "ভাষা",
|
||||
"Latest": "সর্বশেষ",
|
||||
"Liked media": "পছন্দের মিডিয়া",
|
||||
"Manage comments": "মন্তব্য পরিচালনা করুন",
|
||||
"Manage media": "মিডিয়া পরিচালনা করুন",
|
||||
"Manage users": "ব্যবহারকারীদের পরিচালনা করুন",
|
||||
"Media": "মিডিয়া",
|
||||
"Media was edited": "মিডিয়া সম্পাদিত হয়েছে",
|
||||
"Members": "সদস্যরা",
|
||||
"My media": "আমার মিডিয়া",
|
||||
"My playlists": "আমার প্লেলিস্ট",
|
||||
"No": "না",
|
||||
"No comment yet": "এখনও কোন মন্তব্য নেই",
|
||||
"No comments yet": "এখনও কোন মন্তব্য নেই",
|
||||
"No results for": "এর জন্য কোন ফলাফল নেই",
|
||||
"PLAYLISTS": "প্লেলিস্ট",
|
||||
"Playlists": "প্লেলিস্ট",
|
||||
"Powered by": "দ্বারা চালিত",
|
||||
"Published on": "প্রকাশিত",
|
||||
"Recommended": "প্রস্তাবিত",
|
||||
"Register": "নিবন্ধন করুন",
|
||||
"SAVE": "সংরক্ষণ করুন",
|
||||
"SEARCH": "অনুসন্ধান",
|
||||
"SHARE": "শেয়ার করুন",
|
||||
"SHOW MORE": "আরও দেখুন",
|
||||
"SUBMIT": "জমা দিন",
|
||||
"Search": "অনুসন্ধান",
|
||||
"Select": "নির্বাচন করুন",
|
||||
"Sign in": "সাইন ইন করুন",
|
||||
"Sign out": "সাইন আউট করুন",
|
||||
"Subtitle was added": "সাবটাইটেল যোগ করা হয়েছে",
|
||||
"Tags": "ট্যাগ",
|
||||
"Terms": "শর্তাবলী",
|
||||
"UPLOAD": "আপলোড করুন",
|
||||
"Up next": "পরবর্তী",
|
||||
"Upload": "আপলোড করুন",
|
||||
"Upload media": "মিডিয়া আপলোড করুন",
|
||||
"Uploads": "আপলোডসমূহ",
|
||||
"VIEW ALL": "সব দেখুন",
|
||||
"View all": "সব দেখুন",
|
||||
"comment": "মন্তব্য",
|
||||
"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": "একটি আধুনিক, সম্পূর্ণ বৈশিষ্ট্যযুক্ত ওপেন সোর্স ভিডিও এবং মিডিয়া CMS। এটি আধুনিক ওয়েব প্ল্যাটফর্মের জন্য মিডিয়া দেখার এবং শেয়ার করার প্রয়োজন মেটাতে তৈরি করা হয়েছে",
|
||||
"media in category": "বিভাগে মিডিয়া",
|
||||
"media in tag": "ট্যাগে মিডিয়া",
|
||||
"view": "দেখুন",
|
||||
"views": "দেখা হয়েছে",
|
||||
"yet": "এখনও",
|
||||
}
|
||||
|
||||
replacement_strings = {
|
||||
"Apr": "এপ্রিল",
|
||||
"Aug": "আগস্ট",
|
||||
"Dec": "ডিসেম্বর",
|
||||
"Feb": "ফেব্রু",
|
||||
"Jan": "জানু",
|
||||
"Jul": "জুলাই",
|
||||
"Jun": "জুন",
|
||||
"Mar": "মার্চ",
|
||||
"May": "মে",
|
||||
"Nov": "নভেম্বর",
|
||||
"Oct": "অক্টোবর",
|
||||
"Sep": "সেপ্টেম্বর",
|
||||
"day ago": "দিন আগে",
|
||||
"days ago": "দিন আগে",
|
||||
"hour ago": "ঘণ্টা আগে",
|
||||
"hours ago": "ঘণ্টা আগে",
|
||||
"just now": "এখনই",
|
||||
"minute ago": "মিনিট আগে",
|
||||
"minutes ago": "মিনিট আগে",
|
||||
"month ago": "মাস আগে",
|
||||
"months ago": "মাস আগে",
|
||||
"second ago": "সেকেন্ড আগে",
|
||||
"seconds ago": "সেকেন্ড আগে",
|
||||
"week ago": "সপ্তাহ আগে",
|
||||
"weeks ago": "সপ্তাহ আগে",
|
||||
"year ago": "বছর আগে",
|
||||
"years ago": "বছর আগে",
|
||||
}
|
||||
104
files/frontend_translations/de.py
Normal file
@@ -0,0 +1,104 @@
|
||||
translation_strings = {
|
||||
"ABOUT": "Über",
|
||||
"AUTOPLAY": "Automatische Wiedergabe",
|
||||
"About": "Über",
|
||||
"Add a ": "Hinzufügen eines ",
|
||||
"COMMENT": "KOMMENTAR",
|
||||
"Categories": "Kategorien",
|
||||
"Category": "Kategorie",
|
||||
"Change Language": "Sprache ändern",
|
||||
"Change password": "Passwort ändern",
|
||||
"Comment": "Kommentar",
|
||||
"Comments": "Kommentare",
|
||||
"Comments are disabled": "Kommentare sind deaktiviert",
|
||||
"Contact": "Kontakt",
|
||||
"DELETE MEDIA": "MEDIEN LÖSCHEN",
|
||||
"DOWNLOAD": "HERUNTERLADEN",
|
||||
"EDIT MEDIA": "MEDIEN BEARBEITEN",
|
||||
"EDIT PROFILE": "PROFIL BEARBEITEN",
|
||||
"EDIT SUBTITLE": "UNTERTITEL BEARBEITEN",
|
||||
"Edit media": "Medien bearbeiten",
|
||||
"Edit profile": "Profil bearbeiten",
|
||||
"Edit subtitle": "Untertitel bearbeiten",
|
||||
"Featured": "Empfohlen",
|
||||
"Go": "Los",
|
||||
"History": "Verlauf",
|
||||
"Home": "Startseite",
|
||||
"Language": "Sprache",
|
||||
"Latest": "Neueste",
|
||||
"Liked media": "Beliebte Medien",
|
||||
"Manage comments": "Kommentare verwalten",
|
||||
"Manage media": "Medien verwalten",
|
||||
"Manage users": "Benutzer verwalten",
|
||||
"Media": "Medien",
|
||||
"Media was edited": "Medien wurden bearbeitet",
|
||||
"Members": "Mitglieder",
|
||||
"My media": "Meine Medien",
|
||||
"My playlists": "Meine Playlists",
|
||||
"No": "Nein",
|
||||
"No comment yet": "Noch kein Kommentar",
|
||||
"No comments yet": "Noch keine Kommentare",
|
||||
"No results for": "Keine Ergebnisse für",
|
||||
"PLAYLISTS": "PLAYLISTS",
|
||||
"Playlists": "Playlists",
|
||||
"Powered by": "Bereitgestellt von",
|
||||
"Published on": "Veröffentlicht am",
|
||||
"Recommended": "Empfohlen",
|
||||
"Register": "Registrieren",
|
||||
"SAVE": "SPEICHERN",
|
||||
"SEARCH": "SUCHE",
|
||||
"SHARE": "TEILEN",
|
||||
"SHOW MORE": "MEHR ANZEIGEN",
|
||||
"SUBMIT": "ABSENDEN",
|
||||
"Search": "Suche",
|
||||
"Select": "Auswählen",
|
||||
"Sign in": "Anmelden",
|
||||
"Sign out": "Abmelden",
|
||||
"Subtitle was added": "Untertitel wurde hinzugefügt",
|
||||
"Tags": "Tags",
|
||||
"Terms": "Bedingungen",
|
||||
"UPLOAD": "HOCHLADEN",
|
||||
"Up next": "Als nächstes",
|
||||
"Upload": "Hochladen",
|
||||
"Upload media": "Medien hochladen",
|
||||
"Uploads": "Uploads",
|
||||
"VIEW ALL": "ALLE ANZEIGEN",
|
||||
"View all": "Alle anzeigen",
|
||||
"comment": "Kommentar",
|
||||
"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": "ist ein modernes, voll ausgestattetes Open-Source-Video- und Medien-CMS. Es wurde entwickelt, um den Anforderungen moderner Webplattformen für das Ansehen und Teilen von Medien gerecht zu werden",
|
||||
"media in category": "Medien in Kategorie",
|
||||
"media in tag": "Medien in Tag",
|
||||
"view": "Ansicht",
|
||||
"views": "Ansichten",
|
||||
"yet": "noch",
|
||||
}
|
||||
|
||||
replacement_strings = {
|
||||
"Apr": "Apr",
|
||||
"Aug": "Aug",
|
||||
"Dec": "Dez",
|
||||
"Feb": "Feb",
|
||||
"Jan": "Jan",
|
||||
"Jul": "Jul",
|
||||
"Jun": "Jun",
|
||||
"Mar": "Mär",
|
||||
"May": "Mai",
|
||||
"Nov": "Nov",
|
||||
"Oct": "Okt",
|
||||
"Sep": "Sep",
|
||||
"day ago": "vor einem Tag",
|
||||
"days ago": "vor Tagen",
|
||||
"hour ago": "vor einer Stunde",
|
||||
"hours ago": "vor Stunden",
|
||||
"just now": "gerade eben",
|
||||
"minute ago": "vor einer Minute",
|
||||
"minutes ago": "vor Minuten",
|
||||
"month ago": "vor einem Monat",
|
||||
"months ago": "vor Monaten",
|
||||
"second ago": "vor einer Sekunde",
|
||||
"seconds ago": "vor Sekunden",
|
||||
"week ago": "vor einer Woche",
|
||||
"weeks ago": "vor Wochen",
|
||||
"year ago": "vor einem Jahr",
|
||||
"years ago": "vor Jahren",
|
||||
}
|
||||
104
files/frontend_translations/el.py
Normal file
@@ -0,0 +1,104 @@
|
||||
translation_strings = {
|
||||
"ABOUT": "ΣΧΕΤΙΚΑ",
|
||||
"AUTOPLAY": "Αυτόματη αναπαραγωγή",
|
||||
"About": "Σχετικά",
|
||||
"Add a ": "Προσθέστε ένα ",
|
||||
"COMMENT": "ΣΧΟΛΙΟ",
|
||||
"Categories": "Κατηγορίες",
|
||||
"Category": "Κατηγορία",
|
||||
"Change Language": "Αλλαγή Γλώσσας",
|
||||
"Change password": "Αλλαγή κωδικού",
|
||||
"Comment": "Σχόλιο",
|
||||
"Comments": "Σχόλια",
|
||||
"Comments are disabled": "Τα σχόλια είναι απενεργοποιημένα",
|
||||
"Contact": "Επικοινωνία",
|
||||
"DELETE MEDIA": "ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΟΥ",
|
||||
"DOWNLOAD": "ΚΑΤΕΒΑΣΜΑ",
|
||||
"EDIT MEDIA": "ΕΠΕΞΕΡΓΑΣΙΑ ΑΡΧΕΙΟΥ",
|
||||
"EDIT PROFILE": "ΕΠΕΞΕΡΓΑΣΙΑ ΠΡΟΦΙΛ",
|
||||
"EDIT SUBTITLE": "ΕΠΕΞΕΡΓΑΣΙΑ ΥΠΟΤΙΤΛΩΝ",
|
||||
"Edit media": "Επεξεργασία αρχείου",
|
||||
"Edit profile": "Επεξεργασία προφιλ",
|
||||
"Edit subtitle": "Επεξεργασία υποτίτλων",
|
||||
"Featured": "Επιλεγμένα",
|
||||
"Go": "Πήγαινε",
|
||||
"History": "Ιστορικό",
|
||||
"Home": "Αρχική",
|
||||
"Language": "Γλώσσα",
|
||||
"Latest": "Πρόσφατα",
|
||||
"Liked media": "Αγαπημένα",
|
||||
"Manage comments": "Διαχείριση σχολίων",
|
||||
"Manage media": "Διαχείριση αρχείων",
|
||||
"Manage users": "Διαχείριση χρηστών",
|
||||
"Media": "Αρχεία",
|
||||
"Media was edited": "Το αρχείο επεξεργάστηκε",
|
||||
"Members": "Μέλη",
|
||||
"My media": "Τα αρχεία μου",
|
||||
"My playlists": "Οι λίστες μου",
|
||||
"No": "Όχι",
|
||||
"No comment yet": "Δεν υπάρχει ακόμα σχόλιο",
|
||||
"No comments yet": "Δεν υπάρχουν ακόμα σχόλια",
|
||||
"No results for": "Δεν υπάρχουν αποτελέσματα για",
|
||||
"PLAYLISTS": "ΛΙΣΤΕΣ",
|
||||
"Playlists": "Λίστες",
|
||||
"Powered by": "Υποστηρίζεται από το",
|
||||
"Published on": "Δημοσιεύτηκε στις",
|
||||
"Recommended": "Προτεινόμενα",
|
||||
"Register": "Εγγραφή",
|
||||
"SAVE": "ΑΠΟΘΗΚΕΥΣΗ",
|
||||
"SEARCH": "ΑΝΑΖΗΤΗΣΗ",
|
||||
"SHARE": "ΚΟΙΝΟΠΟΙΗΣΗ",
|
||||
"SHOW MORE": "ΠΕΡΙΣΣΟΤΕΡΑ",
|
||||
"SUBMIT": "ΥΠΟΒΟΛΗ",
|
||||
"Search": "Αναζήτηση",
|
||||
"Select": "Επιλογή",
|
||||
"Sign in": "Σύνδεση",
|
||||
"Sign out": "Αποσύνδεση",
|
||||
"Subtitle was added": "Οι υπότιτλοι προστέθηκαν",
|
||||
"Tags": "Ετικέτες",
|
||||
"Terms": "Όροι",
|
||||
"UPLOAD": "ΑΝΕΒΑΣΜΑ",
|
||||
"Up next": "Επόμενο",
|
||||
"Upload": "Ανέβασμα αρχείου",
|
||||
"Upload media": "Ανέβασμα αρχείων",
|
||||
"Uploads": "Ανεβάσματα",
|
||||
"VIEW ALL": "ΔΕΣ ΤΑ ΟΛΑ",
|
||||
"View all": "Δές τα όλα",
|
||||
"comment": "σχόλιο",
|
||||
"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": "είναι ένα σύγχρονο, πλήρως λειτουργικό ανοιχτού κώδικα CMS βίντεο και πολυμέσων. Αναπτύχθηκε για να καλύψει τις ανάγκες των σύγχρονων πλατφορμών ιστού για την προβολή και την κοινοποίηση πολυμέσων",
|
||||
"media in category": "αρχεία στην κατηγορία",
|
||||
"media in tag": "αρχεία με ετικέτα",
|
||||
"view": "προβολή",
|
||||
"views": "προβολές",
|
||||
"yet": "ακόμα",
|
||||
}
|
||||
|
||||
replacement_strings = {
|
||||
"Apr": "Απρ",
|
||||
"Aug": "Αυγ",
|
||||
"Dec": "Δεκ",
|
||||
"Feb": "Φεβ",
|
||||
"Jan": "Ιαν",
|
||||
"Jul": "Ιουλ",
|
||||
"Jun": "Ιουν",
|
||||
"Mar": "Μαρ",
|
||||
"May": "Μαϊ",
|
||||
"Nov": "Νοε",
|
||||
"Oct": "Οκτ",
|
||||
"Sep": "Σεπτ",
|
||||
"day ago": "μέρα πριν",
|
||||
"days ago": "μέρες πριν",
|
||||
"hour ago": "ώρα πριν",
|
||||
"hours ago": "ώρες πριν",
|
||||
"just now": "μόλις τώρα",
|
||||
"minute ago": "λεπτό πριν",
|
||||
"minutes ago": "λεπτά πριν",
|
||||
"month ago": "μήνας πριν",
|
||||
"months ago": "μήνες πριν",
|
||||
"second ago": "δευτερόλεπτο πριν",
|
||||
"seconds ago": "δευτερόλεπτα πριν",
|
||||
"week ago": "εβδομάδα πριν",
|
||||
"weeks ago": "εβδομάδες πριν",
|
||||
"year ago": "χρόνος πριν",
|
||||
"years ago": "χρόνια πριν",
|
||||
}
|
||||
104
files/frontend_translations/en.py
Normal file
@@ -0,0 +1,104 @@
|
||||
translation_strings = {
|
||||
"ABOUT": "",
|
||||
"AUTOPLAY": "",
|
||||
"Add a ": "",
|
||||
"COMMENT": "",
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"Change Language": "",
|
||||
"Change password": "",
|
||||
"About": "",
|
||||
"Comment": "",
|
||||
"Comments": "",
|
||||
"Comments are disabled": "",
|
||||
"Contact": "",
|
||||
"DELETE MEDIA": "",
|
||||
"DOWNLOAD": "",
|
||||
"EDIT MEDIA": "",
|
||||
"EDIT PROFILE": "",
|
||||
"EDIT SUBTITLE": "",
|
||||
"Edit media": "",
|
||||
"Edit profile": "",
|
||||
"Edit subtitle": "",
|
||||
"Featured": "",
|
||||
"Go": "",
|
||||
"History": "",
|
||||
"Home": "",
|
||||
"Language": "",
|
||||
"Latest": "",
|
||||
"Liked media": "",
|
||||
"Manage comments": "",
|
||||
"Manage media": "",
|
||||
"Manage users": "",
|
||||
"Media": "",
|
||||
"Media was edited": "",
|
||||
"Members": "",
|
||||
"My media": "",
|
||||
"My playlists": "",
|
||||
"No": "",
|
||||
"No comment yet": "",
|
||||
"No comments yet": "",
|
||||
"No results for": "",
|
||||
"PLAYLISTS": "",
|
||||
"Playlists": "",
|
||||
"Powered by": "",
|
||||
"Published on": "",
|
||||
"Recommended": "",
|
||||
"Register": "",
|
||||
"SAVE": "",
|
||||
"SEARCH": "",
|
||||
"SHARE": "",
|
||||
"SHOW MORE": "",
|
||||
"SUBMIT": "",
|
||||
"Search": "",
|
||||
"Select": "",
|
||||
"Sign in": "",
|
||||
"Sign out": "",
|
||||
"Subtitle was added": "",
|
||||
"Tags": "",
|
||||
"Terms": "",
|
||||
"UPLOAD": "",
|
||||
"Up next": "",
|
||||
"Upload": "",
|
||||
"Upload media": "",
|
||||
"Uploads": "",
|
||||
"VIEW ALL": "",
|
||||
"View all": "",
|
||||
"comment": "",
|
||||
"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": "",
|
||||
"media in category": "",
|
||||
"media in tag": "",
|
||||
"view": "",
|
||||
"views": "",
|
||||
"yet": "",
|
||||
}
|
||||
|
||||
replacement_strings = {
|
||||
"Apr": "",
|
||||
"Aug": "",
|
||||
"Dec": "",
|
||||
"Feb": "",
|
||||
"Jan": "",
|
||||
"Jul": "",
|
||||
"Jun": "",
|
||||
"Mar": "",
|
||||
"May": "",
|
||||
"Nov": "",
|
||||
"Oct": "",
|
||||
"Sep": "",
|
||||
"day ago": "",
|
||||
"days ago": "",
|
||||
"hour ago": "",
|
||||
"hours ago": "",
|
||||
"just now": "",
|
||||
"minute ago": "",
|
||||
"minutes ago": "",
|
||||
"month ago": "",
|
||||
"months ago": "",
|
||||
"second ago": "",
|
||||
"seconds ago": "",
|
||||
"week ago": "",
|
||||
"weeks ago": "",
|
||||
"year ago": "",
|
||||
"years ago": "",
|
||||
}
|
||||