Compare commits
326 Commits
v1.8
...
feat-lti-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48537515cb | ||
|
|
e6db138d11 | ||
|
|
2f2d32f0db | ||
|
|
f4d3439246 | ||
|
|
7fe9891942 | ||
|
|
9eb8a1ad62 | ||
|
|
23ee0dc7cc | ||
|
|
e5be39f392 | ||
|
|
f0c084fa53 | ||
|
|
571bfcc4ce | ||
|
|
c04380af47 | ||
|
|
97741f780e | ||
|
|
78cce0eb10 | ||
|
|
472b3029c4 | ||
|
|
343f1e7009 | ||
|
|
8c78b67b0c | ||
|
|
29fc7fb861 | ||
|
|
b03a33d93e | ||
|
|
64472be406 | ||
|
|
cc0f4d4645 | ||
|
|
095e4d2cb4 | ||
|
|
5c8978453e | ||
|
|
83189076e4 | ||
|
|
ca6dbf3740 | ||
|
|
8646bd70dc | ||
|
|
1f493c8e15 | ||
|
|
e11cb7ea6e | ||
|
|
3131e76ef7 | ||
|
|
809cdccc42 | ||
|
|
ed36240f45 | ||
|
|
77bafff6f6 | ||
|
|
f6252f4f77 | ||
|
|
764580287f | ||
|
|
ce6c9a0a3c | ||
|
|
1ced023a07 | ||
|
|
981fec296c | ||
|
|
40cd7916e7 | ||
|
|
bcef59c3a9 | ||
|
|
e93c8225c4 | ||
|
|
5c3c33ca84 | ||
|
|
7a954e7a3d | ||
|
|
8610df0c2b | ||
|
|
8ab9030d14 | ||
|
|
15c8dec041 | ||
|
|
9af4686bd4 | ||
|
|
bcc8a0858c | ||
|
|
549b672d48 | ||
|
|
abe950f1da | ||
|
|
5fecda02d6 | ||
|
|
3c6f8c102c | ||
|
|
2d28520cd4 | ||
|
|
4bd56da2d8 | ||
|
|
fdfa857794 | ||
|
|
2c1f27c0be | ||
|
|
2f0bbd2533 | ||
|
|
1c15880ae3 | ||
|
|
54336f6c31 | ||
|
|
37e21f7ebf | ||
|
|
3deee80dd0 | ||
|
|
2e57164831 | ||
|
|
de0c16729b | ||
|
|
2c0bba1427 | ||
|
|
54a8e41f6d | ||
|
|
78fb19b464 | ||
|
|
8e5e7991b7 | ||
|
|
5cf435eca0 | ||
|
|
5026ce73da | ||
|
|
8b2ebe2415 | ||
|
|
8df320e134 | ||
|
|
8c8f737460 | ||
|
|
995faedb08 | ||
|
|
bde300b4bd | ||
|
|
fd5c0a2908 | ||
|
|
9c145da2e2 | ||
|
|
e9e5d44c3e | ||
|
|
a624c2e5b8 | ||
|
|
748d3b39ba | ||
|
|
ddc6bf9e67 | ||
|
|
aa7dbfe534 | ||
|
|
5cc72357c6 | ||
|
|
01b061a47b | ||
|
|
fbc78e7944 | ||
|
|
9e7a8afdda | ||
|
|
5572a67019 | ||
|
|
610590972f | ||
|
|
bdf7d3c2d0 | ||
|
|
a47bf5a3f8 | ||
|
|
38caea3c7c | ||
|
|
30491bf420 | ||
|
|
d0ebe19c2a | ||
|
|
59be9f16c0 | ||
|
|
a2d898c54e | ||
|
|
9733d53c0b | ||
|
|
70e2c67f3d | ||
|
|
77721d9c0e | ||
|
|
06bc64b2c4 | ||
|
|
b9899476b9 | ||
|
|
107750406e | ||
|
|
ae4ae5a07e | ||
|
|
f346a5604c | ||
|
|
56026a1a96 | ||
|
|
a88413ce14 | ||
|
|
9dab3ad858 | ||
|
|
dfe7e8fab0 | ||
|
|
1181d16ab9 | ||
|
|
d032ee3baa | ||
|
|
93f66d206b | ||
|
|
0585513439 | ||
|
|
9667e6b0ad | ||
|
|
f56948a4a2 | ||
|
|
8b3e76b554 | ||
|
|
dc417de628 | ||
|
|
35cd56c85c | ||
|
|
f0b2451815 | ||
|
|
7696251394 | ||
|
|
b95725660b | ||
|
|
d6bf98b30e | ||
|
|
3baa8ef7d7 | ||
|
|
45246eac4f | ||
|
|
9685c1b5d4 | ||
|
|
20a1da22bb | ||
|
|
f9a94321ad | ||
|
|
f85299a600 | ||
|
|
29ab2a715b | ||
|
|
43ce685f08 | ||
|
|
8c682a76af | ||
|
|
ec6b6daa81 | ||
|
|
cf90169240 | ||
|
|
fb3f377e27 | ||
|
|
f5f9a7beac | ||
|
|
726a5b74a1 | ||
|
|
40c31f295a | ||
|
|
1d77293afc | ||
|
|
5c702387ca | ||
|
|
0001f370a9 | ||
|
|
af71d4c906 | ||
|
|
eb7503125d | ||
|
|
f897d0ba2b | ||
|
|
545cca154e | ||
|
|
ef4ff9cb1d | ||
|
|
3a40fc6d88 | ||
|
|
f67d2a4d78 | ||
|
|
295578dae2 | ||
|
|
ed5cfa1a84 | ||
|
|
2fe48d8522 | ||
|
|
90331f3b4a | ||
|
|
c57f528ab1 | ||
|
|
fa67ffffb4 | ||
|
|
872571350f | ||
|
|
665971856b | ||
|
|
d9b1d6cab1 | ||
|
|
aeef8284bf | ||
|
|
a90fcbf8dd | ||
|
|
1b3cdfd302 | ||
|
|
cd7dd4f72c | ||
|
|
9b3d9fe1e7 | ||
|
|
ea340b6a2e | ||
|
|
ba2c31b1e6 | ||
|
|
5eb6fafb8c | ||
|
|
c035bcddf5 | ||
|
|
01912ea1f9 | ||
|
|
d9f299af4d | ||
|
|
e80590a3aa | ||
|
|
2a0cb977f2 | ||
|
|
a5e6e7b9ca | ||
|
|
b39072c8ae | ||
|
|
f4ab60e894 | ||
|
|
8656b40c5b | ||
|
|
553a25a86f | ||
|
|
1c1af489f1 | ||
|
|
c4c5ecf06a | ||
|
|
725cc71960 | ||
|
|
0c1c5bbb09 | ||
|
|
56182f0a6d | ||
|
|
208f0b338b | ||
|
|
cbef629baf | ||
|
|
8e8454d8c2 | ||
|
|
8d982ace92 | ||
|
|
6cee02085c | ||
|
|
e33aa17911 | ||
|
|
a8db23f204 | ||
|
|
d6428e3334 | ||
|
|
fd342fd1d6 | ||
|
|
7a1b32f1ba | ||
|
|
817e16ac60 | ||
|
|
8cbeb72dd2 | ||
|
|
e9f862a0ff | ||
|
|
02eac68b51 | ||
|
|
e790795bfd | ||
|
|
de99d84c18 | ||
|
|
8aa89c0958 | ||
|
|
df98b65704 | ||
|
|
a607996bfa | ||
|
|
79f2e2bb11 | ||
|
|
d54732040a | ||
|
|
e8520bc7cd | ||
|
|
b6e46e7b62 | ||
|
|
36eab954bd | ||
|
|
610716533b | ||
|
|
4f1c4a2b4c | ||
|
|
83f3eec940 | ||
|
|
a5acce4ab1 | ||
|
|
a4e9309350 | ||
|
|
6beaf0bbe2 | ||
|
|
70168299ba | ||
|
|
b28c2d8271 | ||
|
|
d34fc328bf | ||
|
|
ab4d9d67df | ||
|
|
f7a2f049bd | ||
|
|
05414f66c7 | ||
|
|
8fecccce1c | ||
|
|
2a7123ca0b | ||
|
|
20f305e69e | ||
|
|
d1fda05fdc | ||
|
|
a02e0a8a66 | ||
|
|
21f76dbb6e | ||
|
|
50e9f3103f | ||
|
|
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 |
@@ -1,2 +1,69 @@
|
|||||||
node_modules
|
# Node.js/JavaScript dependencies and artifacts
|
||||||
npm-debug.log
|
**/node_modules
|
||||||
|
**/npm-debug.log*
|
||||||
|
**/yarn-debug.log*
|
||||||
|
**/yarn-error.log*
|
||||||
|
**/.yarn/cache
|
||||||
|
**/.yarn/unplugged
|
||||||
|
**/package-lock.json
|
||||||
|
**/.npm
|
||||||
|
**/.cache
|
||||||
|
**/.parcel-cache
|
||||||
|
**/dist
|
||||||
|
**/build
|
||||||
|
**/*.tsbuildinfo
|
||||||
|
|
||||||
|
# Python bytecode and cache
|
||||||
|
**/__pycache__
|
||||||
|
**/*.py[cod]
|
||||||
|
**/*$py.class
|
||||||
|
**/*.so
|
||||||
|
**/.Python
|
||||||
|
**/pip-log.txt
|
||||||
|
**/pip-delete-this-directory.txt
|
||||||
|
**/.pytest_cache
|
||||||
|
**/.coverage
|
||||||
|
**/htmlcov
|
||||||
|
**/.tox
|
||||||
|
**/.mypy_cache
|
||||||
|
**/.ruff_cache
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.gitattributes
|
||||||
|
|
||||||
|
# IDE and editor files
|
||||||
|
**/.DS_Store
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.swp
|
||||||
|
**/*.swo
|
||||||
|
**/*~
|
||||||
|
|
||||||
|
# Logs and runtime files
|
||||||
|
**/logs
|
||||||
|
**/*.log
|
||||||
|
**/celerybeat-schedule*
|
||||||
|
**/.env
|
||||||
|
**/.env.*
|
||||||
|
|
||||||
|
# Media files and data directories (should not be in image)
|
||||||
|
media_files/**
|
||||||
|
postgres_data/**
|
||||||
|
pids/**
|
||||||
|
|
||||||
|
# Static files collected at runtime
|
||||||
|
static_collected/**
|
||||||
|
|
||||||
|
# Documentation and development files
|
||||||
|
**/.github
|
||||||
|
**/CHANGELOG.md
|
||||||
|
|
||||||
|
# Test files and directories
|
||||||
|
**/tests
|
||||||
|
**/test_*.py
|
||||||
|
**/*_test.py
|
||||||
|
|
||||||
|
# Frontend build artifacts (built separately)
|
||||||
|
frontend/dist/**
|
||||||
|
|||||||
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'
|
||||||
78
.github/workflows/docker-build-push.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2.2.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker meta for base image
|
||||||
|
id: meta-base
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
mediacms/mediacms
|
||||||
|
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: Docker meta for full image
|
||||||
|
id: meta-full
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
mediacms/mediacms
|
||||||
|
tags: |
|
||||||
|
type=raw,value=full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||||
|
type=semver,pattern={{version}}-full
|
||||||
|
type=semver,pattern={{major}}.{{minor}}-full
|
||||||
|
type=semver,pattern={{major}}-full
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.title=MediaCMS Full
|
||||||
|
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. This is the full version with additional dependencies.
|
||||||
|
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: Build and push full image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
target: full
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta-full.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-full.outputs.labels }}
|
||||||
|
|
||||||
|
- name: Build and push base image
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
target: base
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta-base.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta-base.outputs.labels }}
|
||||||
42
.github/workflows/frontend-build-and-test.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Frontend build and test
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
node: [20]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
name: '${{ matrix.os }} - node v${{ matrix.node }}'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./frontend
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build script
|
||||||
|
run: npm run dist
|
||||||
|
|
||||||
|
- name: Test script
|
||||||
|
run: npm run test
|
||||||
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 }}
|
||||||
15
.github/workflows/python.yml
vendored
@@ -1,14 +1,11 @@
|
|||||||
name: Python Tests
|
name: Python Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_call:
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: Build & test via docker-compose
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -16,10 +13,10 @@ jobs:
|
|||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Build the Stack
|
- name: Build the Stack
|
||||||
run: docker-compose -f docker-compose-dev.yaml build
|
run: docker compose -f docker-compose-dev.yaml build
|
||||||
|
|
||||||
- name: Start containers
|
- name: Start containers
|
||||||
run: docker-compose -f docker-compose-dev.yaml up -d
|
run: docker compose -f docker-compose-dev.yaml up -d
|
||||||
|
|
||||||
- name: List containers
|
- name: List containers
|
||||||
run: docker ps
|
run: docker ps
|
||||||
@@ -29,10 +26,10 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Run Django Tests
|
- name: Run Django Tests
|
||||||
run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
run: docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
||||||
|
|
||||||
# Run with coverage, saves report on htmlcov dir
|
# 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
|
# 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
|
- name: Tear down the Stack
|
||||||
run: docker-compose -f docker-compose-dev.yaml down
|
run: docker compose -f docker-compose-dev.yaml down
|
||||||
|
|||||||
22
.gitignore
vendored
@@ -5,8 +5,10 @@ media_files/original/
|
|||||||
media_files/hls/
|
media_files/hls/
|
||||||
media_files/chunks/
|
media_files/chunks/
|
||||||
media_files/uploads/
|
media_files/uploads/
|
||||||
|
media_files/tinymce_media/
|
||||||
|
media_files/userlogos/
|
||||||
postgres_data/
|
postgres_data/
|
||||||
celerybeat-schedule
|
celerybeat-schedule*
|
||||||
logs/
|
logs/
|
||||||
pids/
|
pids/
|
||||||
static/admin/
|
static/admin/
|
||||||
@@ -17,3 +19,21 @@ static/rest_framework/
|
|||||||
static/drf-yasg
|
static/drf-yasg
|
||||||
cms/local_settings.py
|
cms/local_settings.py
|
||||||
deploy/docker/local_settings.py
|
deploy/docker/local_settings.py
|
||||||
|
yt.readme.md
|
||||||
|
# Node.js dependencies (covers all node_modules directories, including frontend-tools)
|
||||||
|
**/node_modules/
|
||||||
|
/static_collected
|
||||||
|
/frontend-tools/video-editor-v1
|
||||||
|
frontend-tools/.DS_Store
|
||||||
|
static/video_editor/videos/sample-video-30s.mp4
|
||||||
|
static/video_editor/videos/sample-video-37s.mp4
|
||||||
|
/frontend-tools/video-editor-v2
|
||||||
|
.DS_Store
|
||||||
|
static/video_editor/videos/sample-video-10m.mp4
|
||||||
|
static/video_editor/videos/sample-video-10s.mp4
|
||||||
|
frontend-tools/video-js/public/videos/sample-video-white.mp4
|
||||||
|
frontend-tools/video-editor/client/public/videos/sample-video.mp3
|
||||||
|
frontend-tools/chapters-editor/client/public/videos/sample-video.mp3
|
||||||
|
static/chapters_editor/videos/sample-video.mp3
|
||||||
|
static/video_editor/videos/sample-video.mp3
|
||||||
|
templates/todo-MS4.md
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://gitlab.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
rev: 3.7.9
|
rev: 6.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.5.4
|
rev: 5.12.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.3.0
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
additional_dependencies: [ 'click==8.0.4' ]
|
||||||
|
|||||||
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/templates/cms/*
|
||||||
|
/templates/*.html
|
||||||
|
*.scss
|
||||||
21
.prettierrc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.css", "*.scss"],
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1 @@
|
|||||||
Yiannis Stergiou - ys.stergiou@gmail.com
|
|
||||||
Markos Gogoulos - mgogoulos@gmail.com
|
|
||||||
Swift Ugandan - swiftugandan@gmail.com
|
|
||||||
|
|
||||||
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!
|
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!
|
||||||
148
Dockerfile
@@ -1,69 +1,113 @@
|
|||||||
FROM python:3.8-buster AS compile-image
|
FROM python:3.13.5-slim-bookworm AS build-image
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
# Install system dependencies needed for downloading and extracting
|
||||||
|
RUN apt-get update -y && \
|
||||||
# Set up virtualenv
|
apt-get install -y --no-install-recommends wget xz-utils unzip && \
|
||||||
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} && 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
|
|
||||||
|
|
||||||
# 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 imagemagick procps wget xz-utils -y && \
|
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/* && \
|
||||||
apt-get purge --auto-remove && \
|
apt-get purge --auto-remove && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
||||||
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
|
||||||
mkdir -p ffmpeg-tmp && \
|
|
||||||
|
RUN mkdir -p ffmpeg-tmp && \
|
||||||
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C 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 && \
|
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
|
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
|
||||||
|
|
||||||
|
############ BASE RUNTIME IMAGE ############
|
||||||
|
FROM python:3.13.5-slim-bookworm AS base
|
||||||
|
|
||||||
|
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 system dependencies first
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get -y upgrade && \
|
||||||
|
apt-get install --no-install-recommends -y \
|
||||||
|
supervisor \
|
||||||
|
nginx \
|
||||||
|
imagemagick \
|
||||||
|
procps \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
zlib1g-dev \
|
||||||
|
zlib1g \
|
||||||
|
libxml2-dev \
|
||||||
|
libxmlsec1-dev \
|
||||||
|
libxmlsec1-openssl \
|
||||||
|
libpq-dev \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set up virtualenv first
|
||||||
|
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
|
||||||
|
cd /home/mediacms.io && \
|
||||||
|
python3 -m venv $VIRTUAL_ENV
|
||||||
|
|
||||||
|
# Copy requirements files
|
||||||
|
COPY requirements.txt requirements-dev.txt ./
|
||||||
|
|
||||||
|
# Install Python dependencies using pip (within virtualenv)
|
||||||
|
ARG DEVELOPMENT_MODE=False
|
||||||
|
RUN pip install --no-cache-dir uv && \
|
||||||
|
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
|
||||||
|
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
|
||||||
|
echo "Installing development dependencies..." && \
|
||||||
|
uv pip install -r requirements-dev.txt; \
|
||||||
|
fi && \
|
||||||
|
apt-get purge -y --auto-remove \
|
||||||
|
build-essential \
|
||||||
|
pkg-config \
|
||||||
|
libxml2-dev \
|
||||||
|
libxmlsec1-dev \
|
||||||
|
libpq-dev
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY . /home/mediacms.io/mediacms
|
||||||
WORKDIR /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
|
EXPOSE 9000 80
|
||||||
|
|
||||||
RUN chmod +x ./deploy/docker/entrypoint.sh
|
RUN chmod +x ./deploy/docker/entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
|
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
|
||||||
|
|
||||||
CMD ["./deploy/docker/start.sh"]
|
CMD ["./deploy/docker/start.sh"]
|
||||||
|
|
||||||
|
############ FULL IMAGE ############
|
||||||
|
FROM base AS full
|
||||||
|
COPY requirements-full.txt ./
|
||||||
|
RUN mkdir -p /root/.cache/ && \
|
||||||
|
chmod go+rwx /root/ && \
|
||||||
|
chmod go+rwx /root/.cache/
|
||||||
|
RUN uv pip install -r requirements-full.txt
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
FROM mediacms/mediacms:latest
|
|
||||||
|
|
||||||
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 cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
|
||||||
|
|
||||||
COPY requirements.txt .
|
|
||||||
COPY requirements-dev.txt .
|
|
||||||
RUN pip install -r requirements-dev.txt
|
|
||||||
|
|
||||||
WORKDIR /home/mediacms.io/mediacms
|
|
||||||
253
LTI_SETUP.md
Executable file
@@ -0,0 +1,253 @@
|
|||||||
|
# MediaCMS LTI 1.3 Integration Setup Guide
|
||||||
|
|
||||||
|
This guide walks you through integrating MediaCMS with a Learning Management System (LMS) like Moodle using LTI 1.3.
|
||||||
|
|
||||||
|
## 1. Configure MediaCMS Settings
|
||||||
|
|
||||||
|
Add these settings to `cms/local_settings.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Enable LTI integration
|
||||||
|
USE_LTI = True
|
||||||
|
|
||||||
|
# Enable RBAC for course-based access control
|
||||||
|
USE_RBAC = True
|
||||||
|
|
||||||
|
# Your production domain
|
||||||
|
FRONTEND_HOST = 'https://your-mediacms-domain.com'
|
||||||
|
ALLOWED_HOSTS = ['your-mediacms-domain.com', 'localhost']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** LTI-specific cookie settings (SESSION_COOKIE_SAMESITE='None', etc.) are automatically applied when `USE_LTI=True`.
|
||||||
|
|
||||||
|
## 2. MediaCMS Configuration
|
||||||
|
|
||||||
|
### A. Verify HTTPS Setup
|
||||||
|
|
||||||
|
Ensure your MediaCMS server is running on HTTPS. LTI 1.3 requires HTTPS for security and iframe embedding.
|
||||||
|
|
||||||
|
### B. Register Your LMS Platform
|
||||||
|
|
||||||
|
1. Access Django Admin: `https://your-mediacms-domain.com/admin/lti/ltiplatform/`
|
||||||
|
2. Add new LTI Platform with these settings:
|
||||||
|
|
||||||
|
**Basic Info:**
|
||||||
|
- **Name:** My LMS (or any descriptive name)
|
||||||
|
- **Platform ID (Issuer):** Get this from your LMS (e.g., `https://mylms.example.com`)
|
||||||
|
- **Client ID:** You'll get this from your LMS after registering MediaCMS as an external tool
|
||||||
|
|
||||||
|
**OIDC Endpoints (get from your LMS):**
|
||||||
|
- **Auth Login URL:** `https://mylms.example.com/mod/lti/auth.php`
|
||||||
|
- **Auth Token URL:** `https://mylms.example.com/mod/lti/token.php`
|
||||||
|
- **Key Set URL:** `https://mylms.example.com/mod/lti/certs.php`
|
||||||
|
|
||||||
|
**Deployment IDs:** Add the deployment ID(s) provided by your LMS as a JSON list, e.g., `["1"]`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- тЬУ Enable NRPS (Names and Role Provisioning)
|
||||||
|
- тЬУ Enable Deep Linking
|
||||||
|
- тЬУ Auto-create categories
|
||||||
|
- тЬУ Auto-create users
|
||||||
|
- тЬУ Auto-sync roles
|
||||||
|
|
||||||
|
### C. Note MediaCMS URLs for LMS Configuration
|
||||||
|
|
||||||
|
You'll need these URLs when configuring your LMS:
|
||||||
|
|
||||||
|
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
|
||||||
|
- **OIDC Login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
|
||||||
|
- **JWK Set URL:** `https://your-mediacms-domain.com/lti/jwks/`
|
||||||
|
- **Redirection URI:** `https://your-mediacms-domain.com/lti/launch/`
|
||||||
|
- **Deep Linking URL:** `https://your-mediacms-domain.com/lti/select-media/`
|
||||||
|
|
||||||
|
## 3. LMS Configuration (Moodle Example)
|
||||||
|
|
||||||
|
### A. Register MediaCMS as External Tool
|
||||||
|
|
||||||
|
1. Navigate to: **Site administration тЖТ Plugins тЖТ Activity modules тЖТ External tool тЖТ Manage tools**
|
||||||
|
2. Click **Configure a tool manually** or add new tool
|
||||||
|
|
||||||
|
**Basic Settings:**
|
||||||
|
- **Tool name:** MediaCMS
|
||||||
|
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
|
||||||
|
- **LTI version:** LTI 1.3
|
||||||
|
- **Tool configuration usage:** Show in activity chooser
|
||||||
|
|
||||||
|
**URLs:**
|
||||||
|
- **Public keyset URL:** `https://your-mediacms-domain.com/lti/jwks/`
|
||||||
|
- **Initiate login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
|
||||||
|
- **Redirection URI(s):** `https://your-mediacms-domain.com/lti/launch/`
|
||||||
|
|
||||||
|
**Launch Settings:**
|
||||||
|
- **Default launch container:** Embed (without blocks) or New window
|
||||||
|
- **Accept grades from tool:** Optional
|
||||||
|
- **Share launcher's name:** Always тЪая╕П **REQUIRED for user names**
|
||||||
|
- **Share launcher's email:** Always тЪая╕П **REQUIRED for user emails**
|
||||||
|
|
||||||
|
> **Important:** MediaCMS creates user accounts automatically on first LTI launch. To ensure users have proper names and email addresses in MediaCMS, you **must** set both "Share launcher's name with tool" and "Share launcher's email with tool" to **Always** in the Privacy settings. Without these settings, users will be created with only a username based on their LTI user ID.
|
||||||
|
|
||||||
|
**Services:**
|
||||||
|
- тЬУ IMS LTI Names and Role Provisioning (for roster sync)
|
||||||
|
- тЬУ IMS LTI Deep Linking (for media selection)
|
||||||
|
|
||||||
|
**Tool Settings (Important for Deep Linking):**
|
||||||
|
- тЬУ **Supports Deep Linking (Content-Item Message)** - Enable this to allow instructors to browse and select media from MediaCMS when adding activities
|
||||||
|
|
||||||
|
3. Save the tool configuration
|
||||||
|
|
||||||
|
### B. Copy Platform Details to MediaCMS
|
||||||
|
|
||||||
|
After saving, your LMS will provide:
|
||||||
|
- Platform ID (Issuer URL)
|
||||||
|
- Client ID
|
||||||
|
- Deployment ID
|
||||||
|
|
||||||
|
Copy these values back to the LTIPlatform configuration in MediaCMS admin (step 2B above).
|
||||||
|
|
||||||
|
### C. Using MediaCMS in Courses
|
||||||
|
|
||||||
|
**Option 1: Embed "My Media" view (Default)**
|
||||||
|
- In a course, add activity тЖТ External tool тЖТ MediaCMS
|
||||||
|
- Leave the custom URL blank (uses default launch URL)
|
||||||
|
- Students/teachers will see their MediaCMS profile in an iframe
|
||||||
|
|
||||||
|
**Option 2: Link to a Specific Video**
|
||||||
|
- Add activity тЖТ External tool тЖТ MediaCMS
|
||||||
|
- Activity name: "November 2020 Video" (or any descriptive name)
|
||||||
|
- In the activity settings, find **"Custom parameters"** (may be under "Privacy" or "Additional Settings")
|
||||||
|
- Add this parameter:
|
||||||
|
```
|
||||||
|
media_friendly_token=abc123def
|
||||||
|
```
|
||||||
|
- Replace `abc123def` with your video's token from MediaCMS (found in the URL: `/view?m=abc123def`)
|
||||||
|
- Students clicking this activity will go directly to that specific video
|
||||||
|
|
||||||
|
**Option 3: Link to Any MediaCMS Page**
|
||||||
|
- Add activity тЖТ External tool тЖТ MediaCMS
|
||||||
|
- In **"Custom parameters"**, add:
|
||||||
|
```
|
||||||
|
redirect_path=/featured
|
||||||
|
```
|
||||||
|
- Supported paths:
|
||||||
|
- `/featured` - Featured videos page
|
||||||
|
- `/latest` - Latest videos
|
||||||
|
- `/search/?q=keyword` - Search results
|
||||||
|
- `/category/category-name` - Specific category
|
||||||
|
- `/user/username` - User's profile
|
||||||
|
- Any other MediaCMS page path
|
||||||
|
|
||||||
|
**Option 4: Embed Specific Media via Deep Linking (Interactive)**
|
||||||
|
|
||||||
|
тЪая╕П **Prerequisite:** Ensure "Supports Deep Linking (Content-Item Message)" is enabled in the External Tool configuration (see section 3.A above)
|
||||||
|
|
||||||
|
When adding the activity to your course:
|
||||||
|
1. Add activity тЖТ External tool тЖТ MediaCMS
|
||||||
|
2. In the activity settings, enable **"Supports Deep Linking"** checkbox (may be under "Tool settings" or "Privacy" section)
|
||||||
|
3. Click **"Select content"** button тЖТ This launches the MediaCMS media browser
|
||||||
|
4. Browse and select media from MediaCMS (you can select multiple)
|
||||||
|
5. Click **"Add to course"** тЖТ Returns to Moodle with selected media configured
|
||||||
|
6. The activity will be automatically configured with the selected media's title and embed URL
|
||||||
|
7. Students clicking this activity will go directly to the selected media
|
||||||
|
|
||||||
|
### D. Custom Parameters - Complete Examples
|
||||||
|
|
||||||
|
**Example 1: Link to a specific video titled "Lecture 1 - Introduction"**
|
||||||
|
```
|
||||||
|
Activity Name: Lecture 1 - Introduction
|
||||||
|
Custom Parameters:
|
||||||
|
media_friendly_token=a1b2c3d4e5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 2: Link to course-specific videos**
|
||||||
|
```
|
||||||
|
Activity Name: Course Videos
|
||||||
|
Custom Parameters:
|
||||||
|
redirect_path=/category/biology101
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 3: Link to search results for "genetics"**
|
||||||
|
```
|
||||||
|
Activity Name: Genetics Videos
|
||||||
|
Custom Parameters:
|
||||||
|
redirect_path=/search/?q=genetics
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example 4: Link to featured content**
|
||||||
|
```
|
||||||
|
Activity Name: Featured Videos
|
||||||
|
Custom Parameters:
|
||||||
|
redirect_path=/featured
|
||||||
|
```
|
||||||
|
|
||||||
|
**Where to find Custom Parameters in Moodle:**
|
||||||
|
1. When creating/editing the External Tool activity
|
||||||
|
2. Expand **"Privacy"** section, or look for **"Additional Settings"**
|
||||||
|
3. Find the **"Custom parameters"** text field
|
||||||
|
4. Enter one parameter per line in the format: `key=value`
|
||||||
|
|
||||||
|
## 4. Testing Checklist
|
||||||
|
|
||||||
|
- [ ] HTTPS is working on MediaCMS
|
||||||
|
- [ ] `USE_LTI = True` in local_settings.py
|
||||||
|
- [ ] LTIPlatform configured in Django admin
|
||||||
|
- [ ] External tool registered in LMS
|
||||||
|
- [ ] Launch from LMS creates new user in MediaCMS
|
||||||
|
- [ ] Course is mapped to MediaCMS category
|
||||||
|
- [ ] Users are added to RBAC group with correct roles
|
||||||
|
- [ ] Media from course category is visible to course members
|
||||||
|
- [ ] Public media is accessible
|
||||||
|
- [ ] Private media from other courses is not accessible
|
||||||
|
|
||||||
|
## 5. Default Role Mappings
|
||||||
|
|
||||||
|
The system automatically maps LMS roles to MediaCMS:
|
||||||
|
|
||||||
|
- **Instructor/Teacher** тЖТ advancedUser (global) + manager (course group)
|
||||||
|
- **Student/Learner** тЖТ user (global) + member (course group)
|
||||||
|
- **Teaching Assistant** тЖТ user (global) + contributor (course group)
|
||||||
|
- **Administrator** тЖТ manager (global) + manager (course group)
|
||||||
|
|
||||||
|
You can customize these in Django admin under **LTI Role Mappings**.
|
||||||
|
|
||||||
|
## 6. User Creation and Authentication
|
||||||
|
|
||||||
|
### User Creation via LTI
|
||||||
|
|
||||||
|
When a user launches MediaCMS from your LMS for the first time, a MediaCMS account is automatically created with:
|
||||||
|
- **Username:** Generated from email (preferred) or name, or a unique ID if neither is available
|
||||||
|
- **Email:** From LTI claim (if shared by LMS)
|
||||||
|
- **Name:** From LTI given_name/family_name claims (if shared by LMS)
|
||||||
|
- **Roles:** Mapped from LTI roles to MediaCMS permissions
|
||||||
|
- **Course membership:** Automatically added to the RBAC group for the course
|
||||||
|
|
||||||
|
### Privacy Settings Are Critical
|
||||||
|
|
||||||
|
тЪая╕П **For proper user accounts, you must configure the LTI tool's privacy settings in Moodle:**
|
||||||
|
|
||||||
|
1. Edit the External Tool configuration in Moodle
|
||||||
|
2. Go to the **Privacy** section
|
||||||
|
3. Set **"Share launcher's name with tool"** to **Always**
|
||||||
|
4. Set **"Share launcher's email with tool"** to **Always**
|
||||||
|
|
||||||
|
Without these settings:
|
||||||
|
- Users will not have proper names in MediaCMS
|
||||||
|
- Users will not have email addresses
|
||||||
|
- Usernames will be generic hashes (e.g., `lti_user_abc123def`)
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Users created through LTI integration do **not** have a password set. They can only access MediaCMS through LTI launches from your LMS. This is intentional for security.
|
||||||
|
|
||||||
|
If you need a user to have both LTI access and direct login capability, manually set a password using:
|
||||||
|
```bash
|
||||||
|
python manage.py changepassword <username>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
If you encounter issues, check:
|
||||||
|
- `/admin/lti/ltilaunchlog/` for launch attempt logs
|
||||||
|
- Django logs for detailed error messages
|
||||||
|
- Ensure HTTPS is properly configured (required for iframe cookies)
|
||||||
|
- Verify all URLs are correct and accessible
|
||||||
|
- Check that the Client ID and Deployment ID match between MediaCMS and your LMS
|
||||||
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
|
||||||
|
|
||||||
63
README.md
@@ -1,11 +1,8 @@
|
|||||||
# MediaCMS
|
# MediaCMS
|
||||||
|
|
||||||
[](https://lgtm.com/projects/g/mediacms-io/mediacms/context:python)
|
|
||||||
[](https://lgtm.com/projects/g/mediacms-io/mediacms/context:javascript)
|
|
||||||
<br/>
|
|
||||||
[](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
|
[](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
|
||||||
[](https://github.com/mediacms-io/mediacms/releases/)
|
[](https://github.com/mediacms-io/mediacms/releases/)
|
||||||
[](https://hub.docker.com/repository/docker/mediacms/mediacms/)
|
[](https://hub.docker.com/r/mediacms/mediacms)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -26,11 +23,15 @@ A demo is available at https://demo.mediacms.io
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
- **Complete control over your data**: host it yourself!
|
- **Complete control over your data**: host it yourself!
|
||||||
- **Support for multiple publishing workflows**: public, private, unlisted and custom
|
|
||||||
- **Modern technologies**: Django/Python/Celery, React.
|
- **Modern technologies**: Django/Python/Celery, React.
|
||||||
|
- **Support for multiple publishing workflows**: public, private, unlisted and custom
|
||||||
|
- **Role-Based Access Control (RBAC)**: create RBAC categories and connect users to groups with view/edit access on their media
|
||||||
|
- **Automatic transcription**: through integration with Whisper running locally
|
||||||
- **Multiple media types support**: video, audio, image, pdf
|
- **Multiple media types support**: video, audio, image, pdf
|
||||||
- **Multiple media classification options**: categories, tags and custom
|
- **Multiple media classification options**: categories, tags and custom
|
||||||
- **Multiple media sharing options**: social media share, videos embed code generation
|
- **Multiple media sharing options**: social media share, videos embed code generation
|
||||||
|
- **Video Trimmer**: trim video, replace, save as new or create segments
|
||||||
|
- **SAML support**: with ability to add mappings to system roles and groups
|
||||||
- **Easy media searching**: enriched with live search functionality
|
- **Easy media searching**: enriched with live search functionality
|
||||||
- **Playlists for audio and video content**: create playlists, add and reorder content
|
- **Playlists for audio and video content**: create playlists, add and reorder content
|
||||||
- **Responsive design**: including light and dark themes
|
- **Responsive design**: including light and dark themes
|
||||||
@@ -38,83 +39,83 @@ A demo is available at https://demo.mediacms.io
|
|||||||
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
|
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
|
||||||
- **Configuration options**: change logos, fonts, styling, add more pages
|
- **Configuration options**: change logos, fonts, styling, add more pages
|
||||||
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
|
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
|
||||||
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
|
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (144p, 240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
|
||||||
- **Adaptive video streaming**: possible through HLS protocol
|
- **Adaptive video streaming**: possible through HLS protocol
|
||||||
- **Subtitles/CC**: support for multilingual subtitle files
|
- **Subtitles/CC**: support for multilingual subtitle files
|
||||||
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
|
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
|
||||||
- **Chunked file uploads**: for pausable/resumable upload of content
|
- **Chunked file uploads**: for pausable/resumable upload of content
|
||||||
- **REST API**: Documented through Swagger
|
- **REST API**: Documented through Swagger
|
||||||
|
- **Translation**: Most of the CMS is translated to a number of languages
|
||||||
|
|
||||||
## Example cases
|
## Example cases
|
||||||
|
|
||||||
- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
|
- **Universities, 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.
|
- **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!
|
- **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.
|
- **Personal portal.** Organize, categorize and host your content the way you prefer.
|
||||||
|
|
||||||
|
|
||||||
## Philosophy
|
## Philosophy
|
||||||
|
|
||||||
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
|
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
|
||||||
|
|
||||||
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
|
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
|
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
|
||||||
Copyright Markos Gogoulos and Yiannis Stergiou
|
Copyright Markos Gogoulos.
|
||||||
|
|
||||||
|
|
||||||
## Support and paid services
|
## 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.
|
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Checkout our [services page](https://mediacms.io/#services/) 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)
|
||||||
|
|
||||||
|
## 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.
|
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).
|
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).
|
||||||
|
|
||||||
|
In order to support automatic transcriptions through Whisper, consider more CPUs.
|
||||||
## Releases
|
|
||||||
|
|
||||||
Visit [Releases Page](https://github.com/mediacms-io/mediacms/releases) for detailed Changelog
|
|
||||||
|
|
||||||
|
|
||||||
## Installation / Maintanance
|
## 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. Find the related pages:
|
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:
|
||||||
|
|
||||||
* [Single Server](docs/admins_docs.md#2-server-installation) page
|
- [Single Server](docs/admins_docs.md#2-server-installation) page
|
||||||
* [Docker Compose](docs/admins_docs.md#3-docker-installation) page
|
- [Docker Compose](docs/admins_docs.md#3-docker-installation) page
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
|
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
* [Users documentation](docs/user_docs.md) page
|
* [Users documentation](docs/user_docs.md) page
|
||||||
* [Administrators documentation](docs/admins_docs.md) page
|
* [Administrators documentation](docs/admins_docs.md) page
|
||||||
* [Developers documentation](docs/developers_docs.md) page
|
* [Developers documentation](docs/developers_docs.md) page
|
||||||
|
* [Configuration](docs/admins_docs.md#5-configuration) page
|
||||||
|
* [Transcoding](docs/transcoding.md) page
|
||||||
|
* [Developer Experience](docs/dev_exp.md) page
|
||||||
|
* [Media Permissions](docs/media_permissions.md) page
|
||||||
|
|
||||||
|
|
||||||
## Technology
|
## 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, Gunicorn, React, Fine Uploader, video.js, FFMPEG, Bento4
|
||||||
|
|
||||||
|
|
||||||
## Who is using it
|
## Who is using it
|
||||||
|
- **Multiple Universities** for hosting educational videos
|
||||||
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
||||||
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.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
|
## How to contribute
|
||||||
@@ -124,10 +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
|
- Suggest us to others that are interested to hire us
|
||||||
- Write a blog post/article about MediaCMS
|
- Write a blog post/article about MediaCMS
|
||||||
- Share on social media about the project
|
- 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
|
- Star the project
|
||||||
- Add functionality, work on a PR, fix an issue!
|
- Add functionality, work on a PR, fix an issue!
|
||||||
|
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
info@mediacms.io
|
info@mediacms.io
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
0
admin_customizations/__init__.py
Normal file
0
admin_customizations/admin.py
Normal file
86
admin_customizations/apps.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
|
||||||
|
class AdminCustomizationsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'admin_customizations'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
original_get_app_list = admin.AdminSite.get_app_list
|
||||||
|
|
||||||
|
def get_app_list(self, request, app_label=None):
|
||||||
|
"""Custom get_app_list"""
|
||||||
|
app_list = original_get_app_list(self, request, app_label)
|
||||||
|
# To see the list:
|
||||||
|
# print([a.get('app_label') for a in app_list])
|
||||||
|
|
||||||
|
email_model = None
|
||||||
|
rbac_group_model = None
|
||||||
|
identity_providers_user_log_model = None
|
||||||
|
identity_providers_login_option = None
|
||||||
|
auth_app = None
|
||||||
|
rbac_app = None
|
||||||
|
socialaccount_app = None
|
||||||
|
|
||||||
|
for app in app_list:
|
||||||
|
if app['app_label'] == 'users':
|
||||||
|
auth_app = app
|
||||||
|
|
||||||
|
elif app['app_label'] == 'account':
|
||||||
|
for model in app['models']:
|
||||||
|
if model['object_name'] == 'EmailAddress':
|
||||||
|
email_model = model
|
||||||
|
elif app['app_label'] == 'rbac':
|
||||||
|
if not getattr(settings, 'USE_RBAC', False):
|
||||||
|
continue
|
||||||
|
rbac_app = app
|
||||||
|
for model in app['models']:
|
||||||
|
if model['object_name'] == 'RBACGroup':
|
||||||
|
rbac_group_model = model
|
||||||
|
elif app['app_label'] == 'identity_providers':
|
||||||
|
if not getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
|
continue
|
||||||
|
|
||||||
|
models_to_check = list(app['models'])
|
||||||
|
|
||||||
|
for model in models_to_check:
|
||||||
|
if model['object_name'] == 'IdentityProviderUserLog':
|
||||||
|
identity_providers_user_log_model = model
|
||||||
|
if model['object_name'] == 'LoginOption':
|
||||||
|
identity_providers_login_option = model
|
||||||
|
elif app['app_label'] == 'socialaccount':
|
||||||
|
socialaccount_app = app
|
||||||
|
|
||||||
|
if email_model and auth_app:
|
||||||
|
auth_app['models'].append(email_model)
|
||||||
|
if rbac_group_model and rbac_app and auth_app:
|
||||||
|
auth_app['models'].append(rbac_group_model)
|
||||||
|
if identity_providers_login_option and socialaccount_app:
|
||||||
|
socialaccount_app['models'].append(identity_providers_login_option)
|
||||||
|
if identity_providers_user_log_model and socialaccount_app:
|
||||||
|
socialaccount_app['models'].append(identity_providers_user_log_model)
|
||||||
|
|
||||||
|
# 2. don't include the following apps
|
||||||
|
apps_to_hide = ['authtoken', 'auth', 'account', 'saml_auth', 'rbac']
|
||||||
|
if not getattr(settings, 'USE_RBAC', False):
|
||||||
|
apps_to_hide.append('rbac')
|
||||||
|
if not getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
|
apps_to_hide.append('socialaccount')
|
||||||
|
|
||||||
|
app_list = [app for app in app_list if app['app_label'] not in apps_to_hide]
|
||||||
|
|
||||||
|
# 3. change the ordering
|
||||||
|
app_order = {
|
||||||
|
'files': 1,
|
||||||
|
'users': 2,
|
||||||
|
'socialaccount': 3,
|
||||||
|
'rbac': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
app_list.sort(key=lambda x: app_order.get(x['app_label'], 999))
|
||||||
|
|
||||||
|
return app_list
|
||||||
|
|
||||||
|
admin.AdminSite.get_app_list = get_app_list
|
||||||
0
admin_customizations/migrations/__init__.py
Normal file
0
admin_customizations/models.py
Normal file
0
admin_customizations/tests.py
Normal file
0
admin_customizations/views.py
Normal file
@@ -59,7 +59,7 @@ def login():
|
|||||||
file.writelines(f'USERNAME={json.loads(response.text)["username"]}\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:")
|
print(f"Welcome to MediaCMS [bold blue]{username}[/bold blue]. Your auth creds have been suceesfully stored in the .env file", ":v:")
|
||||||
else:
|
else:
|
||||||
print(f'Error: {"non_field_errors":["User not found."]}')
|
print(f'Error: {"non_field_errors": ["User not found."]}')
|
||||||
|
|
||||||
|
|
||||||
@apis.command()
|
@apis.command()
|
||||||
@@ -73,11 +73,11 @@ def upload_media():
|
|||||||
if os.path.isdir(path):
|
if os.path.isdir(path):
|
||||||
for filename in os.listdir(path):
|
for filename in os.listdir(path):
|
||||||
files = {}
|
files = {}
|
||||||
abs = os.path.abspath("{path}/{filename}")
|
abs = os.path.abspath(f"{path}/{filename}")
|
||||||
files['media_file'] = open(f'{abs}', 'rb')
|
files['media_file'] = open(f'{abs}', 'rb')
|
||||||
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
||||||
if response.status_code == 201:
|
if response.status_code == 201:
|
||||||
print("[bold blue]{filename}[/bold blue] successfully uploaded!")
|
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
|
||||||
else:
|
else:
|
||||||
print(f'Error: {response.text}')
|
print(f'Error: {response.text}')
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ def upload_media():
|
|||||||
files['media_file'] = open(f'{os.path.abspath(path)}', 'rb')
|
files['media_file'] = open(f'{os.path.abspath(path)}', 'rb')
|
||||||
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
||||||
if response.status_code == 201:
|
if response.status_code == 201:
|
||||||
print("[bold blue]{filename}[/bold blue] successfully uploaded!")
|
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
|
||||||
else:
|
else:
|
||||||
print(f'Error: {response.text}')
|
print(f'Error: {response.text}')
|
||||||
|
|
||||||
|
|||||||
10
cms/auth_backends.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
|
||||||
|
|
||||||
|
class ApprovalBackend(ModelBackend):
|
||||||
|
def user_can_authenticate(self, user):
|
||||||
|
can_authenticate = super().user_can_authenticate(user)
|
||||||
|
if can_authenticate and settings.USERS_NEEDS_TO_BE_APPROVED and not user.is_superuser:
|
||||||
|
return getattr(user, 'is_approved', False)
|
||||||
|
return can_authenticate
|
||||||
@@ -18,7 +18,6 @@ class FastPaginationWithoutCount(PageNumberPagination):
|
|||||||
django_paginator_class = FasterDjangoPaginator
|
django_paginator_class = FasterDjangoPaginator
|
||||||
|
|
||||||
def get_paginated_response(self, data):
|
def get_paginated_response(self, data):
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
OrderedDict(
|
OrderedDict(
|
||||||
[
|
[
|
||||||
|
|||||||
58
cms/dev_settings.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Development settings, used in docker-compose-dev.yaml
|
||||||
|
import os
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"admin_customizations",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"allauth",
|
||||||
|
"allauth.account",
|
||||||
|
"allauth.socialaccount",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"jazzmin",
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.sites",
|
||||||
|
"rest_framework",
|
||||||
|
"rest_framework.authtoken",
|
||||||
|
"imagekit",
|
||||||
|
"files.apps.FilesConfig",
|
||||||
|
"users.apps.UsersConfig",
|
||||||
|
"actions.apps.ActionsConfig",
|
||||||
|
"rbac.apps.RbacConfig",
|
||||||
|
"identity_providers.apps.IdentityProvidersConfig",
|
||||||
|
"lti.apps.LtiConfig",
|
||||||
|
"debug_toolbar",
|
||||||
|
"mptt",
|
||||||
|
"crispy_forms",
|
||||||
|
"crispy_bootstrap5",
|
||||||
|
"uploader.apps.UploaderConfig",
|
||||||
|
"djcelery_email",
|
||||||
|
"drf_yasg",
|
||||||
|
"allauth.socialaccount.providers.saml",
|
||||||
|
"saml_auth.apps.SamlAuthConfig",
|
||||||
|
"corsheaders",
|
||||||
|
"tinymce",
|
||||||
|
]
|
||||||
|
|
||||||
|
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')
|
||||||
23
cms/middleware.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
class ApprovalMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
if settings.USERS_NEEDS_TO_BE_APPROVED and request.user.is_authenticated and not request.user.is_superuser and not getattr(request.user, 'is_approved', False):
|
||||||
|
allowed_paths = [
|
||||||
|
reverse('approval_required'),
|
||||||
|
reverse('account_logout'),
|
||||||
|
]
|
||||||
|
if request.path not in allowed_paths:
|
||||||
|
if request.path.startswith('/api/'):
|
||||||
|
return JsonResponse({'detail': 'User account not approved.'}, status=403)
|
||||||
|
return redirect('approval_required')
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
return response
|
||||||
@@ -1,14 +1,29 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
|
||||||
from files.methods import is_mediacms_editor, is_mediacms_manager
|
from files.methods import (
|
||||||
|
is_mediacms_editor,
|
||||||
|
is_mediacms_manager,
|
||||||
|
user_allowed_to_upload,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IsAuthorizedToAdd(permissions.BasePermission):
|
class IsAuthorizedToAdd(permissions.BasePermission):
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
if request.method in permissions.SAFE_METHODS:
|
if request.method in permissions.SAFE_METHODS:
|
||||||
return True
|
return True
|
||||||
return user_allowed_to_upload(request)
|
if not user_allowed_to_upload(request):
|
||||||
|
raise PermissionDenied("You don't have permission to upload media, or have reached max number of media uploads.")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
class IsUserOrManager(permissions.BasePermission):
|
||||||
@@ -48,21 +63,22 @@ class IsUserOrEditor(permissions.BasePermission):
|
|||||||
return obj.user == request.user
|
return obj.user == request.user
|
||||||
|
|
||||||
|
|
||||||
def user_allowed_to_upload(request):
|
def user_allowed_to_comment(request):
|
||||||
"""Any custom logic for whether a user is allowed
|
"""Any custom logic for whether a user is allowed
|
||||||
to upload content lives here
|
to comment lives here
|
||||||
"""
|
"""
|
||||||
if request.user.is_anonymous:
|
if request.user.is_anonymous:
|
||||||
return False
|
return False
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if settings.CAN_ADD_MEDIA == "all":
|
# Default is "all"
|
||||||
|
if not hasattr(settings, "CAN_COMMENT") or settings.CAN_COMMENT == "all":
|
||||||
return True
|
return True
|
||||||
elif settings.CAN_ADD_MEDIA == "email_verified":
|
elif settings.CAN_COMMENT == "email_verified":
|
||||||
if request.user.email_is_verified:
|
if request.user.email_is_verified:
|
||||||
return True
|
return True
|
||||||
elif settings.CAN_ADD_MEDIA == "advancedUser":
|
elif settings.CAN_COMMENT == "advancedUser":
|
||||||
if request.user.advancedUser:
|
if request.user.advancedUser:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
307
cms/settings.py
@@ -1,19 +1,24 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
# PORTAL NAME, this is the portal title and
|
# PORTAL NAME, this is the portal title and
|
||||||
# is also shown on several places as emails
|
# is also shown on several places as emails
|
||||||
PORTAL_NAME = "MediaCMS"
|
PORTAL_NAME = "MediaCMS"
|
||||||
LANGUAGE_CODE = "en-us"
|
PORTAL_DESCRIPTION = ""
|
||||||
TIME_ZONE = "Europe/London"
|
TIME_ZONE = "Europe/London"
|
||||||
|
|
||||||
# who can add media
|
# who can add media
|
||||||
# valid options include 'all', 'email_verified', 'advancedUser'
|
# valid options include 'all', 'email_verified', 'advancedUser'
|
||||||
CAN_ADD_MEDIA = "all"
|
CAN_ADD_MEDIA = "all"
|
||||||
|
|
||||||
|
# who can comment
|
||||||
|
# valid options include 'all', 'email_verified', 'advancedUser'
|
||||||
|
CAN_COMMENT = "all"
|
||||||
|
|
||||||
# valid choices here are 'public', 'private', 'unlisted
|
# valid choices here are 'public', 'private', 'unlisted
|
||||||
PORTAL_WORKFLOW = "public"
|
PORTAL_WORKFLOW = "public"
|
||||||
|
|
||||||
@@ -86,26 +91,51 @@ MAX_MEDIA_PER_PLAYLIST = 70
|
|||||||
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
||||||
|
|
||||||
MAX_CHARS_FOR_COMMENT = 10000 # so that it doesn't end up huge
|
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
|
# valid options: content, author
|
||||||
RELATED_MEDIA_STRATEGY = "content"
|
RELATED_MEDIA_STRATEGY = "content"
|
||||||
|
|
||||||
|
# Whether or not to generate a sitemap.xml listing the pages on the site (default: False)
|
||||||
|
GENERATE_SITEMAP = False
|
||||||
|
|
||||||
|
# Whether to include media count numbers on categories and tags listing pages
|
||||||
|
INCLUDE_LISTING_NUMBERS = True
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
# these are the portal logos (dark and light)
|
||||||
|
# set new paths for svg or png if you want to override
|
||||||
|
# svg has priority over png, so if you want to use
|
||||||
|
# custom pngs and not svgs, remove the lines with svgs
|
||||||
|
# or set as empty strings
|
||||||
|
# example:
|
||||||
|
# PORTAL_LOGO_DARK_SVG = ""
|
||||||
|
# PORTAL_LOGO_LIGHT_SVG = ""
|
||||||
|
# place the files on static/images folder
|
||||||
|
PORTAL_LOGO_DARK_SVG = "/static/images/logo_dark.svg"
|
||||||
|
PORTAL_LOGO_DARK_PNG = "/static/images/logo_dark.png"
|
||||||
|
PORTAL_LOGO_LIGHT_SVG = "/static/images/logo_light.svg"
|
||||||
|
PORTAL_LOGO_LIGHT_PNG = "/static/images/logo_dark.png"
|
||||||
|
|
||||||
|
# paths to extra css files to be included, eg "/static/css/custom.css"
|
||||||
|
# place css inside static/css folder
|
||||||
|
EXTRA_CSS_PATHS = []
|
||||||
# protection agains anonymous users
|
# protection agains anonymous users
|
||||||
# per ip address limit, for actions as like/dislike/report
|
# per ip address limit, for actions as like/dislike/report
|
||||||
TIME_TO_ACTION_ANONYMOUS = 10 * 60
|
TIME_TO_ACTION_ANONYMOUS = 10 * 60
|
||||||
|
|
||||||
# django-allauth settings
|
# django-allauth settings
|
||||||
ACCOUNT_SESSION_REMEMBER = True
|
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_REQUIRED = True # new users need to specify email
|
||||||
ACCOUNT_EMAIL_VERIFICATION = "optional" # 'mandatory' 'none'
|
ACCOUNT_EMAIL_VERIFICATION = "optional" # 'mandatory' 'none'
|
||||||
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
||||||
ACCOUNT_USERNAME_MIN_LENGTH = "4"
|
ACCOUNT_USERNAME_MIN_LENGTH = 4
|
||||||
ACCOUNT_ADAPTER = "users.adapter.MyAccountAdapter"
|
ACCOUNT_ADAPTER = "users.adapter.MyAccountAdapter"
|
||||||
ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.SignupForm"
|
ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.SignupForm"
|
||||||
ACCOUNT_USERNAME_VALIDATORS = "users.validators.custom_username_validators"
|
ACCOUNT_USERNAME_VALIDATORS = "users.validators.custom_username_validators"
|
||||||
@@ -113,13 +143,19 @@ ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
|
|||||||
ACCOUNT_USERNAME_REQUIRED = True
|
ACCOUNT_USERNAME_REQUIRED = True
|
||||||
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
|
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
|
||||||
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
|
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
|
# registration won't be open, might also consider to remove links for register
|
||||||
USERS_CAN_SELF_REGISTER = True
|
USERS_CAN_SELF_REGISTER = True
|
||||||
|
|
||||||
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
|
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
|
||||||
|
|
||||||
|
# by default users do not need to be approved. If this is set to True, then new users
|
||||||
|
# will have to be approved before they can login successfully
|
||||||
|
USERS_NEEDS_TO_BE_APPROVED = False
|
||||||
|
|
||||||
|
# Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"]
|
||||||
|
# Empty list disables.
|
||||||
|
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = []
|
||||||
|
|
||||||
# django rest settings
|
# django rest settings
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
@@ -174,7 +210,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
|
|||||||
VIDEO_CHUNKS_DURATION = 60 * 4
|
VIDEO_CHUNKS_DURATION = 60 * 4
|
||||||
|
|
||||||
# always get these two, even if upscaling
|
# always get these two, even if upscaling
|
||||||
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360]
|
MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
|
||||||
|
|
||||||
# default settings for notifications
|
# default settings for notifications
|
||||||
# not all of them are implemented
|
# not all of them are implemented
|
||||||
@@ -214,13 +250,13 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = ""
|
|||||||
# only in case where unlisted workflow is used and no commentary
|
# only in case where unlisted workflow is used and no commentary
|
||||||
# exists
|
# exists
|
||||||
|
|
||||||
CANNOT_ADD_MEDIA_MESSAGE = ""
|
CANNOT_ADD_MEDIA_MESSAGE = "User cannot add media, or maximum number of media uploads has been reached."
|
||||||
|
|
||||||
# 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"
|
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
|
||||||
|
|
||||||
# highly experimental, related with remote workers
|
# highly experimental, related with remote workers
|
||||||
ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
ADMIN_TOKEN = ""
|
||||||
# this is used by remote workers to push
|
# this is used by remote workers to push
|
||||||
# encodings once they are done
|
# encodings once they are done
|
||||||
# USE_BASIC_HTTP = True
|
# USE_BASIC_HTTP = True
|
||||||
@@ -235,35 +271,6 @@ ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
|
|||||||
# uncomment the two lines related to htpasswd
|
# 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"
|
AUTH_USER_MODEL = "users.User"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
|
|
||||||
@@ -273,7 +280,7 @@ AUTHENTICATION_BACKENDS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"django.contrib.admin",
|
"admin_customizations",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"allauth",
|
"allauth",
|
||||||
"allauth.account",
|
"allauth.account",
|
||||||
@@ -282,6 +289,8 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"jazzmin",
|
||||||
|
"django.contrib.admin",
|
||||||
"django.contrib.sites",
|
"django.contrib.sites",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"rest_framework.authtoken",
|
"rest_framework.authtoken",
|
||||||
@@ -289,24 +298,32 @@ INSTALLED_APPS = [
|
|||||||
"files.apps.FilesConfig",
|
"files.apps.FilesConfig",
|
||||||
"users.apps.UsersConfig",
|
"users.apps.UsersConfig",
|
||||||
"actions.apps.ActionsConfig",
|
"actions.apps.ActionsConfig",
|
||||||
|
"rbac.apps.RbacConfig",
|
||||||
|
"identity_providers.apps.IdentityProvidersConfig",
|
||||||
|
"lti.apps.LtiConfig",
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
"mptt",
|
"mptt",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
|
"crispy_bootstrap5",
|
||||||
"uploader.apps.UploaderConfig",
|
"uploader.apps.UploaderConfig",
|
||||||
"djcelery_email",
|
"djcelery_email",
|
||||||
"ckeditor",
|
|
||||||
"drf_yasg",
|
"drf_yasg",
|
||||||
|
"allauth.socialaccount.providers.saml",
|
||||||
|
"saml_auth.apps.SamlAuthConfig",
|
||||||
|
"tinymce",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||||
|
"allauth.account.middleware.AccountMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = "cms.urls"
|
ROOT_URLCONF = "cms.urls"
|
||||||
@@ -334,11 +351,15 @@ WSGI_APPLICATION = "cms.wsgi.application"
|
|||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
"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",
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"min_length": 5,
|
"min_length": 7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -381,16 +402,7 @@ LOGGING = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "mediacms",
|
|
||||||
"HOST": "127.0.0.1",
|
|
||||||
"PORT": "5432",
|
|
||||||
"USER": "mediacms",
|
|
||||||
"PASSWORD": "mediacms",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
|
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
|
||||||
@@ -450,6 +462,145 @@ CELERY_TASK_ALWAYS_EAGER = False
|
|||||||
if os.environ.get("TESTING"):
|
if os.environ.get("TESTING"):
|
||||||
CELERY_TASK_ALWAYS_EAGER = True
|
CELERY_TASK_ALWAYS_EAGER = True
|
||||||
|
|
||||||
|
# if True, only show original, don't perform any action on videos
|
||||||
|
DO_NOT_TRANSCODE_VIDEO = False
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
|
LANGUAGES = [
|
||||||
|
('ar', _('Arabic')),
|
||||||
|
('bn', _('Bengali')),
|
||||||
|
('da', _('Danish')),
|
||||||
|
('nl', _('Dutch')),
|
||||||
|
('en', _('English')),
|
||||||
|
('fr', _('French')),
|
||||||
|
('de', _('German')),
|
||||||
|
('hi', _('Hindi')),
|
||||||
|
('id', _('Indonesian')),
|
||||||
|
('it', _('Italian')),
|
||||||
|
('ja', _('Japanese')),
|
||||||
|
('ko', _('Korean')),
|
||||||
|
('pt', _('Portuguese')),
|
||||||
|
('ru', _('Russian')),
|
||||||
|
('zh-hans', _('Simplified Chinese')),
|
||||||
|
('sl', _('Slovenian')),
|
||||||
|
('zh-hant', _('Traditional Chinese')),
|
||||||
|
('es', _('Spanish')),
|
||||||
|
('tr', _('Turkish')),
|
||||||
|
('el', _('Greek')),
|
||||||
|
('ur', _('Urdu')),
|
||||||
|
('he', _('Hebrew')),
|
||||||
|
]
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en' # default language
|
||||||
|
|
||||||
|
TINYMCE_DEFAULT_CONFIG = {
|
||||||
|
"theme": "silver",
|
||||||
|
"height": 500,
|
||||||
|
"resize": "both",
|
||||||
|
"menubar": "file edit view insert format tools table help",
|
||||||
|
"menu": {
|
||||||
|
"format": {
|
||||||
|
"title": "Format",
|
||||||
|
"items": "blocks | bold italic underline strikethrough superscript subscript code | " "fontfamily fontsize align lineheight | " "forecolor backcolor removeformat",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"plugins": "advlist,autolink,autosave,lists,link,image,charmap,print,preview,anchor,"
|
||||||
|
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,directionality,"
|
||||||
|
"code,help,wordcount,emoticons,file,image,media",
|
||||||
|
"toolbar": "undo redo | code preview | blocks | "
|
||||||
|
"bold italic | alignleft aligncenter "
|
||||||
|
"alignright alignjustify ltr rtl | bullist numlist outdent indent | "
|
||||||
|
"removeformat | restoredraft help | image media",
|
||||||
|
"branding": False, # remove branding
|
||||||
|
"promotion": False, # remove promotion
|
||||||
|
"body_class": "page-main-inner custom-page-wrapper", # class of the body element in tinymce
|
||||||
|
"block_formats": "Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3;",
|
||||||
|
"formats": { # customize h2 to always have emphasis-large class
|
||||||
|
"h2": {"block": "h2", "classes": "emphasis-large"},
|
||||||
|
},
|
||||||
|
"font_size_formats": "16px 18px 24px 32px",
|
||||||
|
"images_upload_url": "/tinymce/upload/",
|
||||||
|
"images_upload_handler": "tinymce.views.upload_image",
|
||||||
|
"automatic_uploads": True,
|
||||||
|
"file_picker_types": "image",
|
||||||
|
"paste_data_images": True,
|
||||||
|
"paste_as_text": False,
|
||||||
|
"paste_enable_default_filters": True,
|
||||||
|
"paste_word_valid_elements": "b,strong,i,em,h1,h2,h3,h4,h5,h6,p,br,a,ul,ol,li",
|
||||||
|
"paste_retain_style_properties": "all",
|
||||||
|
"paste_remove_styles": False,
|
||||||
|
"paste_merge_formats": True,
|
||||||
|
"sandbox_iframes": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
SPRITE_NUM_SECS = 10
|
||||||
|
# number of seconds for sprite image.
|
||||||
|
# If you plan to change this, you must also follow the instructions on admins_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/"
|
||||||
|
|
||||||
|
# this are used around a number of places and will need to be well documented!!!
|
||||||
|
|
||||||
|
USE_SAML = False
|
||||||
|
USE_RBAC = False
|
||||||
|
USE_IDENTITY_PROVIDERS = False
|
||||||
|
USE_LTI = False # Enable LTI 1.3 integration
|
||||||
|
JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
|
||||||
|
|
||||||
|
USE_ROUNDED_CORNERS = True
|
||||||
|
|
||||||
|
ALLOW_VIDEO_TRIMMER = True
|
||||||
|
|
||||||
|
ALLOW_CUSTOM_MEDIA_URLS = False
|
||||||
|
|
||||||
|
ALLOW_MEDIA_REPLACEMENT = False
|
||||||
|
|
||||||
|
ALLOW_ANONYMOUS_USER_LISTING = True
|
||||||
|
|
||||||
|
# Who can see the members page
|
||||||
|
# valid choices are all, editors, admins
|
||||||
|
CAN_SEE_MEMBERS_PAGE = "all"
|
||||||
|
|
||||||
|
# User search field setting
|
||||||
|
# valid choices are name_username, name_username_email
|
||||||
|
# this searches for users in the share media modal under my media
|
||||||
|
USER_SEARCH_FIELD = "name_username"
|
||||||
|
|
||||||
|
# Maximum number of media a user can upload
|
||||||
|
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 100
|
||||||
|
|
||||||
|
# ffmpeg options
|
||||||
|
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||||
|
|
||||||
|
# If 'all' is in the list, no check is performed
|
||||||
|
ALLOWED_MEDIA_UPLOAD_TYPES = ["video", "audio", "image", "pdf"]
|
||||||
|
|
||||||
|
# transcription options
|
||||||
|
# the mediacms-full docker image needs to be used in order to be able to use transcription
|
||||||
|
# if you are using the mediacms-full image, change USE_WHISPER_TRANSCRIBE to True
|
||||||
|
USE_WHISPER_TRANSCRIBE = False
|
||||||
|
|
||||||
|
# by default all users can request a video to be transcribed. If you want to
|
||||||
|
# allow only editors, set this to False
|
||||||
|
USER_CAN_TRANSCRIBE_VIDEO = True
|
||||||
|
|
||||||
|
# Whisper transcribe options - https://github.com/openai/whisper
|
||||||
|
WHISPER_MODEL = "base"
|
||||||
|
|
||||||
|
# show a custom text in the sidebar footer, otherwise the default will be shown if this is empty
|
||||||
|
SIDEBAR_FOOTER_TEXT = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# keep a local_settings.py file for local overrides
|
# keep a local_settings.py file for local overrides
|
||||||
@@ -461,21 +612,59 @@ except ImportError:
|
|||||||
# local_settings not in use
|
# local_settings not in use
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Don't add new settings below that could be overridden in local_settings.py!!!
|
||||||
|
|
||||||
if "http" not in FRONTEND_HOST:
|
if "http" not in FRONTEND_HOST:
|
||||||
# FRONTEND_HOST needs a http:// preffix
|
# FRONTEND_HOST needs a http:// preffix
|
||||||
FRONTEND_HOST = f"http://{FRONTEND_HOST}"
|
FRONTEND_HOST = f"http://{FRONTEND_HOST}" # noqa
|
||||||
|
|
||||||
if LOCAL_INSTALL:
|
if LOCAL_INSTALL:
|
||||||
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
|
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
|
||||||
else:
|
else:
|
||||||
SSL_FRONTEND_HOST = FRONTEND_HOST
|
SSL_FRONTEND_HOST = FRONTEND_HOST
|
||||||
|
|
||||||
|
|
||||||
|
# CSRF_COOKIE_SECURE = True
|
||||||
|
# SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
PYSUBS_COMMAND = "pysubs2"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
if GLOBAL_LOGIN_REQUIRED:
|
if GLOBAL_LOGIN_REQUIRED:
|
||||||
# this should go after the AuthenticationMiddleware middleware
|
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
|
||||||
MIDDLEWARE.insert(5, "login_required.middleware.LoginRequiredMiddleware")
|
MIDDLEWARE.insert(auth_index + 1, "django.contrib.auth.middleware.LoginRequiredMiddleware")
|
||||||
LOGIN_REQUIRED_IGNORE_PATHS = [
|
|
||||||
r'/accounts/login/$',
|
|
||||||
r'/accounts/logout/$',
|
if USERS_NEEDS_TO_BE_APPROVED:
|
||||||
r'/accounts/signup/$',
|
AUTHENTICATION_BACKENDS = (
|
||||||
]
|
'cms.auth_backends.ApprovalBackend',
|
||||||
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
|
)
|
||||||
|
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
|
||||||
|
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")
|
||||||
|
|
||||||
|
|
||||||
|
# LTI 1.3 Integration Settings
|
||||||
|
if USE_LTI:
|
||||||
|
# Session timeout for LTI launches (seconds)
|
||||||
|
LTI_SESSION_TIMEOUT = 3600 # 1 hour
|
||||||
|
|
||||||
|
# Cookie settings required for iframe embedding from LMS
|
||||||
|
# IMPORTANT: Requires HTTPS to be enabled
|
||||||
|
SESSION_COOKIE_SAMESITE = 'None'
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SAMESITE = 'None'
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
|
# Use cached_db for reliability - stores in both cache AND database
|
||||||
|
# This prevents session loss during multiple simultaneous LTI launches
|
||||||
|
|||||||
14
cms/urls.py
@@ -1,7 +1,8 @@
|
|||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
from django.conf.urls import include, re_path
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.views import get_schema_view
|
from drf_yasg.views import get_schema_view
|
||||||
@@ -13,6 +14,7 @@ schema_view = get_schema_view(
|
|||||||
permission_classes=(AllowAny,),
|
permission_classes=(AllowAny,),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# refactor seriously
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"^__debug__/", include(debug_toolbar.urls)),
|
re_path(r"^__debug__/", include(debug_toolbar.urls)),
|
||||||
@@ -23,9 +25,15 @@ urlpatterns = [
|
|||||||
re_path(r"^", include("files.urls")),
|
re_path(r"^", include("files.urls")),
|
||||||
re_path(r"^", include("users.urls")),
|
re_path(r"^", include("users.urls")),
|
||||||
re_path(r"^accounts/", include("allauth.urls")),
|
re_path(r"^accounts/", include("allauth.urls")),
|
||||||
|
re_path(r"^lti/", include("lti.urls")),
|
||||||
re_path(r"^api-auth/", include("rest_framework.urls")),
|
re_path(r"^api-auth/", include("rest_framework.urls")),
|
||||||
path("admin/", admin.site.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(?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'),
|
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'),
|
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
path("tinymce/", include("tinymce.urls")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
admin.site.site_header = "MediaCMS Admin"
|
||||||
|
admin.site.site_title = "MediaCMS"
|
||||||
|
admin.site.index_title = "Admin"
|
||||||
|
|||||||
1
cms/version.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
VERSION = "7.8124"
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from pytest_factoryboy import register
|
|
||||||
|
|
||||||
from tests.users.factories import UserFactory
|
|
||||||
|
|
||||||
register(UserFactory)
|
|
||||||
75
deic_setup_notes.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# MediaCMS: Document Changes for DEIC
|
||||||
|
|
||||||
|
## Configuration Changes
|
||||||
|
The following changes are required in `deploy/docker/local_settings.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# default workflow
|
||||||
|
PORTAL_WORKFLOW = 'private'
|
||||||
|
|
||||||
|
# Authentication Settings
|
||||||
|
# these two are necessary so that users cannot register through system accounts. They can only register through identity providers
|
||||||
|
REGISTER_ALLOWED = False
|
||||||
|
USERS_CAN_SELF_REGISTER = False
|
||||||
|
|
||||||
|
USE_RBAC = True
|
||||||
|
USE_SAML = True
|
||||||
|
USE_IDENTITY_PROVIDERS = True
|
||||||
|
|
||||||
|
# Proxy and SSL Settings
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
# SAML Configuration
|
||||||
|
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
|
||||||
|
ACCOUNT_USERNAME_VALIDATORS = "users.validators.less_restrictive_username_validators"
|
||||||
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
|
"saml": {
|
||||||
|
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SOCIALACCOUNT_AUTO_SIGNUP = True
|
||||||
|
SOCIALACCOUNT_EMAIL_REQUIRED = False
|
||||||
|
|
||||||
|
# if set to strict, user is created with the email from the saml provider without
|
||||||
|
# checking if the email is already on the system
|
||||||
|
# however if this is ommited, and user tries to login with an email that already exists on
|
||||||
|
# the system, then they get to the ugly form where it suggests they add a username/email/name
|
||||||
|
|
||||||
|
ACCOUNT_PREVENT_ENUMERATION = 'strict'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## SAML Configuration Steps
|
||||||
|
|
||||||
|
### Step 1: Add SAML Identity Provider
|
||||||
|
1. Navigate to Admin panel
|
||||||
|
2. Select "Identity Provider"
|
||||||
|
3. Configure as follows:
|
||||||
|
- **Provider**: saml # ensure this is set with lower case!
|
||||||
|
- **Provider ID**: `wayf.wayf.dk`
|
||||||
|
- **IDP Config Name**: `Deic` (or preferred name)
|
||||||
|
- **Client ID**: `wayf_dk` (important: defines the URL, e.g., `https://deic.mediacms.io/accounts/saml/wayf_dk`)
|
||||||
|
- **Site**: Set the default one
|
||||||
|
|
||||||
|
### Step 2: Add SAML Configuration
|
||||||
|
Can be set through the SAML Configurations tab:
|
||||||
|
|
||||||
|
1. **IDP ID**: Must be a URL, e.g., `https://wayf.wayf.dk`
|
||||||
|
2. **IDP Certificate**: x509cert from your SAML provider
|
||||||
|
3. **SSO URL**: `https://wayf.wayf.dk/saml2/idp/SSOService2.php`
|
||||||
|
4. **SLO URL**: `https://wayf.wayf.dk/saml2/idp/SingleLogoutService.php`
|
||||||
|
5. **SP Metadata URL**: The metadata URL set for the SP, e.g., `https://deic.mediacms.io/saml/metadata`. This should point to the URL of the SP and is autogenerated
|
||||||
|
|
||||||
|
### Step 3: Set the other Options
|
||||||
|
1. **Email Settings**:
|
||||||
|
- `verified_email`: When enabled, emails from SAML responses will be marked as verified
|
||||||
|
- `Remove from groups`: When enabled, user is removed from a group after login, if they have been removed from the group on the IDP
|
||||||
|
2. **Global Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in MediaCMS
|
||||||
|
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
|
||||||
|
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
|
||||||
|
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
|
||||||
@@ -7,6 +7,7 @@ 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
|
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,media_files/hls}
|
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
|
||||||
touch /home/mediacms.io/mediacms/logs/debug.log
|
touch /home/mediacms.io/mediacms/logs/debug.log
|
||||||
|
|
||||||
@@ -28,7 +29,9 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# We should do this only for folders that have a different owner, since it is an expensive operation
|
# 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
|
||||||
|
# Exclude package-lock.json files that may not exist or be removed during frontend setup
|
||||||
|
find /home/mediacms.io/mediacms ! \( -path "*.git*" -o -name "package-lock.json" \) -exec chown www-data:$TARGET_GID {} + 2>/dev/null || true
|
||||||
|
|
||||||
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
|
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
FRONTEND_HOST = 'http://localhost'
|
import os
|
||||||
PORTAL_NAME = 'MediaCMS'
|
|
||||||
SECRET_KEY = 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2'
|
FRONTEND_HOST = os.getenv('FRONTEND_HOST', 'http://localhost')
|
||||||
POSTGRES_HOST = 'db'
|
PORTAL_NAME = os.getenv('PORTAL_NAME', 'MediaCMS')
|
||||||
REDIS_LOCATION = "redis://redis:6379/1"
|
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 = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": "mediacms",
|
"NAME": os.getenv('POSTGRES_NAME', 'mediacms'),
|
||||||
"HOST": POSTGRES_HOST,
|
"HOST": os.getenv('POSTGRES_HOST', 'db'),
|
||||||
"PORT": "5432",
|
"PORT": os.getenv('POSTGRES_PORT', '5432'),
|
||||||
"USER": "mediacms",
|
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
|
||||||
"PASSWORD": "mediacms",
|
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
|
||||||
|
"OPTIONS": {'pool': True},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,4 +33,4 @@ CELERY_RESULT_BACKEND = BROKER_URL
|
|||||||
|
|
||||||
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
|
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = os.getenv('DEBUG', 'False') == 'True'
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
# Use existing X-Forwarded-Proto from reverse proxy if present, otherwise use $scheme
|
||||||
|
map $http_x_forwarded_proto $forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
'' $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80 ;
|
listen 80 ;
|
||||||
|
|
||||||
@@ -16,6 +22,10 @@ server {
|
|||||||
|
|
||||||
location /media {
|
location /media {
|
||||||
alias /home/mediacms.io/mediacms/media_files ;
|
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 / {
|
location / {
|
||||||
@@ -24,7 +34,10 @@ server {
|
|||||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
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';
|
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
proxy_pass http://127.0.0.1:9000;
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
||||||
@@ -37,7 +37,6 @@ fi
|
|||||||
|
|
||||||
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
|
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
|
||||||
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
|
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
|
||||||
cp deploy/docker/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
|
|
||||||
cp deploy/docker/nginx.conf /etc/nginx/
|
cp deploy/docker/nginx.conf /etc/nginx/
|
||||||
|
|
||||||
#### Supervisord Configurations #####
|
#### Supervisord Configurations #####
|
||||||
@@ -45,12 +44,12 @@ cp deploy/docker/nginx.conf /etc/nginx/
|
|||||||
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
|
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
|
||||||
|
|
||||||
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
|
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
|
||||||
echo "Enabling uwsgi app server"
|
echo "Enabling gunicorn app server"
|
||||||
cp deploy/docker/supervisord/supervisord-uwsgi.conf /etc/supervisor/conf.d/supervisord-uwsgi.conf
|
cp deploy/docker/supervisord/supervisord-gunicorn.conf /etc/supervisor/conf.d/supervisord-gunicorn.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
|
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
|
||||||
echo "Enabling nginx as uwsgi app proxy and media server"
|
echo "Enabling nginx as gunicorn app proxy and media server"
|
||||||
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
|
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -67,4 +66,5 @@ fi
|
|||||||
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
|
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
|
||||||
echo "Enabling celery-long task worker"
|
echo "Enabling celery-long task worker"
|
||||||
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
|
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
|
fi
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ else
|
|||||||
echo "There is no script $PRE_START_PATH"
|
echo "There is no script $PRE_START_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start Supervisor, with Nginx and uWSGI
|
# Start Supervisor, with Nginx and Gunicorn
|
||||||
echo "Starting server using supervisord..."
|
echo "Starting server using supervisord..."
|
||||||
|
|
||||||
exec /usr/bin/supervisord
|
exec /usr/bin/supervisord
|
||||||
|
|||||||
9
deploy/docker/supervisord/supervisord-gunicorn.conf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[program:gunicorn]
|
||||||
|
command=/home/mediacms.io/bin/gunicorn cms.wsgi:application --workers=2 --threads=2 --worker-class=gthread --bind=127.0.0.1:9000 --user=www-data --group=www-data --timeout=120 --keep-alive=5 --max-requests=1000 --max-requests-jitter=50 --access-logfile=- --error-logfile=- --log-level=info --chdir=/home/mediacms.io/mediacms
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
priority=100
|
||||||
|
startinorder=true
|
||||||
|
startsecs=0
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[program:uwsgi]
|
|
||||||
command=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/docker/uwsgi.ini
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=100
|
|
||||||
startinorder=true
|
|
||||||
startsecs=0
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
|
|
||||||
chdir = /home/mediacms.io/mediacms/
|
|
||||||
virtualenv = /home/mediacms.io
|
|
||||||
module = cms.wsgi
|
|
||||||
|
|
||||||
uid=www-data
|
|
||||||
gid=www-data
|
|
||||||
|
|
||||||
processes = 2
|
|
||||||
threads = 2
|
|
||||||
|
|
||||||
master = true
|
|
||||||
|
|
||||||
socket = 127.0.0.1:9000
|
|
||||||
|
|
||||||
workers = 2
|
|
||||||
|
|
||||||
vacuum = true
|
|
||||||
|
|
||||||
hook-master-start = unix_signal:15 gracefully_kill_them_all
|
|
||||||
need-app = true
|
|
||||||
die-on-term = true
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
uwsgi_param QUERY_STRING $query_string;
|
|
||||||
uwsgi_param REQUEST_METHOD $request_method;
|
|
||||||
uwsgi_param CONTENT_TYPE $content_type;
|
|
||||||
uwsgi_param CONTENT_LENGTH $content_length;
|
|
||||||
|
|
||||||
uwsgi_param REQUEST_URI $request_uri;
|
|
||||||
uwsgi_param PATH_INFO $document_uri;
|
|
||||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
|
||||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
|
||||||
uwsgi_param REQUEST_SCHEME $scheme;
|
|
||||||
uwsgi_param HTTPS $https if_not_empty;
|
|
||||||
|
|
||||||
uwsgi_param REMOTE_ADDR $remote_addr;
|
|
||||||
uwsgi_param REMOTE_PORT $remote_port;
|
|
||||||
uwsgi_param SERVER_PORT $server_port;
|
|
||||||
uwsgi_param SERVER_NAME $server_name;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery beat
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
Environment=APP_DIR="/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}'
|
|
||||||
ExecStop=/bin/kill -s TERM $MAINPID
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery long queue
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
Environment=APP_DIR="/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}'
|
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '${CELERY_BIN} 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}'
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery short queue
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
Environment=APP_DIR="/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
|
|
||||||
Environment=CELERYD_MULTI="multi"
|
|
||||||
# Extra command-line arguments to the worker
|
|
||||||
Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
|
|
||||||
# - %n will be replaced with the first part of the nodename.
|
|
||||||
# - %I will be replaced with the current child process index
|
|
||||||
# and is important when using the prefork pool to avoid race conditions.
|
|
||||||
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}'
|
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '${CELERY_BIN} 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}'
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
-----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-----
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80 ;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
access_log /var/log/nginx/mediacms.io.access.log;
|
|
||||||
|
|
||||||
error_log /var/log/nginx/mediacms.io.error.log warn;
|
|
||||||
|
|
||||||
# # redirect to https if logged in
|
|
||||||
# if ($http_cookie ~* "sessionid") {
|
|
||||||
# rewrite ^/(.*)$ https://localhost/$1 permanent;
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # redirect basic forms to https
|
|
||||||
# location ~ (login|login_form|register|mail_password_form)$ {
|
|
||||||
# rewrite ^/(.*)$ https://localhost/$1 permanent;
|
|
||||||
# }
|
|
||||||
|
|
||||||
location /static {
|
|
||||||
alias /home/mediacms.io/mediacms/static ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media/original {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files/original;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
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';
|
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
error_log /var/log/nginx/mediacms.io.error.log warn;
|
|
||||||
|
|
||||||
location /static {
|
|
||||||
alias /home/mediacms.io/mediacms/static ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media/original {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files/original;
|
|
||||||
#auth_basic "auth protected area";
|
|
||||||
#auth_basic_user_file /home/mediacms.io/mediacms/deploy/local_install/.htpasswd;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
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';
|
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFTjCCBDagAwIBAgISBNOUeDlerH9MkKmHLvZJeMYgMA0GCSqGSIb3DQEBCwUA
|
|
||||||
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
|
|
||||||
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDAzMTAxNzUxNDFaFw0y
|
|
||||||
MDA2MDgxNzUxNDFaMBYxFDASBgNVBAMTC21lZGlhY21zLmlvMIIBIjANBgkqhkiG
|
|
||||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps5Jn18nW2tq/LYFDgQ1YZGLlpF/B2AAPvvH
|
|
||||||
3yuD+AcT4skKdZouVL/a5pXrptuYL5lthO9dlcja2tuO2ltYrb7Dp01dAIFaJE8O
|
|
||||||
DKd+Sv5wr8VWQZykqzMiMBgviml7TBvUHQjvCJg8UwmnN0XSUILCttd6u4qOzS7d
|
|
||||||
lKMMsKpYzLhElBT0rzhhsWulDiy6aAZbMV95bfR74nIWsBJacy6jx3jvxAuvCtkB
|
|
||||||
OVdOoVL6BPjDE3SNEk53bAZGIb5A9ri0O5jh/zBFT6tQSjUhAUTkmv9oZP547RnV
|
|
||||||
fDj+rdvCVk/fE+Jno36mcT183Qd/Ty3fWuqFoM5g/luhnfvWEwIDAQABo4ICYDCC
|
|
||||||
AlwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
|
|
||||||
AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTd5EZBt74zu5XxT1uXQs6oM8qOuDAf
|
|
||||||
BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
|
|
||||||
LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
|
|
||||||
LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
|
|
||||||
MBYGA1UdEQQPMA2CC21lZGlhY21zLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
|
|
||||||
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
|
|
||||||
cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAXqdz+d9WwOe1Nkh90Eng
|
|
||||||
MnqRmgyEoRIShBh1loFxRVgAAAFwxcnL+AAABAMARzBFAiAb3yeBuW3j9MxcRc0T
|
|
||||||
icUBvEa/rH7Fv2eB0oQlnZ1exQIhAPf+CtTXmzxoeT/BBiivj4AmGDsq4xWhe/U6
|
|
||||||
BytYrKLeAHYAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFwxcnM
|
|
||||||
HAAABAMARzBFAiAuP5gKyyaT0LVXxwjYD9zhezvxf4Icx0P9pk75c5ao+AIhAK0+
|
|
||||||
fSJv+WTXciMT6gA1sk/tuCHuDFAuexSA/6TcRXcVMA0GCSqGSIb3DQEBCwUAA4IB
|
|
||||||
AQCPCYBU4Q/ro2MUkjDPKGmeqdxQycS4R9WvKTG/nmoahKNg30bnLaDPUcpyMU2k
|
|
||||||
sPDemdZ7uTGLZ3ZrlIva8DbrnJmrTPf9BMwaM6j+ZV/QhxvKZVIWkLkZrwiVI57X
|
|
||||||
Ba+rs5IEB4oWJ0EBaeIrzeKG5zLMkRcIdE4Hlhuwu3zGG56c+wmAPuvpIDlYoO6o
|
|
||||||
W22xRdxoTIHBvkzwonpVYUaRcaIw+48xnllxh1dHO+X69DT45wlF4tKveOUi+L50
|
|
||||||
4GWJ8Vjv7Fot/WNHEM4Mnmw0jHj9TPkIZKnPNRMdHmJ5CF/FJFDiptOeuzbfohG+
|
|
||||||
mdvuInb8JDc0XBE99Gf/S4/y
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
|
||||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
|
||||||
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
|
||||||
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
|
||||||
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
|
||||||
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
|
||||||
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
|
||||||
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
|
||||||
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
|
||||||
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
|
||||||
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
|
||||||
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
|
||||||
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
|
||||||
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
|
||||||
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
|
||||||
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
|
||||||
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
|
||||||
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
|
||||||
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
|
||||||
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
|
||||||
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
|
||||||
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
|
||||||
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
|
||||||
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
|
||||||
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmzkmfXydba2r8
|
|
||||||
tgUOBDVhkYuWkX8HYAA++8ffK4P4BxPiyQp1mi5Uv9rmleum25gvmW2E712VyNra
|
|
||||||
247aW1itvsOnTV0AgVokTw4Mp35K/nCvxVZBnKSrMyIwGC+KaXtMG9QdCO8ImDxT
|
|
||||||
Cac3RdJQgsK213q7io7NLt2UowywqljMuESUFPSvOGGxa6UOLLpoBlsxX3lt9Hvi
|
|
||||||
chawElpzLqPHeO/EC68K2QE5V06hUvoE+MMTdI0STndsBkYhvkD2uLQ7mOH/MEVP
|
|
||||||
q1BKNSEBROSa/2hk/njtGdV8OP6t28JWT98T4mejfqZxPXzdB39PLd9a6oWgzmD+
|
|
||||||
W6Gd+9YTAgMBAAECggEADnEJuryYQbf5GUwBAAepP3tEZJLQNqk/HDTcRxwTXuPt
|
|
||||||
+tKBD1F79WZu40vTjSyx7l0QOFQo/BDZsd0Ubx89fD1p3xA5nxOT5FTb2IifzIpe
|
|
||||||
4zjokOGo+BGDQjq10vvy6tH1+VWOrGXRwzawvX5UCRhpFz9sptQGLQmDsZy0Oo9B
|
|
||||||
LtavYVUqsbyqRWlzaclHgbythegIACWkqcalOzOtx+l6TGBRjej+c7URcwYBfr7t
|
|
||||||
XTAzbP+vnpaJovZyZT1eekr0OLzMpnjx4HvRvzL+NxauRpn6KfabsTfZlk8nrs4I
|
|
||||||
UdSjeukj1Iz8rGQilHdN/4dVJ3KzrlHVkVTBSjmMUQKBgQDaVXZnhAScfdiKeZbO
|
|
||||||
rdUAWcnwfkDghtRuAmzHaRM/FhFBEoVhdSbBuu+OUyBnIw/Ra4o2ePuEBcKIUiQO
|
|
||||||
w2tnE1CY5PPAcjw+OCSpvzy5xxjaqaRbm9BJp3FTeEYGLXERnchPpHg/NpexuF22
|
|
||||||
QOJ+FrysPyNMxuQp47ZwO9WT3QKBgQDDlSGjq/eeWxemwf7ZqMVlRyqsdJsgnCew
|
|
||||||
DkC62IGiYCBDfeEmndN+vcA/uzJHYV4iXiqS3aYJCWGaZFMhdIhIn5MgULvO1j5G
|
|
||||||
u/MxuzaaNPz22FlNCWTLBw4T1HOOvyTL+nLtZDKJ/BHxgHCmur1kiGvvZWrcCthD
|
|
||||||
afLEmseqrwKBgBuLZKCymxJTHhp6NHhmndSpfzyD8RNibzJhw+90ZiUzV4HqIEGn
|
|
||||||
Ufhm6Qn/mrroRXqaIpm0saZ6Q4yHMF1cchRS73wahlXlE4yV8KopojOd1pjfhgi4
|
|
||||||
o5JnOXjaV5s36GfcjATgLvtqm8CkDc6MaQaXP75LSNzKysYuIDoQkmVRAoGAAghF
|
|
||||||
rja2Pv4BU+lGJarcSj4gEmSvy/nza5/qSka/qhlHnIvtUAJp1TJRkhf24MkBOmgy
|
|
||||||
Fw6YkBV53ynVt05HsEGAPOC54t9VDFUdpNGmMpoEWuhKnUNQuc9b9RbLEJup3TjA
|
|
||||||
Avl8kPR+lzzXbtQX7biBLp6mKp0uPB0YubRGCN8CgYA0JMxK0x38Q2x3AQVhOmZh
|
|
||||||
YubtIa0JqVJhvpweOCFnkq3ebBpLsWYwiLTn86vuD0jupe5M3sxtefjkJmAKd8xY
|
|
||||||
aBU7QWhjh1fX4mzmggnbjcrIFbkIHsxwMeg567U/4AGxOOUsv9QUn37mqycqRKEn
|
|
||||||
YfUyYNLM6F3MmQAOs2kaHw==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS uwsgi
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/local_install/uwsgi.ini
|
|
||||||
ExecStop=/usr/bin/killall -9 uwsgi
|
|
||||||
RestartSec=3
|
|
||||||
#ExecRestart=killall -9 uwsgi; sleep 5; /home/sss/bin/uwsgi --ini /home/sss/wordgames/uwsgi.ini
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/home/mediacms.io/mediacms/logs/*.log {
|
|
||||||
weekly
|
|
||||||
missingok
|
|
||||||
rotate 7
|
|
||||||
compress
|
|
||||||
notifempty
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 10240;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker_rlimit_nofile 20000; #each connection needs a filehandle (or 2 if you are proxying)
|
|
||||||
http {
|
|
||||||
proxy_connect_timeout 75;
|
|
||||||
proxy_read_timeout 12000;
|
|
||||||
client_max_body_size 5800M;
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 10;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_disable "msie6";
|
|
||||||
|
|
||||||
log_format compression '$remote_addr - $remote_user [$time_local] '
|
|
||||||
'"$request" $status $body_bytes_sent '
|
|
||||||
'"$http_referer" "$http_user_agent" "$gzip_ratio"';
|
|
||||||
|
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
|
|
||||||
chdir = /home/mediacms.io/mediacms/
|
|
||||||
virtualenv = /home/mediacms.io
|
|
||||||
module = cms.wsgi
|
|
||||||
|
|
||||||
uid=www-data
|
|
||||||
gid=www-data
|
|
||||||
|
|
||||||
processes = 2
|
|
||||||
threads = 2
|
|
||||||
|
|
||||||
master = true
|
|
||||||
|
|
||||||
socket = 127.0.0.1:9000
|
|
||||||
#socket = /home/mediacms.io/mediacms/deploy/uwsgi.sock
|
|
||||||
|
|
||||||
|
|
||||||
workers = 2
|
|
||||||
|
|
||||||
|
|
||||||
vacuum = true
|
|
||||||
|
|
||||||
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
|
|
||||||
|
|
||||||
disable-logging = true
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
uwsgi_param QUERY_STRING $query_string;
|
|
||||||
uwsgi_param REQUEST_METHOD $request_method;
|
|
||||||
uwsgi_param CONTENT_TYPE $content_type;
|
|
||||||
uwsgi_param CONTENT_LENGTH $content_length;
|
|
||||||
|
|
||||||
uwsgi_param REQUEST_URI $request_uri;
|
|
||||||
uwsgi_param PATH_INFO $document_uri;
|
|
||||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
|
||||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
|
||||||
uwsgi_param REQUEST_SCHEME $scheme;
|
|
||||||
uwsgi_param HTTPS $https if_not_empty;
|
|
||||||
|
|
||||||
uwsgi_param REMOTE_ADDR $remote_addr;
|
|
||||||
uwsgi_param REMOTE_PORT $remote_port;
|
|
||||||
uwsgi_param SERVER_PORT $server_port;
|
|
||||||
uwsgi_param SERVER_NAME $server_name;
|
|
||||||
40
deploy/scripts/build_and_deploy.sh
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# This script builds the video editor package and deploys the frontend assets to the static directory.
|
||||||
|
# How to run: sh deploy/scripts/build_and_deploy.sh
|
||||||
|
|
||||||
|
# Exit on any error
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Starting build process..."
|
||||||
|
|
||||||
|
# Build video editor package
|
||||||
|
echo "Building video editor package..."
|
||||||
|
cd frontend-tools/video-editor
|
||||||
|
yarn build:django
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# Build chapter editor package
|
||||||
|
echo "Building chapters editor package..."
|
||||||
|
cd frontend-tools/chapters-editor
|
||||||
|
yarn build:django
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# Build video js package
|
||||||
|
echo "Building video js package..."
|
||||||
|
cd frontend-tools/video-js
|
||||||
|
yarn build:django
|
||||||
|
cd ../../
|
||||||
|
|
||||||
|
# Run npm build in the frontend container
|
||||||
|
echo "Building frontend assets..."
|
||||||
|
docker compose -f docker-compose/docker-compose-dev-updated.yaml exec frontend npm run dist
|
||||||
|
|
||||||
|
# Copy static assets to the static directory
|
||||||
|
echo "Copying static assets..."
|
||||||
|
cp -r frontend/dist/static/* static/
|
||||||
|
|
||||||
|
# Restart the web service
|
||||||
|
echo "Restarting web service..."
|
||||||
|
docker compose -f docker-compose/docker-compose-dev-updated.yaml restart web
|
||||||
|
|
||||||
|
echo "Build and deployment completed successfully!"
|
||||||
@@ -1,8 +1,35 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
migrations:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
target: base
|
||||||
|
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:
|
frontend:
|
||||||
image: node:14
|
image: node:20
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
|
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
|
||||||
working_dir: /home/mediacms.io/mediacms/frontend/
|
working_dir: /home/mediacms.io/mediacms/frontend/
|
||||||
@@ -14,21 +41,18 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- web
|
- web
|
||||||
web:
|
web:
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile-dev
|
|
||||||
image: mediacms/mediacms-dev:latest
|
image: mediacms/mediacms-dev:latest
|
||||||
|
command: "python manage.py runserver 0.0.0.0:80"
|
||||||
|
environment:
|
||||||
|
DEVELOPMENT_MODE: True
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/home/mediacms.io/mediacms/
|
- ./:/home/mediacms.io/mediacms/
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
- migrations
|
||||||
condition: service_healthy
|
|
||||||
db:
|
|
||||||
condition: service_healthy
|
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- ../postgres_data:/var/lib/postgresql/data/
|
- ../postgres_data:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -36,8 +60,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -49,3 +74,16 @@ services:
|
|||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
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'
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
|||||||
5
docker-compose.full.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
celery_worker:
|
||||||
|
image: mediacms/mediacms:full
|
||||||
@@ -13,7 +13,7 @@ services:
|
|||||||
ENABLE_CELERY_BEAT: 'no'
|
ENABLE_CELERY_BEAT: 'no'
|
||||||
ADMIN_USER: 'admin'
|
ADMIN_USER: 'admin'
|
||||||
ADMIN_EMAIL: 'admin@localhost'
|
ADMIN_EMAIL: 'admin@localhost'
|
||||||
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
|
||||||
command: "./deploy/docker/prestart.sh"
|
command: "./deploy/docker/prestart.sh"
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -62,7 +62,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- migrations
|
- migrations
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- ../postgres_data:/var/lib/postgresql/data/
|
- ../postgres_data:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -70,8 +70,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -80,6 +81,6 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "redis-cli","ping"]
|
test: ["CMD", "redis-cli","ping"]
|
||||||
interval: 30s
|
interval: 10s
|
||||||
timeout: 10s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
124
docker-compose/docker-compose-dev-updated.yaml
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
name: mediacms-dev
|
||||||
|
services:
|
||||||
|
migrations:
|
||||||
|
platform: linux/amd64
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- DEVELOPMENT_MODE=True
|
||||||
|
image: mediacms/mediacms:latest
|
||||||
|
volumes:
|
||||||
|
- ../:/home/mediacms.io/mediacms/
|
||||||
|
command: "/home/mediacms.io/mediacms/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:20
|
||||||
|
user: "root"
|
||||||
|
volumes:
|
||||||
|
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
|
||||||
|
- frontend_node_modules:/home/mediacms.io/mediacms/frontend/node_modules
|
||||||
|
- scripts_node_modules:/home/mediacms.io/mediacms/frontend/packages/scripts/node_modules
|
||||||
|
- npm_cache:/home/node/.npm
|
||||||
|
working_dir: /home/mediacms.io/mediacms/frontend/
|
||||||
|
command: >
|
||||||
|
bash -c "
|
||||||
|
echo 'Checking dependencies...' &&
|
||||||
|
if [ ! -f node_modules/.install-complete ]; then
|
||||||
|
echo 'First-time setup or dependencies changed, installing...' &&
|
||||||
|
npm install --legacy-peer-deps --cache /home/node/.npm &&
|
||||||
|
cd packages/scripts &&
|
||||||
|
npm install --legacy-peer-deps --cache /home/node/.npm &&
|
||||||
|
npm run build &&
|
||||||
|
cd ../.. &&
|
||||||
|
touch node_modules/.install-complete &&
|
||||||
|
echo 'Dependencies installed successfully'
|
||||||
|
else
|
||||||
|
echo 'Dependencies already installed, skipping installation...' &&
|
||||||
|
if [ ! -d packages/scripts/dist ]; then
|
||||||
|
echo 'Building scripts package...' &&
|
||||||
|
cd packages/scripts &&
|
||||||
|
npm run build &&
|
||||||
|
cd ../..
|
||||||
|
fi
|
||||||
|
fi &&
|
||||||
|
echo 'Starting development server...' &&
|
||||||
|
npm run start
|
||||||
|
"
|
||||||
|
env_file:
|
||||||
|
- ${PWD}/frontend/.env
|
||||||
|
ports:
|
||||||
|
- "8088:8088"
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
restart: unless-stopped
|
||||||
|
web:
|
||||||
|
platform: linux/amd64
|
||||||
|
image: mediacms/mediacms: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:
|
||||||
|
platform: linux/amd64
|
||||||
|
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'
|
||||||
|
DEVELOPMENT_MODE: True
|
||||||
|
depends_on:
|
||||||
|
- web
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
frontend_node_modules:
|
||||||
|
scripts_node_modules:
|
||||||
|
npm_cache:
|
||||||
@@ -68,7 +68,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- migrations
|
- migrations
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- ../postgres_data/:/var/lib/postgresql/data/
|
- ../postgres_data/:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -76,8 +76,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -70,7 +70,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- migrations
|
- migrations
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- ../postgres_data/:/var/lib/postgresql/data/
|
- ../postgres_data/:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -78,8 +78,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -90,7 +90,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- migrations
|
- migrations
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- ../postgres_data:/var/lib/postgresql/data/
|
- ../postgres_data:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -98,8 +98,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
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
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -66,7 +66,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- migrations
|
- migrations
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:17.2-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data/
|
- postgres_data:/var/lib/postgresql/data/
|
||||||
restart: always
|
restart: always
|
||||||
@@ -74,8 +74,9 @@ services:
|
|||||||
POSTGRES_USER: mediacms
|
POSTGRES_USER: mediacms
|
||||||
POSTGRES_PASSWORD: mediacms
|
POSTGRES_PASSWORD: mediacms
|
||||||
POSTGRES_DB: mediacms
|
POSTGRES_DB: mediacms
|
||||||
|
TZ: Europe/London
|
||||||
healthcheck:
|
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
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
- [1. Welcome](#1-welcome)
|
- [1. Welcome](#1-welcome)
|
||||||
- [2. Server Installaton](#2-server-installation)
|
- [2. Single Server Installaton](#2-single-server-installation)
|
||||||
- [3. Docker Installation](#3-docker-installation)
|
- [3. Docker Installation](#3-docker-installation)
|
||||||
- [4. Docker Deployement options](#4-docker-deployment-options)
|
- [4. Docker Deployment options](#4-docker-deployment-options)
|
||||||
- [5. Configuration](#5-configuration)
|
- [5. Configuration](#5-configuration)
|
||||||
- [6. Manage pages](#6-manage-pages)
|
- [6. Manage pages](#6-manage-pages)
|
||||||
- [7. Django admin dashboard](#7-django-admin-dashboard)
|
- [7. Django admin dashboard](#7-django-admin-dashboard)
|
||||||
@@ -16,19 +16,32 @@
|
|||||||
- [13. How To Add A Static Page To The Sidebar](#13-how-to-add-a-static-page-to-the-sidebar)
|
- [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)
|
- [14. Add Google Analytics](#14-add-google-analytics)
|
||||||
- [15. Debugging email issues](#15-debugging-email-issues)
|
- [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-how-to-change-the-video-frames-on-videos)
|
||||||
|
- [22. Role-Based Access Control](#22-role-based-access-control)
|
||||||
|
- [23. SAML setup](#23-saml-setup)
|
||||||
|
- [24. Identity Providers setup](#24-identity-providers-setup)
|
||||||
|
- [25. Custom urls](#25-custom-urls)
|
||||||
|
- [26. Allowed files](#26-allowed-files)
|
||||||
|
- [27. User upload limits](#27-user-upload-limits)
|
||||||
|
- [28. Whisper Transcribe for Automatic Subtitles](#28-whisper-transcribe-for-automatic-subtitles)
|
||||||
|
|
||||||
|
|
||||||
## 1. Welcome
|
## 1. Welcome
|
||||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||||
|
|
||||||
## 2. Server Installation
|
## 2. 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.
|
The core dependencies are python3, Django, celery, PostgreSQL, redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But the install.sh is only tested in Linux Ubuntu 24 and 22 versions.
|
||||||
|
|
||||||
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
|
Installation on an Ubuntu 22/24 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.
|
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
|
```bash
|
||||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||||
@@ -47,10 +60,25 @@ If you've used the above way to install MediaCMS, update with the following:
|
|||||||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||||
source /home/mediacms.io/bin/activate # use virtualenv
|
source /home/mediacms.io/bin/activate # use virtualenv
|
||||||
git pull # update code
|
git pull # update code
|
||||||
|
pip install -r requirements.txt -U # run pip install to update
|
||||||
python manage.py migrate # run Django migrations
|
python manage.py migrate # run Django migrations
|
||||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
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
|
### Configuration
|
||||||
Checkout the configuration section here.
|
Checkout the configuration section here.
|
||||||
|
|
||||||
@@ -64,13 +92,11 @@ Database can be backed up with pg_dump and media_files on /home/mediacms.io/medi
|
|||||||
## Installation
|
## Installation
|
||||||
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||||
|
|
||||||
For Ubuntu 18/20 systems this is:
|
For Ubuntu systems this is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
sudo sh 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
|
Then run as root
|
||||||
@@ -86,7 +112,7 @@ If you want to explore more options (including setup of https with letsencrypt c
|
|||||||
Run
|
Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up
|
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
|
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
|
||||||
@@ -99,6 +125,12 @@ 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
|
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
|
||||||
|
|
||||||
|
`Note`: if you want to use the automatic transcriptions, you have to do one of the following:
|
||||||
|
* either use the docker-compose.full.yaml, so in this case run `docker-compose -f docker-compose.yaml -f docker-compose.full.yaml up`
|
||||||
|
* or edit the docker-compose.yaml file and set the image for the celery_worker service as mediacms/mediacms:full instead of mediacms/mediacms:latest
|
||||||
|
|
||||||
|
Plus set variable `USE_WHISPER_TRANSCRIBE = True` in the settings.py file
|
||||||
|
|
||||||
### Update
|
### Update
|
||||||
|
|
||||||
Get latest MediaCMS image and stop/start containers
|
Get latest MediaCMS image and stop/start containers
|
||||||
@@ -106,10 +138,22 @@ Get latest MediaCMS image and stop/start containers
|
|||||||
```bash
|
```bash
|
||||||
cd /path/to/mediacms/installation
|
cd /path/to/mediacms/installation
|
||||||
docker pull mediacms/mediacms
|
docker pull mediacms/mediacms
|
||||||
docker-compose down
|
docker compose down
|
||||||
docker-compose up
|
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
|
## Configuration
|
||||||
Checkout the configuration docs here.
|
Checkout the configuration docs here.
|
||||||
|
|
||||||
@@ -133,9 +177,7 @@ By default, all these services are enabled, but in order to create a scaleable d
|
|||||||
|
|
||||||
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.
|
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`
|
||||||
|
|
||||||
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
|
### Simple Deployment, accessed as http://localhost
|
||||||
|
|
||||||
@@ -152,7 +194,7 @@ Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my
|
|||||||
|
|
||||||
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
|
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!
|
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
|
### Advanced Deployment, accessed as http://localhost:8000
|
||||||
|
|
||||||
@@ -193,12 +235,17 @@ Single server installation: edit `cms/local_settings.py`, make a change and rest
|
|||||||
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
|
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#docker-compose restart web celery_worker celery_beat
|
#docker compose restart web celery_worker celery_beat
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.1 Change portal logo
|
### 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`)
|
Find the default svg files for the white theme on `static/images/logo_dark.svg` and for the dark theme on `static/images/logo_light.svg`
|
||||||
|
You can specify new svg paths to override by editing the `PORTAL_LOGO_DARK_SVG` and `PORTAL_LOGO_LIGHT_SVG` variables in `settings.py`.
|
||||||
|
|
||||||
|
You can also use custom pngs, by setting the variables `PORTAL_LOGO_DARK_PNG` and `PORTAL_LOGO_LIGHT_PNG` in `settings.py`. The svg files have priority over png files, so if both are set, svg files will be used.
|
||||||
|
|
||||||
|
In any case, make sure the files are placed on the static/images folder.
|
||||||
|
|
||||||
### 5.2 Set global portal title
|
### 5.2 Set global portal title
|
||||||
|
|
||||||
@@ -323,13 +370,22 @@ ADMIN_EMAIL_LIST = ['info@mediacms.io']
|
|||||||
|
|
||||||
### 5.13 Disallow user registrations from specific domains
|
### 5.13 Disallow user registrations from specific domains
|
||||||
|
|
||||||
set domains that are not valid for registration via this variable:
|
Set domains that are not valid for registration via this variable:
|
||||||
|
|
||||||
```
|
```
|
||||||
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
|
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
|
||||||
'xxx.com', 'emaildomainwhatever.com']
|
'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
|
### 5.14 Require a review by MediaCMS editors/managers/admins
|
||||||
|
|
||||||
set value
|
set value
|
||||||
@@ -441,6 +497,86 @@ ADMINS_NOTIFICATIONS = {
|
|||||||
- Make the portal workflow public, but at the same time set `GLOBAL_LOGIN_REQUIRED = True` so that only logged in users can see content.
|
- 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.
|
- 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.
|
||||||
|
|
||||||
|
### 5.26 Control whether anonymous users can list all users
|
||||||
|
|
||||||
|
By default, anonymous users can view the list of all users on the platform. To restrict this to authenticated users only, set:
|
||||||
|
|
||||||
|
```
|
||||||
|
ALLOW_ANONYMOUS_USER_LISTING = False
|
||||||
|
```
|
||||||
|
|
||||||
|
When set to False, only logged-in users will be able to access the user listing API endpoint.
|
||||||
|
|
||||||
|
|
||||||
|
### 5.27 Control who can see the members page
|
||||||
|
|
||||||
|
By default `CAN_SEE_MEMBERS_PAGE = "all"` means that all registered users can see the members page. Other valid options are:
|
||||||
|
|
||||||
|
- **editors**, only MediaCMS editors can view the page
|
||||||
|
- **admins**, only MediaCMS admins can view the page
|
||||||
|
|
||||||
|
|
||||||
|
### 5.28 Configure user search fields
|
||||||
|
|
||||||
|
By default, when searching for users (e.g., in bulk actions modals or the users API), the search is performed on the user's name and username. You can configure this behavior using the `USER_SEARCH_FIELD` setting:
|
||||||
|
|
||||||
|
```
|
||||||
|
USER_SEARCH_FIELD = "name_username" # Default - searches in name and username
|
||||||
|
```
|
||||||
|
|
||||||
|
To also include email addresses in the search and display them in the user interface:
|
||||||
|
|
||||||
|
```
|
||||||
|
USER_SEARCH_FIELD = "name_username_email" # Searches in name, username, and email
|
||||||
|
```
|
||||||
|
|
||||||
|
When set to `"name_username_email"`:
|
||||||
|
- The user search will also match email addresses
|
||||||
|
- The email field will be returned in the API response
|
||||||
|
- Frontend components will display users as "Name - Email" instead of "Name - Username"
|
||||||
|
|
||||||
|
This setting is useful when you want to make it easier to find users by their email addresses, particularly in administrative interfaces like bulk action modals.
|
||||||
|
|
||||||
|
|
||||||
|
### 5.29 Require user approval on registration
|
||||||
|
|
||||||
|
By default, users do not require approval, so they can login immediately after registration (if registration is open). However, if the parameter `USERS_NEEDS_TO_BE_APPROVED` is set to `True`, they will first have to have their accounts approved by an administrator before they can successfully sign in.
|
||||||
|
Administrators can approve users through the following ways: 1. through Django administration, 2. through the users management page, 3. through editing the profile page directly. In all cases, set 'Is approved' to True.
|
||||||
|
|
||||||
|
### 5.30 Show or hide media count numbers on categories and tags pages
|
||||||
|
|
||||||
|
By default, the number of media items is displayed next to each category and tag on the `/categories` and `/tags` pages. To hide these numbers:
|
||||||
|
|
||||||
|
```
|
||||||
|
INCLUDE_LISTING_NUMBERS = False
|
||||||
|
```
|
||||||
|
|
||||||
|
To show the numbers (default behavior):
|
||||||
|
|
||||||
|
```
|
||||||
|
INCLUDE_LISTING_NUMBERS = True
|
||||||
|
```
|
||||||
|
|
||||||
|
This setting affects only the visual display on the categories and tags listing pages and does not impact the functionality of filtering by categories or tags.
|
||||||
|
|
||||||
|
|
||||||
## 6. Manage pages
|
## 6. Manage pages
|
||||||
to be written
|
to be written
|
||||||
|
|
||||||
@@ -459,7 +595,9 @@ to be written
|
|||||||
Through the admin section - http://your_installation/admin/
|
Through the admin section - http://your_installation/admin/
|
||||||
|
|
||||||
## 12. Video transcoding
|
## 12. Video transcoding
|
||||||
Add / remove resolutions and profiles through http://your_installation/admin/encodeprofile
|
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
|
## 13. How To Add A Static Page To The Sidebar
|
||||||
|
|
||||||
@@ -687,3 +825,254 @@ 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')
|
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, set `USE_ROUNDED_CORNERS = False` in `local_settings.py`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 22. Role-Based Access Control
|
||||||
|
|
||||||
|
By default there are 3 statuses for any Media that lives on the system, public, unlisted, private. When RBAC support is added, a user that is part of a group has access to media that are published to one or more categories that the group is associated with. The workflow is this:
|
||||||
|
|
||||||
|
|
||||||
|
1. A Group is created
|
||||||
|
2. A Category is associated with the Group
|
||||||
|
3. A User is added to the Group
|
||||||
|
|
||||||
|
Now user can view the Media even if it is in private state. User also sees all media in Category page
|
||||||
|
|
||||||
|
When user is added to group, they can be set as Member, Contributor, Manager.
|
||||||
|
|
||||||
|
- Member: user can view media that are published on one or more categories that this group is associated with
|
||||||
|
- Contributor: besides viewing, user can also edit the Media in a category associated with this Group. They can also publish Media to this category
|
||||||
|
- Manager: same as Contributor for now
|
||||||
|
|
||||||
|
Use cases facilitated with RBAC:
|
||||||
|
- viewing a Media in private state: if RBAC is enabled, if user is Member on a Group that is associated with a Category, and the media is published to this Category, then user can view the media
|
||||||
|
- editing a Media: if RBAC is enabled, and user is Contributor to one or more Categories, they can publish media to these Categories as long as they are associated with one Group
|
||||||
|
- viewing all media of a category: if RBAC is enabled, and user visits a Category, they are able to see the listing of all media that are published in this category, independent of their state, provided that the category is associated with a group that the user is member of
|
||||||
|
- viewing all categories associated with groups the user is member of: if RBAC is enabled, and user visits the listing of categories, they can view all categories that are associated with a group the user is member
|
||||||
|
|
||||||
|
How to enable RBAC support:
|
||||||
|
|
||||||
|
```
|
||||||
|
USE_RBAC = True
|
||||||
|
```
|
||||||
|
|
||||||
|
on `local_settings.py` and restart the instance.
|
||||||
|
|
||||||
|
|
||||||
|
## 23. SAML setup
|
||||||
|
SAML authentication is supported along with the option to utilize the SAML response and do useful things as setting up the user role in MediaCMS or participation in groups.
|
||||||
|
|
||||||
|
To enable SAML support, edit local_settings.py and set the following options:
|
||||||
|
|
||||||
|
```
|
||||||
|
USE_RBAC = True
|
||||||
|
USE_SAML = True
|
||||||
|
USE_IDENTITY_PROVIDERS = True
|
||||||
|
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
|
||||||
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
|
"saml": {
|
||||||
|
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
To set a SAML provider:
|
||||||
|
|
||||||
|
- Step 1: Add SAML Identity Provider
|
||||||
|
1. Navigate to Admin panel
|
||||||
|
2. Select "Identity Provider"
|
||||||
|
3. Configure as follows:
|
||||||
|
- **Provider**: saml
|
||||||
|
- **Provider ID**: an ID for the provider
|
||||||
|
- **IDP Config Name**: a name for the provider
|
||||||
|
- **Client ID**: the identifier that is part of the login, and that is shared with the IDP.
|
||||||
|
- **Site**: Set the default one
|
||||||
|
|
||||||
|
- Step 2: Add SAML Configuration
|
||||||
|
Select the SAML Configurations tab, create a new one and set:
|
||||||
|
|
||||||
|
1. **IDP ID**: Must be a URL
|
||||||
|
2. **IDP Certificate**: x509cert from your SAML provider
|
||||||
|
3. **SSO URL**:
|
||||||
|
4. **SLO URL**:
|
||||||
|
5. **SP Metadata URL**: The metadata URL that the IDP will utilize. This can be https://{portal}/saml/metadata and is autogenerated by MediaCMS
|
||||||
|
|
||||||
|
- Step 3: Set other Options
|
||||||
|
1. **Email Settings**:
|
||||||
|
- `verified_email`: When enabled, emails from SAML responses will be marked as verified
|
||||||
|
- `Remove from groups`: When enabled, user is removed from a group after login, if they have been removed from the group on the IDP
|
||||||
|
2. **Global Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in MediaCMS
|
||||||
|
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
|
||||||
|
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
|
||||||
|
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
|
||||||
|
|
||||||
|
A full SAML deployment with [EntraID guide and troubleshooting steps is available here.](./saml_entraid_setup.md). This guide can be used as reference for other IDPs too.
|
||||||
|
|
||||||
|
## 24. Identity Providers setup
|
||||||
|
|
||||||
|
A separate Django app identity_providers has been added in order to facilitate a number of configurations related to different identity providers. If this is enabled, it gives the following options:
|
||||||
|
|
||||||
|
- allows to add an Identity Provider through Django admin, and set a number of mappings, as Group Mapping, Global Role mapping and more. While SAML is the only provider that can be added out of the box, any identity provider supported by django allauth can be added with minimal effort. If the response of the identity provider contains attributes as role, or groups, then these can be mapped to MediaCMS specific roles (advanced user, editor, manager, admin) and groups (rbac groups)
|
||||||
|
- saves SAML response logs after user is authenticated (can be utilized for other providers too)
|
||||||
|
- allows to specify a list of login options through the admin (eg system login, identity provider login)
|
||||||
|
|
||||||
|
|
||||||
|
to enable the identity providers, set the following setting on `local_settings.py`:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
USE_IDENTITY_PROVIDERS = True
|
||||||
|
```
|
||||||
|
|
||||||
|
Visiting the admin, you will see the Identity Providers tab and you can add one.
|
||||||
|
|
||||||
|
## 25. Custom urls
|
||||||
|
To enable custom urls, set `ALLOW_CUSTOM_MEDIA_URLS = True` on settings.py or local_settings.py
|
||||||
|
This will enable editing the URL of the media, while editing a media. If the URL is already taken you get a message you cannot update this.
|
||||||
|
|
||||||
|
## 26. Allowed files
|
||||||
|
MediaCMS performs identification attempts on new file uploads and only allows certain file types specified in the `ALLOWED_MEDIA_UPLOAD_TYPES` setting. By default, only ["video", "audio", "image", "pdf"] files are allowed.
|
||||||
|
|
||||||
|
When a file is not identified as one of these allowed types, the file gets removed from the system and there's an entry indicating that this is not a supported media type.
|
||||||
|
|
||||||
|
If you want to change the allowed file types, edit the `ALLOWED_MEDIA_UPLOAD_TYPES` list in your `settings.py` or `local_settings.py` file. If 'all' is specified in this list, no check is performed and all files are allowed.
|
||||||
|
|
||||||
|
## 27. User upload limits
|
||||||
|
MediaCMS allows you to set a maximum number of media files that each user can upload. This is controlled by the `NUMBER_OF_MEDIA_USER_CAN_UPLOAD` setting in `settings.py` or `local_settings.py`. By default, this is set to 100 media items per user.
|
||||||
|
|
||||||
|
When a user reaches this limit, they will no longer be able to upload new media until they delete some of their existing content. This limit applies regardless of the user's role or permissions in the system.
|
||||||
|
|
||||||
|
To change the maximum number of uploads allowed per user, modify the `NUMBER_OF_MEDIA_USER_CAN_UPLOAD` value in your settings file:
|
||||||
|
|
||||||
|
```
|
||||||
|
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 28. Whisper Transcribe for Automatic Subtitles
|
||||||
|
MediaCMS can integrate with OpenAI's Whisper to automatically generate subtitles for your media files. This feature is useful for making your content more accessible.
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
When the whisper transcribe task is triggered for a media file, MediaCMS runs the `whisper` command-line tool to process the audio and generate a subtitle file in VTT format. The generated subtitles are then associated with the media and are available under the "automatic" language option.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Transcription functionality is available only for the Docker installation. To enable this feature, you must either use the `docker-compose.full.yaml` file, as it contains an image with the necessary requirements, or you can also set that celery_worker service is usine mediacms:full image instead of mediacms:latest. Then you also have to set the setting: `USE_WHISPER_TRANSCRIBE = True` in your local_settings.py file.
|
||||||
|
|
||||||
|
By default, all users have the ability to send a request for a video to be transcribed, as well as transcribed and translated to English. If you wish to change this behavior, you can edit the `settings.py` file and set `USER_CAN_TRANSCRIBE_VIDEO=False`.
|
||||||
|
|
||||||
|
The transcription uses the base model of Whisper speech-to-text by default. However, you can change the model by editing the `WHISPER_MODEL` setting in `settings.py`.
|
||||||
|
|||||||
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`
|
||||||
|
* gunicorn 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
|
||||||
|
...
|
||||||
|
```
|
||||||
@@ -50,15 +50,22 @@ Checkout the [Code of conduct page](../CODE_OF_CONDUCT.md) if you want to contri
|
|||||||
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)
|
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 build
|
||||||
docker-compose -f docker-compose-dev.yaml up
|
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
|
### 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
|
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
|
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,
|
And then in order for the changes to be visible on the application while served through nginx,
|
||||||
@@ -83,7 +90,7 @@ http://localhost:8088/manage-media.html manage_media
|
|||||||
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
|
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
|
docker compose -f docker-compose-dev.yaml restart web
|
||||||
```
|
```
|
||||||
|
|
||||||
## How video is transcoded
|
## How video is transcoded
|
||||||
@@ -115,19 +122,19 @@ This instructions assume that you're using the docker installation
|
|||||||
1. start docker-compose
|
1. start docker-compose
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose up
|
docker compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Install the requirements on `requirements-dev.txt ` on web container (we'll use the web container for this)
|
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
|
docker compose exec -T web pip install -r requirements-dev.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Now you can run the existing tests
|
3. Now you can run the existing tests
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose exec --env TESTING=True -T web pytest
|
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)
|
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)
|
||||||
@@ -136,13 +143,13 @@ The `TESTING=True` is passed for Django to be aware this is a testing environmen
|
|||||||
4. You may try a single test, by specifying the path, for example
|
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
|
docker compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
|
||||||
```
|
```
|
||||||
|
|
||||||
5. You can also see the coverage
|
5. You can also see the coverage
|
||||||
|
|
||||||
```
|
```
|
||||||
docker-compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
|
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 ;)
|
and of course...you are very welcome to help us increase it ;)
|
||||||
|
|||||||
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/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/cookie_consent.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
166
docs/media_permissions.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# Media Permissions in MediaCMS
|
||||||
|
|
||||||
|
This document explains the permission system in MediaCMS, which controls who can view, edit, and manage media files.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MediaCMS provides a flexible permission system that allows fine-grained control over media access. The system supports:
|
||||||
|
|
||||||
|
1. **Basic permissions** - Public, private, and unlisted media
|
||||||
|
2. **User-specific permissions** - Direct permissions granted to specific users
|
||||||
|
3. **Role-Based Access Control (RBAC)** - Category-based permissions through group membership
|
||||||
|
|
||||||
|
## Media States
|
||||||
|
|
||||||
|
Every media file has a state that determines its basic visibility:
|
||||||
|
|
||||||
|
- **Public** - Visible to everyone
|
||||||
|
- **Private** - Only visible to the owner and users with explicit permissions
|
||||||
|
- **Unlisted** - Not listed in public listings but accessible via direct link
|
||||||
|
|
||||||
|
|
||||||
|
## User Roles
|
||||||
|
|
||||||
|
MediaCMS has several user roles that affect permissions:
|
||||||
|
|
||||||
|
- **Regular User** - Can upload and manage their own media
|
||||||
|
- **Advanced User** - Additional capabilities (configurable)
|
||||||
|
- **MediaCMS Editor** - Can edit and review content across the platform
|
||||||
|
- **MediaCMS Manager** - Full management capabilities
|
||||||
|
- **Admin** - Complete system access
|
||||||
|
|
||||||
|
## Direct Media Permissions
|
||||||
|
|
||||||
|
The `MediaPermission` model allows granting specific permissions to individual users:
|
||||||
|
|
||||||
|
### Permission Levels
|
||||||
|
|
||||||
|
- **Viewer** - Can view the media even if it's private
|
||||||
|
- **Editor** - Can view and edit the media's metadata
|
||||||
|
- **Owner** - Full control, including deletion
|
||||||
|
|
||||||
|
## Role-Based Access Control (RBAC)
|
||||||
|
|
||||||
|
When RBAC is enabled (`USE_RBAC` setting), permissions can be managed through categories and groups:
|
||||||
|
|
||||||
|
1. Categories can be marked as RBAC-controlled
|
||||||
|
2. Users are assigned to RBAC groups with specific roles
|
||||||
|
3. RBAC groups are associated with categories
|
||||||
|
4. Users inherit permissions to media in those categories based on their role
|
||||||
|
|
||||||
|
### RBAC Roles
|
||||||
|
|
||||||
|
- **Member** - Can view media in the category
|
||||||
|
- **Contributor** - Can view and edit media in the category
|
||||||
|
- **Manager** - Full control over media in the category
|
||||||
|
|
||||||
|
## Permission Checking Methods
|
||||||
|
|
||||||
|
The User model provides several methods to check permissions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# From users/models.py
|
||||||
|
def has_member_access_to_media(self, media):
|
||||||
|
# Check if user can view the media
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def has_contributor_access_to_media(self, media):
|
||||||
|
# Check if user can edit the media
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def has_owner_access_to_media(self, media):
|
||||||
|
# Check if user has full control over the media
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## How Permissions Are Applied
|
||||||
|
|
||||||
|
When a user attempts to access media, the system checks permissions in this order:
|
||||||
|
|
||||||
|
1. Is the media public? If yes, allow access.
|
||||||
|
2. Is the user the owner of the media? If yes, allow full access.
|
||||||
|
3. Does the user have direct permissions through MediaPermission? If yes, grant the corresponding access level.
|
||||||
|
4. If RBAC is enabled, does the user have access through category membership? If yes, grant the corresponding access level.
|
||||||
|
5. If none of the above, deny access.
|
||||||
|
|
||||||
|
## Media Sharing
|
||||||
|
|
||||||
|
Users can share media with others by:
|
||||||
|
|
||||||
|
1. Making it public or unlisted
|
||||||
|
2. Granting direct permissions to specific users
|
||||||
|
3. Adding it to a category that's accessible to an RBAC group
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Media Listing
|
||||||
|
|
||||||
|
When listing media, the system filters based on permissions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Simplified example from files/views/media.py
|
||||||
|
def _get_media_queryset(self, request, user=None):
|
||||||
|
# 1. Public media
|
||||||
|
listable_media = Media.objects.filter(listable=True)
|
||||||
|
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return listable_media
|
||||||
|
|
||||||
|
# 2. User permissions for authenticated users
|
||||||
|
user_media = Media.objects.filter(permissions__user=request.user)
|
||||||
|
|
||||||
|
# 3. RBAC for authenticated users
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||||
|
rbac_media = Media.objects.filter(category__in=rbac_categories)
|
||||||
|
|
||||||
|
# Combine all accessible media
|
||||||
|
return listable_media.union(user_media, rbac_media)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission Checking
|
||||||
|
|
||||||
|
The system uses helper methods to check permissions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# From users/models.py
|
||||||
|
def has_member_access_to_media(self, media):
|
||||||
|
# First check if user is the owner
|
||||||
|
if media.user == self:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Then check RBAC permissions
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
rbac_groups = RBACGroup.objects.filter(
|
||||||
|
memberships__user=self,
|
||||||
|
memberships__role__in=["member", "contributor", "manager"],
|
||||||
|
categories__in=media.category.all()
|
||||||
|
).distinct()
|
||||||
|
if rbac_groups.exists():
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Then check MediaShare permissions for any access
|
||||||
|
media_permission_exists = MediaPermission.objects.filter(
|
||||||
|
user=self,
|
||||||
|
media=media,
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
return media_permission_exists
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Default to Private** - Consider setting new uploads to private by default
|
||||||
|
2. **Use Categories** - Organize media into categories for easier permission management
|
||||||
|
3. **RBAC for Teams** - Use RBAC for team collaboration scenarios
|
||||||
|
4. **Direct Permissions for Exceptions** - Use direct permissions for one-off sharing
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The permission system can be configured through several settings:
|
||||||
|
|
||||||
|
- `USE_RBAC` - Enable/disable Role-Based Access Control
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
MediaCMS provides a flexible and powerful permission system that can accommodate various use cases, from simple personal media libraries to complex team collaboration scenarios with fine-grained access control.
|
||||||
315
docs/saml_entraid_setup.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# Integrating Microsoft Entra ID (formerly Azure AD) with MediaCMS via SAML Authentication
|
||||||
|
|
||||||
|
This guide provides step-by-step instructions on how to configure Microsoft Entra ID as a SAML Identity Provider (IdP) for MediaCMS, an open-source content management system. The goal is to enable single sign-on (SSO) authentication for users in a secure and scalable way.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Overview](#overview)
|
||||||
|
2. [Prerequisites](#prerequisites)
|
||||||
|
3. [Step 1: Configure MediaCMS for SAML](#step-1-configure-mediacms-for-saml)
|
||||||
|
4. [Step 2: Register MediaCMS as an Enterprise App in Entra ID](#step-2-register-mediacms-as-an-enterprise-app-in-entra-id)
|
||||||
|
5. [Step 3: Configure SAML Settings in Entra ID](#step-3-configure-saml-settings-in-entra-id)
|
||||||
|
6. [Step 4: Configure SAML Settings in MediaCMS](#step-4-configure-saml-settings-in-mediacms)
|
||||||
|
7. [Step 5: Allow Users or Groups to Log Into the Application](#step-5-allow-users-or-groups-to-log-into-the-application)
|
||||||
|
8. [Step 6: Test and Validate Login Flow](#step-6-test-and-validate-login-flow)
|
||||||
|
9. [Troubleshooting](#troubleshooting)
|
||||||
|
10. [Resources](#resources)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MediaCMS supports SAML 2.0 authentication by acting as a Service Provider (SP). By integrating with Microsoft Entra ID, organizations can allow users to authenticate using their existing enterprise credentials.
|
||||||
|
|
||||||
|
In our particular deployment of MediaCMS, the application is hosted internally with no direct inbound access from the public Internet. As an internal company application, it was essential to integrate it with our existing authentication systems and provide a seamless single sign-on experience. This is where the SAML protocol shines.
|
||||||
|
|
||||||
|
One of the major advantages of SAML authentication is that all communication between the Identity Provider (IdP) тАФ in this case, Microsoft Entra ID тАФ and the Service Provider (SP) тАФ MediaCMS тАФ is brokered entirely by the end user's browser. The browser initiates the authentication flow, communicates securely with MicrosoftтАЩs login portal, receives the identity assertion, and then passes it back to the internal MediaCMS server.
|
||||||
|
|
||||||
|
This architecture enables the MediaCMS server to remain isolated from the Internet while still participating in a modern and seamless federated login experience.
|
||||||
|
|
||||||
|
Even though the deployment method outlined in this tutorial is for EntraID on an isolated MediaCMS server, the same steps and general information could be applied to another authentication SAML provider/identity provider on a non-isolated system.
|
||||||
|
|
||||||
|
> **Note**: This guide assumes you are running MediaCMS with Django backend and that the `django-allauth` library is enabled and configured.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before beginning, ensure the following:
|
||||||
|
|
||||||
|
* You have administrator access to both MediaCMS and Microsoft Entra ID (Azure portal).
|
||||||
|
* MediaCMS is installed and accessible via HTTPS, with a valid SSL certificate.
|
||||||
|
* Your MediaCMS installation has SAML support enabled (via `django-allauth`).
|
||||||
|
* You have a dedicated domain or subdomain for MediaCMS (e.g., `https://<MyMediaCMS.MyDomain.com>`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Configure MediaCMS for SAML
|
||||||
|
|
||||||
|
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./deploy/docker/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
|
||||||
|
|
||||||
|
```python
|
||||||
|
USE_RBAC = True
|
||||||
|
USE_SAML = True
|
||||||
|
USE_IDENTITY_PROVIDERS = True
|
||||||
|
|
||||||
|
USE_X_FORWARDED_HOST = True
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
SECURE_SSL_REDIRECT = True
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
|
||||||
|
SOCIALACCOUNT_PROVIDERS = {
|
||||||
|
"saml": {
|
||||||
|
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
These settings enable SAML authentication, configure MediaCMS to respect role-based access, and apply important headers and cookie policies for secure browser handling тАФ all of which are necessary for the SAML flow to function properly.
|
||||||
|
|
||||||
|
> тЪая╕П **Important**: After updating the `local_settings.py` file, you must restart your MediaCMS service (e.g., by rebooting the Docker container) in order for the changes to take effect. This step must be completed before proceeding to the next configuration stage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Register MediaCMS as an Enterprise App in Entra ID
|
||||||
|
|
||||||
|
To begin the integration process on the Microsoft Entra ID (formerly Azure AD) side, follow the steps below to register MediaCMS as a new Enterprise Application.
|
||||||
|
|
||||||
|
### 1. Navigate to Enterprise Applications
|
||||||
|
|
||||||
|
* Log in to your [Azure Portal](https://portal.azure.com).
|
||||||
|
* Navigate to **Enterprise Applications**.
|
||||||
|
|
||||||
|
> *Note: This guide assumes you already have an existing Azure tenant and Entra ID configured with users and groups.*
|
||||||
|
|
||||||
|
### 2. Create a New Application
|
||||||
|
|
||||||
|
* Click the **+ New Application** button.
|
||||||
|
* On the next screen, choose **Create your own application**.
|
||||||
|
* Enter a name for the application (e.g., `MediaCMS`).
|
||||||
|
* Under "What are you looking to do with your application?", select **Integrate any other application you don't find in the gallery (Non-gallery)**.
|
||||||
|
* Click **Create**.
|
||||||
|
|
||||||
|
After a few moments, Azure will create the new application and redirect you to its configuration page.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Configure SAML Settings in Entra ID
|
||||||
|
|
||||||
|
### 1. Configure SAML-Based Single Sign-On
|
||||||
|
|
||||||
|
* From the application overview page, in the left-hand menu under **Manage**, click **Single sign-on**.
|
||||||
|
* You will be prompted to choose a sign-on method. Select **SAML**.
|
||||||
|
|
||||||
|
### 2. Choose a Client ID Name
|
||||||
|
|
||||||
|
Before filling out the SAML configuration, you must decide on a client ID name. This name will uniquely identify your SAML integration and appear in your login URL.
|
||||||
|
|
||||||
|
* Choose a name that is descriptive and easy to remember (e.g., `mediacms_entraid`).
|
||||||
|
* You will use this name in both MediaCMS and Entra ID configuration settings.
|
||||||
|
|
||||||
|
### 3. Fill Out Basic SAML Configuration
|
||||||
|
|
||||||
|
Now input the following values under the **Basic SAML Configuration** section:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| -------------------------- | --------------------------------------------------------------------- |
|
||||||
|
| **Identifier (Entity ID)** | `https://<MyMediaCMS.MyDomain.com>/saml/metadata/` |
|
||||||
|
| **Reply URL (ACS URL)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/` |
|
||||||
|
| **Sign-on URL** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/` |
|
||||||
|
| **Relay State (Optional)** | `https://<MyMediaCMS.MyDomain.com>/` |
|
||||||
|
| **Logout URL (Optional)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/sls/` |
|
||||||
|
|
||||||
|
> ЁЯФР Replace `<MyClientID>` with your own chosen client ID if different.
|
||||||
|
|
||||||
|
Once these fields are filled in, save your configuration.
|
||||||
|
|
||||||
|
Keep the Azure Enterprise single sign-on configuration window up, as we are now going to configure some of the details from this Azure page into our MediaCMS system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Configure SAML Settings in MediaCMS
|
||||||
|
|
||||||
|
In MediaCMS, start by logging into the back-end administrative web page. You will now have new options under the left-hand menu bar.
|
||||||
|
|
||||||
|
### 1. Add Login Option
|
||||||
|
|
||||||
|
* Navigate to **Identity Providers тЖТ Login Options**.
|
||||||
|
|
||||||
|
* Click **Add Login Option**.
|
||||||
|
|
||||||
|
* Give the login option a title. This title can be anything you like but it will appear to the end-user when they select a method of logging in, so ensure the name is clear. (e.g., `EntraID-SSO`).
|
||||||
|
|
||||||
|
* Set the **Login URL** to the same Sign-on URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/
|
||||||
|
```
|
||||||
|
|
||||||
|
* Leave the ordering at `0` if you have no other authentication methods.
|
||||||
|
|
||||||
|
* Ensure the **Active** box is checked to make this an active login method.
|
||||||
|
|
||||||
|
* Click **Save** to continue.
|
||||||
|
|
||||||
|
### 2. Add ID Provider
|
||||||
|
|
||||||
|
* Navigate to **Identity Providers тЖТ ID Providers**.
|
||||||
|
* Click **Add ID Provider**.
|
||||||
|
|
||||||
|
Back in your Azure Enterprise application configuration window (at the bottom of the Single Sign-On configuration menu), find your application-specific details. They will look like the following example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Example unique AppID: 123456ab-1234-12ab-ab12-abc123abc123
|
||||||
|
The unique AppID is automatically generated when you create the application.
|
||||||
|
|
||||||
|
-- Example URLs --
|
||||||
|
Login URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
|
||||||
|
Microsoft Entra Identifier: https://sts.windows.net/123456ab-1234-12ab-ab12-abc123abc123/
|
||||||
|
Logout URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
|
||||||
|
```
|
||||||
|
|
||||||
|
Back in MediaCMS's new ID Provider window, under the **General** tab:
|
||||||
|
|
||||||
|
* **Protocol**: `saml` (all lowercase)
|
||||||
|
* **Provider ID**: The Microsoft Entra Identifier (as shown above), the whole URL.
|
||||||
|
* **IDP Configuration Name**: Any unique name (e.g., `EntraID`)
|
||||||
|
* **Client ID**: The exact same client ID you used earlier when configuring EntraID (e.g., `mediacms_entraid`).
|
||||||
|
* **Sites**: Add all the sites you want this login to appear on (e.g., all of them)
|
||||||
|
|
||||||
|
Click **Save and Continue**, then go to the **SAML Configuration** tab.
|
||||||
|
|
||||||
|
On the **SAML Configuration** tab:
|
||||||
|
|
||||||
|
* **SSO URL**: Use the same Logon URL from EntraID example listed above.
|
||||||
|
|
||||||
|
* **SLO URL**: Use the Logout URL from EntraID example listed above.
|
||||||
|
|
||||||
|
* **SP Metadata URL**:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
|
||||||
|
```
|
||||||
|
|
||||||
|
* **IdP ID**: Use the same Microsoft Entra Identifier URL as listed above.
|
||||||
|
|
||||||
|
#### LDP Certificate
|
||||||
|
|
||||||
|
Back in Azure's Enterprise Application page (SAML certificates section), download the **Base64 Certificate**, open it in a text editor, and copy the contents into the **LDP Certificate** setting inside of MediaCMS.
|
||||||
|
|
||||||
|
### 3. Configure Identity Mappings
|
||||||
|
|
||||||
|
Map the identity attributes that Entra ID will provide to MediaCMS. Even though only UID is specified as mandatory, Entra ID will not work unless all of these details are filled in(YES, you must type NA in the fields; you cannot leave anything blank. You will get 500 errors if this is not done). You can use the exact settings below:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| -------------- | -------------------------------------------------------------------- |
|
||||||
|
| **Uid** | `http://schemas.microsoft.com/identity/claims/objectidentifier` |
|
||||||
|
| **Name** | `http://schemas.microsoft.com/identity/claims/displayname` |
|
||||||
|
| **Email** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` |
|
||||||
|
| **Groups** | `NA` |
|
||||||
|
| **First name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` |
|
||||||
|
| **Last name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` |
|
||||||
|
| **User logo** | `NA` |
|
||||||
|
| **Role** | `NA` |
|
||||||
|
|
||||||
|
> тД╣я╕П Groups and Role can be changed or remapped inside the Azure Enterprise Application under **Attributes and Claims**.
|
||||||
|
|
||||||
|
Check the **Verified Email** box (since EntraID will verify the user for you). While setting up, you can enable **Save SAML Response Log** for troubleshooting purposes.
|
||||||
|
|
||||||
|
Finally, click **Save** to finish adding the new ID provider.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Allow Users or Groups to Log Into the Application
|
||||||
|
|
||||||
|
Back inside Azure AD, within your MediaCMS Enterprise Application, you must assign users or groups that are allowed to use the MediaCMS authentication sign-on.
|
||||||
|
|
||||||
|
### 1. Navigate to Users and Groups
|
||||||
|
|
||||||
|
* Open the Azure Portal and go to your **MediaCMS Enterprise Application**.
|
||||||
|
* In the left-hand **Manage** menu, click **Users and Groups**.
|
||||||
|
|
||||||
|
### 2. Assign Users or Groups
|
||||||
|
|
||||||
|
* Add individual users or groups of users who are allowed to use the EntraID authentication method with MediaCMS.
|
||||||
|
* In this example, the application was provided to all registered users inside of EntraID by using the special group **All Users**, which grants any registered user in the tenant access to MediaCMS.
|
||||||
|
|
||||||
|
> тЪая╕П **Important**: Nested groups will not work. All users must be directly assigned to the group you are giving permission to. If a group contains another group, the users of the nested group will not inherit the permissions to use this application from the parent group.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Test and Validate Login Flow
|
||||||
|
|
||||||
|
At this point, you should go to your MediaCMS webpage and attempt to log in using the authentication method that you have just set up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you're experiencing logon issues, it is helpful to first review the SAML authentication data directly.
|
||||||
|
|
||||||
|
1. Go to MediaCMS's login page. It should redirect you to Microsoft's login page.
|
||||||
|
2. Before completing the Microsoft authentication, open Firefox or Chrome Developer Tools (press **F12**) and navigate to the **Network** tab.
|
||||||
|
3. Enable **Persistent Logging**.
|
||||||
|
4. Complete the Microsoft authentication steps on your page (including two-factor authentication if enabled).
|
||||||
|
|
||||||
|
On the final step of the authentication (usually after entering a code and confirming "Stay signed in?"), you will see several POST requests going back to your MediaCMS server URL. Find the POST request that is going to your MediaCMS server's Assertion Consumer Service (ACS) URL, which will look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the request section of the Network tab, you will see a **Form Data** field labeled **SAMLResponse**, which contains a Base64-encoded XML string of your authenticated assertion from EntraID.
|
||||||
|
|
||||||
|
* Click into the data field of the SAML response so you can highlight and copy all of the Base64-encoded text.
|
||||||
|
* You can then take this Base64-encoded text to a tool like [CyberChef](https://gchq.github.io/CyberChef/) and use the **From Base64** decoder and **XML Beautify** to reveal the XML-formatted SAML response.
|
||||||
|
|
||||||
|
This decoded XML contains all the assertion and token details passed back to MediaCMS. You can use this information to troubleshoot any issues or misconfigurations that arise.
|
||||||
|
|
||||||
|
You can also confirm your MediaCMS server has the SAML authentication settings correct by opening a private browsing window and navigating to the following URL, which will output the current XML data that your MediaCMS server is configured with:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use the returned XML data from this URL to confirm that MediaCMS is configured appropriately as expected and is providing the correct information to the identity provider.
|
||||||
|
|
||||||
|
### Infinite Redirect Loop
|
||||||
|
|
||||||
|
Another issue you might encounter is an **infinite redirect loop**. This can happen when global login is enforced and local user login is disabled.
|
||||||
|
|
||||||
|
**Symptoms:** The system continuously redirects between the homepage and the login URL.
|
||||||
|
|
||||||
|
**Root Cause:** With global login required and local login disabled, Django attempts to redirect users to the default local login page. Since that login method is unavailable, users are bounced back to the homepage, triggering the same redirect logic again тАФ resulting in a loop.
|
||||||
|
|
||||||
|
**Solution:** Specify the correct SAML authentication URL in your local settings. For example:
|
||||||
|
|
||||||
|
* "Login Option" URL configured for EntraID in MediaCMS:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<MyDomainName>/accounts/saml/mediacms_entraid/login/
|
||||||
|
```
|
||||||
|
|
||||||
|
* Add the following line to `./deploy/docker/local_settings.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
LOGIN_URL = "/accounts/saml/mediacms_entraid/login/"
|
||||||
|
```
|
||||||
|
|
||||||
|
This change ensures Django uses the proper SAML login route, breaking the redirect loop and allowing authentication via EntraID as intended.
|
||||||
|
|
||||||
|
> **Note:** The `LOGIN_URL` setting works because we are using the Django AllAuth module to perform the SAML authentication. If you review the AllAuth Django configuration settings, you will find that this is a setting, among other settings, that you can set inside of your local settings file that Django will pick up when using the AllAuth module. You can review the module documentation at the following URL for more details and additional settings that can be set through AllAuth via `local_settings.py`: [https://django-allauth.readthedocs.io/en/latest/account/configuration.html](https://django-allauth.readthedocs.io/en/latest/account/configuration.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* [MediaCMS SAML Docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#24-identity-providers-setup)
|
||||||
|
* [Enable SAML single sign-on for an enterprise application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal-setup-sso)
|
||||||
|
* [Django AllAuth](https://django-allauth.readthedocs.io/en/latest/index.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This documentation is a work-in-progress and will be updated as further steps are dictated or completed.*
|
||||||
50
docs/transcoding.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Transcoding in MediaCMS
|
||||||
|
|
||||||
|
MediaCMS uses FFmpeg for transcoding media files. Most of the transcoding settings and configurations are defined in `files/helpers.py`.
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Several transcoding parameters can be customized in `cms/settings.py`:
|
||||||
|
|
||||||
|
### FFmpeg Preset
|
||||||
|
|
||||||
|
The default FFmpeg preset is set to "medium". This setting controls the encoding speed and compression efficiency trade-off.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# ffmpeg options
|
||||||
|
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||||
|
```
|
||||||
|
|
||||||
|
Available presets include:
|
||||||
|
- ultrafast
|
||||||
|
- superfast
|
||||||
|
- veryfast
|
||||||
|
- faster
|
||||||
|
- fast
|
||||||
|
- medium (default)
|
||||||
|
- slow
|
||||||
|
- slower
|
||||||
|
- veryslow
|
||||||
|
|
||||||
|
Faster presets result in larger file sizes for the same quality, while slower presets provide better compression but take longer to encode.
|
||||||
|
|
||||||
|
### Other Transcoding Settings
|
||||||
|
|
||||||
|
Additional transcoding settings in `settings.py` include:
|
||||||
|
|
||||||
|
- `FFMPEG_COMMAND`: Path to the FFmpeg executable
|
||||||
|
- `FFPROBE_COMMAND`: Path to the FFprobe executable
|
||||||
|
- `DO_NOT_TRANSCODE_VIDEO`: If set to True, only the original video is shown without transcoding
|
||||||
|
- `CHUNKIZE_VIDEO_DURATION`: For videos longer than this duration (in seconds), they get split into chunks and encoded independently
|
||||||
|
- `VIDEO_CHUNKS_DURATION`: Duration of each chunk (must be smaller than CHUNKIZE_VIDEO_DURATION)
|
||||||
|
- `MINIMUM_RESOLUTIONS_TO_ENCODE`: Always encode these resolutions, even if upscaling is required
|
||||||
|
|
||||||
|
## Advanced Configuration
|
||||||
|
|
||||||
|
For more advanced transcoding settings, you may need to modify the following in `files/helpers.py`:
|
||||||
|
|
||||||
|
- Video bitrates for different codecs and resolutions
|
||||||
|
- Audio encoders and bitrates
|
||||||
|
- CRF (Constant Rate Factor) values
|
||||||
|
- Keyframe settings
|
||||||
|
- Encoding parameters for different codecs (H.264, H.265, VP9)
|
||||||
@@ -6,9 +6,12 @@
|
|||||||
- [Adding captions/subtitles](#adding-captionssubtitles)
|
- [Adding captions/subtitles](#adding-captionssubtitles)
|
||||||
- [Search media](#search-media)
|
- [Search media](#search-media)
|
||||||
- [Using Timestamps for sharing](#using-timestamps-for-sharing)
|
- [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)
|
- [Share media](#share-media)
|
||||||
- [Embed media](#embed-media)
|
- [Embed media](#embed-media)
|
||||||
- [Customize my profile options](#customize-my-profile-options)
|
- [Customize my profile options](#customize-my-profile-options)
|
||||||
|
- [Trim videos](#trim-videos)
|
||||||
|
|
||||||
## Uploading media
|
## Uploading media
|
||||||
|
|
||||||
@@ -220,6 +223,30 @@ Comments can also include timestamps. They are automatically detected upon posti
|
|||||||
<img src="./images/Demo3.png"/>
|
<img src="./images/Demo3.png"/>
|
||||||
</p>
|
</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
|
## Search media
|
||||||
How search can be used
|
How search can be used
|
||||||
|
|
||||||
@@ -231,3 +258,7 @@ How to use the embed media option
|
|||||||
|
|
||||||
## Customize my profile options
|
## Customize my profile options
|
||||||
Customize profile and channel
|
Customize profile and channel
|
||||||
|
|
||||||
|
## Trim videos
|
||||||
|
Once a video is uploaded, you can trim it to create a new video or to replace the original one. You can also create segments of the video, which will be available as separate videos. Edit the video and click on the "Trime Video" option. If the original video has finished processing (encodings are created for all resolutions), then this is an action that runs instantly. If the original video hasn't processed, which is the case when you upload a video and edit it right away, then the trim action will trigger processing of the video and will take some time to finish. In all cases, you get to see the original video (or the trimmed versions) immediately, so you are sure of what you have uploaded or trimmed, with a message that the video is being processed.
|
||||||
|
|
||||||
|
|||||||
214
files/admin.py
@@ -1,4 +1,11 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import transaction
|
||||||
|
from tinymce.widgets import TinyMCE
|
||||||
|
|
||||||
|
from rbac.models import RBACGroup
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Category,
|
Category,
|
||||||
@@ -7,8 +14,12 @@ from .models import (
|
|||||||
Encoding,
|
Encoding,
|
||||||
Language,
|
Language,
|
||||||
Media,
|
Media,
|
||||||
|
Page,
|
||||||
Subtitle,
|
Subtitle,
|
||||||
Tag,
|
Tag,
|
||||||
|
TinyMCEMedia,
|
||||||
|
TranscriptionRequest,
|
||||||
|
VideoTrimRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -40,15 +51,144 @@ class MediaAdmin(admin.ModelAdmin):
|
|||||||
def get_comments_count(self, obj):
|
def get_comments_count(self, obj):
|
||||||
return obj.comments.count()
|
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"
|
get_comments_count.short_description = "Comments count"
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryAdminForm(forms.ModelForm):
|
||||||
|
rbac_groups = forms.ModelMultipleChoiceField(queryset=RBACGroup.objects.all(), required=False, widget=admin.widgets.FilteredSelectMultiple('Groups', False))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
# LTI fields will be shown as read-only when USE_LTI is enabled
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
is_rbac_category = cleaned_data.get('is_rbac_category')
|
||||||
|
identity_provider = cleaned_data.get('identity_provider')
|
||||||
|
# Check if this category has any RBAC groups
|
||||||
|
if self.instance.pk:
|
||||||
|
has_rbac_groups = cleaned_data.get('rbac_groups')
|
||||||
|
else:
|
||||||
|
has_rbac_groups = False
|
||||||
|
|
||||||
|
if not is_rbac_category:
|
||||||
|
if has_rbac_groups:
|
||||||
|
cleaned_data['is_rbac_category'] = True
|
||||||
|
# self.add_error('is_rbac_category', ValidationError('This category has RBAC groups assigned. "Is RBAC Category" must be enabled.'))
|
||||||
|
|
||||||
|
for rbac_group in cleaned_data.get('rbac_groups'):
|
||||||
|
if rbac_group.identity_provider != identity_provider:
|
||||||
|
self.add_error('rbac_groups', ValidationError('Chosen Groups are associated with a different Identity Provider than the one selected here.'))
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['rbac_groups'].initial = self.instance.rbac_groups.all()
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
category = super().save(commit=True)
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
self.save_m2m()
|
||||||
|
|
||||||
|
if self.instance.rbac_groups.exists() or self.cleaned_data.get('rbac_groups'):
|
||||||
|
if not self.cleaned_data['is_rbac_category']:
|
||||||
|
category.is_rbac_category = True
|
||||||
|
category.save(update_fields=['is_rbac_category'])
|
||||||
|
return category
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save_m2m(self):
|
||||||
|
if self.instance.pk:
|
||||||
|
rbac_groups = self.cleaned_data['rbac_groups']
|
||||||
|
self._update_rbac_groups(rbac_groups)
|
||||||
|
|
||||||
|
def _update_rbac_groups(self, rbac_groups):
|
||||||
|
new_rbac_group_ids = RBACGroup.objects.filter(pk__in=rbac_groups).values_list('pk', flat=True)
|
||||||
|
|
||||||
|
existing_rbac_groups = RBACGroup.objects.filter(categories=self.instance)
|
||||||
|
existing_rbac_groups_ids = existing_rbac_groups.values_list('pk', flat=True)
|
||||||
|
|
||||||
|
rbac_groups_to_add = RBACGroup.objects.filter(pk__in=new_rbac_group_ids).exclude(pk__in=existing_rbac_groups_ids)
|
||||||
|
rbac_groups_to_remove = existing_rbac_groups.exclude(pk__in=new_rbac_group_ids)
|
||||||
|
|
||||||
|
for rbac_group in rbac_groups_to_add:
|
||||||
|
rbac_group.categories.add(self.instance)
|
||||||
|
|
||||||
|
for rbac_group in rbac_groups_to_remove:
|
||||||
|
rbac_group.categories.remove(self.instance)
|
||||||
|
|
||||||
|
|
||||||
class CategoryAdmin(admin.ModelAdmin):
|
class CategoryAdmin(admin.ModelAdmin):
|
||||||
search_fields = ["title"]
|
form = CategoryAdminForm
|
||||||
list_display = ["title", "user", "add_date", "is_global", "media_count"]
|
|
||||||
list_filter = ["is_global"]
|
search_fields = ["title", "uid"]
|
||||||
|
list_display = ["title", "user", "add_date", "media_count"]
|
||||||
|
list_filter = []
|
||||||
ordering = ("-add_date",)
|
ordering = ("-add_date",)
|
||||||
readonly_fields = ("user", "media_count")
|
readonly_fields = ("user", "media_count", "lti_platform", "lti_context_id")
|
||||||
|
change_form_template = 'admin/files/category/change_form.html'
|
||||||
|
|
||||||
|
def get_list_filter(self, request):
|
||||||
|
list_filter = list(self.list_filter)
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
list_filter.insert(0, "is_rbac_category")
|
||||||
|
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
|
list_filter.insert(-1, "identity_provider")
|
||||||
|
|
||||||
|
return list_filter
|
||||||
|
|
||||||
|
def get_list_display(self, request):
|
||||||
|
list_display = list(self.list_display)
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
list_display.insert(-1, "is_rbac_category")
|
||||||
|
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
|
list_display.insert(-1, "identity_provider")
|
||||||
|
|
||||||
|
return list_display
|
||||||
|
|
||||||
|
def get_fieldsets(self, request, obj=None):
|
||||||
|
basic_fieldset = [
|
||||||
|
(
|
||||||
|
'Category Information',
|
||||||
|
{
|
||||||
|
'fields': ['uid', 'title', 'description', 'user', 'media_count', 'thumbnail', 'listings_thumbnail'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
additional_fieldsets = []
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
lti_fieldset = [
|
||||||
|
('LTI Integration', {'fields': ['lti_platform', 'lti_context_id'], 'classes': ['tab'], 'description': 'LTI/LMS integration settings (automatically managed by LTI provisioning)'}),
|
||||||
|
]
|
||||||
|
additional_fieldsets.extend(lti_fieldset)
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
rbac_fieldset = [
|
||||||
|
('RBAC Settings', {'fields': ['is_rbac_category'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
||||||
|
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
|
||||||
|
]
|
||||||
|
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
|
||||||
|
rbac_fieldset = [
|
||||||
|
('RBAC Settings', {'fields': ['is_rbac_category', 'identity_provider'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
||||||
|
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
|
||||||
|
]
|
||||||
|
additional_fieldsets.extend(rbac_fieldset)
|
||||||
|
|
||||||
|
return basic_fieldset + additional_fieldsets
|
||||||
|
|
||||||
|
|
||||||
class TagAdmin(admin.ModelAdmin):
|
class TagAdmin(admin.ModelAdmin):
|
||||||
@@ -70,11 +210,68 @@ class LanguageAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
class SubtitleAdmin(admin.ModelAdmin):
|
class SubtitleAdmin(admin.ModelAdmin):
|
||||||
pass
|
list_display = ["id", "language", "media"]
|
||||||
|
list_filter = ["language"]
|
||||||
|
search_fields = ["media__title"]
|
||||||
|
readonly_fields = ("media", "user")
|
||||||
|
|
||||||
|
|
||||||
|
class VideoTrimRequestAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["media", "status", "add_date", "video_action", "media_trim_style", "timestamps"]
|
||||||
|
list_filter = ["status", "video_action", "media_trim_style", "add_date"]
|
||||||
|
search_fields = ["media__title"]
|
||||||
|
readonly_fields = ("add_date",)
|
||||||
|
ordering = ("-add_date",)
|
||||||
|
|
||||||
|
|
||||||
class EncodingAdmin(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"
|
||||||
|
|
||||||
|
|
||||||
|
class TranscriptionRequestAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["media", "add_date", "status", "translate_to_english"]
|
||||||
|
list_filter = ["status", "translate_to_english", "add_date"]
|
||||||
|
search_fields = ["media__title"]
|
||||||
|
readonly_fields = ("add_date", "logs")
|
||||||
|
ordering = ("-add_date",)
|
||||||
|
|
||||||
|
|
||||||
|
class PageAdminForm(forms.ModelForm):
|
||||||
|
description = forms.CharField(widget=TinyMCE())
|
||||||
|
|
||||||
|
def clean_description(self):
|
||||||
|
content = self.cleaned_data['description']
|
||||||
|
# Add sandbox attribute to all iframes
|
||||||
|
content = content.replace('<iframe ', '<iframe sandbox="allow-scripts allow-same-origin allow-presentation" ')
|
||||||
|
return content
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Page
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class PageAdmin(admin.ModelAdmin):
|
||||||
|
form = PageAdminForm
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(TinyMCEMedia)
|
||||||
|
class TinyMCEMediaAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['original_filename', 'file_type', 'uploaded_at', 'user']
|
||||||
|
list_filter = ['file_type', 'uploaded_at']
|
||||||
|
search_fields = ['original_filename']
|
||||||
|
readonly_fields = ['uploaded_at']
|
||||||
|
date_hierarchy = 'uploaded_at'
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(EncodeProfile, EncodeProfileAdmin)
|
admin.site.register(EncodeProfile, EncodeProfileAdmin)
|
||||||
@@ -82,6 +279,11 @@ admin.site.register(Comment, CommentAdmin)
|
|||||||
admin.site.register(Media, MediaAdmin)
|
admin.site.register(Media, MediaAdmin)
|
||||||
admin.site.register(Encoding, EncodingAdmin)
|
admin.site.register(Encoding, EncodingAdmin)
|
||||||
admin.site.register(Category, CategoryAdmin)
|
admin.site.register(Category, CategoryAdmin)
|
||||||
|
admin.site.register(Page, PageAdmin)
|
||||||
admin.site.register(Tag, TagAdmin)
|
admin.site.register(Tag, TagAdmin)
|
||||||
admin.site.register(Subtitle, SubtitleAdmin)
|
admin.site.register(Subtitle, SubtitleAdmin)
|
||||||
admin.site.register(Language, LanguageAdmin)
|
admin.site.register(Language, LanguageAdmin)
|
||||||
|
admin.site.register(VideoTrimRequest, VideoTrimRequestAdmin)
|
||||||
|
admin.site.register(TranscriptionRequest, TranscriptionRequestAdmin)
|
||||||
|
|
||||||
|
Media._meta.app_config.verbose_name = "Media"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class VideoEncodingError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
RE_TIMECODE = re.compile(r"time=(\d+:\d+:\d+.\d+)")
|
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):
|
class FFmpegBackend(object):
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from cms.version import VERSION
|
||||||
|
|
||||||
|
from .frontend_translations import get_translation, get_translation_strings
|
||||||
from .methods import is_mediacms_editor, is_mediacms_manager
|
from .methods import is_mediacms_editor, is_mediacms_manager
|
||||||
|
|
||||||
|
|
||||||
def stuff(request):
|
def stuff(request):
|
||||||
"""Pass settings to the frontend"""
|
"""Pass settings to the frontend"""
|
||||||
ret = {}
|
ret = {}
|
||||||
ret["FRONTEND_HOST"] = request.build_absolute_uri('/')
|
ret["FRONTEND_HOST"] = request.build_absolute_uri('/').rstrip('/')
|
||||||
ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
|
ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
|
||||||
ret["PORTAL_NAME"] = settings.PORTAL_NAME
|
ret["PORTAL_NAME"] = settings.PORTAL_NAME
|
||||||
|
|
||||||
|
ret["PORTAL_LOGO_DARK_SVG"] = getattr(settings, 'PORTAL_LOGO_DARK_SVG', "")
|
||||||
|
ret["PORTAL_LOGO_DARK_PNG"] = getattr(settings, 'PORTAL_LOGO_DARK_PNG', "")
|
||||||
|
ret["PORTAL_LOGO_LIGHT_SVG"] = getattr(settings, 'PORTAL_LOGO_LIGHT_SVG', "")
|
||||||
|
ret["PORTAL_LOGO_LIGHT_PNG"] = getattr(settings, 'PORTAL_LOGO_LIGHT_PNG', "")
|
||||||
|
ret["EXTRA_CSS_PATHS"] = getattr(settings, 'EXTRA_CSS_PATHS', [])
|
||||||
|
ret["PORTAL_DESCRIPTION"] = settings.PORTAL_DESCRIPTION
|
||||||
ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
|
ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
|
||||||
ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
|
ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
|
||||||
ret["CAN_REGISTER"] = settings.REGISTER_ALLOWED
|
ret["CAN_REGISTER"] = settings.REGISTER_ALLOWED
|
||||||
ret["CAN_UPLOAD_MEDIA"] = settings.UPLOAD_MEDIA_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_LIKE_MEDIA"] = settings.CAN_LIKE_MEDIA
|
||||||
ret["CAN_DISLIKE_MEDIA"] = settings.CAN_DISLIKE_MEDIA
|
ret["CAN_DISLIKE_MEDIA"] = settings.CAN_DISLIKE_MEDIA
|
||||||
ret["CAN_REPORT_MEDIA"] = settings.CAN_REPORT_MEDIA
|
ret["CAN_REPORT_MEDIA"] = settings.CAN_REPORT_MEDIA
|
||||||
@@ -20,12 +32,42 @@ def stuff(request):
|
|||||||
ret["UPLOAD_MAX_SIZE"] = settings.UPLOAD_MAX_SIZE
|
ret["UPLOAD_MAX_SIZE"] = settings.UPLOAD_MAX_SIZE
|
||||||
ret["UPLOAD_MAX_FILES_NUMBER"] = settings.UPLOAD_MAX_FILES_NUMBER
|
ret["UPLOAD_MAX_FILES_NUMBER"] = settings.UPLOAD_MAX_FILES_NUMBER
|
||||||
ret["PRE_UPLOAD_MEDIA_MESSAGE"] = settings.PRE_UPLOAD_MEDIA_MESSAGE
|
ret["PRE_UPLOAD_MEDIA_MESSAGE"] = settings.PRE_UPLOAD_MEDIA_MESSAGE
|
||||||
|
ret["SIDEBAR_FOOTER_TEXT"] = settings.SIDEBAR_FOOTER_TEXT
|
||||||
ret["POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY"] = settings.POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY
|
ret["POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY"] = settings.POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY
|
||||||
ret["IS_MEDIACMS_ADMIN"] = request.user.is_superuser
|
ret["IS_MEDIACMS_ADMIN"] = request.user.is_superuser
|
||||||
ret["IS_MEDIACMS_EDITOR"] = is_mediacms_editor(request.user)
|
ret["IS_MEDIACMS_EDITOR"] = is_mediacms_editor(request.user)
|
||||||
ret["IS_MEDIACMS_MANAGER"] = is_mediacms_manager(request.user)
|
ret["IS_MEDIACMS_MANAGER"] = is_mediacms_manager(request.user)
|
||||||
|
ret["USERS_NEEDS_TO_BE_APPROVED"] = settings.USERS_NEEDS_TO_BE_APPROVED
|
||||||
|
|
||||||
|
can_see_members_page = False
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
if settings.CAN_SEE_MEMBERS_PAGE == "all":
|
||||||
|
can_see_members_page = True
|
||||||
|
elif settings.CAN_SEE_MEMBERS_PAGE == "editors" and is_mediacms_editor(request.user):
|
||||||
|
can_see_members_page = True
|
||||||
|
elif settings.CAN_SEE_MEMBERS_PAGE == "admins" and request.user.is_superuser:
|
||||||
|
can_see_members_page = True
|
||||||
|
ret["CAN_SEE_MEMBERS_PAGE"] = can_see_members_page
|
||||||
ret["ALLOW_RATINGS"] = settings.ALLOW_RATINGS
|
ret["ALLOW_RATINGS"] = settings.ALLOW_RATINGS
|
||||||
ret["ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY"] = settings.ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY
|
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["VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE"] = settings.VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE
|
||||||
ret["RSS_URL"] = "/rss"
|
ret["RSS_URL"] = "/rss"
|
||||||
|
ret["TRANSLATION"] = get_translation(request.LANGUAGE_CODE)
|
||||||
|
ret["REPLACEMENTS"] = get_translation_strings(request.LANGUAGE_CODE)
|
||||||
|
ret["USE_SAML"] = settings.USE_SAML
|
||||||
|
ret["USE_RBAC"] = settings.USE_RBAC
|
||||||
|
ret["USE_ROUNDED_CORNERS"] = settings.USE_ROUNDED_CORNERS
|
||||||
|
ret["INCLUDE_LISTING_NUMBERS"] = settings.INCLUDE_LISTING_NUMBERS
|
||||||
|
ret["ALLOW_MEDIA_REPLACEMENT"] = getattr(settings, 'ALLOW_MEDIA_REPLACEMENT', False)
|
||||||
|
ret["VERSION"] = VERSION
|
||||||
|
|
||||||
|
if request.user.is_superuser:
|
||||||
|
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
lti_session = request.session.get('lti_session')
|
||||||
|
|
||||||
|
if lti_session and request.user.is_authenticated:
|
||||||
|
ret['lti_session'] = lti_session
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class IndexRSSFeed(Feed):
|
|||||||
return item.edit_date
|
return item.edit_date
|
||||||
|
|
||||||
def item_link(self, item):
|
def item_link(self, item):
|
||||||
return reverse("get_media") + "?m={0}".format(item.friendly_token)
|
return f"{reverse('get_media')}?m={item.friendly_token}"
|
||||||
|
|
||||||
def item_extra_kwargs(self, item):
|
def item_extra_kwargs(self, item):
|
||||||
item = {
|
item = {
|
||||||
@@ -102,7 +102,7 @@ class SearchRSSFeed(Feed):
|
|||||||
description = "Latest Media RSS feed"
|
description = "Latest Media RSS feed"
|
||||||
|
|
||||||
def link(self, obj):
|
def link(self, obj):
|
||||||
return f"/rss/search"
|
return "/rss/search"
|
||||||
|
|
||||||
def get_object(self, request):
|
def get_object(self, request):
|
||||||
category = request.GET.get("c", "")
|
category = request.GET.get("c", "")
|
||||||
@@ -151,7 +151,7 @@ class SearchRSSFeed(Feed):
|
|||||||
return item.edit_date
|
return item.edit_date
|
||||||
|
|
||||||
def item_link(self, item):
|
def item_link(self, item):
|
||||||
return reverse("get_media") + "?m={0}".format(item.friendly_token)
|
return f"{reverse('get_media')}?m={item.friendly_token}"
|
||||||
|
|
||||||
def item_extra_kwargs(self, item):
|
def item_extra_kwargs(self, item):
|
||||||
item = {
|
item = {
|
||||||
|
|||||||
362
files/forms.py
@@ -1,48 +1,105 @@
|
|||||||
|
from crispy_forms.bootstrap import FormActions
|
||||||
|
from crispy_forms.helper import FormHelper
|
||||||
|
from crispy_forms.layout import HTML, Field, Layout, Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from .methods import get_next_state, is_mediacms_editor
|
from .methods import get_next_state, is_mediacms_editor
|
||||||
from .models import Media, Subtitle
|
from .models import MEDIA_STATES, Category, Media, Subtitle
|
||||||
|
from .widgets import CategoryModalWidget
|
||||||
|
|
||||||
|
|
||||||
|
class CustomField(Field):
|
||||||
|
template = 'cms/crispy_custom_field.html'
|
||||||
|
|
||||||
|
|
||||||
class MultipleSelect(forms.CheckboxSelectMultiple):
|
class MultipleSelect(forms.CheckboxSelectMultiple):
|
||||||
input_type = "checkbox"
|
input_type = "checkbox"
|
||||||
|
|
||||||
|
|
||||||
class MediaForm(forms.ModelForm):
|
class MediaMetadataForm(forms.ModelForm):
|
||||||
new_tags = forms.CharField(label="Tags", help_text="a comma separated list of new tags.", required=False)
|
new_tags = forms.CharField(label="Tags", help_text="a comma separated list of tags.", required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Media
|
model = Media
|
||||||
fields = (
|
fields = (
|
||||||
|
"friendly_token",
|
||||||
"title",
|
"title",
|
||||||
"category",
|
|
||||||
"new_tags",
|
"new_tags",
|
||||||
"add_date",
|
"add_date",
|
||||||
"uploaded_poster",
|
"uploaded_poster",
|
||||||
"description",
|
"description",
|
||||||
"state",
|
|
||||||
"enable_comments",
|
"enable_comments",
|
||||||
"featured",
|
|
||||||
"thumbnail_time",
|
"thumbnail_time",
|
||||||
"reported_times",
|
|
||||||
"is_reviewed",
|
|
||||||
"allow_download",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
"tags": MultipleSelect(),
|
"new_tags": MultipleSelect(),
|
||||||
|
"description": forms.Textarea(attrs={'rows': 4}),
|
||||||
|
"add_date": forms.DateTimeInput(attrs={'type': 'datetime-local', 'step': '1'}, format='%Y-%m-%dT%H:%M:%S'),
|
||||||
|
"thumbnail_time": forms.NumberInput(attrs={'min': 0, 'step': 0.1}),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
"friendly_token": "Slug",
|
||||||
|
"uploaded_poster": "Poster Image",
|
||||||
|
"thumbnail_time": "Thumbnail Time (seconds)",
|
||||||
|
}
|
||||||
|
help_texts = {
|
||||||
|
"title": "",
|
||||||
|
"friendly_token": "Media URL slug",
|
||||||
|
"thumbnail_time": "Select the time in seconds for the video thumbnail",
|
||||||
|
"uploaded_poster": "Maximum file size: 5MB",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, user, *args, **kwargs):
|
def __init__(self, user, *args, **kwargs):
|
||||||
self.user = user
|
self.user = user
|
||||||
super(MediaForm, self).__init__(*args, **kwargs)
|
super(MediaMetadataForm, self).__init__(*args, **kwargs)
|
||||||
|
if not getattr(settings, 'ALLOW_CUSTOM_MEDIA_URLS', False):
|
||||||
|
self.fields.pop("friendly_token")
|
||||||
if self.instance.media_type != "video":
|
if self.instance.media_type != "video":
|
||||||
self.fields.pop("thumbnail_time")
|
self.fields.pop("thumbnail_time")
|
||||||
if not is_mediacms_editor(user):
|
if self.instance.media_type == "image":
|
||||||
self.fields.pop("featured")
|
self.fields.pop("uploaded_poster")
|
||||||
self.fields.pop("reported_times")
|
|
||||||
self.fields.pop("is_reviewed")
|
|
||||||
self.fields["new_tags"].initial = ", ".join([tag.title for tag in self.instance.tags.all()])
|
self.fields["new_tags"].initial = ", ".join([tag.title for tag in self.instance.tags.all()])
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = True
|
||||||
|
self.helper.form_class = 'post-form'
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_enctype = "multipart/form-data"
|
||||||
|
self.helper.form_show_errors = False
|
||||||
|
|
||||||
|
layout_fields = [
|
||||||
|
CustomField('title'),
|
||||||
|
CustomField('new_tags'),
|
||||||
|
CustomField('add_date'),
|
||||||
|
CustomField('description'),
|
||||||
|
CustomField('enable_comments'),
|
||||||
|
]
|
||||||
|
if self.instance.media_type != "image":
|
||||||
|
layout_fields.append(CustomField('uploaded_poster'))
|
||||||
|
|
||||||
|
self.helper.layout = Layout(*layout_fields)
|
||||||
|
|
||||||
|
if self.instance.media_type == "video":
|
||||||
|
self.helper.layout.append(CustomField('thumbnail_time'))
|
||||||
|
if getattr(settings, 'ALLOW_CUSTOM_MEDIA_URLS', False):
|
||||||
|
self.helper.layout.insert(0, CustomField('friendly_token'))
|
||||||
|
|
||||||
|
self.helper.layout.append(FormActions(Submit('submit', 'Update Media', css_class='primaryAction')))
|
||||||
|
|
||||||
|
def clean_friendly_token(self):
|
||||||
|
token = self.cleaned_data.get("friendly_token", "").strip()
|
||||||
|
|
||||||
|
if token:
|
||||||
|
if not all(c.isalnum() or c in "-_" for c in token):
|
||||||
|
raise forms.ValidationError("Slug can only contain alphanumeric characters, underscores, or hyphens.")
|
||||||
|
|
||||||
|
if Media.objects.filter(friendly_token=token).exclude(pk=self.instance.pk).exists():
|
||||||
|
raise forms.ValidationError("This slug is already in use. Please choose a different one.")
|
||||||
|
return token
|
||||||
|
|
||||||
def clean_uploaded_poster(self):
|
def clean_uploaded_poster(self):
|
||||||
image = self.cleaned_data.get("uploaded_poster", False)
|
image = self.cleaned_data.get("uploaded_poster", False)
|
||||||
if image:
|
if image:
|
||||||
@@ -50,31 +107,272 @@ class MediaForm(forms.ModelForm):
|
|||||||
raise forms.ValidationError("Image file too large ( > 5mb )")
|
raise forms.ValidationError("Image file too large ( > 5mb )")
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
data = self.cleaned_data # noqa
|
||||||
|
|
||||||
|
media = super(MediaMetadataForm, self).save(*args, **kwargs)
|
||||||
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
class MediaPublishForm(forms.ModelForm):
|
||||||
|
confirm_state = forms.BooleanField(required=False, initial=False, label="Acknowledge sharing status", help_text="")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Media
|
||||||
|
fields = ("category", "state", "featured", "reported_times", "is_reviewed", "allow_download")
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
"category": CategoryModalWidget(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, user, *args, **kwargs):
|
||||||
|
self.user = user
|
||||||
|
super(MediaPublishForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.has_custom_permissions = self.instance.permissions.exists() if self.instance.pk else False
|
||||||
|
self.has_rbac_categories = self.instance.category.filter(is_rbac_category=True).exists() if self.instance.pk else False
|
||||||
|
self.is_shared = self.has_custom_permissions or self.has_rbac_categories
|
||||||
|
self.actual_state = self.instance.state if self.instance.pk else None
|
||||||
|
|
||||||
|
if not is_mediacms_editor(user):
|
||||||
|
for field in ["featured", "reported_times", "is_reviewed"]:
|
||||||
|
self.fields[field].disabled = True
|
||||||
|
self.fields[field].widget.attrs['class'] = 'read-only-field'
|
||||||
|
self.fields[field].widget.attrs['title'] = "This field can only be modified by MediaCMS admins or editors"
|
||||||
|
|
||||||
|
if settings.PORTAL_WORKFLOW not in ["public"]:
|
||||||
|
valid_states = ["unlisted", "private"]
|
||||||
|
if self.instance.state and self.instance.state not in valid_states:
|
||||||
|
valid_states.append(self.instance.state)
|
||||||
|
self.fields["state"].choices = [(state, dict(MEDIA_STATES).get(state, state)) for state in valid_states]
|
||||||
|
|
||||||
|
if self.is_shared:
|
||||||
|
current_choices = list(self.fields["state"].choices)
|
||||||
|
current_choices.insert(0, ("shared", "Shared"))
|
||||||
|
self.fields["state"].choices = current_choices
|
||||||
|
self.fields["state"].initial = "shared"
|
||||||
|
self.initial["state"] = "shared"
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_RBAC', False) and 'category' in self.fields:
|
||||||
|
if is_mediacms_editor(user):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fields['category'].initial = self.instance.category.all()
|
||||||
|
|
||||||
|
non_rbac_categories = Category.objects.filter(is_rbac_category=False)
|
||||||
|
rbac_categories = user.get_rbac_categories_as_contributor()
|
||||||
|
combined_category_ids = list(non_rbac_categories.values_list('id', flat=True)) + list(rbac_categories.values_list('id', flat=True))
|
||||||
|
|
||||||
|
if self.instance.pk:
|
||||||
|
instance_category_ids = list(self.instance.category.all().values_list('id', flat=True))
|
||||||
|
combined_category_ids = list(set(combined_category_ids + instance_category_ids))
|
||||||
|
|
||||||
|
self.fields['category'].queryset = Category.objects.filter(id__in=combined_category_ids).order_by('title')
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = True
|
||||||
|
self.helper.form_class = 'post-form'
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_enctype = "multipart/form-data"
|
||||||
|
self.helper.form_show_errors = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
CustomField('category'),
|
||||||
|
CustomField('state'),
|
||||||
|
CustomField('featured'),
|
||||||
|
CustomField('reported_times'),
|
||||||
|
CustomField('is_reviewed'),
|
||||||
|
CustomField('allow_download'),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.helper.layout.append(FormActions(Submit('submit', 'Publish Media', css_class='primaryAction')))
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
state = cleaned_data.get("state")
|
||||||
|
categories = cleaned_data.get("category")
|
||||||
|
|
||||||
|
if self.is_shared and state != "shared":
|
||||||
|
self.fields['confirm_state'].widget = forms.CheckboxInput()
|
||||||
|
state_index = None
|
||||||
|
for i, layout_item in enumerate(self.helper.layout):
|
||||||
|
if isinstance(layout_item, CustomField) and layout_item.fields[0] == 'state':
|
||||||
|
state_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if state_index is not None:
|
||||||
|
layout_items = list(self.helper.layout)
|
||||||
|
layout_items.insert(state_index + 1, CustomField('confirm_state'))
|
||||||
|
self.helper.layout = Layout(*layout_items)
|
||||||
|
|
||||||
|
if not cleaned_data.get('confirm_state'):
|
||||||
|
if state == 'private':
|
||||||
|
error_parts = []
|
||||||
|
if self.has_rbac_categories:
|
||||||
|
rbac_cat_titles = self.instance.category.filter(is_rbac_category=True).values_list('title', flat=True)
|
||||||
|
error_parts.append(f"shared with users that have access to categories: {', '.join(rbac_cat_titles)}")
|
||||||
|
if self.has_custom_permissions:
|
||||||
|
error_parts.append("shared by me with other users (visible in 'Shared by me' page)")
|
||||||
|
|
||||||
|
error_message = f"I understand that changing to Private will remove all sharing. Currently this media is {' and '.join(error_parts)}. All this sharing will be removed."
|
||||||
|
self.add_error('confirm_state', error_message)
|
||||||
|
else:
|
||||||
|
error_message = f"I understand that changing to {state.title()} will maintain existing sharing settings."
|
||||||
|
self.add_error('confirm_state', error_message)
|
||||||
|
|
||||||
|
elif state in ['private', 'unlisted']:
|
||||||
|
custom_permissions = self.instance.permissions.exists()
|
||||||
|
rbac_categories = categories.filter(is_rbac_category=True).values_list('title', flat=True)
|
||||||
|
if rbac_categories or custom_permissions:
|
||||||
|
self.fields['confirm_state'].widget = forms.CheckboxInput()
|
||||||
|
state_index = None
|
||||||
|
for i, layout_item in enumerate(self.helper.layout):
|
||||||
|
if isinstance(layout_item, CustomField) and layout_item.fields[0] == 'state':
|
||||||
|
state_index = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if state_index is not None:
|
||||||
|
layout_items = list(self.helper.layout)
|
||||||
|
layout_items.insert(state_index + 1, CustomField('confirm_state'))
|
||||||
|
self.helper.layout = Layout(*layout_items)
|
||||||
|
|
||||||
|
if not cleaned_data.get('confirm_state'):
|
||||||
|
if rbac_categories:
|
||||||
|
error_message = f"I understand that although media state is {state}, the media is also shared with users that have access to categories: {', '.join(rbac_categories)}"
|
||||||
|
self.add_error('confirm_state', error_message)
|
||||||
|
if custom_permissions:
|
||||||
|
error_message = f"I understand that although media state is {state}, the media is also shared by me with other users, that I can see in the 'Shared by me' page"
|
||||||
|
self.add_error('confirm_state', error_message)
|
||||||
|
|
||||||
|
# Convert "shared" state to actual underlying state for saving. we dont keep shared state in DB
|
||||||
|
if state == "shared":
|
||||||
|
cleaned_data["state"] = self.actual_state
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
data = self.cleaned_data
|
data = self.cleaned_data
|
||||||
state = data.get("state")
|
state = data.get("state")
|
||||||
|
|
||||||
|
# If transitioning from shared to private, remove all sharing
|
||||||
|
if self.is_shared and state == 'private' and data.get('confirm_state'):
|
||||||
|
# Remove all custom permissions
|
||||||
|
self.instance.permissions.all().delete()
|
||||||
|
# Remove RBAC categories
|
||||||
|
rbac_cats = self.instance.category.filter(is_rbac_category=True)
|
||||||
|
self.instance.category.remove(*rbac_cats)
|
||||||
|
|
||||||
if state != self.initial["state"]:
|
if state != self.initial["state"]:
|
||||||
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
|
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
|
||||||
|
|
||||||
media = super(MediaForm, self).save(*args, **kwargs)
|
media = super(MediaPublishForm, self).save(*args, **kwargs)
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
class WhisperSubtitlesForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Media
|
||||||
|
fields = (
|
||||||
|
"allow_whisper_transcribe",
|
||||||
|
"allow_whisper_transcribe_and_translate",
|
||||||
|
)
|
||||||
|
labels = {
|
||||||
|
"allow_whisper_transcribe": "Transcription",
|
||||||
|
"allow_whisper_transcribe_and_translate": "English Translation",
|
||||||
|
}
|
||||||
|
help_texts = {
|
||||||
|
"allow_whisper_transcribe": "",
|
||||||
|
"allow_whisper_transcribe_and_translate": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, user, *args, **kwargs):
|
||||||
|
self.user = user
|
||||||
|
super(WhisperSubtitlesForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.instance.allow_whisper_transcribe:
|
||||||
|
self.fields['allow_whisper_transcribe'].widget.attrs['readonly'] = True
|
||||||
|
self.fields['allow_whisper_transcribe'].widget.attrs['disabled'] = True
|
||||||
|
if self.instance.allow_whisper_transcribe_and_translate:
|
||||||
|
self.fields['allow_whisper_transcribe_and_translate'].widget.attrs['readonly'] = True
|
||||||
|
self.fields['allow_whisper_transcribe_and_translate'].widget.attrs['disabled'] = True
|
||||||
|
|
||||||
|
both_readonly = self.instance.allow_whisper_transcribe and self.instance.allow_whisper_transcribe_and_translate
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = True
|
||||||
|
self.helper.form_class = 'post-form'
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_enctype = "multipart/form-data"
|
||||||
|
self.helper.form_show_errors = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
CustomField('allow_whisper_transcribe'),
|
||||||
|
CustomField('allow_whisper_transcribe_and_translate'),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not both_readonly:
|
||||||
|
self.helper.layout.append(FormActions(Submit('submit_whisper', 'Submit', css_class='primaryAction')))
|
||||||
|
else:
|
||||||
|
# Optional: Add a disabled button with explanatory text
|
||||||
|
self.helper.layout.append(
|
||||||
|
FormActions(Submit('submit_whisper', 'Submit', css_class='primaryAction', disabled=True), HTML('<small class="text-muted">Cannot submit - both options are already enabled</small>'))
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_allow_whisper_transcribe(self):
|
||||||
|
# Ensure the field value doesn't change if it was originally True
|
||||||
|
if self.instance and self.instance.allow_whisper_transcribe:
|
||||||
|
return self.instance.allow_whisper_transcribe
|
||||||
|
return self.cleaned_data['allow_whisper_transcribe']
|
||||||
|
|
||||||
|
def clean_allow_whisper_transcribe_and_translate(self):
|
||||||
|
# Ensure the field value doesn't change if it was originally True
|
||||||
|
if self.instance and self.instance.allow_whisper_transcribe_and_translate:
|
||||||
|
return self.instance.allow_whisper_transcribe_and_translate
|
||||||
|
return self.cleaned_data['allow_whisper_transcribe_and_translate']
|
||||||
|
|
||||||
|
|
||||||
class SubtitleForm(forms.ModelForm):
|
class SubtitleForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Subtitle
|
model = Subtitle
|
||||||
fields = ["language", "subtitle_file"]
|
fields = ["language", "subtitle_file"]
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"subtitle_file": "Upload Caption File",
|
||||||
|
}
|
||||||
|
help_texts = {
|
||||||
|
"subtitle_file": "SubRip (.srt) and WebVTT (.vtt) are supported file formats.",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, media_item, *args, **kwargs):
|
def __init__(self, media_item, *args, **kwargs):
|
||||||
super(SubtitleForm, self).__init__(*args, **kwargs)
|
super(SubtitleForm, self).__init__(*args, **kwargs)
|
||||||
self.instance.media = media_item
|
self.instance.media = media_item
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = True
|
||||||
|
self.helper.form_class = 'post-form'
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_enctype = "multipart/form-data"
|
||||||
|
self.helper.form_show_errors = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
CustomField('subtitle_file'),
|
||||||
|
CustomField('language'),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.helper.layout.append(FormActions(Submit('submit', 'Submit', css_class='primaryAction')))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.instance.user = self.instance.media.user
|
self.instance.user = self.instance.media.user
|
||||||
media = super(SubtitleForm, self).save(*args, **kwargs)
|
media = super(SubtitleForm, self).save(*args, **kwargs)
|
||||||
return media
|
return media
|
||||||
|
|
||||||
|
|
||||||
|
class EditSubtitleForm(forms.Form):
|
||||||
|
subtitle = forms.CharField(widget=forms.Textarea, required=True)
|
||||||
|
|
||||||
|
def __init__(self, subtitle, *args, **kwargs):
|
||||||
|
super(EditSubtitleForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields["subtitle"].initial = subtitle.subtitle_file.read().decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
class ContactForm(forms.Form):
|
class ContactForm(forms.Form):
|
||||||
from_email = forms.EmailField(required=True)
|
from_email = forms.EmailField(required=True)
|
||||||
name = forms.CharField(required=False)
|
name = forms.CharField(required=False)
|
||||||
@@ -89,3 +387,35 @@ class ContactForm(forms.Form):
|
|||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
self.fields.pop("name")
|
self.fields.pop("name")
|
||||||
self.fields.pop("from_email")
|
self.fields.pop("from_email")
|
||||||
|
|
||||||
|
|
||||||
|
class ReplaceMediaForm(forms.Form):
|
||||||
|
new_media_file = forms.FileField(
|
||||||
|
required=True,
|
||||||
|
label="New Media File",
|
||||||
|
help_text="Select a new file to replace the current media",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, media_instance, *args, **kwargs):
|
||||||
|
self.media_instance = media_instance
|
||||||
|
super(ReplaceMediaForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.helper = FormHelper()
|
||||||
|
self.helper.form_tag = True
|
||||||
|
self.helper.form_class = 'post-form'
|
||||||
|
self.helper.form_method = 'post'
|
||||||
|
self.helper.form_enctype = "multipart/form-data"
|
||||||
|
self.helper.form_show_errors = False
|
||||||
|
self.helper.layout = Layout(
|
||||||
|
CustomField('new_media_file'),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.helper.layout.append(FormActions(Submit('submit', 'Replace Media', css_class='primaryAction')))
|
||||||
|
|
||||||
|
def clean_new_media_file(self):
|
||||||
|
file = self.cleaned_data.get("new_media_file", False)
|
||||||
|
if file:
|
||||||
|
if file.size > settings.UPLOAD_MAX_SIZE:
|
||||||
|
max_size_mb = settings.UPLOAD_MAX_SIZE / (1024 * 1024)
|
||||||
|
raise forms.ValidationError(f"File too large. Maximum size: {max_size_mb:.0f}MB")
|
||||||
|
return file
|
||||||
|
|||||||
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)
|
||||||
289
files/frontend_translations/ar.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ ╪е┘Ж╪┤╪з╪б ┘В╪з╪ж┘Е╪й ╪к╪┤╪║┘К┘Д",
|
||||||
|
"00 - 20 min": "00 - 20 ╪п┘В┘К┘В╪й",
|
||||||
|
"1 result for": "┘Ж╪к┘К╪м╪й ┘И╪з╪н╪п╪й ┘Д┘А",
|
||||||
|
"20 - 40 min": "20 - 40 ╪п┘В┘К┘В╪й",
|
||||||
|
"40 - 60 min": "40 - 60 ╪п┘В┘К┘В╪й",
|
||||||
|
"60 - 120 min+": "60 - 120 ╪п┘В┘К┘В╪й+",
|
||||||
|
"ABOUT": "╪н┘И┘Д",
|
||||||
|
"AUTOPLAY": "╪к╪┤╪║┘К┘Д ╪к┘Д┘В╪з╪ж┘К",
|
||||||
|
"About": "╪н┘И┘Д",
|
||||||
|
"Add / Remove Co-Editors": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ╪з┘Д┘Е╪н╪▒╪▒┘К┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘К┘Ж",
|
||||||
|
"Add / Remove Co-Owners": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ╪з┘Д┘Е╪з┘Д┘Г┘К┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘К┘Ж",
|
||||||
|
"Add / Remove Co-Viewers": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ╪з┘Д┘Е╪┤╪з┘З╪п┘К┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘К┘Ж",
|
||||||
|
"Add / Remove Tags": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Add / Remove from Categories": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ┘Е┘Ж ╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Add a ": "╪г╪╢┘Б ",
|
||||||
|
"Add to": "╪е╪╢╪з┘Б╪й ╪е┘Д┘Й",
|
||||||
|
"Add to / Remove from Category": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ┘Е┘Ж ╪з┘Д┘Б╪ж╪й",
|
||||||
|
"Add to / Remove from Playlist": "╪е╪╢╪з┘Б╪й / ╪е╪▓╪з┘Д╪й ┘Е┘Ж ┘В╪з╪ж┘Е╪й ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"All": "╪з┘Д┘Г┘Д",
|
||||||
|
"All categories already added": "╪к┘Е╪к ╪е╪╢╪з┘Б╪й ╪м┘Е┘К╪╣ ╪з┘Д┘Б╪ж╪з╪к ╪и╪з┘Д┘Б╪╣┘Д",
|
||||||
|
"All tags already added": "╪к┘Е╪к ╪е╪╢╪з┘Б╪й ╪м┘Е┘К╪╣ ╪з┘Д╪╣┘Д╪з┘Е╪з╪к ╪и╪з┘Д┘Б╪╣┘Д",
|
||||||
|
"Alphabetically - A-Z": "╪г╪и╪м╪п┘К╪з┘Л - ╪г-┘К",
|
||||||
|
"Alphabetically - Z-A": "╪г╪и╪м╪п┘К╪з┘Л - ┘К-╪г",
|
||||||
|
"Audio": "╪╡┘И╪к",
|
||||||
|
"Browse your files": "╪к╪╡┘Б╪н ┘Е┘Д┘Б╪з╪к┘Г",
|
||||||
|
"Bulk Actions": "╪е╪м╪▒╪з╪б╪з╪к ╪м┘Е╪з╪╣┘К╪й",
|
||||||
|
"COMMENT": "╪к╪╣┘Д┘К┘В",
|
||||||
|
"Cancel": "╪е┘Д╪║╪з╪б",
|
||||||
|
"Categories": "╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Category": "╪з┘Д┘Б╪ж╪й",
|
||||||
|
"Change Language": "╪к╪║┘К┘К╪▒ ╪з┘Д┘Д╪║╪й",
|
||||||
|
"Change Owner": "╪к╪║┘К┘К╪▒ ╪з┘Д┘Е╪з┘Д┘Г",
|
||||||
|
"Change password": "╪к╪║┘К┘К╪▒ ┘Г┘Д┘Е╪й ╪з┘Д┘Е╪▒┘И╪▒",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "╪з┘Ж┘В╪▒ ╪╣┘Д┘Й '╪и╪п╪б ╪з┘Д╪к╪│╪м┘К┘Д' ┘И╪з╪о╪к╪▒ ╪з┘Д╪┤╪з╪┤╪й ╪г┘И ╪╣┘Д╪з┘Е╪й ╪з┘Д╪к╪и┘И┘К╪и ╪з┘Д┘Е╪▒╪з╪п ╪к╪│╪м┘К┘Д┘З╪з. ╪и┘Е╪м╪▒╪п ╪з┘Д╪з┘Ж╪к┘З╪з╪б ┘Е┘Ж ╪з┘Д╪к╪│╪м┘К┘Д╪М ╪з┘Ж┘В╪▒ ╪╣┘Д┘Й '╪е┘К┘В╪з┘Б ╪з┘Д╪к╪│╪м┘К┘Д'╪М ┘И╪│┘К╪к┘Е ╪к╪н┘Е┘К┘Д ╪з┘Д╪к╪│╪м┘К┘Д.",
|
||||||
|
"Co-Editors": "╪з┘Д┘Е╪н╪▒╪▒┘И┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘И┘Ж",
|
||||||
|
"Co-Owners": "╪з┘Д┘Е╪з┘Д┘Г┘И┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘И┘Ж",
|
||||||
|
"Co-Viewers": "╪з┘Д┘Е╪┤╪з┘З╪п┘И┘Ж ╪з┘Д┘Е╪┤╪з╪▒┘Г┘И┘Ж",
|
||||||
|
"Comment": "╪к╪╣┘Д┘К┘В",
|
||||||
|
"Comments": "╪к╪╣┘Д┘К┘В╪з╪к",
|
||||||
|
"Comments are disabled": "╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к ┘Е╪╣╪╖┘Д╪й",
|
||||||
|
"Confirm": "╪к╪г┘Г┘К╪п",
|
||||||
|
"Confirm Action": "╪к╪г┘Г┘К╪п ╪з┘Д╪е╪м╪▒╪з╪б",
|
||||||
|
"Contact": "╪з╪к╪╡┘Д",
|
||||||
|
"Copy Media": "┘Ж╪│╪о ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Create": "╪е┘Ж╪┤╪з╪б",
|
||||||
|
"DELETE": "╪н╪░┘Б",
|
||||||
|
"DELETE MEDIA": "╪н╪░┘Б ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"DOWNLOAD": "╪к╪н┘Е┘К┘Д",
|
||||||
|
"DURATION": "╪з┘Д┘Е╪п╪й",
|
||||||
|
"Delete Media": "╪н╪░┘Б ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Delete media": "╪н╪░┘Б ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Disable Comments": "╪к╪╣╪╖┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к",
|
||||||
|
"Disable Download": "╪к╪╣╪╖┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д",
|
||||||
|
"Drag and drop files": "╪│╪н╪и ┘И╪е┘Б┘Д╪з╪к ╪з┘Д┘Е┘Д┘Б╪з╪к",
|
||||||
|
"EDIT MEDIA": "╪к╪╣╪п┘К┘Д ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"EDIT PROFILE": "╪к╪╣╪п┘К┘Д ╪з┘Д┘Е┘Д┘Б ╪з┘Д╪┤╪о╪╡┘К",
|
||||||
|
"EDIT SUBTITLE": "╪к╪╣╪п┘К┘Д ╪з┘Д╪к╪▒╪м┘Е╪й",
|
||||||
|
"Edit media": "╪к╪╣╪п┘К┘Д ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Edit profile": "╪к╪╣╪п┘К┘Д ╪з┘Д┘Е┘Д┘Б ╪з┘Д╪┤╪о╪╡┘К",
|
||||||
|
"Edit subtitle": "╪к╪╣╪п┘К┘Д ╪з┘Д╪к╪▒╪м┘Е╪й",
|
||||||
|
"Enable Comments": "╪к┘Б╪╣┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к",
|
||||||
|
"Enable Download": "╪к┘Б╪╣┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д",
|
||||||
|
"Enter playlist name...": "╪г╪п╪о┘Д ╪з╪│┘Е ┘В╪з╪ж┘Е╪й ╪з┘Д╪к╪┤╪║┘К┘Д...",
|
||||||
|
"Failed to add categories": "┘Б╪┤┘Д ╪е╪╢╪з┘Б╪й ╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Failed to add media to playlists": "┘Б╪┤┘Д ╪е╪╢╪з┘Б╪й ╪з┘Д┘И╪│╪з╪ж╪╖ ╪е┘Д┘Й ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to add tags": "┘Б╪┤┘Д ╪е╪╢╪з┘Б╪й ╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Failed to add users": "┘Б╪┤┘Д ╪е╪╢╪з┘Б╪й ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж",
|
||||||
|
"Failed to change owner": "┘Б╪┤┘Д ╪к╪║┘К┘К╪▒ ╪з┘Д┘Е╪з┘Д┘Г",
|
||||||
|
"Failed to change owner. Please try again.": "┘Б╪┤┘Д ╪к╪║┘К┘К╪▒ ╪з┘Д┘Е╪з┘Д┘Г. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to copy media.": "┘Б╪┤┘Д ┘Ж╪│╪о ╪з┘Д┘И╪│╪з╪ж╪╖.",
|
||||||
|
"Failed to create playlist": "┘Б╪┤┘Д ╪е┘Ж╪┤╪з╪б ┘В╪з╪ж┘Е╪й ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to delete media. Please try again.": "┘Б╪┤┘Д ╪н╪░┘Б ╪з┘Д┘И╪│╪з╪ж╪╖. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to disable comments.": "┘Б╪┤┘Д ╪к╪╣╪╖┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к.",
|
||||||
|
"Failed to disable download.": "┘Б╪┤┘Д ╪к╪╣╪╖┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д.",
|
||||||
|
"Failed to enable comments.": "┘Б╪┤┘Д ╪к┘Б╪╣┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к.",
|
||||||
|
"Failed to enable download.": "┘Б╪┤┘Д ╪к┘Б╪╣┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д.",
|
||||||
|
"Failed to fetch all categories": "┘Б╪┤┘Д ╪м┘Д╪и ╪м┘Е┘К╪╣ ╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Failed to fetch all tags": "┘Б╪┤┘Д ╪м┘Д╪и ╪м┘Е┘К╪╣ ╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Failed to fetch existing categories": "┘Б╪┤┘Д ╪м┘Д╪и ╪з┘Д┘Б╪ж╪з╪к ╪з┘Д┘Е┘И╪м┘И╪п╪й",
|
||||||
|
"Failed to fetch existing tags": "┘Б╪┤┘Д ╪м┘Д╪и ╪з┘Д╪╣┘Д╪з┘Е╪з╪к ╪з┘Д┘Е┘И╪м┘И╪п╪й",
|
||||||
|
"Failed to fetch existing users": "┘Б╪┤┘Д ╪м┘Д╪и ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж ╪з┘Д┘Е┘И╪м┘И╪п┘К┘Ж",
|
||||||
|
"Failed to fetch playlist membership": "┘Б╪┤┘Д ╪м┘Д╪и ╪╣╪╢┘И┘К╪й ┘В╪з╪ж┘Е╪й ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to fetch playlists": "┘Б╪┤┘Д ╪м┘Д╪и ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to load categories": "┘Б╪┤┘Д ╪к╪н┘Е┘К┘Д ╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Failed to load existing permissions": "┘Б╪┤┘Д ╪к╪н┘Е┘К┘Д ╪з┘Д╪г╪░┘И┘Ж╪з╪к ╪з┘Д┘Е┘И╪м┘И╪п╪й",
|
||||||
|
"Failed to load playlists": "┘Б╪┤┘Д ╪к╪н┘Е┘К┘Д ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to load tags": "┘Б╪┤┘Д ╪к╪н┘Е┘К┘Д ╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Failed to remove categories": "┘Б╪┤┘Д ╪е╪▓╪з┘Д╪й ╪з┘Д┘Б╪ж╪з╪к",
|
||||||
|
"Failed to remove media from playlists": "┘Б╪┤┘Д ╪е╪▓╪з┘Д╪й ╪з┘Д┘И╪│╪з╪ж╪╖ ┘Е┘Ж ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Failed to remove tags": "┘Б╪┤┘Д ╪е╪▓╪з┘Д╪й ╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Failed to remove users": "┘Б╪┤┘Д ╪е╪▓╪з┘Д╪й ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж",
|
||||||
|
"Failed to search users": "┘Б╪┤┘Д ╪з┘Д╪и╪н╪л ╪╣┘Ж ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж",
|
||||||
|
"Failed to set publish state": "┘Б╪┤┘Д ╪к╪╣┘К┘К┘Ж ╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒",
|
||||||
|
"Failed to set publish state. Please try again.": "┘Б╪┤┘Д ╪к╪╣┘К┘К┘Ж ╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to update categories. Please try again.": "┘Б╪┤┘Д ╪к╪н╪п┘К╪л ╪з┘Д┘Б╪ж╪з╪к. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to update permissions. Please try again.": "┘Б╪┤┘Д ╪к╪н╪п┘К╪л ╪з┘Д╪г╪░┘И┘Ж╪з╪к. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to update playlists. Please try again.": "┘Б╪┤┘Д ╪к╪н╪п┘К╪л ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Failed to update tags. Please try again.": "┘Б╪┤┘Д ╪к╪н╪п┘К╪л ╪з┘Д╪╣┘Д╪з┘Е╪з╪к. ┘К╪▒╪м┘Й ╪з┘Д┘Е╪н╪з┘И┘Д╪й ┘Е╪▒╪й ╪г╪о╪▒┘Й.",
|
||||||
|
"Featured": "┘Е┘Е┘К╪▓",
|
||||||
|
"Filter existing users...": "╪к╪╡┘Б┘К╪й ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж ╪з┘Д┘Е┘И╪м┘И╪п┘К┘Ж...",
|
||||||
|
"Filter playlists...": "╪к╪╡┘Б┘К╪й ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д...",
|
||||||
|
"Filters": "╪з┘Д┘Б┘Д╪з╪к╪▒",
|
||||||
|
"Go": "╪з╪░┘З╪и",
|
||||||
|
"History": "╪з┘Д╪к╪з╪▒┘К╪о",
|
||||||
|
"Home": "╪з┘Д╪▒╪ж┘К╪│┘К╪й",
|
||||||
|
"Image": "╪╡┘И╪▒╪й",
|
||||||
|
"Language": "╪з┘Д┘Д╪║╪й",
|
||||||
|
"Latest": "╪з┘Д╪г╪н╪п╪л",
|
||||||
|
"Like count": "╪╣╪п╪п ╪з┘Д╪е╪╣╪м╪з╪и╪з╪к",
|
||||||
|
"Liked media": "╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д┘Е┘Б╪╢┘Д╪й",
|
||||||
|
"Likes - Least": "╪з┘Д╪е╪╣╪м╪з╪и╪з╪к - ╪з┘Д╪г┘В┘Д",
|
||||||
|
"Likes - Most": "╪з┘Д╪е╪╣╪м╪з╪и╪з╪к - ╪з┘Д╪г┘Г╪л╪▒",
|
||||||
|
"Loading categories...": "╪м╪з╪▒┘Н ╪к╪н┘Е┘К┘Д ╪з┘Д┘Б╪ж╪з╪к...",
|
||||||
|
"Loading existing users...": "╪м╪з╪▒┘Н ╪к╪н┘Е┘К┘Д ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж ╪з┘Д┘Е┘И╪м┘И╪п┘К┘Ж...",
|
||||||
|
"Loading playlists...": "╪м╪з╪▒┘Н ╪к╪н┘Е┘К┘Д ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д...",
|
||||||
|
"Loading tags...": "╪м╪з╪▒┘Н ╪к╪н┘Е┘К┘Д ╪з┘Д╪╣┘Д╪з┘Е╪з╪к...",
|
||||||
|
"MEDIA TYPE": "┘Ж┘И╪╣ ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Manage": "╪е╪п╪з╪▒╪й",
|
||||||
|
"Manage Playlists": "╪е╪п╪з╪▒╪й ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Manage comments": "╪е╪п╪з╪▒╪й ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к",
|
||||||
|
"Manage media": "╪е╪п╪з╪▒╪й ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Manage users": "╪е╪п╪з╪▒╪й ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж",
|
||||||
|
"Media": "┘И╪│╪з╪ж╪╖",
|
||||||
|
"Media I own": "╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д╪к┘К ╪г┘Е╪к┘Д┘Г┘З╪з",
|
||||||
|
"Media was edited": "╪к┘Е ╪к╪╣╪п┘К┘Д ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Members": "╪з┘Д╪г╪╣╪╢╪з╪б",
|
||||||
|
"My media": "┘И╪│╪з╪ж╪╖┘К",
|
||||||
|
"My playlists": "┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д ╪з┘Д╪о╪з╪╡╪й ╪и┘К",
|
||||||
|
"No": "┘Д╪з",
|
||||||
|
"No categories": "┘Д╪з ╪к┘И╪м╪п ┘Б╪ж╪з╪к",
|
||||||
|
"No comment yet": "┘Д╪з ┘К┘И╪м╪п ╪к╪╣┘Д┘К┘В ╪и╪╣╪п",
|
||||||
|
"No comments yet": "┘Д╪з ╪к┘И╪м╪п ╪к╪╣┘Д┘К┘В╪з╪к ╪и╪╣╪п",
|
||||||
|
"No existing": "┘Д╪з ┘К┘И╪м╪п ┘Е┘И╪м┘И╪п",
|
||||||
|
"No playlists available": "┘Д╪з ╪к┘И╪м╪п ┘В┘И╪з╪ж┘Е ╪к╪┤╪║┘К┘Д ┘Е╪к╪з╪н╪й",
|
||||||
|
"No playlists selected": "┘Д┘Е ┘К╪к┘Е ╪к╪н╪п┘К╪п ┘В┘И╪з╪ж┘Е ╪к╪┤╪║┘К┘Д",
|
||||||
|
"No results for": "┘Д╪з ╪к┘И╪м╪п ┘Ж╪к╪з╪ж╪м ┘Д┘А",
|
||||||
|
"No tags": "┘Д╪з ╪к┘И╪м╪п ╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"No users to add": "┘Д╪з ┘К┘И╪м╪п ┘Е╪│╪к╪о╪п┘Е┘И┘Ж ┘Д╪е╪╢╪з┘Б╪к┘З┘Е",
|
||||||
|
"PLAYLISTS": "┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"PUBLISH STATE": "╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д",
|
||||||
|
"Plays - Least": "╪з┘Д┘Е╪┤╪з┘З╪п╪з╪к - ╪з┘Д╪г┘В┘Д",
|
||||||
|
"Plays - Most": "╪з┘Д┘Е╪┤╪з┘З╪п╪з╪к - ╪з┘Д╪г┘Г╪л╪▒",
|
||||||
|
"Please select a publish state": "┘К╪▒╪м┘Й ╪к╪н╪п┘К╪п ╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒",
|
||||||
|
"Please select a user": "┘К╪▒╪м┘Й ╪к╪н╪п┘К╪п ┘Е╪│╪к╪о╪п┘Е",
|
||||||
|
"Powered by": "┘Е╪п╪╣┘И┘Е ┘Е┘Ж",
|
||||||
|
"Private": "╪о╪з╪╡",
|
||||||
|
"Proceed": "┘Е╪к╪з╪и╪╣╪й",
|
||||||
|
"Processing...": "╪м╪з╪▒┘Н ╪з┘Д┘Е╪╣╪з┘Д╪м╪й...",
|
||||||
|
"Public": "╪╣╪з┘Е",
|
||||||
|
"Publish": "┘Ж╪┤╪▒",
|
||||||
|
"Publish State": "╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒",
|
||||||
|
"Published": "┘Е┘Ж╪┤┘И╪▒",
|
||||||
|
"Published on": "┘Ж╪┤╪▒ ┘Б┘К",
|
||||||
|
"Recent uploads": "╪з┘Д╪к╪н┘Е┘К┘Д╪з╪к ╪з┘Д╪г╪о┘К╪▒╪й",
|
||||||
|
"Recommended": "┘Е┘И╪╡┘Й ╪и┘З",
|
||||||
|
"Record Screen": "╪к╪│╪м┘К┘Д ╪з┘Д╪┤╪з╪┤╪й",
|
||||||
|
"Register": "╪к╪│╪м┘К┘Д",
|
||||||
|
"Remove category": "╪е╪▓╪з┘Д╪й ╪з┘Д┘Б╪ж╪й",
|
||||||
|
"Remove from list": "╪е╪▓╪з┘Д╪й ┘Е┘Ж ╪з┘Д┘В╪з╪ж┘Е╪й",
|
||||||
|
"Remove tag": "╪е╪▓╪з┘Д╪й ╪з┘Д╪╣┘Д╪з┘Е╪й",
|
||||||
|
"Remove user": "╪е╪▓╪з┘Д╪й ╪з┘Д┘Е╪│╪к╪о╪п┘Е",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "╪н┘Б╪╕",
|
||||||
|
"SEARCH": "╪и╪н╪л",
|
||||||
|
"SHARE": "┘Е╪┤╪з╪▒┘Г╪й",
|
||||||
|
"SHOW MORE": "╪╣╪▒╪╢ ╪з┘Д┘Е╪▓┘К╪п",
|
||||||
|
"SORT BY": "╪к╪▒╪к┘К╪и ╪н╪│╪и",
|
||||||
|
"SUBMIT": "╪е╪▒╪│╪з┘Д",
|
||||||
|
"Search": "╪и╪н╪л",
|
||||||
|
"Search for user...": "╪з┘Д╪и╪н╪л ╪╣┘Ж ┘Е╪│╪к╪о╪п┘Е...",
|
||||||
|
"Search users to add...": "╪з┘Д╪и╪н╪л ╪╣┘Ж ┘Е╪│╪к╪о╪п┘Е┘К┘Ж ┘Д╪е╪╢╪з┘Б╪к┘З┘Е...",
|
||||||
|
"Select": "╪з╪о╪к╪▒",
|
||||||
|
"Select Owner": "╪з╪о╪к╪▒ ╪з┘Д┘Е╪з┘Д┘Г",
|
||||||
|
"Select all": "╪к╪н╪п┘К╪п ╪з┘Д┘Г┘Д",
|
||||||
|
"Select all media": "╪к╪н╪п┘К╪п ╪м┘Е┘К╪╣ ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Select publish state:": "╪з╪о╪к╪▒ ╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒:",
|
||||||
|
"Selected": "┘Е╪н╪п╪п",
|
||||||
|
"Shared by me": "┘Е╪┤╪з╪▒┘Г╪й ┘Е┘Ж┘К",
|
||||||
|
"Shared with me": "┘Е╪┤╪з╪▒┘Г╪й ┘Е╪╣┘К",
|
||||||
|
"Sign in": "╪к╪│╪м┘К┘Д ╪з┘Д╪п╪о┘И┘Д",
|
||||||
|
"Sign out": "╪к╪│╪м┘К┘Д ╪з┘Д╪о╪▒┘И╪м",
|
||||||
|
"Sort By": "╪к╪▒╪к┘К╪и ╪н╪│╪и",
|
||||||
|
"Start Recording": "╪и╪п╪б ╪з┘Д╪к╪│╪м┘К┘Д",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "╪з╪и╪п╪г ┘Б┘К ╪к╪н┘Е┘К┘Д ╪з┘Д┘И╪│╪з╪ж╪╖ ┘И┘Е╪┤╪з╪▒┘Г╪й ╪╣┘Е┘Д┘Г. ╪│╪к╪╕┘З╪▒ ╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д╪к┘К ╪к╪н┘Е┘Д┘З╪з ┘З┘Ж╪з.",
|
||||||
|
"Stop Recording": "╪е┘К┘В╪з┘Б ╪з┘Д╪к╪│╪м┘К┘Д",
|
||||||
|
"Submit": "╪е╪▒╪│╪з┘Д",
|
||||||
|
"Subtitle was added": "╪к┘Е╪к ╪е╪╢╪з┘Б╪й ╪з┘Д╪к╪▒╪м┘Е╪й",
|
||||||
|
"Subtitles": "╪к╪▒╪м┘Е╪з╪к",
|
||||||
|
"Successfully Copied": "╪к┘Е ╪з┘Д┘Ж╪│╪о ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully Disabled Download": "╪к┘Е ╪к╪╣╪╖┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully Disabled comments": "╪к┘Е ╪к╪╣╪╖┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully Enabled Download": "╪к┘Е ╪к┘Б╪╣┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully Enabled comments": "╪к┘Е ╪к┘Б╪╣┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully changed owner": "╪к┘Е ╪к╪║┘К┘К╪▒ ╪з┘Д┘Е╪з┘Д┘Г ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully deleted": "╪к┘Е ╪з┘Д╪н╪░┘Б ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully updated": "╪к┘Е ╪з┘Д╪к╪н╪п┘К╪л ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully updated categories": "╪к┘Е ╪к╪н╪п┘К╪л ╪з┘Д┘Б╪ж╪з╪к ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully updated playlist membership": "╪к┘Е ╪к╪н╪п┘К╪л ╪╣╪╢┘И┘К╪й ┘В╪з╪ж┘Е╪й ╪з┘Д╪к╪┤╪║┘К┘Д ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully updated publish state": "╪к┘Е ╪к╪н╪п┘К╪л ╪н╪з┘Д╪й ╪з┘Д┘Ж╪┤╪▒ ╪и┘Ж╪м╪з╪н",
|
||||||
|
"Successfully updated tags": "╪к┘Е ╪к╪н╪п┘К╪л ╪з┘Д╪╣┘Д╪з┘Е╪з╪к ╪и┘Ж╪м╪з╪н",
|
||||||
|
"TAGS": "╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Tag": "╪╣┘Д╪з┘Е╪й",
|
||||||
|
"Tags": "╪з┘Д╪╣┘Д╪з┘Е╪з╪к",
|
||||||
|
"Terms": "╪з┘Д╪┤╪▒┘И╪╖",
|
||||||
|
"The intersection of categories in the selected media is shown": "┘К╪к┘Е ╪╣╪▒╪╢ ╪к┘В╪з╪╖╪╣ ╪з┘Д┘Б╪ж╪з╪к ┘Б┘К ╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д┘Е╪н╪п╪п╪й",
|
||||||
|
"The intersection of playlists in the selected media is shown": "┘К╪к┘Е ╪╣╪▒╪╢ ╪к┘В╪з╪╖╪╣ ┘В┘И╪з╪ж┘Е ╪з┘Д╪к╪┤╪║┘К┘Д ┘Б┘К ╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д┘Е╪н╪п╪п╪й",
|
||||||
|
"The intersection of tags in the selected media is shown": "┘К╪к┘Е ╪╣╪▒╪╢ ╪к┘В╪з╪╖╪╣ ╪з┘Д╪╣┘Д╪з┘Е╪з╪к ┘Б┘К ╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д┘Е╪н╪п╪п╪й",
|
||||||
|
"The intersection of users in the selected media is shown": "┘К╪к┘Е ╪╣╪▒╪╢ ╪к┘В╪з╪╖╪╣ ╪з┘Д┘Е╪│╪к╪о╪п┘Е┘К┘Ж ┘Б┘К ╪з┘Д┘И╪│╪з╪ж╪╖ ╪з┘Д┘Е╪н╪п╪п╪й",
|
||||||
|
"The media was deleted successfully.": "╪к┘Е ╪н╪░┘Б ╪з┘Д┘И╪│╪з╪ж╪╖ ╪и┘Ж╪м╪з╪н.",
|
||||||
|
"This month": "┘З╪░╪з ╪з┘Д╪┤┘З╪▒",
|
||||||
|
"This week": "┘З╪░╪з ╪з┘Д╪г╪│╪и┘И╪╣",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "┘З╪░╪з ┘К╪╣┘Е┘Д ┘Б┘К ┘Е╪к╪╡┘Б╪н╪з╪к Chrome ┘И Safari ┘И Edge.",
|
||||||
|
"This year": "┘З╪░╪з ╪з┘Д╪╣╪з┘Е",
|
||||||
|
"To add": "┘Д┘Д╪е╪╢╪з┘Б╪й",
|
||||||
|
"Today": "╪з┘Д┘К┘И┘Е",
|
||||||
|
"Trim": "┘В╪╡",
|
||||||
|
"UPLOAD": "╪▒┘Б╪╣",
|
||||||
|
"UPLOAD DATE": "╪к╪з╪▒┘К╪о ╪з┘Д╪к╪н┘Е┘К┘Д",
|
||||||
|
"UPLOAD MEDIA": "╪к╪н┘Е┘К┘Д ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Undo removal": "╪з┘Д╪к╪▒╪з╪м╪╣ ╪╣┘Ж ╪з┘Д╪е╪▓╪з┘Д╪й",
|
||||||
|
"Unlisted": "╪║┘К╪▒ ┘Е╪п╪▒╪м",
|
||||||
|
"Up Next": "╪з┘Д╪к╪з┘Д┘К",
|
||||||
|
"Up next": "╪з┘Д╪к╪з┘Д┘К",
|
||||||
|
"Upload": "╪▒┘Б╪╣",
|
||||||
|
"Upload date (newest)": "╪к╪з╪▒┘К╪о ╪з┘Д╪к╪н┘Е┘К┘Д (╪з┘Д╪г╪н╪п╪л)",
|
||||||
|
"Upload date (oldest)": "╪к╪з╪▒┘К╪о ╪з┘Д╪к╪н┘Е┘К┘Д (╪з┘Д╪г┘В╪п┘Е)",
|
||||||
|
"Upload date - Newest": "╪к╪з╪▒┘К╪о ╪з┘Д╪к╪н┘Е┘К┘Д - ╪з┘Д╪г╪н╪п╪л",
|
||||||
|
"Upload date - Oldest": "╪к╪з╪▒┘К╪о ╪з┘Д╪к╪н┘Е┘К┘Д - ╪з┘Д╪г┘В╪п┘Е",
|
||||||
|
"Upload media": "╪▒┘Б╪╣ ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Uploads": "╪з┘Д╪к╪н┘Е┘К┘Д╪з╪к",
|
||||||
|
"Users": "╪з┘Д┘Е╪│╪к╪о╪п┘Е┘И┘Ж",
|
||||||
|
"VIEW ALL": "╪╣╪▒╪╢ ╪з┘Д┘Г┘Д",
|
||||||
|
"Video": "┘Б┘К╪п┘К┘И",
|
||||||
|
"View all": "╪╣╪▒╪╢ ╪з┘Д┘Г┘Д",
|
||||||
|
"View count": "╪╣╪п╪п ╪з┘Д┘Е╪┤╪з┘З╪п╪з╪к",
|
||||||
|
"View media": "╪╣╪▒╪╢ ╪з┘Д┘И╪│╪з╪ж╪╖",
|
||||||
|
"Welcome": "┘Е╪▒╪н╪и╪з┘Л",
|
||||||
|
"You are going to copy": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪з┘Д┘Ж╪│╪о",
|
||||||
|
"You are going to delete": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪з┘Д╪н╪░┘Б",
|
||||||
|
"You are going to disable comments to": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪к╪╣╪╖┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к ┘Д┘А",
|
||||||
|
"You are going to disable download for": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪к╪╣╪╖┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д ┘Д┘А",
|
||||||
|
"You are going to enable comments to": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪к┘Б╪╣┘К┘Д ╪з┘Д╪к╪╣┘Д┘К┘В╪з╪к ┘Д┘А",
|
||||||
|
"You are going to enable download for": "╪│┘И┘Б ╪к┘В┘И┘Е ╪и╪к┘Б╪╣┘К┘Д ╪з┘Д╪к┘Ж╪▓┘К┘Д ┘Д┘А",
|
||||||
|
"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": "┘И╪│╪з╪ж╪╖ ┘Б┘К ╪з┘Д╪╣┘Д╪з┘Е╪й",
|
||||||
|
"media, are you sure?": "┘И╪│╪з╪ж╪╖╪М ┘З┘Д ╪г┘Ж╪к ┘Е╪к╪г┘Г╪п╪Я",
|
||||||
|
"media.": "┘И╪│╪з╪ж╪╖.",
|
||||||
|
"or": "╪г┘И",
|
||||||
|
"results for": "┘Ж╪к╪з╪ж╪м ┘Д┘А",
|
||||||
|
"selected": "┘Е╪н╪п╪п",
|
||||||
|
"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": "┘Е┘Ж╪░ ╪│┘Ж┘И╪з╪к",
|
||||||
|
}
|
||||||
289
files/frontend_translations/bn.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "",
|
||||||
|
"00 - 20 min": "00 - 20 ржорж┐ржирж┐ржЯ",
|
||||||
|
"1 result for": "1ржЯрж┐ ржлрж▓рж╛ржлрж▓",
|
||||||
|
"20 - 40 min": "20 - 40 ржорж┐ржирж┐ржЯ",
|
||||||
|
"40 - 60 min": "40 - 60 ржорж┐ржирж┐ржЯ",
|
||||||
|
"60 - 120 min+": "60 - 120 ржорж┐ржирж┐ржЯ+",
|
||||||
|
"ABOUT": "рж╕ржорзНржкрж░рзНржХрзЗ",
|
||||||
|
"AUTOPLAY": "рж╕рзНржмржпрж╝ржВржХрзНрж░рж┐ржпрж╝ ржкрзНрж▓рзЗ",
|
||||||
|
"About": "рж╕ржорзНржкрж░рзНржХрзЗ",
|
||||||
|
"Add / Remove Co-Editors": "",
|
||||||
|
"Add / Remove Co-Owners": "",
|
||||||
|
"Add / Remove Co-Viewers": "",
|
||||||
|
"Add / Remove Tags": "",
|
||||||
|
"Add / Remove from Categories": "",
|
||||||
|
"Add a ": "ржпрзЛржЧ ржХрж░рзБржи",
|
||||||
|
"Add to": "",
|
||||||
|
"Add to / Remove from Category": "",
|
||||||
|
"Add to / Remove from Playlist": "",
|
||||||
|
"All": "рж╕ржм",
|
||||||
|
"All categories already added": "",
|
||||||
|
"All tags already added": "",
|
||||||
|
"Alphabetically - A-Z": "ржмрж░рзНржгрж╛ржирзБржХрзНрж░ржорж┐ржХ - A-Z",
|
||||||
|
"Alphabetically - Z-A": "ржмрж░рзНржгрж╛ржирзБржХрзНрж░ржорж┐ржХ - Z-A",
|
||||||
|
"Audio": "ржЕржбрж┐ржУ",
|
||||||
|
"Browse your files": "ржЖржкржирж╛рж░ ржлрж╛ржЗрж▓ ржмрзНрж░рж╛ржЙржЬ ржХрж░рзБржи",
|
||||||
|
"Bulk Actions": "",
|
||||||
|
"COMMENT": "ржоржирзНрждржмрзНржп",
|
||||||
|
"Cancel": "",
|
||||||
|
"Categories": "ржмрж┐ржнрж╛ржЧрж╕ржорзВрж╣",
|
||||||
|
"Category": "ржмрж┐ржнрж╛ржЧ",
|
||||||
|
"Change Language": "ржнрж╛рж╖рж╛ ржкрж░рж┐ржмрж░рзНрждржи ржХрж░рзБржи",
|
||||||
|
"Change Owner": "",
|
||||||
|
"Change password": "ржкрж╛рж╕ржУржпрж╝рж╛рж░рзНржб ржкрж░рж┐ржмрж░рзНрждржи ржХрж░рзБржи",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'рж░рзЗржХрж░рзНржбрж┐ржВ рж╢рзБрж░рзБ ржХрж░рзБржи'-ржП ржХрзНрж▓рж┐ржХ ржХрж░рзБржи ржПржмржВ рж░рзЗржХрж░рзНржб ржХрж░рж╛рж░ ржЬржирзНржп рж╕рзНржХрзНрж░рж┐ржи ржмрж╛ ржЯрзНржпрж╛ржм ржирж┐рж░рзНржмрж╛ржЪржи ржХрж░рзБржиред рж░рзЗржХрж░рзНржбрж┐ржВ рж╢рзЗрж╖ рж╣рж▓рзЗ, 'рж░рзЗржХрж░рзНржбрж┐ржВ ржмржирзНржз ржХрж░рзБржи'-ржП ржХрзНрж▓рж┐ржХ ржХрж░рзБржи ржПржмржВ рж░рзЗржХрж░рзНржбрж┐ржВ ржЖржкрж▓рзЛржб рж╣ржпрж╝рзЗ ржпрж╛ржмрзЗред",
|
||||||
|
"Co-Editors": "",
|
||||||
|
"Co-Owners": "",
|
||||||
|
"Co-Viewers": "",
|
||||||
|
"Comment": "ржоржирзНрждржмрзНржп",
|
||||||
|
"Comments": "ржоржирзНрждржмрзНржпрж╕ржорзВрж╣",
|
||||||
|
"Comments are disabled": "ржоржирзНрждржмрзНржп ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ ржХрж░рж╛ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Confirm": "",
|
||||||
|
"Confirm Action": "",
|
||||||
|
"Contact": "ржпрзЛржЧрж╛ржпрзЛржЧ",
|
||||||
|
"Copy Media": "",
|
||||||
|
"Create": "",
|
||||||
|
"DELETE": "ржорзБржЫрзЗ ржлрзЗрж▓рзБржи",
|
||||||
|
"DELETE MEDIA": "ржорж┐ржбрж┐ржпрж╝рж╛ ржорзБржЫрзБржи",
|
||||||
|
"DOWNLOAD": "ржбрж╛ржЙржирж▓рзЛржб",
|
||||||
|
"DURATION": "рж╕ржоржпрж╝ржХрж╛рж▓",
|
||||||
|
"Delete Media": "",
|
||||||
|
"Delete media": "ржорж┐ржбрж┐ржпрж╝рж╛ ржорзБржЫрзБржи",
|
||||||
|
"Disable Comments": "",
|
||||||
|
"Disable Download": "",
|
||||||
|
"Drag and drop files": "ржлрж╛ржЗрж▓ ржЯрзЗржирзЗ ржЖржирзБржи",
|
||||||
|
"EDIT MEDIA": "ржорж┐ржбрж┐ржпрж╝рж╛ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"EDIT PROFILE": "ржкрзНрж░рзЛржлрж╛ржЗрж▓ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"EDIT SUBTITLE": "рж╕рж╛ржмржЯрж╛ржЗржЯрзЗрж▓ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"Edit media": "ржорж┐ржбрж┐ржпрж╝рж╛ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"Edit profile": "ржкрзНрж░рзЛржлрж╛ржЗрж▓ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"Edit subtitle": "рж╕рж╛ржмржЯрж╛ржЗржЯрзЗрж▓ рж╕ржорзНржкрж╛ржжржирж╛ ржХрж░рзБржи",
|
||||||
|
"Enable Comments": "",
|
||||||
|
"Enable Download": "",
|
||||||
|
"Enter playlist name...": "",
|
||||||
|
"Failed to add categories": "",
|
||||||
|
"Failed to add media to playlists": "",
|
||||||
|
"Failed to add tags": "",
|
||||||
|
"Failed to add users": "",
|
||||||
|
"Failed to change owner": "",
|
||||||
|
"Failed to change owner. Please try again.": "",
|
||||||
|
"Failed to copy media.": "ржорж┐ржбрж┐ржпрж╝рж╛ ржХржкрж┐ ржХрж░рждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete media. Please try again.": "ржорж┐ржбрж┐ржпрж╝рж╛ ржорзБржЫрждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред ржжржпрж╝рж╛ ржХрж░рзЗ ржЖржмрж╛рж░ ржЪрзЗрж╖рзНржЯрж╛ ржХрж░рзБржиред",
|
||||||
|
"Failed to disable comments.": "ржоржирзНрждржмрзНржп ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"Failed to disable download.": "ржбрж╛ржЙржирж▓рзЛржб ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"Failed to enable comments.": "ржоржирзНрждржмрзНржп рж╕ржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"Failed to enable download.": "ржбрж╛ржЙржирж▓рзЛржб рж╕ржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржмрзНржпрж░рзНрже рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"Failed to fetch all categories": "",
|
||||||
|
"Failed to fetch all tags": "",
|
||||||
|
"Failed to fetch existing categories": "",
|
||||||
|
"Failed to fetch existing tags": "",
|
||||||
|
"Failed to fetch existing users": "",
|
||||||
|
"Failed to fetch playlist membership": "",
|
||||||
|
"Failed to fetch playlists": "",
|
||||||
|
"Failed to load categories": "",
|
||||||
|
"Failed to load existing permissions": "",
|
||||||
|
"Failed to load playlists": "",
|
||||||
|
"Failed to load tags": "",
|
||||||
|
"Failed to remove categories": "",
|
||||||
|
"Failed to remove media from playlists": "",
|
||||||
|
"Failed to remove tags": "",
|
||||||
|
"Failed to remove users": "",
|
||||||
|
"Failed to search users": "",
|
||||||
|
"Failed to set publish state": "",
|
||||||
|
"Failed to set publish state. Please try again.": "",
|
||||||
|
"Failed to update categories. Please try again.": "",
|
||||||
|
"Failed to update permissions. Please try again.": "",
|
||||||
|
"Failed to update playlists. Please try again.": "",
|
||||||
|
"Failed to update tags. Please try again.": "",
|
||||||
|
"Featured": "ржмрзИрж╢рж┐рж╖рзНржЯрзНржпржпрзБржХрзНржд",
|
||||||
|
"Filter existing users...": "",
|
||||||
|
"Filter playlists...": "",
|
||||||
|
"Filters": "ржлрж┐рж▓рзНржЯрж╛рж░",
|
||||||
|
"Go": "ржпрж╛ржУ",
|
||||||
|
"History": "ржЗрждрж┐рж╣рж╛рж╕",
|
||||||
|
"Home": "ржмрж╛ржбрж╝рж┐",
|
||||||
|
"Image": "ржЫржмрж┐",
|
||||||
|
"Language": "ржнрж╛рж╖рж╛",
|
||||||
|
"Latest": "рж╕рж░рзНржмрж╢рзЗрж╖",
|
||||||
|
"Like count": "ржкржЫржирзНржжрзЗрж░ рж╕ржВржЦрзНржпрж╛",
|
||||||
|
"Liked media": "ржкржЫржирзНржжрзЗрж░ ржорж┐ржбрж┐ржпрж╝рж╛",
|
||||||
|
"Likes - Least": "ржкржЫржирзНржж - рж╕ржмржЪрзЗржпрж╝рзЗ ржХржо",
|
||||||
|
"Likes - Most": "ржкржЫржирзНржж - рж╕ржмржЪрзЗржпрж╝рзЗ ржмрзЗрж╢рж┐",
|
||||||
|
"Loading categories...": "",
|
||||||
|
"Loading existing users...": "",
|
||||||
|
"Loading playlists...": "",
|
||||||
|
"Loading tags...": "",
|
||||||
|
"MEDIA TYPE": "ржорж┐ржбрж┐ржпрж╝рж╛рж░ ржзрж░ржи",
|
||||||
|
"Manage": "",
|
||||||
|
"Manage Playlists": "",
|
||||||
|
"Manage comments": "ржоржирзНрждржмрзНржп ржкрж░рж┐ржЪрж╛рж▓ржирж╛ ржХрж░рзБржи",
|
||||||
|
"Manage media": "ржорж┐ржбрж┐ржпрж╝рж╛ ржкрж░рж┐ржЪрж╛рж▓ржирж╛ ржХрж░рзБржи",
|
||||||
|
"Manage users": "ржмрзНржпржмрж╣рж╛рж░ржХрж╛рж░рзАржжрзЗрж░ ржкрж░рж┐ржЪрж╛рж▓ржирж╛ ржХрж░рзБржи",
|
||||||
|
"Media": "ржорж┐ржбрж┐ржпрж╝рж╛",
|
||||||
|
"Media I own": "",
|
||||||
|
"Media was edited": "ржорж┐ржбрж┐ржпрж╝рж╛ рж╕ржорзНржкрж╛ржжрж┐ржд рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Members": "рж╕ржжрж╕рзНржпрж░рж╛",
|
||||||
|
"My media": "ржЖржорж╛рж░ ржорж┐ржбрж┐ржпрж╝рж╛",
|
||||||
|
"My playlists": "ржЖржорж╛рж░ ржкрзНрж▓рзЗрж▓рж┐рж╕рзНржЯ",
|
||||||
|
"No": "ржирж╛",
|
||||||
|
"No categories": "",
|
||||||
|
"No comment yet": "ржПржЦржиржУ ржХрзЛржи ржоржирзНрждржмрзНржп ржирзЗржЗ",
|
||||||
|
"No comments yet": "ржПржЦржиржУ ржХрзЛржи ржоржирзНрждржмрзНржп ржирзЗржЗ",
|
||||||
|
"No existing": "",
|
||||||
|
"No playlists available": "",
|
||||||
|
"No playlists selected": "",
|
||||||
|
"No results for": "ржПрж░ ржЬржирзНржп ржХрзЛржи ржлрж▓рж╛ржлрж▓ ржирзЗржЗ",
|
||||||
|
"No tags": "",
|
||||||
|
"No users to add": "",
|
||||||
|
"PLAYLISTS": "ржкрзНрж▓рзЗрж▓рж┐рж╕рзНржЯ",
|
||||||
|
"PUBLISH STATE": "ржкрзНрж░ржХрж╛рж╢рзЗрж░ ржЕржмрж╕рзНржерж╛",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "ржкрзНрж▓рзЗрж▓рж┐рж╕рзНржЯ",
|
||||||
|
"Plays - Least": "ржкрзНрж▓рзЗ - рж╕ржмржЪрзЗржпрж╝рзЗ ржХржо",
|
||||||
|
"Plays - Most": "ржкрзНрж▓рзЗ - рж╕ржмржЪрзЗржпрж╝рзЗ ржмрзЗрж╢рж┐",
|
||||||
|
"Please select a publish state": "",
|
||||||
|
"Please select a user": "",
|
||||||
|
"Powered by": "ржжрзНржмрж╛рж░рж╛ ржЪрж╛рж▓рж┐ржд",
|
||||||
|
"Private": "ржмрзНржпржХрзНрждрж┐ржЧржд",
|
||||||
|
"Proceed": "",
|
||||||
|
"Processing...": "",
|
||||||
|
"Public": "",
|
||||||
|
"Publish": "ржкрзНрж░ржХрж╛рж╢ ржХрж░рзБржи",
|
||||||
|
"Publish State": "",
|
||||||
|
"Published": "ржкрзНрж░ржХрж╛рж╢рж┐ржд",
|
||||||
|
"Published on": "ржкрзНрж░ржХрж╛рж╢рж┐ржд",
|
||||||
|
"Recent uploads": "рж╕рж╛ржорзНржкрзНрж░рждрж┐ржХ ржЖржкрж▓рзЛржб",
|
||||||
|
"Recommended": "ржкрзНрж░рж╕рзНрждрж╛ржмрж┐ржд",
|
||||||
|
"Record Screen": "рж╕рзНржХрзНрж░рж┐ржи рж░рзЗржХрж░рзНржб ржХрж░рзБржи",
|
||||||
|
"Register": "ржирж┐ржмржирзНржзржи ржХрж░рзБржи",
|
||||||
|
"Remove category": "",
|
||||||
|
"Remove from list": "",
|
||||||
|
"Remove tag": "",
|
||||||
|
"Remove user": "",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "рж╕ржВрж░ржХрзНрж╖ржг ржХрж░рзБржи",
|
||||||
|
"SEARCH": "ржЕржирзБрж╕ржирзНржзрж╛ржи",
|
||||||
|
"SHARE": "рж╢рзЗржпрж╝рж╛рж░ ржХрж░рзБржи",
|
||||||
|
"SHOW MORE": "ржЖрж░ржУ ржжрзЗржЦрзБржи",
|
||||||
|
"SORT BY": "рж╕рж╛ржЬрж╛ржи",
|
||||||
|
"SUBMIT": "ржЬржорж╛ ржжрж┐ржи",
|
||||||
|
"Search": "ржЕржирзБрж╕ржирзНржзрж╛ржи",
|
||||||
|
"Search for user...": "",
|
||||||
|
"Search users to add...": "",
|
||||||
|
"Select": "ржирж┐рж░рзНржмрж╛ржЪржи ржХрж░рзБржи",
|
||||||
|
"Select Owner": "",
|
||||||
|
"Select all": "",
|
||||||
|
"Select all media": "",
|
||||||
|
"Select publish state:": "",
|
||||||
|
"Selected": "",
|
||||||
|
"Shared by me": "ржЖржорж╛рж░ ржжрзНржмрж╛рж░рж╛ рж╢рзЗржпрж╝рж╛рж░ ржХрж░рж╛",
|
||||||
|
"Shared with me": "ржЖржорж╛рж░ рж╕рж╛ржерзЗ рж╢рзЗржпрж╝рж╛рж░ ржХрж░рж╛",
|
||||||
|
"Sign in": "рж╕рж╛ржЗржи ржЗржи ржХрж░рзБржи",
|
||||||
|
"Sign out": "рж╕рж╛ржЗржи ржЖржЙржЯ ржХрж░рзБржи",
|
||||||
|
"Sort By": "рж╕рж╛ржЬрж╛ржи",
|
||||||
|
"Start Recording": "рж░рзЗржХрж░рзНржбрж┐ржВ рж╢рзБрж░рзБ ржХрж░рзБржи",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "ржорж┐ржбрж┐ржпрж╝рж╛ ржЖржкрж▓рзЛржб ржХрж░рж╛ ржПржмржВ ржЖржкржирж╛рж░ ржХрж╛ржЬ рж╢рзЗржпрж╝рж╛рж░ ржХрж░рж╛ рж╢рзБрж░рзБ ржХрж░рзБржиред ржЖржкржирж┐ ржпрзЗ ржорж┐ржбрж┐ржпрж╝рж╛ ржЖржкрж▓рзЛржб ржХрж░ржмрзЗржи рждрж╛ ржПржЦрж╛ржирзЗ ржкрзНрж░ржжрж░рзНрж╢рж┐ржд рж╣ржмрзЗред",
|
||||||
|
"Stop Recording": "рж░рзЗржХрж░рзНржбрж┐ржВ ржмржирзНржз ржХрж░рзБржи",
|
||||||
|
"Submit": "",
|
||||||
|
"Subtitle was added": "рж╕рж╛ржмржЯрж╛ржЗржЯрзЗрж▓ ржпрзЛржЧ ржХрж░рж╛ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Subtitles": "рж╕рж╛ржмржЯрж╛ржЗржЯрзЗрж▓",
|
||||||
|
"Successfully Copied": "рж╕ржлрж▓ржнрж╛ржмрзЗ ржХржкрж┐ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully Disabled Download": "ржбрж╛ржЙржирж▓рзЛржб рж╕ржлрж▓ржнрж╛ржмрзЗ ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully Disabled comments": "ржоржирзНрждржмрзНржп рж╕ржлрж▓ржнрж╛ржмрзЗ ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully Enabled Download": "ржбрж╛ржЙржирж▓рзЛржб рж╕ржлрж▓ржнрж╛ржмрзЗ рж╕ржХрзНрж░рж┐ржпрж╝ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully Enabled comments": "ржоржирзНрждржмрзНржп рж╕ржлрж▓ржнрж╛ржмрзЗ рж╕ржХрзНрж░рж┐ржпрж╝ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully changed owner": "",
|
||||||
|
"Successfully deleted": "рж╕ржлрж▓ржнрж╛ржмрзЗ ржорзБржЫрзЗ ржлрзЗрж▓рж╛ рж╣ржпрж╝рзЗржЫрзЗ",
|
||||||
|
"Successfully updated": "",
|
||||||
|
"Successfully updated categories": "",
|
||||||
|
"Successfully updated playlist membership": "",
|
||||||
|
"Successfully updated publish state": "",
|
||||||
|
"Successfully updated tags": "",
|
||||||
|
"TAGS": "ржЯрзНржпрж╛ржЧ",
|
||||||
|
"Tag": "ржЯрзНржпрж╛ржЧ",
|
||||||
|
"Tags": "ржЯрзНржпрж╛ржЧ",
|
||||||
|
"Terms": "рж╢рж░рзНрждрж╛ржмрж▓рзА",
|
||||||
|
"The intersection of categories in the selected media is shown": "",
|
||||||
|
"The intersection of playlists in the selected media is shown": "",
|
||||||
|
"The intersection of tags in the selected media is shown": "",
|
||||||
|
"The intersection of users in the selected media is shown": "",
|
||||||
|
"The media was deleted successfully.": "ржорж┐ржбрж┐ржпрж╝рж╛ рж╕ржлрж▓ржнрж╛ржмрзЗ ржорзБржЫрзЗ ржлрзЗрж▓рж╛ рж╣ржпрж╝рзЗржЫрзЗред",
|
||||||
|
"This month": "ржПржЗ ржорж╛рж╕рзЗ",
|
||||||
|
"This week": "ржПржЗ рж╕ржкрзНрждрж╛рж╣рзЗ",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "ржПржЯрж┐ ржХрзНрж░рзЛржо, рж╕рж╛ржлрж╛рж░рж┐ ржПржмржВ ржПржЬ ржмрзНрж░рж╛ржЙржЬрж╛рж░рзЗ ржХрж╛ржЬ ржХрж░рзЗред",
|
||||||
|
"This year": "ржПржЗ ржмржЫрж░",
|
||||||
|
"To add": "",
|
||||||
|
"Today": "ржЖржЬ",
|
||||||
|
"Trim": "ржЫрж╛ржБржЯрж╛ржЗ",
|
||||||
|
"UPLOAD": "ржЖржкрж▓рзЛржб ржХрж░рзБржи",
|
||||||
|
"UPLOAD DATE": "ржЖржкрж▓рзЛржбрзЗрж░ рждрж╛рж░рж┐ржЦ",
|
||||||
|
"UPLOAD MEDIA": "ржорж┐ржбрж┐ржпрж╝рж╛ ржЖржкрж▓рзЛржб ржХрж░рзБржи",
|
||||||
|
"Undo removal": "",
|
||||||
|
"Unlisted": "рждрж╛рж▓рж┐ржХрж╛ржнрзБржХрзНржд ржиржпрж╝",
|
||||||
|
"Up Next": "ржкрж░ржмрж░рзНрждрзА",
|
||||||
|
"Up next": "ржкрж░ржмрж░рзНрждрзА",
|
||||||
|
"Upload": "ржЖржкрж▓рзЛржб ржХрж░рзБржи",
|
||||||
|
"Upload date (newest)": "ржЖржкрж▓рзЛржбрзЗрж░ рждрж╛рж░рж┐ржЦ (ржирждрзБржи)",
|
||||||
|
"Upload date (oldest)": "ржЖржкрж▓рзЛржбрзЗрж░ рждрж╛рж░рж┐ржЦ (ржкрзБрж░рж╛рждржи)",
|
||||||
|
"Upload date - Newest": "ржЖржкрж▓рзЛржбрзЗрж░ рждрж╛рж░рж┐ржЦ - ржирждрзБржи",
|
||||||
|
"Upload date - Oldest": "ржЖржкрж▓рзЛржбрзЗрж░ рждрж╛рж░рж┐ржЦ - ржкрзБрж░рж╛рждржи",
|
||||||
|
"Upload media": "ржорж┐ржбрж┐ржпрж╝рж╛ ржЖржкрж▓рзЛржб ржХрж░рзБржи",
|
||||||
|
"Uploads": "ржЖржкрж▓рзЛржбрж╕ржорзВрж╣",
|
||||||
|
"Users": "",
|
||||||
|
"VIEW ALL": "рж╕ржм ржжрзЗржЦрзБржи",
|
||||||
|
"Video": "ржнрж┐ржбрж┐ржУ",
|
||||||
|
"View all": "рж╕ржм ржжрзЗржЦрзБржи",
|
||||||
|
"View count": "ржжрзЗржЦрж╛рж░ рж╕ржВржЦрзНржпрж╛",
|
||||||
|
"View media": "ржорж┐ржбрж┐ржпрж╝рж╛ ржжрзЗржЦрзБржи",
|
||||||
|
"Welcome": "рж╕рзНржмрж╛ржЧрждржо",
|
||||||
|
"You are going to copy": "ржЖржкржирж┐ ржХржкрж┐ ржХрж░рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"You are going to delete": "ржЖржкржирж┐ ржорзБржЫрзЗ ржлрзЗрж▓рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"You are going to disable comments to": "ржЖржкржирж┐ ржоржирзНрждржмрзНржп ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"You are going to disable download for": "ржЖржкржирж┐ ржбрж╛ржЙржирж▓рзЛржб ржирж┐рж╖рзНржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"You are going to enable comments to": "ржЖржкржирж┐ ржоржирзНрждржмрзНржп рж╕ржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"You are going to enable download for": "ржЖржкржирж┐ ржбрж╛ржЙржирж▓рзЛржб рж╕ржХрзНрж░рж┐ржпрж╝ ржХрж░рждрзЗ ржЪрж▓рзЗржЫрзЗржи",
|
||||||
|
"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": "ржЯрзНржпрж╛ржЧрзЗ ржорж┐ржбрж┐ржпрж╝рж╛",
|
||||||
|
"media, are you sure?": "ржорж┐ржбрж┐ржпрж╝рж╛, ржЖржкржирж┐ ржХрж┐ ржирж┐рж╢рзНржЪрж┐ржд?",
|
||||||
|
"media.": "ржорж┐ржбрж┐ржпрж╝рж╛ред",
|
||||||
|
"or": "ржЕржержмрж╛",
|
||||||
|
"results for": "ржПрж░ ржЬржирзНржп ржлрж▓рж╛ржлрж▓",
|
||||||
|
"selected": "",
|
||||||
|
"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": "ржмржЫрж░ ржЖржЧрзЗ",
|
||||||
|
}
|
||||||
289
files/frontend_translations/da.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ Opret Playliste",
|
||||||
|
"00 - 20 min": "00 - 20 min",
|
||||||
|
"1 result for": "1 resultat for",
|
||||||
|
"20 - 40 min": "20 - 40 min",
|
||||||
|
"40 - 60 min": "40 - 60 min",
|
||||||
|
"60 - 120 min+": "60 - 120 min+",
|
||||||
|
"ABOUT": "OM",
|
||||||
|
"AUTOPLAY": "Automatisk afspilning",
|
||||||
|
"About": "Om",
|
||||||
|
"Add / Remove Co-Editors": "Tilf├╕j / Fjern Medredakt├╕rer",
|
||||||
|
"Add / Remove Co-Owners": "Tilf├╕j / Fjern Medejere",
|
||||||
|
"Add / Remove Co-Viewers": "Tilf├╕j / Fjern Medseere",
|
||||||
|
"Add / Remove Tags": "Tilf├╕j / Fjern Tags",
|
||||||
|
"Add / Remove from Categories": "Tilf├╕j / Fjern fra Kategorier",
|
||||||
|
"Add a ": "Tilf├╕j en ",
|
||||||
|
"Add to": "Tilf├╕j til",
|
||||||
|
"Add to / Remove from Category": "Tilf├╕j til / Fjern fra Kategori",
|
||||||
|
"Add to / Remove from Playlist": "Tilf├╕j til / Fjern fra Playliste",
|
||||||
|
"All": "Alle",
|
||||||
|
"All categories already added": "Alle kategorier allerede tilf├╕jet",
|
||||||
|
"All tags already added": "Alle tags allerede tilf├╕jet",
|
||||||
|
"Alphabetically - A-Z": "Alfabetisk - A-├Е",
|
||||||
|
"Alphabetically - Z-A": "Alfabetisk - ├Е-A",
|
||||||
|
"Audio": "Lyd",
|
||||||
|
"Browse your files": "Gennemse dine filer",
|
||||||
|
"Bulk Actions": "Massehandlinger",
|
||||||
|
"COMMENT": "KOMMENTAR",
|
||||||
|
"Cancel": "Annuller",
|
||||||
|
"Categories": "Kategorier",
|
||||||
|
"Category": "Kategori",
|
||||||
|
"Change Language": "Skift sprog",
|
||||||
|
"Change Owner": "Skift Ejer",
|
||||||
|
"Change password": "Skift adgangskode",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Klik p├е 'Start optagelse' og v├жlg den sk├жrm eller fane, du vil optage. N├еr optagelsen er f├жrdig, skal du klikke p├е 'Stop optagelse', og optagelsen vil blive uploadet.",
|
||||||
|
"Co-Editors": "Medredakt├╕rer",
|
||||||
|
"Co-Owners": "Medejere",
|
||||||
|
"Co-Viewers": "Medseere",
|
||||||
|
"Comment": "Kommentar",
|
||||||
|
"Comments": "Kommentarer",
|
||||||
|
"Comments are disabled": "Kommentarer er sl├еet fra",
|
||||||
|
"Confirm": "Bekr├жft",
|
||||||
|
"Confirm Action": "Bekr├жft Handling",
|
||||||
|
"Contact": "Kontakt",
|
||||||
|
"Copy Media": "Kopier Medie",
|
||||||
|
"Create": "Opret",
|
||||||
|
"DELETE": "SLET",
|
||||||
|
"DELETE MEDIA": "SLET MEDIE",
|
||||||
|
"DOWNLOAD": "HENT",
|
||||||
|
"DURATION": "VARIGHED",
|
||||||
|
"Delete Media": "Slet Medie",
|
||||||
|
"Delete media": "Slet medie",
|
||||||
|
"Disable Comments": "Deaktiver Kommentarer",
|
||||||
|
"Disable Download": "Deaktiver Download",
|
||||||
|
"Drag and drop files": "Tr├жk og slip filer",
|
||||||
|
"EDIT MEDIA": "REDIGER MEDIE",
|
||||||
|
"EDIT PROFILE": "REDIGER PROFIL",
|
||||||
|
"EDIT SUBTITLE": "REDIGER UNDERTEKSTER",
|
||||||
|
"Edit media": "Rediger medie",
|
||||||
|
"Edit profile": "Rediger profil",
|
||||||
|
"Edit subtitle": "Rediger undertekster",
|
||||||
|
"Enable Comments": "Aktiver Kommentarer",
|
||||||
|
"Enable Download": "Aktiver Download",
|
||||||
|
"Enter playlist name...": "Indtast playlistenavn...",
|
||||||
|
"Failed to add categories": "Tilf├╕jelse af kategorier mislykkedes",
|
||||||
|
"Failed to add media to playlists": "Tilf├╕jelse af medie til playlister mislykkedes",
|
||||||
|
"Failed to add tags": "Tilf├╕jelse af tags mislykkedes",
|
||||||
|
"Failed to add users": "Tilf├╕jelse af brugere mislykkedes",
|
||||||
|
"Failed to change owner": "├Жndring af ejer mislykkedes",
|
||||||
|
"Failed to change owner. Please try again.": "├Жndring af ejer mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to copy media.": "Kopiering af medie mislykkedes.",
|
||||||
|
"Failed to create playlist": "Oprettelse af playliste mislykkedes",
|
||||||
|
"Failed to delete media. Please try again.": "Sletning af medie mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to disable comments.": "Deaktivering af kommentarer mislykkedes.",
|
||||||
|
"Failed to disable download.": "Deaktivering af download mislykkedes.",
|
||||||
|
"Failed to enable comments.": "Aktivering af kommentarer mislykkedes.",
|
||||||
|
"Failed to enable download.": "Aktivering af download mislykkedes.",
|
||||||
|
"Failed to fetch all categories": "Hentning af alle kategorier mislykkedes",
|
||||||
|
"Failed to fetch all tags": "Hentning af alle tags mislykkedes",
|
||||||
|
"Failed to fetch existing categories": "Hentning af eksisterende kategorier mislykkedes",
|
||||||
|
"Failed to fetch existing tags": "Hentning af eksisterende tags mislykkedes",
|
||||||
|
"Failed to fetch existing users": "Hentning af eksisterende brugere mislykkedes",
|
||||||
|
"Failed to fetch playlist membership": "Hentning af playlistemedlemskab mislykkedes",
|
||||||
|
"Failed to fetch playlists": "Hentning af playlister mislykkedes",
|
||||||
|
"Failed to load categories": "Indl├жsning af kategorier mislykkedes",
|
||||||
|
"Failed to load existing permissions": "Indl├жsning af eksisterende tilladelser mislykkedes",
|
||||||
|
"Failed to load playlists": "Indl├жsning af playlister mislykkedes",
|
||||||
|
"Failed to load tags": "Indl├жsning af tags mislykkedes",
|
||||||
|
"Failed to remove categories": "Fjernelse af kategorier mislykkedes",
|
||||||
|
"Failed to remove media from playlists": "Fjernelse af medie fra playlister mislykkedes",
|
||||||
|
"Failed to remove tags": "Fjernelse af tags mislykkedes",
|
||||||
|
"Failed to remove users": "Fjernelse af brugere mislykkedes",
|
||||||
|
"Failed to search users": "S├╕gning af brugere mislykkedes",
|
||||||
|
"Failed to set publish state": "Indstilling af publiceringsstatus mislykkedes",
|
||||||
|
"Failed to set publish state. Please try again.": "Indstilling af publiceringsstatus mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to update categories. Please try again.": "Opdatering af kategorier mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to update permissions. Please try again.": "Opdatering af tilladelser mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to update playlists. Please try again.": "Opdatering af playlister mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Failed to update tags. Please try again.": "Opdatering af tags mislykkedes. Pr├╕v venligst igen.",
|
||||||
|
"Featured": "Fremh├жvede",
|
||||||
|
"Filter existing users...": "Filtrer eksisterende brugere...",
|
||||||
|
"Filter playlists...": "Filtrer playlister...",
|
||||||
|
"Filters": "Filtre",
|
||||||
|
"Go": "V├жlg",
|
||||||
|
"History": "Historik",
|
||||||
|
"Home": "Hjem",
|
||||||
|
"Image": "Billede",
|
||||||
|
"Language": "Sprog",
|
||||||
|
"Latest": "Nyeste",
|
||||||
|
"Like count": "Antal likes",
|
||||||
|
"Liked media": "Medier du har liket",
|
||||||
|
"Likes - Least": "Likes - F├жrrest",
|
||||||
|
"Likes - Most": "Likes - Flest",
|
||||||
|
"Loading categories...": "Indl├жser kategorier...",
|
||||||
|
"Loading existing users...": "Indl├жser eksisterende brugere...",
|
||||||
|
"Loading playlists...": "Indl├жser playlister...",
|
||||||
|
"Loading tags...": "Indl├жser tags...",
|
||||||
|
"MEDIA TYPE": "MEDIETYPE",
|
||||||
|
"Manage": "Administrer",
|
||||||
|
"Manage Playlists": "Administrer Playlister",
|
||||||
|
"Manage comments": "Administrer kommentarer",
|
||||||
|
"Manage media": "Administrer medier",
|
||||||
|
"Manage users": "Administrer brugere",
|
||||||
|
"Media": "Medier",
|
||||||
|
"Media I own": "Medier jeg ejer",
|
||||||
|
"Media was edited": "Mediet er blevet redigeret",
|
||||||
|
"Members": "Medlemmer",
|
||||||
|
"My media": "Mine medier",
|
||||||
|
"My playlists": "Mine playlister",
|
||||||
|
"No": "Nej",
|
||||||
|
"No categories": "Ingen kategorier",
|
||||||
|
"No comment yet": "Ingen kommentar endnu",
|
||||||
|
"No comments yet": "Ingen komentarer endnu",
|
||||||
|
"No existing": "Ingen eksisterende",
|
||||||
|
"No playlists available": "Ingen playlister tilg├жngelige",
|
||||||
|
"No playlists selected": "Ingen playlister valgt",
|
||||||
|
"No results for": "Ingen resultater for",
|
||||||
|
"No tags": "Ingen tags",
|
||||||
|
"No users to add": "Ingen brugere at tilf├╕je",
|
||||||
|
"PLAYLISTS": "PLAYLISTER",
|
||||||
|
"PUBLISH STATE": "PUBLICERINGSSTATUS",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "Playlister",
|
||||||
|
"Plays - Least": "Afspilninger - F├жrrest",
|
||||||
|
"Plays - Most": "Afspilninger - Flest",
|
||||||
|
"Please select a publish state": "V├жlg venligst en publiceringsstatus",
|
||||||
|
"Please select a user": "V├жlg venligst en bruger",
|
||||||
|
"Powered by": "Drevet af",
|
||||||
|
"Private": "Privat",
|
||||||
|
"Proceed": "Forts├жt",
|
||||||
|
"Processing...": "Behandler...",
|
||||||
|
"Public": "Offentlig",
|
||||||
|
"Publish": "Udgiv",
|
||||||
|
"Publish State": "Publiceringsstatus",
|
||||||
|
"Published": "Publiceret",
|
||||||
|
"Published on": "Udgivet p├е",
|
||||||
|
"Recent uploads": "Nylige uploads",
|
||||||
|
"Recommended": "Anbefalet",
|
||||||
|
"Record Screen": "Optag sk├жrm",
|
||||||
|
"Register": "Registrer",
|
||||||
|
"Remove category": "Fjern kategori",
|
||||||
|
"Remove from list": "Fjern fra liste",
|
||||||
|
"Remove tag": "Fjern tag",
|
||||||
|
"Remove user": "Fjern bruger",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "GEM",
|
||||||
|
"SEARCH": "S├ШG",
|
||||||
|
"SHARE": "DEL",
|
||||||
|
"SHOW MORE": "VIS MERE",
|
||||||
|
"SORT BY": "SORTER EFTER",
|
||||||
|
"SUBMIT": "INDSEND",
|
||||||
|
"Search": "S├╕g",
|
||||||
|
"Search for user...": "S├╕g efter bruger...",
|
||||||
|
"Search users to add...": "S├╕g efter brugere at tilf├╕je...",
|
||||||
|
"Select": "V├жlg",
|
||||||
|
"Select Owner": "V├жlg Ejer",
|
||||||
|
"Select all": "V├жlg alle",
|
||||||
|
"Select all media": "V├жlg alle medier",
|
||||||
|
"Select publish state:": "V├жlg publiceringsstatus:",
|
||||||
|
"Selected": "Valgt",
|
||||||
|
"Shared by me": "Delt af mig",
|
||||||
|
"Shared with me": "Delt med mig",
|
||||||
|
"Sign in": "Log ind",
|
||||||
|
"Sign out": "Log ud",
|
||||||
|
"Sort By": "Sorter efter",
|
||||||
|
"Start Recording": "Start optagelse",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "Begynd at uploade medier og dele dit arbejde. Medier, du uploader, vil blive vist her.",
|
||||||
|
"Stop Recording": "Stop optagelse",
|
||||||
|
"Submit": "Indsend",
|
||||||
|
"Subtitle was added": "Undertekster tilf├╕jet",
|
||||||
|
"Subtitles": "Undertekster",
|
||||||
|
"Successfully Copied": "Kopieret med succes",
|
||||||
|
"Successfully Disabled Download": "Download deaktiveret med succes",
|
||||||
|
"Successfully Disabled comments": "Kommentarer deaktiveret med succes",
|
||||||
|
"Successfully Enabled Download": "Download aktiveret med succes",
|
||||||
|
"Successfully Enabled comments": "Kommentarer aktiveret med succes",
|
||||||
|
"Successfully changed owner": "Ejer ├жndret med succes",
|
||||||
|
"Successfully deleted": "Slettet med succes",
|
||||||
|
"Successfully updated": "Opdateret med succes",
|
||||||
|
"Successfully updated categories": "Kategorier opdateret med succes",
|
||||||
|
"Successfully updated playlist membership": "Playlistemedlemskab opdateret med succes",
|
||||||
|
"Successfully updated publish state": "Publiceringsstatus opdateret med succes",
|
||||||
|
"Successfully updated tags": "Tags opdateret med succes",
|
||||||
|
"TAGS": "TAGS",
|
||||||
|
"Tag": "Tag",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Terms": "Vilk├еr",
|
||||||
|
"The intersection of categories in the selected media is shown": "F├жlles kategorier i de valgte medier vises",
|
||||||
|
"The intersection of playlists in the selected media is shown": "F├жlles playlister i de valgte medier vises",
|
||||||
|
"The intersection of tags in the selected media is shown": "F├жlles tags i de valgte medier vises",
|
||||||
|
"The intersection of users in the selected media is shown": "F├жlles brugere i de valgte medier vises",
|
||||||
|
"The media was deleted successfully.": "Mediet blev slettet med succes.",
|
||||||
|
"This month": "Denne m├еned",
|
||||||
|
"This week": "Denne uge",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "Dette virker i Chrome, Safari og Edge browsere.",
|
||||||
|
"This year": "Dette ├еr",
|
||||||
|
"To add": "At tilf├╕je",
|
||||||
|
"Today": "I dag",
|
||||||
|
"Trim": "Besk├жr",
|
||||||
|
"UPLOAD": "UPLOAD",
|
||||||
|
"UPLOAD DATE": "UPLOADDATO",
|
||||||
|
"UPLOAD MEDIA": "UPLOAD MEDIE",
|
||||||
|
"Undo removal": "Fortryd fjernelse",
|
||||||
|
"Unlisted": "Ikke listet",
|
||||||
|
"Up Next": "N├жste",
|
||||||
|
"Up next": "N├жste",
|
||||||
|
"Upload": "Upload",
|
||||||
|
"Upload date (newest)": "Uploaddato (nyeste)",
|
||||||
|
"Upload date (oldest)": "Uploaddato (├жldste)",
|
||||||
|
"Upload date - Newest": "Uploaddato - Nyeste",
|
||||||
|
"Upload date - Oldest": "Uploaddato - ├Жldste",
|
||||||
|
"Upload media": "Upload medie",
|
||||||
|
"Uploads": "Uploads",
|
||||||
|
"Users": "Brugere",
|
||||||
|
"VIEW ALL": "SE ALLE",
|
||||||
|
"Video": "Video",
|
||||||
|
"View all": "Se alle",
|
||||||
|
"View count": "Antal visninger",
|
||||||
|
"View media": "Se medie",
|
||||||
|
"Welcome": "Velkommen",
|
||||||
|
"You are going to copy": "Du er ved at kopiere",
|
||||||
|
"You are going to delete": "Du er ved at slette",
|
||||||
|
"You are going to disable comments to": "Du er ved at deaktivere kommentarer til",
|
||||||
|
"You are going to disable download for": "Du er ved at deaktivere download for",
|
||||||
|
"You are going to enable comments to": "Du er ved at aktivere kommentarer til",
|
||||||
|
"You are going to enable download for": "Du er ved at aktivere download for",
|
||||||
|
"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": "er et moderne, fuldt udstyret open source video og medie CMS. Det er udviklet til at im├╕dekomme behovene for moderne webplatforme til visning og deling af medier.",
|
||||||
|
"media in category": "medier i kategori",
|
||||||
|
"media in tag": "medier i tag",
|
||||||
|
"media, are you sure?": "medie, er du sikker?",
|
||||||
|
"media.": "medie.",
|
||||||
|
"or": "eller",
|
||||||
|
"results for": "resultater for",
|
||||||
|
"selected": "valgt",
|
||||||
|
"view": "visning",
|
||||||
|
"views": "visninger",
|
||||||
|
"yet": "endnu",
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement_strings = {
|
||||||
|
"Apr": "Apr",
|
||||||
|
"Aug": "Aug",
|
||||||
|
"Dec": "Dec",
|
||||||
|
"Feb": "Feb",
|
||||||
|
"Jan": "Jan",
|
||||||
|
"Jul": "Jul",
|
||||||
|
"Jun": "Jun",
|
||||||
|
"Mar": "Mar",
|
||||||
|
"May": "Maj",
|
||||||
|
"Nov": "Nov",
|
||||||
|
"Oct": "Okt",
|
||||||
|
"Sep": "Sep",
|
||||||
|
"day ago": "dag siden",
|
||||||
|
"days ago": "dage siden",
|
||||||
|
"hour ago": "time siden",
|
||||||
|
"hours ago": "timer siden",
|
||||||
|
"just now": "lige nu",
|
||||||
|
"minute ago": "minut siden",
|
||||||
|
"minutes ago": "minutter siden",
|
||||||
|
"month ago": "m├еned siden",
|
||||||
|
"months ago": "m├еneder siden",
|
||||||
|
"second ago": "sekund siden",
|
||||||
|
"seconds ago": "sekunder siden",
|
||||||
|
"week ago": "uge siden",
|
||||||
|
"weeks ago": "uger siden",
|
||||||
|
"year ago": "├еr siden",
|
||||||
|
"years ago": "├еr siden",
|
||||||
|
}
|
||||||
289
files/frontend_translations/de.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ Playlist erstellen",
|
||||||
|
"00 - 20 min": "00 - 20 Min",
|
||||||
|
"1 result for": "1 Ergebnis f├╝r",
|
||||||
|
"20 - 40 min": "20 - 40 Min",
|
||||||
|
"40 - 60 min": "40 - 60 Min",
|
||||||
|
"60 - 120 min+": "60 - 120 Min+",
|
||||||
|
"ABOUT": "├Ьber",
|
||||||
|
"AUTOPLAY": "Automatische Wiedergabe",
|
||||||
|
"About": "├Ьber",
|
||||||
|
"Add / Remove Co-Editors": "Co-Editoren hinzuf├╝gen/entfernen",
|
||||||
|
"Add / Remove Co-Owners": "Co-Eigent├╝mer hinzuf├╝gen/entfernen",
|
||||||
|
"Add / Remove Co-Viewers": "Co-Zuschauer hinzuf├╝gen/entfernen",
|
||||||
|
"Add / Remove Tags": "Tags hinzuf├╝gen/entfernen",
|
||||||
|
"Add / Remove from Categories": "Zu/aus Kategorien hinzuf├╝gen/entfernen",
|
||||||
|
"Add a ": "Hinzuf├╝gen eines ",
|
||||||
|
"Add to": "Hinzuf├╝gen zu",
|
||||||
|
"Add to / Remove from Category": "Zu/aus Kategorie hinzuf├╝gen/entfernen",
|
||||||
|
"Add to / Remove from Playlist": "Zu/aus Playlist hinzuf├╝gen/entfernen",
|
||||||
|
"All": "Alle",
|
||||||
|
"All categories already added": "Alle Kategorien bereits hinzugef├╝gt",
|
||||||
|
"All tags already added": "Alle Tags bereits hinzugef├╝gt",
|
||||||
|
"Alphabetically - A-Z": "Alphabetisch - A-Z",
|
||||||
|
"Alphabetically - Z-A": "Alphabetisch - Z-A",
|
||||||
|
"Audio": "Audio",
|
||||||
|
"Browse your files": "Durchsuchen Sie Ihre Dateien",
|
||||||
|
"Bulk Actions": "Massenaktionen",
|
||||||
|
"COMMENT": "KOMMENTAR",
|
||||||
|
"Cancel": "Abbrechen",
|
||||||
|
"Categories": "Kategorien",
|
||||||
|
"Category": "Kategorie",
|
||||||
|
"Change Language": "Sprache ├дndern",
|
||||||
|
"Change Owner": "Eigent├╝mer ├дndern",
|
||||||
|
"Change password": "Passwort ├дndern",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Klicken Sie auf 'Aufnahme starten' und w├дhlen Sie den Bildschirm oder Tab aus, den Sie aufnehmen m├╢chten. Sobald die Aufnahme beendet ist, klicken Sie auf 'Aufnahme beenden', und die Aufnahme wird hochgeladen.",
|
||||||
|
"Co-Editors": "Co-Editoren",
|
||||||
|
"Co-Owners": "Co-Eigent├╝mer",
|
||||||
|
"Co-Viewers": "Co-Zuschauer",
|
||||||
|
"Comment": "Kommentar",
|
||||||
|
"Comments": "Kommentare",
|
||||||
|
"Comments are disabled": "Kommentare sind deaktiviert",
|
||||||
|
"Confirm": "Best├дtigen",
|
||||||
|
"Confirm Action": "Aktion best├дtigen",
|
||||||
|
"Contact": "Kontakt",
|
||||||
|
"Copy Media": "Medien kopieren",
|
||||||
|
"Create": "Erstellen",
|
||||||
|
"DELETE": "L├ЦSCHEN",
|
||||||
|
"DELETE MEDIA": "MEDIEN L├ЦSCHEN",
|
||||||
|
"DOWNLOAD": "HERUNTERLADEN",
|
||||||
|
"DURATION": "DAUER",
|
||||||
|
"Delete Media": "Medien l├╢schen",
|
||||||
|
"Delete media": "Medien l├╢schen",
|
||||||
|
"Disable Comments": "Kommentare deaktivieren",
|
||||||
|
"Disable Download": "Download deaktivieren",
|
||||||
|
"Drag and drop files": "Dateien per Drag & Drop verschieben",
|
||||||
|
"EDIT MEDIA": "MEDIEN BEARBEITEN",
|
||||||
|
"EDIT PROFILE": "PROFIL BEARBEITEN",
|
||||||
|
"EDIT SUBTITLE": "UNTERTITEL BEARBEITEN",
|
||||||
|
"Edit media": "Medien bearbeiten",
|
||||||
|
"Edit profile": "Profil bearbeiten",
|
||||||
|
"Edit subtitle": "Untertitel bearbeiten",
|
||||||
|
"Enable Comments": "Kommentare aktivieren",
|
||||||
|
"Enable Download": "Download aktivieren",
|
||||||
|
"Enter playlist name...": "Playlist-Namen eingeben...",
|
||||||
|
"Failed to add categories": "Fehler beim Hinzuf├╝gen der Kategorien",
|
||||||
|
"Failed to add media to playlists": "Fehler beim Hinzuf├╝gen der Medien zu Playlists",
|
||||||
|
"Failed to add tags": "Fehler beim Hinzuf├╝gen der Tags",
|
||||||
|
"Failed to add users": "Fehler beim Hinzuf├╝gen der Benutzer",
|
||||||
|
"Failed to change owner": "Fehler beim ├Дndern des Eigent├╝mers",
|
||||||
|
"Failed to change owner. Please try again.": "Fehler beim ├Дndern des Eigent├╝mers. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to copy media.": "Fehler beim Kopieren der Medien.",
|
||||||
|
"Failed to create playlist": "Fehler beim Erstellen der Playlist",
|
||||||
|
"Failed to delete media. Please try again.": "Fehler beim L├╢schen der Medien. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to disable comments.": "Fehler beim Deaktivieren der Kommentare.",
|
||||||
|
"Failed to disable download.": "Fehler beim Deaktivieren des Downloads.",
|
||||||
|
"Failed to enable comments.": "Fehler beim Aktivieren der Kommentare.",
|
||||||
|
"Failed to enable download.": "Fehler beim Aktivieren des Downloads.",
|
||||||
|
"Failed to fetch all categories": "Fehler beim Laden aller Kategorien",
|
||||||
|
"Failed to fetch all tags": "Fehler beim Laden aller Tags",
|
||||||
|
"Failed to fetch existing categories": "Fehler beim Laden vorhandener Kategorien",
|
||||||
|
"Failed to fetch existing tags": "Fehler beim Laden vorhandener Tags",
|
||||||
|
"Failed to fetch existing users": "Fehler beim Laden vorhandener Benutzer",
|
||||||
|
"Failed to fetch playlist membership": "Fehler beim Laden der Playlist-Mitgliedschaft",
|
||||||
|
"Failed to fetch playlists": "Fehler beim Laden der Playlists",
|
||||||
|
"Failed to load categories": "Fehler beim Laden der Kategorien",
|
||||||
|
"Failed to load existing permissions": "Fehler beim Laden vorhandener Berechtigungen",
|
||||||
|
"Failed to load playlists": "Fehler beim Laden der Playlists",
|
||||||
|
"Failed to load tags": "Fehler beim Laden der Tags",
|
||||||
|
"Failed to remove categories": "Fehler beim Entfernen der Kategorien",
|
||||||
|
"Failed to remove media from playlists": "Fehler beim Entfernen der Medien aus Playlists",
|
||||||
|
"Failed to remove tags": "Fehler beim Entfernen der Tags",
|
||||||
|
"Failed to remove users": "Fehler beim Entfernen der Benutzer",
|
||||||
|
"Failed to search users": "Fehler bei der Benutzersuche",
|
||||||
|
"Failed to set publish state": "Fehler beim Festlegen des Ver├╢ffentlichungsstatus",
|
||||||
|
"Failed to set publish state. Please try again.": "Fehler beim Festlegen des Ver├╢ffentlichungsstatus. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to update categories. Please try again.": "Fehler beim Aktualisieren der Kategorien. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to update permissions. Please try again.": "Fehler beim Aktualisieren der Berechtigungen. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to update playlists. Please try again.": "Fehler beim Aktualisieren der Playlists. Bitte versuchen Sie es erneut.",
|
||||||
|
"Failed to update tags. Please try again.": "Fehler beim Aktualisieren der Tags. Bitte versuchen Sie es erneut.",
|
||||||
|
"Featured": "Empfohlen",
|
||||||
|
"Filter existing users...": "Vorhandene Benutzer filtern...",
|
||||||
|
"Filter playlists...": "Playlists filtern...",
|
||||||
|
"Filters": "Filter",
|
||||||
|
"Go": "Los",
|
||||||
|
"History": "Verlauf",
|
||||||
|
"Home": "Startseite",
|
||||||
|
"Image": "Bild",
|
||||||
|
"Language": "Sprache",
|
||||||
|
"Latest": "Neueste",
|
||||||
|
"Like count": "Anzahl der Likes",
|
||||||
|
"Liked media": "Beliebte Medien",
|
||||||
|
"Likes - Least": "Likes - Wenigste",
|
||||||
|
"Likes - Most": "Likes - Meiste",
|
||||||
|
"Loading categories...": "Kategorien werden geladen...",
|
||||||
|
"Loading existing users...": "Vorhandene Benutzer werden geladen...",
|
||||||
|
"Loading playlists...": "Playlists werden geladen...",
|
||||||
|
"Loading tags...": "Tags werden geladen...",
|
||||||
|
"MEDIA TYPE": "MEDIENTYP",
|
||||||
|
"Manage": "Verwalten",
|
||||||
|
"Manage Playlists": "Playlists verwalten",
|
||||||
|
"Manage comments": "Kommentare verwalten",
|
||||||
|
"Manage media": "Medien verwalten",
|
||||||
|
"Manage users": "Benutzer verwalten",
|
||||||
|
"Media": "Medien",
|
||||||
|
"Media I own": "Medien, die mir geh├╢ren",
|
||||||
|
"Media was edited": "Medien wurden bearbeitet",
|
||||||
|
"Members": "Mitglieder",
|
||||||
|
"My media": "Meine Medien",
|
||||||
|
"My playlists": "Meine Playlists",
|
||||||
|
"No": "Nein",
|
||||||
|
"No categories": "Keine Kategorien",
|
||||||
|
"No comment yet": "Noch kein Kommentar",
|
||||||
|
"No comments yet": "Noch keine Kommentare",
|
||||||
|
"No existing": "Keine vorhanden",
|
||||||
|
"No playlists available": "Keine Playlists verf├╝gbar",
|
||||||
|
"No playlists selected": "Keine Playlists ausgew├дhlt",
|
||||||
|
"No results for": "Keine Ergebnisse f├╝r",
|
||||||
|
"No tags": "Keine Tags",
|
||||||
|
"No users to add": "Keine Benutzer hinzuzuf├╝gen",
|
||||||
|
"PLAYLISTS": "PLAYLISTS",
|
||||||
|
"PUBLISH STATE": "VER├ЦFFENTLICHUNGSSTATUS",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Plays - Least": "Wiedergaben - Wenigste",
|
||||||
|
"Plays - Most": "Wiedergaben - Meiste",
|
||||||
|
"Please select a publish state": "Bitte w├дhlen Sie einen Ver├╢ffentlichungsstatus aus",
|
||||||
|
"Please select a user": "Bitte w├дhlen Sie einen Benutzer aus",
|
||||||
|
"Powered by": "Bereitgestellt von",
|
||||||
|
"Private": "Privat",
|
||||||
|
"Proceed": "Fortfahren",
|
||||||
|
"Processing...": "Wird verarbeitet...",
|
||||||
|
"Public": "├Цffentlich",
|
||||||
|
"Publish": "Ver├╢ffentlichen",
|
||||||
|
"Publish State": "Ver├╢ffentlichungsstatus",
|
||||||
|
"Published": "Ver├╢ffentlicht",
|
||||||
|
"Published on": "Ver├╢ffentlicht am",
|
||||||
|
"Recent uploads": "Neue Uploads",
|
||||||
|
"Recommended": "Empfohlen",
|
||||||
|
"Record Screen": "Bildschirm aufnehmen",
|
||||||
|
"Register": "Registrieren",
|
||||||
|
"Remove category": "Kategorie entfernen",
|
||||||
|
"Remove from list": "Aus Liste entfernen",
|
||||||
|
"Remove tag": "Tag entfernen",
|
||||||
|
"Remove user": "Benutzer entfernen",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "SPEICHERN",
|
||||||
|
"SEARCH": "SUCHE",
|
||||||
|
"SHARE": "TEILEN",
|
||||||
|
"SHOW MORE": "MEHR ANZEIGEN",
|
||||||
|
"SORT BY": "SORTIEREN NACH",
|
||||||
|
"SUBMIT": "ABSENDEN",
|
||||||
|
"Search": "Suche",
|
||||||
|
"Search for user...": "Nach Benutzer suchen...",
|
||||||
|
"Search users to add...": "Nach Benutzern zum Hinzuf├╝gen suchen...",
|
||||||
|
"Select": "Ausw├дhlen",
|
||||||
|
"Select Owner": "Eigent├╝mer ausw├дhlen",
|
||||||
|
"Select all": "Alle ausw├дhlen",
|
||||||
|
"Select all media": "Alle Medien ausw├дhlen",
|
||||||
|
"Select publish state:": "Ver├╢ffentlichungsstatus ausw├дhlen:",
|
||||||
|
"Selected": "Ausgew├дhlt",
|
||||||
|
"Shared by me": "Von mir geteilt",
|
||||||
|
"Shared with me": "Mit mir geteilt",
|
||||||
|
"Sign in": "Anmelden",
|
||||||
|
"Sign out": "Abmelden",
|
||||||
|
"Sort By": "Sortieren nach",
|
||||||
|
"Start Recording": "Aufnahme starten",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "Beginnen Sie mit dem Hochladen von Medien und dem Teilen Ihrer Arbeit. Hochgeladene Medien werden hier angezeigt.",
|
||||||
|
"Stop Recording": "Aufnahme stoppen",
|
||||||
|
"Submit": "Absenden",
|
||||||
|
"Subtitle was added": "Untertitel wurde hinzugef├╝gt",
|
||||||
|
"Subtitles": "Untertitel",
|
||||||
|
"Successfully Copied": "Erfolgreich kopiert",
|
||||||
|
"Successfully Disabled Download": "Download erfolgreich deaktiviert",
|
||||||
|
"Successfully Disabled comments": "Kommentare erfolgreich deaktiviert",
|
||||||
|
"Successfully Enabled Download": "Download erfolgreich aktiviert",
|
||||||
|
"Successfully Enabled comments": "Kommentare erfolgreich aktiviert",
|
||||||
|
"Successfully changed owner": "Eigent├╝mer erfolgreich ge├дndert",
|
||||||
|
"Successfully deleted": "Erfolgreich gel├╢scht",
|
||||||
|
"Successfully updated": "Erfolgreich aktualisiert",
|
||||||
|
"Successfully updated categories": "Kategorien erfolgreich aktualisiert",
|
||||||
|
"Successfully updated playlist membership": "Playlist-Mitgliedschaft erfolgreich aktualisiert",
|
||||||
|
"Successfully updated publish state": "Ver├╢ffentlichungsstatus erfolgreich aktualisiert",
|
||||||
|
"Successfully updated tags": "Tags erfolgreich aktualisiert",
|
||||||
|
"TAGS": "TAGS",
|
||||||
|
"Tag": "Tag",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Terms": "Bedingungen",
|
||||||
|
"The intersection of categories in the selected media is shown": "Die Schnittmenge der Kategorien der ausgew├дhlten Medien wird angezeigt",
|
||||||
|
"The intersection of playlists in the selected media is shown": "Die Schnittmenge der Playlists der ausgew├дhlten Medien wird angezeigt",
|
||||||
|
"The intersection of tags in the selected media is shown": "Die Schnittmenge der Tags der ausgew├дhlten Medien wird angezeigt",
|
||||||
|
"The intersection of users in the selected media is shown": "Die Schnittmenge der Benutzer der ausgew├дhlten Medien wird angezeigt",
|
||||||
|
"The media was deleted successfully.": "Die Medien wurden erfolgreich gel├╢scht.",
|
||||||
|
"This month": "Dieser Monat",
|
||||||
|
"This week": "Diese Woche",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "Dies funktioniert in den Browsern Chrome, Safari und Edge.",
|
||||||
|
"This year": "Dieses Jahr",
|
||||||
|
"To add": "Hinzuzuf├╝gen",
|
||||||
|
"Today": "Heute",
|
||||||
|
"Trim": "Trimmen",
|
||||||
|
"UPLOAD": "HOCHLADEN",
|
||||||
|
"UPLOAD DATE": "UPLOAD-DATUM",
|
||||||
|
"UPLOAD MEDIA": "MEDIEN HOCHLADEN",
|
||||||
|
"Undo removal": "Entfernen r├╝ckg├дngig machen",
|
||||||
|
"Unlisted": "Nicht gelistet",
|
||||||
|
"Up Next": "Als n├дchstes",
|
||||||
|
"Up next": "Als n├дchstes",
|
||||||
|
"Upload": "Hochladen",
|
||||||
|
"Upload date (newest)": "Upload-Datum (neueste)",
|
||||||
|
"Upload date (oldest)": "Upload-Datum (├дlteste)",
|
||||||
|
"Upload date - Newest": "Upload-Datum - Neueste",
|
||||||
|
"Upload date - Oldest": "Upload-Datum - ├Дlteste",
|
||||||
|
"Upload media": "Medien hochladen",
|
||||||
|
"Uploads": "Uploads",
|
||||||
|
"Users": "Benutzer",
|
||||||
|
"VIEW ALL": "ALLE ANZEIGEN",
|
||||||
|
"Video": "Video",
|
||||||
|
"View all": "Alle anzeigen",
|
||||||
|
"View count": "Anzahl der Aufrufe",
|
||||||
|
"View media": "Medien anzeigen",
|
||||||
|
"Welcome": "Willkommen",
|
||||||
|
"You are going to copy": "Sie werden kopieren",
|
||||||
|
"You are going to delete": "Sie werden l├╢schen",
|
||||||
|
"You are going to disable comments to": "Sie werden Kommentare deaktivieren f├╝r",
|
||||||
|
"You are going to disable download for": "Sie werden Download deaktivieren f├╝r",
|
||||||
|
"You are going to enable comments to": "Sie werden Kommentare aktivieren f├╝r",
|
||||||
|
"You are going to enable download for": "Sie werden Download aktivieren f├╝r",
|
||||||
|
"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",
|
||||||
|
"media, are you sure?": "Medien, sind Sie sicher?",
|
||||||
|
"media.": "Medien.",
|
||||||
|
"or": "oder",
|
||||||
|
"results for": "Ergebnisse f├╝r",
|
||||||
|
"selected": "ausgew├дhlt",
|
||||||
|
"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",
|
||||||
|
}
|
||||||
289
files/frontend_translations/el.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ ╬Ф╬╖╬╝╬╣╬┐╧Е╧Б╬│╬п╬▒ ╬Ы╬п╧Г╧Д╬▒╧В",
|
||||||
|
"00 - 20 min": "00 - 20 ╬╗╬╡╧А╧Д╬м",
|
||||||
|
"1 result for": "1 ╬▒╧А╬┐╧Д╬н╬╗╬╡╧Г╬╝╬▒ ╬│╬╣╬▒",
|
||||||
|
"20 - 40 min": "20 - 40 ╬╗╬╡╧А╧Д╬м",
|
||||||
|
"40 - 60 min": "40 - 60 ╬╗╬╡╧А╧Д╬м",
|
||||||
|
"60 - 120 min+": "60 - 120 ╬╗╬╡╧А╧Д╬м+",
|
||||||
|
"ABOUT": "╬г╬з╬Х╬д╬Щ╬Ъ╬С",
|
||||||
|
"AUTOPLAY": "╬С╧Е╧Д╧М╬╝╬▒╧Д╬╖ ╬▒╬╜╬▒╧А╬▒╧Б╬▒╬│╧Й╬│╬о",
|
||||||
|
"About": "╬г╧З╬╡╧Д╬╣╬║╬м",
|
||||||
|
"Add / Remove Co-Editors": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬г╧Е╬╜-╬г╧Е╬╜╧Д╬▒╬║╧Д╧О╬╜",
|
||||||
|
"Add / Remove Co-Owners": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬г╧Е╬╜-╬Щ╬┤╬╣╬┐╬║╧Д╬╖╧Д╧О╬╜",
|
||||||
|
"Add / Remove Co-Viewers": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬г╧Е╬╜-╬Ш╬╡╬▒╧Д╧О╬╜",
|
||||||
|
"Add / Remove Tags": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬Х╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Add / Remove from Categories": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬▒╧А╧М ╬Ъ╬▒╧Д╬╖╬│╬┐╧Б╬п╬╡╧В",
|
||||||
|
"Add a ": "╬а╧Б╬┐╧Г╬╕╬н╧Г╧Д╬╡ ╬н╬╜╬▒ ",
|
||||||
|
"Add to": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ ╧Г╬╡",
|
||||||
|
"Add to / Remove from Category": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬▒╧А╧М ╬Ъ╬▒╧Д╬╖╬│╬┐╧Б╬п╬▒",
|
||||||
|
"Add to / Remove from Playlist": "╬а╧Б╬┐╧Г╬╕╬о╬║╬╖ / ╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬▒╧А╧М ╬Ы╬п╧Г╧Д╬▒",
|
||||||
|
"All": "╬М╬╗╬▒",
|
||||||
|
"All categories already added": "╬М╬╗╬╡╧В ╬┐╬╣ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬п╬╡╧В ╬н╧З╬┐╧Е╬╜ ╬о╬┤╬╖ ╧А╧Б╬┐╧Г╧Д╬╡╬╕╬╡╬п",
|
||||||
|
"All tags already added": "╬М╬╗╬╡╧В ╬┐╬╣ ╬╡╧Д╬╣╬║╬н╧Д╬╡╧В ╬н╧З╬┐╧Е╬╜ ╬о╬┤╬╖ ╧А╧Б╬┐╧Г╧Д╬╡╬╕╬╡╬п",
|
||||||
|
"Alphabetically - A-Z": "╬С╬╗╧Ж╬▒╬▓╬╖╧Д╬╣╬║╬м - ╬С-╬й",
|
||||||
|
"Alphabetically - Z-A": "╬С╬╗╧Ж╬▒╬▓╬╖╧Д╬╣╬║╬м - ╬й-╬С",
|
||||||
|
"Audio": "╬Й╧З╬┐╧В",
|
||||||
|
"Browse your files": "╬а╬╡╧Б╬╣╬о╬│╬╖╧Г╬╖ ╧Г╧Д╬▒ ╬▒╧Б╧З╬╡╬п╬▒ ╧Г╬▒╧В",
|
||||||
|
"Bulk Actions": "╬Ь╬▒╬╢╬╣╬║╬н╧В ╬Х╬╜╬н╧Б╬│╬╡╬╣╬╡╧В",
|
||||||
|
"COMMENT": "╬г╬з╬Я╬Ы╬Щ╬Я",
|
||||||
|
"Cancel": "╬С╬║╧Н╧Б╧Й╧Г╬╖",
|
||||||
|
"Categories": "╬Ъ╬▒╧Д╬╖╬│╬┐╧Б╬п╬╡╧В",
|
||||||
|
"Category": "╬Ъ╬▒╧Д╬╖╬│╬┐╧Б╬п╬▒",
|
||||||
|
"Change Language": "╬С╬╗╬╗╬▒╬│╬о ╬У╬╗╧О╧Г╧Г╬▒╧В",
|
||||||
|
"Change Owner": "╬С╬╗╬╗╬▒╬│╬о ╬Щ╬┤╬╣╬┐╬║╧Д╬о╧Д╬╖",
|
||||||
|
"Change password": "╬С╬╗╬╗╬▒╬│╬о ╬║╧Й╬┤╬╣╬║╬┐╧Н",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "╬Ъ╬м╬╜╧Д╬╡ ╬║╬╗╬╣╬║ ╧Г╧Д╬┐ '╬И╬╜╬▒╧Б╬╛╬╖ ╬╡╬│╬│╧Б╬▒╧Ж╬о╧В' ╬║╬▒╬╣ ╬╡╧А╬╣╬╗╬н╬╛╧Д╬╡ ╧Д╬╖╬╜ ╬┐╬╕╧М╬╜╬╖ ╬о ╧Д╬╖╬╜ ╬║╬▒╧Б╧Д╬н╬╗╬▒ ╬│╬╣╬▒ ╬╡╬│╬│╧Б╬▒╧Ж╬о. ╬Ь╧М╬╗╬╣╧В ╬┐╬╗╬┐╬║╬╗╬╖╧Б╧Й╬╕╬╡╬п ╬╖ ╬╡╬│╬│╧Б╬▒╧Ж╬о, ╬║╬м╬╜╧Д╬╡ ╬║╬╗╬╣╬║ ╧Г╧Д╬┐ '╬Ф╬╣╬▒╬║╬┐╧А╬о ╬╡╬│╬│╧Б╬▒╧Ж╬о╧В' ╬║╬▒╬╣ ╬╖ ╬╡╬│╬│╧Б╬▒╧Ж╬о ╬╕╬▒ ╬╝╬╡╧Д╬▒╧Ж╬┐╧Б╧Д╧Й╬╕╬╡╬п.",
|
||||||
|
"Co-Editors": "╬г╧Е╬╜-╬г╧Е╬╜╧Д╬м╬║╧Д╬╡╧В",
|
||||||
|
"Co-Owners": "╬г╧Е╬╜-╬Щ╬┤╬╣╬┐╬║╧Д╬о╧Д╬╡╧В",
|
||||||
|
"Co-Viewers": "╬г╧Е╬╜-╬Ш╬╡╬▒╧Д╬н╧В",
|
||||||
|
"Comment": "╬г╧З╧М╬╗╬╣╬┐",
|
||||||
|
"Comments": "╬г╧З╧М╬╗╬╣╬▒",
|
||||||
|
"Comments are disabled": "╬д╬▒ ╧Г╧З╧М╬╗╬╣╬▒ ╬╡╬п╬╜╬▒╬╣ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬╖╬╝╬н╬╜╬▒",
|
||||||
|
"Confirm": "╬Х╧А╬╣╬▓╬╡╬▓╬▒╬п╧Й╧Г╬╖",
|
||||||
|
"Confirm Action": "╬Х╧А╬╣╬▓╬╡╬▓╬▒╬п╧Й╧Г╬╖ ╬Х╬╜╬н╧Б╬│╬╡╬╣╬▒╧В",
|
||||||
|
"Contact": "╬Х╧А╬╣╬║╬┐╬╣╬╜╧Й╬╜╬п╬▒",
|
||||||
|
"Copy Media": "╬С╬╜╧Д╬╣╬│╧Б╬▒╧Ж╬о ╬С╧Б╧З╬╡╬п╬┐╧Е",
|
||||||
|
"Create": "╬Ф╬╖╬╝╬╣╬┐╧Е╧Б╬│╬п╬▒",
|
||||||
|
"DELETE": "╬Ф╬Щ╬С╬У╬б╬С╬ж╬Ч",
|
||||||
|
"DELETE MEDIA": "╬Ф╬Щ╬С╬У╬б╬С╬ж╬Ч ╬С╬б╬з╬Х╬Щ╬Я╬е",
|
||||||
|
"DOWNLOAD": "╬Ъ╬С╬д╬Х╬Т╬С╬г╬Ь╬С",
|
||||||
|
"DURATION": "╬Ф╬Щ╬С╬б╬Ъ╬Х╬Щ╬С",
|
||||||
|
"Delete Media": "╬Ф╬╣╬▒╬│╧Б╬▒╧Ж╬о ╬С╧Б╧З╬╡╬п╬┐╧Е",
|
||||||
|
"Delete media": "╬Ф╬╣╬▒╬│╧Б╬▒╧Ж╬о ╬▒╧Б╧З╬╡╬п╬┐╧Е",
|
||||||
|
"Disable Comments": "╬С╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖ ╬г╧З╬┐╬╗╬п╧Й╬╜",
|
||||||
|
"Disable Download": "╬С╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖ ╬Ы╬о╧И╬╖╧В",
|
||||||
|
"Drag and drop files": "╬г╧Н╧Б╬╡╧Д╬╡ ╬║╬▒╬╣ ╬▒╧А╬┐╬╕╬н╧Г╧Д╬╡ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"EDIT MEDIA": "╬Х╬а╬Х╬Ю╬Х╬б╬У╬С╬г╬Щ╬С ╬С╬б╬з╬Х╬Щ╬Я╬е",
|
||||||
|
"EDIT PROFILE": "╬Х╬а╬Х╬Ю╬Х╬б╬У╬С╬г╬Щ╬С ╬а╬б╬Я╬ж╬Щ╬Ы",
|
||||||
|
"EDIT SUBTITLE": "╬Х╬а╬Х╬Ю╬Х╬б╬У╬С╬г╬Щ╬С ╬е╬а╬Я╬д╬Щ╬д╬Ы╬й╬Э",
|
||||||
|
"Edit media": "╬Х╧А╬╡╬╛╬╡╧Б╬│╬▒╧Г╬п╬▒ ╬▒╧Б╧З╬╡╬п╬┐╧Е",
|
||||||
|
"Edit profile": "╬Х╧А╬╡╬╛╬╡╧Б╬│╬▒╧Г╬п╬▒ ╧А╧Б╬┐╧Ж╬п╬╗",
|
||||||
|
"Edit subtitle": "╬Х╧А╬╡╬╛╬╡╧Б╬│╬▒╧Г╬п╬▒ ╧Е╧А╬┐╧Д╬п╧Д╬╗╧Й╬╜",
|
||||||
|
"Enable Comments": "╬Х╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖ ╬г╧З╬┐╬╗╬п╧Й╬╜",
|
||||||
|
"Enable Download": "╬Х╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖ ╬Ы╬о╧И╬╖╧В",
|
||||||
|
"Enter playlist name...": "╬Х╬╣╧Г╬м╬│╬╡╧Д╬╡ ╧М╬╜╬┐╬╝╬▒ ╬╗╬п╧Г╧Д╬▒╧В...",
|
||||||
|
"Failed to add categories": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖╧В ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜",
|
||||||
|
"Failed to add media to playlists": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖╧В ╬▒╧Б╧З╬╡╬п╧Й╬╜ ╧Г╬╡ ╬╗╬п╧Г╧Д╬╡╧В",
|
||||||
|
"Failed to add tags": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖╧В ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Failed to add users": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖╧В ╧З╧Б╬╖╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to change owner": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╗╬╗╬▒╬│╬о╧В ╬╣╬┤╬╣╬┐╬║╧Д╬о╧Д╬╖",
|
||||||
|
"Failed to change owner. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╗╬╗╬▒╬│╬о╧В ╬╣╬┤╬╣╬┐╬║╧Д╬о╧Д╬╖. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to copy media.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╧Д╬╣╬│╧Б╬▒╧Ж╬о╧В ╬▒╧Б╧З╬╡╬п╬┐╧Е.",
|
||||||
|
"Failed to create playlist": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬┤╬╖╬╝╬╣╬┐╧Е╧Б╬│╬п╬▒╧В ╬╗╬п╧Г╧Д╬▒╧В",
|
||||||
|
"Failed to delete media. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬┤╬╣╬▒╬│╧Б╬▒╧Ж╬о╧В ╬▒╧Б╧З╬╡╬п╬┐╧Е. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to disable comments.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖╧В ╧Г╧З╬┐╬╗╬п╧Й╬╜.",
|
||||||
|
"Failed to disable download.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖╧В ╬╗╬о╧И╬╖╧В.",
|
||||||
|
"Failed to enable comments.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖╧В ╧Г╧З╬┐╬╗╬п╧Й╬╜.",
|
||||||
|
"Failed to enable download.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬п╬╖╧Г╬╖╧В ╬╗╬о╧И╬╖╧В.",
|
||||||
|
"Failed to fetch all categories": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧М╬╗╧Й╬╜ ╧Д╧Й╬╜ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜",
|
||||||
|
"Failed to fetch all tags": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧М╬╗╧Й╬╜ ╧Д╧Й╬╜ ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Failed to fetch existing categories": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜",
|
||||||
|
"Failed to fetch existing tags": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Failed to fetch existing users": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╧З╧Б╬╖╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to fetch playlist membership": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╧Г╧Е╬╝╬╝╬╡╧Д╬┐╧З╬о╧В ╧Г╬╡ ╬╗╬п╧Г╧Д╬▒",
|
||||||
|
"Failed to fetch playlists": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬м╬║╧Д╬╖╧Г╬╖╧В ╬╗╬╣╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to load categories": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧Ж╧М╧Б╧Д╧Й╧Г╬╖╧В ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜",
|
||||||
|
"Failed to load existing permissions": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧Ж╧М╧Б╧Д╧Й╧Г╬╖╧В ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╬┤╬╣╬║╬▒╬╣╧Й╬╝╬м╧Д╧Й╬╜",
|
||||||
|
"Failed to load playlists": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧Ж╧М╧Б╧Д╧Й╧Г╬╖╧В ╬╗╬╣╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to load tags": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╧Ж╧М╧Б╧Д╧Й╧Г╬╖╧В ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Failed to remove categories": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧Ж╬▒╬п╧Б╬╡╧Г╬╖╧В ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜",
|
||||||
|
"Failed to remove media from playlists": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧Ж╬▒╬п╧Б╬╡╧Г╬╖╧В ╬▒╧Б╧З╬╡╬п╧Й╬╜ ╬▒╧А╧М ╬╗╬п╧Г╧Д╬╡╧В",
|
||||||
|
"Failed to remove tags": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧Ж╬▒╬п╧Б╬╡╧Г╬╖╧В ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜",
|
||||||
|
"Failed to remove users": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╧Ж╬▒╬п╧Б╬╡╧Г╬╖╧В ╧З╧Б╬╖╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to search users": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬▒╬╜╬▒╬╢╬о╧Д╬╖╧Г╬╖╧В ╧З╧Б╬╖╧Г╧Д╧О╬╜",
|
||||||
|
"Failed to set publish state": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬┐╧Б╬╣╧Г╬╝╬┐╧Н ╬║╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖╧В ╬┤╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В",
|
||||||
|
"Failed to set publish state. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬┐╧Б╬╣╧Г╬╝╬┐╧Н ╬║╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖╧В ╬┤╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to update categories. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╖╬╝╬н╧Б╧Й╧Г╬╖╧В ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to update permissions. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╖╬╝╬н╧Б╧Й╧Г╬╖╧В ╬┤╬╣╬║╬▒╬╣╧Й╬╝╬м╧Д╧Й╬╜. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to update playlists. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╖╬╝╬н╧Б╧Й╧Г╬╖╧В ╬╗╬╣╧Г╧Д╧О╬╜. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Failed to update tags. Please try again.": "╬С╧А╬┐╧Д╧Е╧З╬п╬▒ ╬╡╬╜╬╖╬╝╬н╧Б╧Й╧Г╬╖╧В ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜. ╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬┤╬┐╬║╬╣╬╝╬м╧Г╧Д╬╡ ╬╛╬▒╬╜╬м.",
|
||||||
|
"Featured": "╬Х╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒",
|
||||||
|
"Filter existing users...": "╬ж╬╣╬╗╧Д╧Б╬м╧Б╬╣╧Г╬╝╬▒ ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╧З╧Б╬╖╧Г╧Д╧О╬╜...",
|
||||||
|
"Filter playlists...": "╬ж╬╣╬╗╧Д╧Б╬м╧Б╬╣╧Г╬╝╬▒ ╬╗╬╣╧Г╧Д╧О╬╜...",
|
||||||
|
"Filters": "╬ж╬п╬╗╧Д╧Б╬▒",
|
||||||
|
"Go": "╬Ь╬╡╧Д╬м╬▓╬▒╧Г╬╖",
|
||||||
|
"History": "╬Щ╧Г╧Д╬┐╧Б╬╣╬║╧М",
|
||||||
|
"Home": "╬С╧Б╧З╬╣╬║╬о",
|
||||||
|
"Image": "╬Х╬╣╬║╧М╬╜╬▒",
|
||||||
|
"Language": "╬У╬╗╧О╧Г╧Г╬▒",
|
||||||
|
"Latest": "╬а╧Б╧М╧Г╧Ж╬▒╧Д╬▒",
|
||||||
|
"Like count": "╬С╧Б╬╣╬╕╬╝╧М╧В likes",
|
||||||
|
"Liked media": "╬С╬│╬▒╧А╬╖╬╝╬н╬╜╬▒ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"Likes - Least": "Likes - ╬Ы╬╣╬│╧М╧Д╬╡╧Б╬▒",
|
||||||
|
"Likes - Most": "Likes - ╬а╬╡╧Б╬╣╧Г╧Г╧М╧Д╬╡╧Б╬▒",
|
||||||
|
"Loading categories...": "╬ж╧М╧Б╧Д╧Й╧Г╬╖ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜...",
|
||||||
|
"Loading existing users...": "╬ж╧М╧Б╧Д╧Й╧Г╬╖ ╧Е╧А╬▒╧Б╧З╧М╬╜╧Д╧Й╬╜ ╧З╧Б╬╖╧Г╧Д╧О╬╜...",
|
||||||
|
"Loading playlists...": "╬ж╧М╧Б╧Д╧Й╧Г╬╖ ╬╗╬╣╧Г╧Д╧О╬╜...",
|
||||||
|
"Loading tags...": "╬ж╧М╧Б╧Д╧Й╧Г╬╖ ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜...",
|
||||||
|
"MEDIA TYPE": "╬д╬е╬а╬Я╬г ╬С╬б╬з╬Х╬Щ╬Я╬е",
|
||||||
|
"Manage": "╬Ф╬╣╬▒╧З╬╡╬п╧Б╬╣╧Г╬╖",
|
||||||
|
"Manage Playlists": "╬Ф╬╣╬▒╧З╬╡╬п╧Б╬╣╧Г╬╖ ╬Ы╬╣╧Г╧Д╧О╬╜",
|
||||||
|
"Manage comments": "╬Ф╬╣╬▒╧З╬╡╬п╧Б╬╣╧Г╬╖ ╧Г╧З╬┐╬╗╬п╧Й╬╜",
|
||||||
|
"Manage media": "╬Ф╬╣╬▒╧З╬╡╬п╧Б╬╣╧Г╬╖ ╬▒╧Б╧З╬╡╬п╧Й╬╜",
|
||||||
|
"Manage users": "╬Ф╬╣╬▒╧З╬╡╬п╧Б╬╣╧Г╬╖ ╧З╧Б╬╖╧Г╧Д╧О╬╜",
|
||||||
|
"Media": "╬С╧Б╧З╬╡╬п╬▒",
|
||||||
|
"Media I own": "╬Ф╬╣╬║╬м ╬╝╬┐╧Е ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"Media was edited": "╬д╬┐ ╬▒╧Б╧З╬╡╬п╬┐ ╬╡╧А╬╡╬╛╬╡╧Б╬│╬м╧Г╧Д╬╖╬║╬╡",
|
||||||
|
"Members": "╬Ь╬н╬╗╬╖",
|
||||||
|
"My media": "╬д╬▒ ╬▒╧Б╧З╬╡╬п╬▒ ╬╝╬┐╧Е",
|
||||||
|
"My playlists": "╬Я╬╣ ╬╗╬п╧Г╧Д╬╡╧В ╬╝╬┐╧Е",
|
||||||
|
"No": "╬М╧З╬╣",
|
||||||
|
"No categories": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬п╬╡╧В",
|
||||||
|
"No comment yet": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬╡╬╣ ╬▒╬║╧М╬╝╬▒ ╧Г╧З╧М╬╗╬╣╬┐",
|
||||||
|
"No comments yet": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╬▒╬║╧М╬╝╬▒ ╧Г╧З╧М╬╗╬╣╬▒",
|
||||||
|
"No existing": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╧Е╧А╬м╧Б╧З╬┐╬╜╧Д╬▒",
|
||||||
|
"No playlists available": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╬┤╬╣╬▒╬╕╬н╧Г╬╣╬╝╬╡╧В ╬╗╬п╧Г╧Д╬╡╧В",
|
||||||
|
"No playlists selected": "╬Ф╬╡╬╜ ╬н╧З╬┐╧Е╬╜ ╬╡╧А╬╣╬╗╬╡╬│╬╡╬п ╬╗╬п╧Г╧Д╬╡╧В",
|
||||||
|
"No results for": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╬▒╧А╬┐╧Д╬╡╬╗╬н╧Г╬╝╬▒╧Д╬▒ ╬│╬╣╬▒",
|
||||||
|
"No tags": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╬╡╧Д╬╣╬║╬н╧Д╬╡╧В",
|
||||||
|
"No users to add": "╬Ф╬╡╬╜ ╧Е╧А╬м╧Б╧З╬┐╧Е╬╜ ╧З╧Б╬о╧Г╧Д╬╡╧В ╬│╬╣╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖",
|
||||||
|
"PLAYLISTS": "╬Ы╬Щ╬г╬д╬Х╬г",
|
||||||
|
"PUBLISH STATE": "╬Ъ╬С╬д╬С╬г╬д╬С╬г╬Ч ╬Ф╬Ч╬Ь╬Я╬г╬Щ╬Х╬е╬г╬Ч╬г",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "╬Ы╬п╧Г╧Д╬╡╧В",
|
||||||
|
"Plays - Least": "╬С╬╜╬▒╧А╬▒╧Б╬▒╬│╧Й╬│╬н╧В - ╬Ы╬╣╬│╧М╧Д╬╡╧Б╬╡╧В",
|
||||||
|
"Plays - Most": "╬С╬╜╬▒╧А╬▒╧Б╬▒╬│╧Й╬│╬н╧В - ╬а╬╡╧Б╬╣╧Г╧Г╧М╧Д╬╡╧Б╬╡╧В",
|
||||||
|
"Please select a publish state": "╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬╡╧А╬╣╬╗╬н╬╛╧Д╬╡ ╬║╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖ ╬┤╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В",
|
||||||
|
"Please select a user": "╬а╬▒╧Б╬▒╬║╬▒╬╗╧О ╬╡╧А╬╣╬╗╬н╬╛╧Д╬╡ ╧З╧Б╬о╧Г╧Д╬╖",
|
||||||
|
"Powered by": "╬е╧А╬┐╧Г╧Д╬╖╧Б╬п╬╢╬╡╧Д╬▒╬╣ ╬▒╧А╧М ╧Д╬┐",
|
||||||
|
"Private": "╬Щ╬┤╬╣╧Й╧Д╬╣╬║╧М",
|
||||||
|
"Proceed": "╬г╧Е╬╜╬н╧З╬╡╬╣╬▒",
|
||||||
|
"Processing...": "╬Х╧А╬╡╬╛╬╡╧Б╬│╬▒╧Г╬п╬▒...",
|
||||||
|
"Public": "╬Ф╬╖╬╝╧М╧Г╬╣╬┐",
|
||||||
|
"Publish": "╬Ф╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖",
|
||||||
|
"Publish State": "╬Ъ╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖ ╬Ф╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В",
|
||||||
|
"Published": "╬Ф╬╖╬╝╬┐╧Г╬╣╬╡╧Е╬╝╬н╬╜╬┐",
|
||||||
|
"Published on": "╬Ф╬╖╬╝╬┐╧Г╬╣╬╡╧Н╧Д╬╖╬║╬╡ ╧Г╧Д╬╣╧В",
|
||||||
|
"Recent uploads": "╬а╧Б╧М╧Г╧Ж╬▒╧Д╬▒ ╬▒╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬▒",
|
||||||
|
"Recommended": "╬а╧Б╬┐╧Д╬╡╬╣╬╜╧М╬╝╬╡╬╜╬▒",
|
||||||
|
"Record Screen": "╬Ъ╬▒╧Д╬▒╬│╧Б╬▒╧Ж╬о ╬┐╬╕╧М╬╜╬╖╧В",
|
||||||
|
"Register": "╬Х╬│╬│╧Б╬▒╧Ж╬о",
|
||||||
|
"Remove category": "╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬п╬▒╧В",
|
||||||
|
"Remove from list": "╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬▒╧А╧М ╬╗╬п╧Г╧Д╬▒",
|
||||||
|
"Remove tag": "╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╬╡╧Д╬╣╬║╬н╧Д╬▒╧В",
|
||||||
|
"Remove user": "╬С╧Ж╬▒╬п╧Б╬╡╧Г╬╖ ╧З╧Б╬о╧Г╧Д╬╖",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "╬С╬а╬Я╬Ш╬Ч╬Ъ╬Х╬е╬г╬Ч",
|
||||||
|
"SEARCH": "╬С╬Э╬С╬Ц╬Ч╬д╬Ч╬г╬Ч",
|
||||||
|
"SHARE": "╬Ъ╬Я╬Щ╬Э╬Я╬а╬Я╬Щ╬Ч╬г╬Ч",
|
||||||
|
"SHOW MORE": "╬а╬Х╬б╬Щ╬г╬г╬Я╬д╬Х╬б╬С",
|
||||||
|
"SORT BY": "╬д╬С╬Ю╬Щ╬Э╬Я╬Ь╬Ч╬г╬Ч",
|
||||||
|
"SUBMIT": "╬е╬а╬Я╬Т╬Я╬Ы╬Ч",
|
||||||
|
"Search": "╬С╬╜╬▒╬╢╬о╧Д╬╖╧Г╬╖",
|
||||||
|
"Search for user...": "╬С╬╜╬▒╬╢╬о╧Д╬╖╧Г╬╖ ╧З╧Б╬о╧Г╧Д╬╖...",
|
||||||
|
"Search users to add...": "╬С╬╜╬▒╬╢╬о╧Д╬╖╧Г╬╖ ╧З╧Б╬╖╧Г╧Д╧О╬╜ ╬│╬╣╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖...",
|
||||||
|
"Select": "╬Х╧А╬╣╬╗╬┐╬│╬о",
|
||||||
|
"Select Owner": "╬Х╧А╬╣╬╗╬┐╬│╬о ╬Щ╬┤╬╣╬┐╬║╧Д╬о╧Д╬╖",
|
||||||
|
"Select all": "╬Х╧А╬╣╬╗╬┐╬│╬о ╧М╬╗╧Й╬╜",
|
||||||
|
"Select all media": "╬Х╧А╬╣╬╗╬┐╬│╬о ╧М╬╗╧Й╬╜ ╧Д╧Й╬╜ ╬▒╧Б╧З╬╡╬п╧Й╬╜",
|
||||||
|
"Select publish state:": "╬Х╧А╬╣╬╗╬н╬╛╧Д╬╡ ╬║╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖ ╬┤╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В:",
|
||||||
|
"Selected": "╬Х╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒",
|
||||||
|
"Shared by me": "╬Ъ╬┐╬╣╬╜╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬▒╬╜ ╬▒╧А╧М ╬╡╬╝╬н╬╜╬▒",
|
||||||
|
"Shared with me": "╬Ъ╬┐╬╣╬╜╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬▒╬╜ ╧Г╬╡ ╬╡╬╝╬н╬╜╬▒",
|
||||||
|
"Sign in": "╬г╧Н╬╜╬┤╬╡╧Г╬╖",
|
||||||
|
"Sign out": "╬С╧А╬┐╧Г╧Н╬╜╬┤╬╡╧Г╬╖",
|
||||||
|
"Sort By": "╬д╬▒╬╛╬╣╬╜╧М╬╝╬╖╧Г╬╖",
|
||||||
|
"Start Recording": "╬И╬╜╬▒╧Б╬╛╬╖ ╬╡╬│╬│╧Б╬▒╧Ж╬о╧В",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "╬Ю╬╡╬║╬╣╬╜╬о╧Г╧Д╬╡ ╬╜╬▒ ╬▒╬╜╬╡╬▓╬м╬╢╬╡╧Д╬╡ ╬▒╧Б╧З╬╡╬п╬▒ ╬║╬▒╬╣ ╬╜╬▒ ╬║╬┐╬╣╬╜╬┐╧А╬┐╬╣╬╡╬п╧Д╬╡ ╧Д╬╖ ╬┤╬┐╧Е╬╗╬╡╬╣╬м ╧Г╬▒╧В. ╬д╬▒ ╬▒╧Б╧З╬╡╬п╬▒ ╧А╬┐╧Е ╬▒╬╜╬╡╬▓╬м╬╢╬╡╧Д╬╡ ╬╕╬▒ ╬╡╬╝╧Ж╬▒╬╜╬п╬╢╬┐╬╜╧Д╬▒╬╣ ╬╡╬┤╧О.",
|
||||||
|
"Stop Recording": "╬Ф╬╣╬▒╬║╬┐╧А╬о ╬╡╬│╬│╧Б╬▒╧Ж╬о╧В",
|
||||||
|
"Submit": "╬е╧А╬┐╬▓╬┐╬╗╬о",
|
||||||
|
"Subtitle was added": "╬Я╬╣ ╧Е╧А╧М╧Д╬╣╧Д╬╗╬┐╬╣ ╧А╧Б╬┐╧Г╧Д╬н╬╕╬╖╬║╬▒╬╜",
|
||||||
|
"Subtitles": "╬е╧А╧М╧Д╬╣╧Д╬╗╬┐╬╣",
|
||||||
|
"Successfully Copied": "╬С╬╜╧Д╬╣╬│╧Б╬м╧Ж╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully Disabled Download": "╬Ч ╬╗╬о╧И╬╖ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully Disabled comments": "╬д╬▒ ╧Г╧З╧М╬╗╬╣╬▒ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬▒╬╜ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully Enabled Download": "╬Ч ╬╗╬о╧И╬╖ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully Enabled comments": "╬д╬▒ ╧Г╧З╧М╬╗╬╣╬▒ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╬╕╬╖╬║╬▒╬╜ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully changed owner": "╬Я ╬╣╬┤╬╣╬┐╬║╧Д╬о╧Д╬╖╧В ╬м╬╗╬╗╬▒╬╛╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully deleted": "╬Ф╬╣╬▒╬│╧Б╬м╧Ж╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully updated": "╬Х╬╜╬╖╬╝╬╡╧Б╧О╬╕╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully updated categories": "╬Я╬╣ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬п╬╡╧В ╬╡╬╜╬╖╬╝╬╡╧Б╧О╬╕╬╖╬║╬▒╬╜ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully updated playlist membership": "╬Ч ╧Г╧Е╬╝╬╝╬╡╧Д╬┐╧З╬о ╧Г╧Д╬╖ ╬╗╬п╧Г╧Д╬▒ ╬╡╬╜╬╖╬╝╬╡╧Б╧О╬╕╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully updated publish state": "╬Ч ╬║╬▒╧Д╬м╧Г╧Д╬▒╧Г╬╖ ╬┤╬╖╬╝╬┐╧Г╬п╬╡╧Е╧Г╬╖╧В ╬╡╬╜╬╖╬╝╬╡╧Б╧О╬╕╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"Successfully updated tags": "╬Я╬╣ ╬╡╧Д╬╣╬║╬н╧Д╬╡╧В ╬╡╬╜╬╖╬╝╬╡╧Б╧О╬╕╬╖╬║╬▒╬╜ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒",
|
||||||
|
"TAGS": "╬Х╬д╬Щ╬Ъ╬Х╬д╬Х╬г",
|
||||||
|
"Tag": "╬Х╧Д╬╣╬║╬н╧Д╬▒",
|
||||||
|
"Tags": "╬Х╧Д╬╣╬║╬н╧Д╬╡╧В",
|
||||||
|
"Terms": "╬М╧Б╬┐╬╣",
|
||||||
|
"The intersection of categories in the selected media is shown": "╬Х╬╝╧Ж╬▒╬╜╬п╬╢╬╡╧Д╬▒╬╣ ╬╖ ╧Д╬┐╬╝╬о ╧Д╧Й╬╜ ╬║╬▒╧Д╬╖╬│╬┐╧Б╬╣╧О╬╜ ╧Г╧Д╬▒ ╬╡╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"The intersection of playlists in the selected media is shown": "╬Х╬╝╧Ж╬▒╬╜╬п╬╢╬╡╧Д╬▒╬╣ ╬╖ ╧Д╬┐╬╝╬о ╧Д╧Й╬╜ ╬╗╬╣╧Г╧Д╧О╬╜ ╧Г╧Д╬▒ ╬╡╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"The intersection of tags in the selected media is shown": "╬Х╬╝╧Ж╬▒╬╜╬п╬╢╬╡╧Д╬▒╬╣ ╬╖ ╧Д╬┐╬╝╬о ╧Д╧Й╬╜ ╬╡╧Д╬╣╬║╬╡╧Д╧О╬╜ ╧Г╧Д╬▒ ╬╡╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"The intersection of users in the selected media is shown": "╬Х╬╝╧Ж╬▒╬╜╬п╬╢╬╡╧Д╬▒╬╣ ╬╖ ╧Д╬┐╬╝╬о ╧Д╧Й╬╜ ╧З╧Б╬╖╧Г╧Д╧О╬╜ ╧Г╧Д╬▒ ╬╡╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒ ╬▒╧Б╧З╬╡╬п╬▒",
|
||||||
|
"The media was deleted successfully.": "╬д╬┐ ╬▒╧Б╧З╬╡╬п╬┐ ╬┤╬╣╬▒╬│╧Б╬м╧Ж╬╖╬║╬╡ ╬╝╬╡ ╬╡╧А╬╣╧Д╧Е╧З╬п╬▒.",
|
||||||
|
"This month": "╬С╧Е╧Д╧М╬╜ ╧Д╬┐╬╜ ╬╝╬о╬╜╬▒",
|
||||||
|
"This week": "╬С╧Е╧Д╬о ╧Д╬╖╬╜ ╬╡╬▓╬┤╬┐╬╝╬м╬┤╬▒",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "╬С╧Е╧Д╧М ╬╗╬╡╬╣╧Д╬┐╧Е╧Б╬│╬╡╬п ╧Г╬╡ ╧А╧Б╬┐╬│╧Б╬м╬╝╬╝╬▒╧Д╬▒ ╧А╬╡╧Б╬╣╬о╬│╬╖╧Г╬╖╧В Chrome, Safari ╬║╬▒╬╣ Edge.",
|
||||||
|
"This year": "╬ж╬н╧Д╬┐╧В",
|
||||||
|
"To add": "╬У╬╣╬▒ ╧А╧Б╬┐╧Г╬╕╬о╬║╬╖",
|
||||||
|
"Today": "╬г╬о╬╝╬╡╧Б╬▒",
|
||||||
|
"Trim": "╬а╬╡╧Б╬╣╬║╬┐╧А╬о",
|
||||||
|
"UPLOAD": "╬С╬Э╬Х╬Т╬С╬г╬Ь╬С",
|
||||||
|
"UPLOAD DATE": "╬Ч╬Ь╬Х╬б╬Я╬Ь╬Ч╬Э╬Щ╬С ╬С╬Э╬Х╬Т╬С╬г╬Ь╬С╬д╬Я╬г",
|
||||||
|
"UPLOAD MEDIA": "╬С╬Э╬Х╬Т╬С╬г╬Ь╬С ╬С╬б╬з╬Х╬Щ╬й╬Э",
|
||||||
|
"Undo removal": "╬С╬╜╬▒╬п╧Б╬╡╧Г╬╖ ╬▒╧Ж╬▒╬п╧Б╬╡╧Г╬╖╧В",
|
||||||
|
"Unlisted": "╬Ь╬╖ ╬║╬▒╧Д╬▒╧З╧Й╧Б╬╖╬╝╬н╬╜╬┐",
|
||||||
|
"Up Next": "╬Х╧А╧М╬╝╬╡╬╜╬┐",
|
||||||
|
"Up next": "╬Х╧А╧М╬╝╬╡╬╜╬┐",
|
||||||
|
"Upload": "╬С╬╜╬н╬▓╬▒╧Г╬╝╬▒",
|
||||||
|
"Upload date (newest)": "╬Ч╬╝╬╡╧Б╬┐╬╝╬╖╬╜╬п╬▒ ╬▒╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬┐╧В (╬╜╬╡╧М╧Д╬╡╧Б╬▒)",
|
||||||
|
"Upload date (oldest)": "╬Ч╬╝╬╡╧Б╬┐╬╝╬╖╬╜╬п╬▒ ╬▒╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬┐╧В (╧А╬▒╬╗╬▒╬╣╧М╧Д╬╡╧Б╬▒)",
|
||||||
|
"Upload date - Newest": "╬Ч╬╝╬╡╧Б╬┐╬╝╬╖╬╜╬п╬▒ ╬▒╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬┐╧В - ╬Э╬╡╧М╧Д╬╡╧Б╬▒",
|
||||||
|
"Upload date - Oldest": "╬Ч╬╝╬╡╧Б╬┐╬╝╬╖╬╜╬п╬▒ ╬▒╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬┐╧В - ╬а╬▒╬╗╬▒╬╣╧М╧Д╬╡╧Б╬▒",
|
||||||
|
"Upload media": "╬С╬╜╬н╬▓╬▒╧Г╬╝╬▒ ╬▒╧Б╧З╬╡╬п╧Й╬╜",
|
||||||
|
"Uploads": "╬С╬╜╬╡╬▓╬м╧Г╬╝╬▒╧Д╬▒",
|
||||||
|
"Users": "╬з╧Б╬о╧Г╧Д╬╡╧В",
|
||||||
|
"VIEW ALL": "╬Ф╬Х╬г ╬д╬С ╬Я╬Ы╬С",
|
||||||
|
"Video": "╬Т╬п╬╜╧Д╬╡╬┐",
|
||||||
|
"View all": "╬Ф╬╡╧В ╧Д╬▒ ╧М╬╗╬▒",
|
||||||
|
"View count": "╬С╧Б╬╣╬╕╬╝╧М╧В ╧А╧Б╬┐╬▓╬┐╬╗╧О╬╜",
|
||||||
|
"View media": "╬а╧Б╬┐╬▓╬┐╬╗╬о ╬▒╧Б╧З╬╡╬п╬┐╧Е",
|
||||||
|
"Welcome": "╬Ъ╬▒╬╗╧О╧В ╬о╧Б╬╕╬▒╧Д╬╡",
|
||||||
|
"You are going to copy": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬▒╬╜╧Д╬╣╬│╧Б╬м╧И╬╡╧Д╬╡",
|
||||||
|
"You are going to delete": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬┤╬╣╬▒╬│╧Б╬м╧И╬╡╧Д╬╡",
|
||||||
|
"You are going to disable comments to": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╧Г╬╡╧Д╬╡ ╧Д╬▒ ╧Г╧З╧М╬╗╬╣╬▒ ╬│╬╣╬▒",
|
||||||
|
"You are going to disable download for": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬▒╧А╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╧Г╬╡╧Д╬╡ ╧Д╬╖ ╬╗╬о╧И╬╖ ╬│╬╣╬▒",
|
||||||
|
"You are going to enable comments to": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╧Г╬╡╧Д╬╡ ╧Д╬▒ ╧Г╧З╧М╬╗╬╣╬▒ ╬│╬╣╬▒",
|
||||||
|
"You are going to enable download for": "╬а╧Б╧М╬║╬╡╬╣╧Д╬▒╬╣ ╬╜╬▒ ╬╡╬╜╬╡╧Б╬│╬┐╧А╬┐╬╣╬о╧Г╬╡╧Д╬╡ ╧Д╬╖ ╬╗╬о╧И╬╖ ╬│╬╣╬▒",
|
||||||
|
"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": "╬▒╧Б╧З╬╡╬п╬▒ ╬╝╬╡ ╬╡╧Д╬╣╬║╬н╧Д╬▒",
|
||||||
|
"media, are you sure?": "╬▒╧Б╧З╬╡╬п╬┐, ╬╡╬п╧Г╧Д╬╡ ╧Г╬п╬│╬┐╧Е╧Б╬┐╧В;",
|
||||||
|
"media.": "╬▒╧Б╧З╬╡╬п╬┐.",
|
||||||
|
"or": "╬о",
|
||||||
|
"results for": "╬▒╧А╬┐╧Д╬╡╬╗╬н╧Г╬╝╬▒╧Д╬▒ ╬│╬╣╬▒",
|
||||||
|
"selected": "╬╡╧А╬╣╬╗╬╡╬│╬╝╬н╬╜╬▒",
|
||||||
|
"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": "╧З╧Б╧М╬╜╬╣╬▒ ╧А╧Б╬╣╬╜",
|
||||||
|
}
|
||||||
289
files/frontend_translations/en.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "",
|
||||||
|
"00 - 20 min": "",
|
||||||
|
"1 result for": "",
|
||||||
|
"20 - 40 min": "",
|
||||||
|
"40 - 60 min": "",
|
||||||
|
"60 - 120 min+": "",
|
||||||
|
"ABOUT": "",
|
||||||
|
"About": "",
|
||||||
|
"Add / Remove Co-Editors": "",
|
||||||
|
"Add / Remove Co-Owners": "",
|
||||||
|
"Add / Remove Co-Viewers": "",
|
||||||
|
"Add / Remove from Categories": "",
|
||||||
|
"Add / Remove Tags": "",
|
||||||
|
"Add a ": "",
|
||||||
|
"Add to": "",
|
||||||
|
"Add to / Remove from Category": "",
|
||||||
|
"Add to / Remove from Playlist": "",
|
||||||
|
"All": "",
|
||||||
|
"All categories already added": "",
|
||||||
|
"All tags already added": "",
|
||||||
|
"Alphabetically - A-Z": "",
|
||||||
|
"Alphabetically - Z-A": "",
|
||||||
|
"Audio": "",
|
||||||
|
"AUTOPLAY": "",
|
||||||
|
"Browse your files": "",
|
||||||
|
"Bulk Actions": "",
|
||||||
|
"Cancel": "",
|
||||||
|
"Categories": "",
|
||||||
|
"Category": "",
|
||||||
|
"Change Language": "",
|
||||||
|
"Change Owner": "",
|
||||||
|
"Change password": "",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "",
|
||||||
|
"Co-Editors": "",
|
||||||
|
"Co-Owners": "",
|
||||||
|
"Co-Viewers": "",
|
||||||
|
"COMMENT": "",
|
||||||
|
"Comment": "",
|
||||||
|
"comment": "",
|
||||||
|
"Comments": "",
|
||||||
|
"Comments are disabled": "",
|
||||||
|
"Confirm": "",
|
||||||
|
"Confirm Action": "",
|
||||||
|
"Contact": "",
|
||||||
|
"Copy Media": "",
|
||||||
|
"Create": "",
|
||||||
|
"DELETE": "",
|
||||||
|
"DELETE MEDIA": "",
|
||||||
|
"Delete media": "",
|
||||||
|
"Delete Media": "",
|
||||||
|
"Disable Comments": "",
|
||||||
|
"Disable Download": "",
|
||||||
|
"DOWNLOAD": "",
|
||||||
|
"Drag and drop files": "",
|
||||||
|
"DURATION": "",
|
||||||
|
"EDIT MEDIA": "",
|
||||||
|
"Edit media": "",
|
||||||
|
"EDIT PROFILE": "",
|
||||||
|
"Edit profile": "",
|
||||||
|
"EDIT SUBTITLE": "",
|
||||||
|
"Edit subtitle": "",
|
||||||
|
"Enable Comments": "",
|
||||||
|
"Enable Download": "",
|
||||||
|
"Enter playlist name...": "",
|
||||||
|
"Failed to add categories": "",
|
||||||
|
"Failed to add media to playlists": "",
|
||||||
|
"Failed to add tags": "",
|
||||||
|
"Failed to add users": "",
|
||||||
|
"Failed to change owner": "",
|
||||||
|
"Failed to change owner. Please try again.": "",
|
||||||
|
"Failed to copy media.": "",
|
||||||
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete media. Please try again.": "",
|
||||||
|
"Failed to disable comments.": "",
|
||||||
|
"Failed to disable download.": "",
|
||||||
|
"Failed to enable comments.": "",
|
||||||
|
"Failed to enable download.": "",
|
||||||
|
"Failed to fetch all categories": "",
|
||||||
|
"Failed to fetch all tags": "",
|
||||||
|
"Failed to fetch existing categories": "",
|
||||||
|
"Failed to fetch existing tags": "",
|
||||||
|
"Failed to fetch existing users": "",
|
||||||
|
"Failed to fetch playlist membership": "",
|
||||||
|
"Failed to fetch playlists": "",
|
||||||
|
"Failed to load categories": "",
|
||||||
|
"Failed to load existing permissions": "",
|
||||||
|
"Failed to load playlists": "",
|
||||||
|
"Failed to load tags": "",
|
||||||
|
"Failed to remove categories": "",
|
||||||
|
"Failed to remove media from playlists": "",
|
||||||
|
"Failed to remove tags": "",
|
||||||
|
"Failed to remove users": "",
|
||||||
|
"Failed to search users": "",
|
||||||
|
"Failed to set publish state": "",
|
||||||
|
"Failed to set publish state. Please try again.": "",
|
||||||
|
"Failed to update categories. Please try again.": "",
|
||||||
|
"Failed to update permissions. Please try again.": "",
|
||||||
|
"Failed to update playlists. Please try again.": "",
|
||||||
|
"Failed to update tags. Please try again.": "",
|
||||||
|
"Featured": "",
|
||||||
|
"Filter existing users...": "",
|
||||||
|
"Filter playlists...": "",
|
||||||
|
"Filters": "",
|
||||||
|
"Go": "",
|
||||||
|
"History": "",
|
||||||
|
"Home": "",
|
||||||
|
"Image": "",
|
||||||
|
"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": "",
|
||||||
|
"Language": "",
|
||||||
|
"Latest": "",
|
||||||
|
"Like count": "",
|
||||||
|
"Liked media": "",
|
||||||
|
"Likes - Least": "",
|
||||||
|
"Likes - Most": "",
|
||||||
|
"Loading categories...": "",
|
||||||
|
"Loading existing users...": "",
|
||||||
|
"Loading playlists...": "",
|
||||||
|
"Loading tags...": "",
|
||||||
|
"Manage": "",
|
||||||
|
"Manage comments": "",
|
||||||
|
"Manage media": "",
|
||||||
|
"Manage Playlists": "",
|
||||||
|
"Manage users": "",
|
||||||
|
"Media": "",
|
||||||
|
"Media I own": "",
|
||||||
|
"media in category": "",
|
||||||
|
"media in tag": "",
|
||||||
|
"MEDIA TYPE": "",
|
||||||
|
"Media was edited": "",
|
||||||
|
"media, are you sure?": "",
|
||||||
|
"media.": "",
|
||||||
|
"Members": "",
|
||||||
|
"My media": "",
|
||||||
|
"My playlists": "",
|
||||||
|
"No": "",
|
||||||
|
"No categories": "",
|
||||||
|
"No comment yet": "",
|
||||||
|
"No comments yet": "",
|
||||||
|
"No existing": "",
|
||||||
|
"No playlists available": "",
|
||||||
|
"No playlists selected": "",
|
||||||
|
"No results for": "",
|
||||||
|
"No tags": "",
|
||||||
|
"No users to add": "",
|
||||||
|
"or": "",
|
||||||
|
"Pdf": "",
|
||||||
|
"PLAYLISTS": "",
|
||||||
|
"Playlists": "",
|
||||||
|
"Plays - Least": "",
|
||||||
|
"Plays - Most": "",
|
||||||
|
"Please select a publish state": "",
|
||||||
|
"Please select a user": "",
|
||||||
|
"Powered by": "",
|
||||||
|
"Private": "",
|
||||||
|
"Proceed": "",
|
||||||
|
"Processing...": "",
|
||||||
|
"Public": "",
|
||||||
|
"Publish": "",
|
||||||
|
"PUBLISH STATE": "",
|
||||||
|
"Publish State": "",
|
||||||
|
"Published": "",
|
||||||
|
"Published on": "",
|
||||||
|
"Recent uploads": "",
|
||||||
|
"Recommended": "",
|
||||||
|
"Record Screen": "",
|
||||||
|
"Register": "",
|
||||||
|
"Replace": "",
|
||||||
|
"Remove category": "",
|
||||||
|
"Remove from list": "",
|
||||||
|
"Remove tag": "",
|
||||||
|
"Remove user": "",
|
||||||
|
"results for": "",
|
||||||
|
"SAVE": "",
|
||||||
|
"SEARCH": "",
|
||||||
|
"Search": "",
|
||||||
|
"Search for user...": "",
|
||||||
|
"Search users to add...": "",
|
||||||
|
"Select": "",
|
||||||
|
"Select all": "",
|
||||||
|
"Select all media": "",
|
||||||
|
"Select Owner": "",
|
||||||
|
"Select publish state:": "",
|
||||||
|
"Selected": "",
|
||||||
|
"selected": "",
|
||||||
|
"SHARE": "",
|
||||||
|
"Shared by me": "",
|
||||||
|
"Shared with me": "",
|
||||||
|
"SHOW MORE": "",
|
||||||
|
"Sign in": "",
|
||||||
|
"Sign out": "",
|
||||||
|
"SORT BY": "",
|
||||||
|
"Sort By": "",
|
||||||
|
"Start Recording": "",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "",
|
||||||
|
"Stop Recording": "",
|
||||||
|
"SUBMIT": "",
|
||||||
|
"Submit": "",
|
||||||
|
"Subtitle was added": "",
|
||||||
|
"Subtitles": "",
|
||||||
|
"Successfully changed owner": "",
|
||||||
|
"Successfully Copied": "",
|
||||||
|
"Successfully deleted": "",
|
||||||
|
"Successfully Disabled comments": "",
|
||||||
|
"Successfully Disabled Download": "",
|
||||||
|
"Successfully Enabled comments": "",
|
||||||
|
"Successfully Enabled Download": "",
|
||||||
|
"Successfully updated": "",
|
||||||
|
"Successfully updated categories": "",
|
||||||
|
"Successfully updated playlist membership": "",
|
||||||
|
"Successfully updated publish state": "",
|
||||||
|
"Successfully updated tags": "",
|
||||||
|
"Tag": "",
|
||||||
|
"TAGS": "",
|
||||||
|
"Tags": "",
|
||||||
|
"Terms": "",
|
||||||
|
"The intersection of categories in the selected media is shown": "",
|
||||||
|
"The intersection of playlists in the selected media is shown": "",
|
||||||
|
"The intersection of tags in the selected media is shown": "",
|
||||||
|
"The intersection of users in the selected media is shown": "",
|
||||||
|
"The media was deleted successfully.": "",
|
||||||
|
"This month": "",
|
||||||
|
"This week": "",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "",
|
||||||
|
"This year": "",
|
||||||
|
"To add": "",
|
||||||
|
"Today": "",
|
||||||
|
"Trim": "",
|
||||||
|
"Undo removal": "",
|
||||||
|
"Unlisted": "",
|
||||||
|
"Up Next": "",
|
||||||
|
"Up next": "",
|
||||||
|
"UPLOAD": "",
|
||||||
|
"Upload": "",
|
||||||
|
"UPLOAD DATE": "",
|
||||||
|
"Upload date (newest)": "",
|
||||||
|
"Upload date (oldest)": "",
|
||||||
|
"Upload date - Newest": "",
|
||||||
|
"Upload date - Oldest": "",
|
||||||
|
"UPLOAD MEDIA": "",
|
||||||
|
"Upload media": "",
|
||||||
|
"Uploads": "",
|
||||||
|
"Users": "",
|
||||||
|
"Video": "",
|
||||||
|
"view": "",
|
||||||
|
"VIEW ALL": "",
|
||||||
|
"View all": "",
|
||||||
|
"View count": "",
|
||||||
|
"View media": "",
|
||||||
|
"views": "",
|
||||||
|
"Welcome": "",
|
||||||
|
"yet": "",
|
||||||
|
"You are going to copy": "",
|
||||||
|
"You are going to delete": "",
|
||||||
|
"You are going to disable comments to": "",
|
||||||
|
"You are going to disable download for": "",
|
||||||
|
"You are going to enable comments to": "",
|
||||||
|
"You are going to enable download for": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
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": "",
|
||||||
|
}
|
||||||
289
files/frontend_translations/es.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ Crear Lista de Reproducci├│n",
|
||||||
|
"00 - 20 min": "00 - 20 min",
|
||||||
|
"1 result for": "1 resultado para",
|
||||||
|
"20 - 40 min": "20 - 40 min",
|
||||||
|
"40 - 60 min": "40 - 60 min",
|
||||||
|
"60 - 120 min+": "60 - 120 min+",
|
||||||
|
"ABOUT": "Acerca de",
|
||||||
|
"AUTOPLAY": "Reproducci├│n autom├бtica",
|
||||||
|
"About": "Acerca de",
|
||||||
|
"Add / Remove Co-Editors": "Agregar / Eliminar Coeditores",
|
||||||
|
"Add / Remove Co-Owners": "Agregar / Eliminar Copropietarios",
|
||||||
|
"Add / Remove Co-Viewers": "Agregar / Eliminar Covisores",
|
||||||
|
"Add / Remove Tags": "Agregar / Eliminar Etiquetas",
|
||||||
|
"Add / Remove from Categories": "Agregar / Eliminar de Categor├нas",
|
||||||
|
"Add a ": "Agregar un ",
|
||||||
|
"Add to": "Agregar a",
|
||||||
|
"Add to / Remove from Category": "Agregar / Eliminar de Categor├нa",
|
||||||
|
"Add to / Remove from Playlist": "Agregar / Eliminar de Lista de Reproducci├│n",
|
||||||
|
"All": "Todos",
|
||||||
|
"All categories already added": "Todas las categor├нas ya agregadas",
|
||||||
|
"All tags already added": "Todas las etiquetas ya agregadas",
|
||||||
|
"Alphabetically - A-Z": "Alfab├йticamente - A-Z",
|
||||||
|
"Alphabetically - Z-A": "Alfab├йticamente - Z-A",
|
||||||
|
"Audio": "Audio",
|
||||||
|
"Browse your files": "Explorar sus archivos",
|
||||||
|
"Bulk Actions": "Acciones Masivas",
|
||||||
|
"COMMENT": "COMENTARIO",
|
||||||
|
"Cancel": "Cancelar",
|
||||||
|
"Categories": "Categor├нas",
|
||||||
|
"Category": "Categor├нa",
|
||||||
|
"Change Language": "Cambiar idioma",
|
||||||
|
"Change Owner": "Cambiar Propietario",
|
||||||
|
"Change password": "Cambiar contrase├▒a",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Haga clic en 'Iniciar grabaci├│n' y seleccione la pantalla o pesta├▒a para grabar. Una vez finalizada la grabaci├│n, haga clic en 'Detener grabaci├│n' y la grabaci├│n se subir├б.",
|
||||||
|
"Co-Editors": "Coeditores",
|
||||||
|
"Co-Owners": "Copropietarios",
|
||||||
|
"Co-Viewers": "Covisores",
|
||||||
|
"Comment": "Comentario",
|
||||||
|
"Comments": "Comentarios",
|
||||||
|
"Comments are disabled": "Los comentarios est├бn deshabilitados",
|
||||||
|
"Confirm": "Confirmar",
|
||||||
|
"Confirm Action": "Confirmar Acci├│n",
|
||||||
|
"Contact": "Contacto",
|
||||||
|
"Copy Media": "Copiar Medio",
|
||||||
|
"Create": "Crear",
|
||||||
|
"DELETE": "ELIMINAR",
|
||||||
|
"DELETE MEDIA": "ELIMINAR MEDIOS",
|
||||||
|
"DOWNLOAD": "DESCARGAR",
|
||||||
|
"DURATION": "DURACI├УN",
|
||||||
|
"Delete Media": "Eliminar Medio",
|
||||||
|
"Delete media": "Eliminar medios",
|
||||||
|
"Disable Comments": "Deshabilitar Comentarios",
|
||||||
|
"Disable Download": "Deshabilitar Descarga",
|
||||||
|
"Drag and drop files": "Arrastre y suelte archivos",
|
||||||
|
"EDIT MEDIA": "EDITAR MEDIOS",
|
||||||
|
"EDIT PROFILE": "EDITAR PERFIL",
|
||||||
|
"EDIT SUBTITLE": "EDITAR SUBT├НTULO",
|
||||||
|
"Edit media": "Editar medios",
|
||||||
|
"Edit profile": "Editar perfil",
|
||||||
|
"Edit subtitle": "Editar subt├нtulo",
|
||||||
|
"Enable Comments": "Habilitar Comentarios",
|
||||||
|
"Enable Download": "Habilitar Descarga",
|
||||||
|
"Enter playlist name...": "Ingrese nombre de lista de reproducci├│n...",
|
||||||
|
"Failed to add categories": "Error al agregar categor├нas",
|
||||||
|
"Failed to add media to playlists": "Error al agregar medios a listas de reproducci├│n",
|
||||||
|
"Failed to add tags": "Error al agregar etiquetas",
|
||||||
|
"Failed to add users": "Error al agregar usuarios",
|
||||||
|
"Failed to change owner": "Error al cambiar propietario",
|
||||||
|
"Failed to change owner. Please try again.": "Error al cambiar propietario. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to copy media.": "Error al copiar medios.",
|
||||||
|
"Failed to create playlist": "Error al crear lista de reproducci├│n",
|
||||||
|
"Failed to delete media. Please try again.": "Error al eliminar medios. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to disable comments.": "Error al deshabilitar comentarios.",
|
||||||
|
"Failed to disable download.": "Error al deshabilitar descarga.",
|
||||||
|
"Failed to enable comments.": "Error al habilitar comentarios.",
|
||||||
|
"Failed to enable download.": "Error al habilitar descarga.",
|
||||||
|
"Failed to fetch all categories": "Error al obtener todas las categor├нas",
|
||||||
|
"Failed to fetch all tags": "Error al obtener todas las etiquetas",
|
||||||
|
"Failed to fetch existing categories": "Error al obtener categor├нas existentes",
|
||||||
|
"Failed to fetch existing tags": "Error al obtener etiquetas existentes",
|
||||||
|
"Failed to fetch existing users": "Error al obtener usuarios existentes",
|
||||||
|
"Failed to fetch playlist membership": "Error al obtener membres├нa de lista de reproducci├│n",
|
||||||
|
"Failed to fetch playlists": "Error al obtener listas de reproducci├│n",
|
||||||
|
"Failed to load categories": "Error al cargar categor├нas",
|
||||||
|
"Failed to load existing permissions": "Error al cargar permisos existentes",
|
||||||
|
"Failed to load playlists": "Error al cargar listas de reproducci├│n",
|
||||||
|
"Failed to load tags": "Error al cargar etiquetas",
|
||||||
|
"Failed to remove categories": "Error al eliminar categor├нas",
|
||||||
|
"Failed to remove media from playlists": "Error al eliminar medios de listas de reproducci├│n",
|
||||||
|
"Failed to remove tags": "Error al eliminar etiquetas",
|
||||||
|
"Failed to remove users": "Error al eliminar usuarios",
|
||||||
|
"Failed to search users": "Error al buscar usuarios",
|
||||||
|
"Failed to set publish state": "Error al establecer estado de publicaci├│n",
|
||||||
|
"Failed to set publish state. Please try again.": "Error al establecer estado de publicaci├│n. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to update categories. Please try again.": "Error al actualizar categor├нas. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to update permissions. Please try again.": "Error al actualizar permisos. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to update playlists. Please try again.": "Error al actualizar listas de reproducci├│n. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Failed to update tags. Please try again.": "Error al actualizar etiquetas. Por favor, int├йntelo de nuevo.",
|
||||||
|
"Featured": "Destacado",
|
||||||
|
"Filter existing users...": "Filtrar usuarios existentes...",
|
||||||
|
"Filter playlists...": "Filtrar listas de reproducci├│n...",
|
||||||
|
"Filters": "Filtros",
|
||||||
|
"Go": "Ir",
|
||||||
|
"History": "Historial",
|
||||||
|
"Home": "Inicio",
|
||||||
|
"Image": "Imagen",
|
||||||
|
"Language": "Idioma",
|
||||||
|
"Latest": "├Ъltimo",
|
||||||
|
"Like count": "Cantidad de me gusta",
|
||||||
|
"Liked media": "Medios que me gustan",
|
||||||
|
"Likes - Least": "Me gusta - Menos",
|
||||||
|
"Likes - Most": "Me gusta - M├бs",
|
||||||
|
"Loading categories...": "Cargando categor├нas...",
|
||||||
|
"Loading existing users...": "Cargando usuarios existentes...",
|
||||||
|
"Loading playlists...": "Cargando listas de reproducci├│n...",
|
||||||
|
"Loading tags...": "Cargando etiquetas...",
|
||||||
|
"MEDIA TYPE": "TIPO DE MEDIO",
|
||||||
|
"Manage": "Gestionar",
|
||||||
|
"Manage Playlists": "Gestionar Listas de Reproducci├│n",
|
||||||
|
"Manage comments": "Gestionar comentarios",
|
||||||
|
"Manage media": "Gestionar medios",
|
||||||
|
"Manage users": "Gestionar usuarios",
|
||||||
|
"Media": "Medios",
|
||||||
|
"Media I own": "Medios que poseo",
|
||||||
|
"Media was edited": "El medio fue editado",
|
||||||
|
"Members": "Miembros",
|
||||||
|
"My media": "Mis medios",
|
||||||
|
"My playlists": "Mis listas de reproducci├│n",
|
||||||
|
"No": "No",
|
||||||
|
"No categories": "Sin categor├нas",
|
||||||
|
"No comment yet": "A├║n no hay comentarios",
|
||||||
|
"No comments yet": "A├║n no hay comentarios",
|
||||||
|
"No existing": "No existente",
|
||||||
|
"No playlists available": "No hay listas de reproducci├│n disponibles",
|
||||||
|
"No playlists selected": "No hay listas de reproducci├│n seleccionadas",
|
||||||
|
"No results for": "No hay resultados para",
|
||||||
|
"No tags": "Sin etiquetas",
|
||||||
|
"No users to add": "No hay usuarios para agregar",
|
||||||
|
"PLAYLISTS": "LISTAS DE REPRODUCCI├УN",
|
||||||
|
"PUBLISH STATE": "ESTADO DE PUBLICACI├УN",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "Listas de reproducci├│n",
|
||||||
|
"Plays - Least": "Reproducciones - Menos",
|
||||||
|
"Plays - Most": "Reproducciones - M├бs",
|
||||||
|
"Please select a publish state": "Por favor seleccione un estado de publicaci├│n",
|
||||||
|
"Please select a user": "Por favor seleccione un usuario",
|
||||||
|
"Powered by": "Desarrollado por",
|
||||||
|
"Private": "Privado",
|
||||||
|
"Proceed": "Continuar",
|
||||||
|
"Processing...": "Procesando...",
|
||||||
|
"Public": "P├║blico",
|
||||||
|
"Publish": "Publicar",
|
||||||
|
"Publish State": "Estado de Publicaci├│n",
|
||||||
|
"Published": "Publicado",
|
||||||
|
"Published on": "Publicado en",
|
||||||
|
"Recent uploads": "Subidas recientes",
|
||||||
|
"Recommended": "Recomendado",
|
||||||
|
"Record Screen": "Grabar pantalla",
|
||||||
|
"Register": "Registrarse",
|
||||||
|
"Remove category": "Eliminar categor├нa",
|
||||||
|
"Remove from list": "Eliminar de la lista",
|
||||||
|
"Remove tag": "Eliminar etiqueta",
|
||||||
|
"Remove user": "Eliminar usuario",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "GUARDAR",
|
||||||
|
"SEARCH": "BUSCAR",
|
||||||
|
"SHARE": "COMPARTIR",
|
||||||
|
"SHOW MORE": "MOSTRAR M├БS",
|
||||||
|
"SORT BY": "ORDENAR POR",
|
||||||
|
"SUBMIT": "ENVIAR",
|
||||||
|
"Search": "Buscar",
|
||||||
|
"Search for user...": "Buscar usuario...",
|
||||||
|
"Search users to add...": "Buscar usuarios para agregar...",
|
||||||
|
"Select": "Seleccionar",
|
||||||
|
"Select Owner": "Seleccionar Propietario",
|
||||||
|
"Select all": "Seleccionar todo",
|
||||||
|
"Select all media": "Seleccionar todos los medios",
|
||||||
|
"Select publish state:": "Seleccionar estado de publicaci├│n:",
|
||||||
|
"Selected": "Seleccionado",
|
||||||
|
"Shared by me": "Compartido por m├н",
|
||||||
|
"Shared with me": "Compartido conmigo",
|
||||||
|
"Sign in": "Iniciar sesi├│n",
|
||||||
|
"Sign out": "Cerrar sesi├│n",
|
||||||
|
"Sort By": "Ordenar por",
|
||||||
|
"Start Recording": "Iniciar grabaci├│n",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "Comience a subir medios y compartir su trabajo. Los medios que suba aparecer├бn aqu├н.",
|
||||||
|
"Stop Recording": "Detener grabaci├│n",
|
||||||
|
"Submit": "Enviar",
|
||||||
|
"Subtitle was added": "El subt├нtulo fue agregado",
|
||||||
|
"Subtitles": "Subt├нtulos",
|
||||||
|
"Successfully Copied": "Copiado exitosamente",
|
||||||
|
"Successfully Disabled Download": "Descarga deshabilitada exitosamente",
|
||||||
|
"Successfully Disabled comments": "Comentarios deshabilitados exitosamente",
|
||||||
|
"Successfully Enabled Download": "Descarga habilitada exitosamente",
|
||||||
|
"Successfully Enabled comments": "Comentarios habilitados exitosamente",
|
||||||
|
"Successfully changed owner": "Propietario cambiado exitosamente",
|
||||||
|
"Successfully deleted": "Eliminado exitosamente",
|
||||||
|
"Successfully updated": "Actualizado exitosamente",
|
||||||
|
"Successfully updated categories": "Categor├нas actualizadas exitosamente",
|
||||||
|
"Successfully updated playlist membership": "Membres├нa de lista de reproducci├│n actualizada exitosamente",
|
||||||
|
"Successfully updated publish state": "Estado de publicaci├│n actualizado exitosamente",
|
||||||
|
"Successfully updated tags": "Etiquetas actualizadas exitosamente",
|
||||||
|
"TAGS": "ETIQUETAS",
|
||||||
|
"Tag": "Etiqueta",
|
||||||
|
"Tags": "Etiquetas",
|
||||||
|
"Terms": "T├йrminos",
|
||||||
|
"The intersection of categories in the selected media is shown": "Se muestran las categor├нas comunes en los medios seleccionados",
|
||||||
|
"The intersection of playlists in the selected media is shown": "Se muestran las listas de reproducci├│n comunes en los medios seleccionados",
|
||||||
|
"The intersection of tags in the selected media is shown": "Se muestran las etiquetas comunes en los medios seleccionados",
|
||||||
|
"The intersection of users in the selected media is shown": "Se muestran los usuarios comunes en los medios seleccionados",
|
||||||
|
"The media was deleted successfully.": "El medio fue eliminado exitosamente.",
|
||||||
|
"This month": "Este mes",
|
||||||
|
"This week": "Esta semana",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "Esto funciona en los navegadores Chrome, Safari y Edge.",
|
||||||
|
"This year": "Este a├▒o",
|
||||||
|
"To add": "Para agregar",
|
||||||
|
"Today": "Hoy",
|
||||||
|
"Trim": "Recortar",
|
||||||
|
"UPLOAD": "SUBIR",
|
||||||
|
"UPLOAD DATE": "FECHA DE SUBIDA",
|
||||||
|
"UPLOAD MEDIA": "SUBIR MEDIOS",
|
||||||
|
"Undo removal": "Deshacer eliminaci├│n",
|
||||||
|
"Unlisted": "No listado",
|
||||||
|
"Up Next": "A continuaci├│n",
|
||||||
|
"Up next": "A continuaci├│n",
|
||||||
|
"Upload": "Subir",
|
||||||
|
"Upload date (newest)": "Fecha de subida (m├бs reciente)",
|
||||||
|
"Upload date (oldest)": "Fecha de subida (m├бs antigua)",
|
||||||
|
"Upload date - Newest": "Fecha de subida - M├бs reciente",
|
||||||
|
"Upload date - Oldest": "Fecha de subida - M├бs antigua",
|
||||||
|
"Upload media": "Subir medios",
|
||||||
|
"Uploads": "Subidas",
|
||||||
|
"Users": "Usuarios",
|
||||||
|
"VIEW ALL": "VER TODO",
|
||||||
|
"Video": "Video",
|
||||||
|
"View all": "Ver todo",
|
||||||
|
"View count": "Cantidad de vistas",
|
||||||
|
"View media": "Ver medios",
|
||||||
|
"Welcome": "Bienvenido",
|
||||||
|
"You are going to copy": "Vas a copiar",
|
||||||
|
"You are going to delete": "Vas a eliminar",
|
||||||
|
"You are going to disable comments to": "Vas a deshabilitar comentarios de",
|
||||||
|
"You are going to disable download for": "Vas a deshabilitar descarga de",
|
||||||
|
"You are going to enable comments to": "Vas a habilitar comentarios de",
|
||||||
|
"You are going to enable download for": "Vas a habilitar descarga de",
|
||||||
|
"comment": "comentario",
|
||||||
|
"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": "es un CMS de video y medios de c├│digo abierto, moderno y completamente equipado. Est├б desarrollado para satisfacer las necesidades de las plataformas web modernas para ver y compartir medios",
|
||||||
|
"media in category": "medios en la categor├нa",
|
||||||
|
"media in tag": "medios en la etiqueta",
|
||||||
|
"media, are you sure?": "medios, ┬┐est├б seguro?",
|
||||||
|
"media.": "medios.",
|
||||||
|
"or": "o",
|
||||||
|
"results for": "resultados para",
|
||||||
|
"selected": "seleccionado",
|
||||||
|
"view": "vista",
|
||||||
|
"views": "vistas",
|
||||||
|
"yet": "a├║n",
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement_strings = {
|
||||||
|
"Apr": "Abr",
|
||||||
|
"Aug": "Ago",
|
||||||
|
"Dec": "Dic",
|
||||||
|
"Feb": "Feb",
|
||||||
|
"Jan": "Ene",
|
||||||
|
"Jul": "Jul",
|
||||||
|
"Jun": "Jun",
|
||||||
|
"Mar": "Mar",
|
||||||
|
"May": "May",
|
||||||
|
"Nov": "Nov",
|
||||||
|
"Oct": "Oct",
|
||||||
|
"Sep": "Sep",
|
||||||
|
"day ago": "hace un d├нa",
|
||||||
|
"days ago": "hace d├нas",
|
||||||
|
"hour ago": "hace una hora",
|
||||||
|
"hours ago": "hace horas",
|
||||||
|
"just now": "justo ahora",
|
||||||
|
"minute ago": "hace un minuto",
|
||||||
|
"minutes ago": "hace minutos",
|
||||||
|
"month ago": "hace un mes",
|
||||||
|
"months ago": "hace meses",
|
||||||
|
"second ago": "hace un segundo",
|
||||||
|
"seconds ago": "hace segundos",
|
||||||
|
"week ago": "hace una semana",
|
||||||
|
"weeks ago": "hace semanas",
|
||||||
|
"year ago": "hace un a├▒o",
|
||||||
|
"years ago": "hace a├▒os",
|
||||||
|
}
|
||||||
290
files/frontend_translations/fr.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ Cr├йer une playlist",
|
||||||
|
"00 - 20 min": "00 - 20 min",
|
||||||
|
"1 result for": "1 r├йsultat pour",
|
||||||
|
"20 - 40 min": "20 - 40 min",
|
||||||
|
"40 - 60 min": "40 - 60 min",
|
||||||
|
"60 - 120 min+": "60 - 120 min+",
|
||||||
|
"ABOUT": "├А PROPOS",
|
||||||
|
"AUTOPLAY": "Lecture automatique",
|
||||||
|
"About": "├А propos",
|
||||||
|
"Add / Remove Co-Editors": "Ajouter / Supprimer des co-├йditeurs",
|
||||||
|
"Add / Remove Co-Owners": "Ajouter / Supprimer des co-propri├йtaires",
|
||||||
|
"Add / Remove Co-Viewers": "Ajouter / Supprimer des co-visualisateurs",
|
||||||
|
"Add / Remove Tags": "Ajouter / Supprimer des tags",
|
||||||
|
"Add / Remove from Categories": "Ajouter / Supprimer des cat├йgories",
|
||||||
|
"Add a": "Ajouter un",
|
||||||
|
"Add a ": "Ajouter un ",
|
||||||
|
"Add to": "Ajouter ├а",
|
||||||
|
"Add to / Remove from Category": "Ajouter / Supprimer de la cat├йgorie",
|
||||||
|
"Add to / Remove from Playlist": "Ajouter / Supprimer de la playlist",
|
||||||
|
"All": "Tout",
|
||||||
|
"All categories already added": "Toutes les cat├йgories d├йj├а ajout├йes",
|
||||||
|
"All tags already added": "Tous les tags d├йj├а ajout├йs",
|
||||||
|
"Alphabetically - A-Z": "Alphab├йtiquement - A-Z",
|
||||||
|
"Alphabetically - Z-A": "Alphab├йtiquement - Z-A",
|
||||||
|
"Audio": "Audio",
|
||||||
|
"Browse your files": "Parcourir vos fichiers",
|
||||||
|
"Bulk Actions": "Actions group├йes",
|
||||||
|
"COMMENT": "COMMENTAIRE",
|
||||||
|
"Cancel": "Annuler",
|
||||||
|
"Categories": "Cat├йgories",
|
||||||
|
"Category": "Cat├йgorie",
|
||||||
|
"Change Language": "Changer de langue",
|
||||||
|
"Change Owner": "Changer de propri├йtaire",
|
||||||
|
"Change password": "Changer le mot de passe",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Cliquez sur 'D├йmarrer l'enregistrement' et s├йlectionnez l'├йcran ou l'onglet ├а enregistrer. Une fois l'enregistrement termin├й, cliquez sur 'Arr├кter l'enregistrement', et l'enregistrement sera t├йl├йvers├й.",
|
||||||
|
"Co-Editors": "Co-├йditeurs",
|
||||||
|
"Co-Owners": "Co-propri├йtaires",
|
||||||
|
"Co-Viewers": "Co-visualisateurs",
|
||||||
|
"Comment": "Commentaire",
|
||||||
|
"Comments": "Commentaires",
|
||||||
|
"Comments are disabled": "Les commentaires sont d├йsactiv├йs",
|
||||||
|
"Confirm": "Confirmer",
|
||||||
|
"Confirm Action": "Confirmer l'action",
|
||||||
|
"Contact": "Contact",
|
||||||
|
"Copy Media": "Copier le m├йdia",
|
||||||
|
"Create": "Cr├йer",
|
||||||
|
"DELETE": "SUPPRIMER",
|
||||||
|
"DELETE MEDIA": "SUPPRIMER LE M├ЙDIA",
|
||||||
|
"DOWNLOAD": "T├ЙL├ЙCHARGER",
|
||||||
|
"DURATION": "DUR├ЙE",
|
||||||
|
"Delete Media": "Supprimer le m├йdia",
|
||||||
|
"Delete media": "Supprimer le m├йdia",
|
||||||
|
"Disable Comments": "D├йsactiver les commentaires",
|
||||||
|
"Disable Download": "D├йsactiver le t├йl├йchargement",
|
||||||
|
"Drag and drop files": "Glisser-d├йposer des fichiers",
|
||||||
|
"EDIT MEDIA": "MODIFIER LE M├ЙDIA",
|
||||||
|
"EDIT PROFILE": "MODIFIER LE PROFIL",
|
||||||
|
"EDIT SUBTITLE": "MODIFIER LE SOUS-TITRE",
|
||||||
|
"Edit media": "Modifier le m├йdia",
|
||||||
|
"Edit profile": "Modifier le profil",
|
||||||
|
"Edit subtitle": "Modifier le sous-titre",
|
||||||
|
"Enable Comments": "Activer les commentaires",
|
||||||
|
"Enable Download": "Activer le t├йl├йchargement",
|
||||||
|
"Enter playlist name...": "Entrez le nom de la playlist...",
|
||||||
|
"Failed to add categories": "├Йchec de l'ajout des cat├йgories",
|
||||||
|
"Failed to add media to playlists": "├Йchec de l'ajout du m├йdia aux playlists",
|
||||||
|
"Failed to add tags": "├Йchec de l'ajout des tags",
|
||||||
|
"Failed to add users": "├Йchec de l'ajout des utilisateurs",
|
||||||
|
"Failed to change owner": "├Йchec du changement de propri├йtaire",
|
||||||
|
"Failed to change owner. Please try again.": "├Йchec du changement de propri├йtaire. Veuillez r├йessayer.",
|
||||||
|
"Failed to copy media.": "├Йchec de la copie du m├йdia.",
|
||||||
|
"Failed to create playlist": "├Йchec de la cr├йation de la playlist",
|
||||||
|
"Failed to delete media. Please try again.": "├Йchec de la suppression du m├йdia. Veuillez r├йessayer.",
|
||||||
|
"Failed to disable comments.": "├Йchec de la d├йsactivation des commentaires.",
|
||||||
|
"Failed to disable download.": "├Йchec de la d├йsactivation du t├йl├йchargement.",
|
||||||
|
"Failed to enable comments.": "├Йchec de l'activation des commentaires.",
|
||||||
|
"Failed to enable download.": "├Йchec de l'activation du t├йl├йchargement.",
|
||||||
|
"Failed to fetch all categories": "├Йchec de la r├йcup├йration de toutes les cat├йgories",
|
||||||
|
"Failed to fetch all tags": "├Йchec de la r├йcup├йration de tous les tags",
|
||||||
|
"Failed to fetch existing categories": "├Йchec de la r├йcup├йration des cat├йgories existantes",
|
||||||
|
"Failed to fetch existing tags": "├Йchec de la r├йcup├йration des tags existants",
|
||||||
|
"Failed to fetch existing users": "├Йchec de la r├йcup├йration des utilisateurs existants",
|
||||||
|
"Failed to fetch playlist membership": "├Йchec de la r├йcup├йration de l'adh├йsion ├а la playlist",
|
||||||
|
"Failed to fetch playlists": "├Йchec de la r├йcup├йration des playlists",
|
||||||
|
"Failed to load categories": "├Йchec du chargement des cat├йgories",
|
||||||
|
"Failed to load existing permissions": "├Йchec du chargement des permissions existantes",
|
||||||
|
"Failed to load playlists": "├Йchec du chargement des playlists",
|
||||||
|
"Failed to load tags": "├Йchec du chargement des tags",
|
||||||
|
"Failed to remove categories": "├Йchec de la suppression des cat├йgories",
|
||||||
|
"Failed to remove media from playlists": "├Йchec de la suppression du m├йdia des playlists",
|
||||||
|
"Failed to remove tags": "├Йchec de la suppression des tags",
|
||||||
|
"Failed to remove users": "├Йchec de la suppression des utilisateurs",
|
||||||
|
"Failed to search users": "├Йchec de la recherche d'utilisateurs",
|
||||||
|
"Failed to set publish state": "├Йchec de la d├йfinition de l'├йtat de publication",
|
||||||
|
"Failed to set publish state. Please try again.": "├Йchec de la d├йfinition de l'├йtat de publication. Veuillez r├йessayer.",
|
||||||
|
"Failed to update categories. Please try again.": "├Йchec de la mise ├а jour des cat├йgories. Veuillez r├йessayer.",
|
||||||
|
"Failed to update permissions. Please try again.": "├Йchec de la mise ├а jour des permissions. Veuillez r├йessayer.",
|
||||||
|
"Failed to update playlists. Please try again.": "├Йchec de la mise ├а jour des playlists. Veuillez r├йessayer.",
|
||||||
|
"Failed to update tags. Please try again.": "├Йchec de la mise ├а jour des tags. Veuillez r├йessayer.",
|
||||||
|
"Featured": "En vedette",
|
||||||
|
"Filter existing users...": "Filtrer les utilisateurs existants...",
|
||||||
|
"Filter playlists...": "Filtrer les playlists...",
|
||||||
|
"Filters": "Filtres",
|
||||||
|
"Go": "Aller",
|
||||||
|
"History": "Historique",
|
||||||
|
"Home": "Accueil",
|
||||||
|
"Image": "Image",
|
||||||
|
"Language": "Langue",
|
||||||
|
"Latest": "Dernier",
|
||||||
|
"Like count": "Nombre de j'aime",
|
||||||
|
"Liked media": "M├йdias aim├йs",
|
||||||
|
"Likes - Least": "J'aime - Moins",
|
||||||
|
"Likes - Most": "J'aime - Plus",
|
||||||
|
"Loading categories...": "Chargement des cat├йgories...",
|
||||||
|
"Loading existing users...": "Chargement des utilisateurs existants...",
|
||||||
|
"Loading playlists...": "Chargement des playlists...",
|
||||||
|
"Loading tags...": "Chargement des tags...",
|
||||||
|
"MEDIA TYPE": "TYPE DE M├ЙDIA",
|
||||||
|
"Manage": "G├йrer",
|
||||||
|
"Manage Playlists": "G├йrer les playlists",
|
||||||
|
"Manage comments": "G├йrer les commentaires",
|
||||||
|
"Manage media": "G├йrer les m├йdias",
|
||||||
|
"Manage users": "G├йrer les utilisateurs",
|
||||||
|
"Media": "M├йdia",
|
||||||
|
"Media I own": "M├йdias que je poss├иde",
|
||||||
|
"Media was edited": "Le m├йdia a ├йt├й modifi├й",
|
||||||
|
"Members": "Membres",
|
||||||
|
"My media": "Mes m├йdias",
|
||||||
|
"My playlists": "Mes playlists",
|
||||||
|
"No": "Non",
|
||||||
|
"No categories": "Aucune cat├йgorie",
|
||||||
|
"No comment yet": "Pas encore de commentaire",
|
||||||
|
"No comments yet": "Pas encore de commentaires",
|
||||||
|
"No existing": "Aucun existant",
|
||||||
|
"No playlists available": "Aucune playlist disponible",
|
||||||
|
"No playlists selected": "Aucune playlist s├йlectionn├йe",
|
||||||
|
"No results for": "Aucun r├йsultat pour",
|
||||||
|
"No tags": "Aucun tag",
|
||||||
|
"No users to add": "Aucun utilisateur ├а ajouter",
|
||||||
|
"PLAYLISTS": "PLAYLISTS",
|
||||||
|
"PUBLISH STATE": "├ЙTAT DE PUBLICATION",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Plays - Least": "Lectures - Moins",
|
||||||
|
"Plays - Most": "Lectures - Plus",
|
||||||
|
"Please select a publish state": "Veuillez s├йlectionner un ├йtat de publication",
|
||||||
|
"Please select a user": "Veuillez s├йlectionner un utilisateur",
|
||||||
|
"Powered by": "Propuls├й par",
|
||||||
|
"Private": "Priv├й",
|
||||||
|
"Proceed": "Continuer",
|
||||||
|
"Processing...": "Traitement en cours...",
|
||||||
|
"Public": "Public",
|
||||||
|
"Publish": "Publier",
|
||||||
|
"Publish State": "├Йtat de publication",
|
||||||
|
"Published": "Publi├й",
|
||||||
|
"Published on": "Publi├й le",
|
||||||
|
"Recent uploads": "T├йl├йchargements r├йcents",
|
||||||
|
"Recommended": "Recommand├й",
|
||||||
|
"Record Screen": "Enregistrer l'├йcran",
|
||||||
|
"Register": "S'inscrire",
|
||||||
|
"Remove category": "Supprimer la cat├йgorie",
|
||||||
|
"Remove from list": "Supprimer de la liste",
|
||||||
|
"Remove tag": "Supprimer le tag",
|
||||||
|
"Remove user": "Supprimer l'utilisateur",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "ENREGISTRER",
|
||||||
|
"SEARCH": "RECHERCHER",
|
||||||
|
"SHARE": "PARTAGER",
|
||||||
|
"SHOW MORE": "MONTRER PLUS",
|
||||||
|
"SORT BY": "TRIER PAR",
|
||||||
|
"SUBMIT": "SOUMETTRE",
|
||||||
|
"Search": "Rechercher",
|
||||||
|
"Search for user...": "Rechercher un utilisateur...",
|
||||||
|
"Search users to add...": "Rechercher des utilisateurs ├а ajouter...",
|
||||||
|
"Select": "S├йlectionner",
|
||||||
|
"Select Owner": "S├йlectionner le propri├йtaire",
|
||||||
|
"Select all": "Tout s├йlectionner",
|
||||||
|
"Select all media": "S├йlectionner tous les m├йdias",
|
||||||
|
"Select publish state:": "S├йlectionner l'├йtat de publication:",
|
||||||
|
"Selected": "S├йlectionn├й",
|
||||||
|
"Shared by me": "Partag├й par moi",
|
||||||
|
"Shared with me": "Partag├й avec moi",
|
||||||
|
"Sign in": "Se connecter",
|
||||||
|
"Sign out": "Se d├йconnecter",
|
||||||
|
"Sort By": "Trier par",
|
||||||
|
"Start Recording": "Commencer l'enregistrement",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "Commencez ├а t├йl├йcharger des m├йdias et ├а partager votre travail. Les m├йdias que vous t├йl├йchargez appara├оtront ici.",
|
||||||
|
"Stop Recording": "Arr├кter l'enregistrement",
|
||||||
|
"Submit": "Soumettre",
|
||||||
|
"Subtitle was added": "Le sous-titre a ├йt├й ajout├й",
|
||||||
|
"Subtitles": "Sous-titres",
|
||||||
|
"Successfully Copied": "Copi├й avec succ├иs",
|
||||||
|
"Successfully Disabled Download": "T├йl├йchargement d├йsactiv├й avec succ├иs",
|
||||||
|
"Successfully Disabled comments": "Commentaires d├йsactiv├йs avec succ├иs",
|
||||||
|
"Successfully Enabled Download": "T├йl├йchargement activ├й avec succ├иs",
|
||||||
|
"Successfully Enabled comments": "Commentaires activ├йs avec succ├иs",
|
||||||
|
"Successfully changed owner": "Propri├йtaire chang├й avec succ├иs",
|
||||||
|
"Successfully deleted": "Supprim├й avec succ├иs",
|
||||||
|
"Successfully updated": "Mis ├а jour avec succ├иs",
|
||||||
|
"Successfully updated categories": "Cat├йgories mises ├а jour avec succ├иs",
|
||||||
|
"Successfully updated playlist membership": "Adh├йsion ├а la playlist mise ├а jour avec succ├иs",
|
||||||
|
"Successfully updated publish state": "├Йtat de publication mis ├а jour avec succ├иs",
|
||||||
|
"Successfully updated tags": "Tags mis ├а jour avec succ├иs",
|
||||||
|
"TAGS": "TAGS",
|
||||||
|
"Tag": "Tag",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Terms": "Conditions",
|
||||||
|
"The intersection of categories in the selected media is shown": "L'intersection des cat├йgories dans le m├йdia s├йlectionn├й est affich├йe",
|
||||||
|
"The intersection of playlists in the selected media is shown": "L'intersection des playlists dans le m├йdia s├йlectionn├й est affich├йe",
|
||||||
|
"The intersection of tags in the selected media is shown": "L'intersection des tags dans le m├йdia s├йlectionn├й est affich├йe",
|
||||||
|
"The intersection of users in the selected media is shown": "L'intersection des utilisateurs dans le m├йdia s├йlectionn├й est affich├йe",
|
||||||
|
"The media was deleted successfully.": "Le m├йdia a ├йt├й supprim├й avec succ├иs.",
|
||||||
|
"This month": "Ce mois-ci",
|
||||||
|
"This week": "Cette semaine",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "Cela fonctionne dans les navigateurs Chrome, Safari et Edge.",
|
||||||
|
"This year": "Cette ann├йe",
|
||||||
|
"To add": "├А ajouter",
|
||||||
|
"Today": "Aujourd'hui",
|
||||||
|
"Trim": "Couper",
|
||||||
|
"UPLOAD": "T├ЙL├ЙCHARGER",
|
||||||
|
"UPLOAD DATE": "DATE DE T├ЙL├ЙCHARGEMENT",
|
||||||
|
"UPLOAD MEDIA": "T├ЙL├ЙCHARGER DES M├ЙDIAS",
|
||||||
|
"Undo removal": "Annuler la suppression",
|
||||||
|
"Unlisted": "Non r├йpertori├й",
|
||||||
|
"Up Next": "├А suivre",
|
||||||
|
"Up next": "├А suivre",
|
||||||
|
"Upload": "T├йl├йcharger",
|
||||||
|
"Upload date (newest)": "Date de t├йl├йchargement (plus r├йcent)",
|
||||||
|
"Upload date (oldest)": "Date de t├йl├йchargement (plus ancien)",
|
||||||
|
"Upload date - Newest": "Date de t├йl├йchargement - Plus r├йcent",
|
||||||
|
"Upload date - Oldest": "Date de t├йl├йchargement - Plus ancien",
|
||||||
|
"Upload media": "T├йl├йcharger des m├йdias",
|
||||||
|
"Uploads": "T├йl├йchargements",
|
||||||
|
"Users": "Utilisateurs",
|
||||||
|
"VIEW ALL": "VOIR TOUT",
|
||||||
|
"Video": "Vid├йo",
|
||||||
|
"View all": "Voir tout",
|
||||||
|
"View count": "Nombre de vues",
|
||||||
|
"View media": "Voir le m├йdia",
|
||||||
|
"Welcome": "Bienvenue",
|
||||||
|
"You are going to copy": "Vous allez copier",
|
||||||
|
"You are going to delete": "Vous allez supprimer",
|
||||||
|
"You are going to disable comments to": "Vous allez d├йsactiver les commentaires de",
|
||||||
|
"You are going to disable download for": "Vous allez d├йsactiver le t├йl├йchargement de",
|
||||||
|
"You are going to enable comments to": "Vous allez activer les commentaires de",
|
||||||
|
"You are going to enable download for": "Vous allez activer le t├йl├йchargement de",
|
||||||
|
"comment": "commentaire",
|
||||||
|
"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": "est un CMS vid├йo et m├йdia open source moderne et complet. Il est d├йvelopp├й pour r├йpondre aux besoins des plateformes web modernes pour la visualisation et le partage de m├йdias",
|
||||||
|
"media in category": "m├йdia dans la cat├йgorie",
|
||||||
|
"media in tag": "m├йdia dans le tag",
|
||||||
|
"media, are you sure?": "m├йdias, ├кtes-vous s├╗r?",
|
||||||
|
"media.": "m├йdias.",
|
||||||
|
"or": "ou",
|
||||||
|
"results for": "r├йsultats pour",
|
||||||
|
"selected": "s├йlectionn├й",
|
||||||
|
"view": "vue",
|
||||||
|
"views": "vues",
|
||||||
|
"yet": "encore",
|
||||||
|
}
|
||||||
|
|
||||||
|
replacement_strings = {
|
||||||
|
"Apr": "Avr",
|
||||||
|
"Aug": "Ao├╗",
|
||||||
|
"Dec": "D├йc",
|
||||||
|
"Feb": "F├йv",
|
||||||
|
"Jan": "Jan",
|
||||||
|
"Jul": "Juil",
|
||||||
|
"Jun": "Juin",
|
||||||
|
"Mar": "Mar",
|
||||||
|
"May": "Mai",
|
||||||
|
"Nov": "Nov",
|
||||||
|
"Oct": "Oct",
|
||||||
|
"Sep": "Sep",
|
||||||
|
"day ago": "il y a un jour",
|
||||||
|
"days ago": "il y a quelques jours",
|
||||||
|
"hour ago": "il y a une heure",
|
||||||
|
"hours ago": "il y a quelques heures",
|
||||||
|
"just now": "├а l'instant",
|
||||||
|
"minute ago": "il y a une minute",
|
||||||
|
"minutes ago": "il y a quelques minutes",
|
||||||
|
"month ago": "il y a un mois",
|
||||||
|
"months ago": "il y a quelques mois",
|
||||||
|
"second ago": "il y a une seconde",
|
||||||
|
"seconds ago": "il y a quelques secondes",
|
||||||
|
"week ago": "il y a une semaine",
|
||||||
|
"weeks ago": "il y a quelques semaines",
|
||||||
|
"year ago": "il y a un an",
|
||||||
|
"years ago": "il y a quelques ann├йes",
|
||||||
|
}
|
||||||
289
files/frontend_translations/he.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "",
|
||||||
|
"00 - 20 min": "00 - 20 ╫У╫з╫Х╫к",
|
||||||
|
"1 result for": "╫к╫Х╫ж╫Р╫Ф ╫Р╫Ч╫к ╫в╫С╫Х╫и",
|
||||||
|
"20 - 40 min": "20 - 40 ╫У╫з╫Х╫к",
|
||||||
|
"40 - 60 min": "40 - 60 ╫У╫з╫Х╫к",
|
||||||
|
"60 - 120 min+": "60 - 120 ╫У╫з╫Х╫к+",
|
||||||
|
"ABOUT": "╫в╫Ь ╫Р╫Х╫У╫Х╫к",
|
||||||
|
"AUTOPLAY": "╫а╫Щ╫Т╫Х╫Я ╫Р╫Х╫Ш╫Х╫Ю╫Ш╫Щ",
|
||||||
|
"About": "╫в╫Ь ╫Р╫Х╫У╫Х╫к",
|
||||||
|
"Add / Remove Co-Editors": "",
|
||||||
|
"Add / Remove Co-Owners": "",
|
||||||
|
"Add / Remove Co-Viewers": "",
|
||||||
|
"Add / Remove Tags": "",
|
||||||
|
"Add / Remove from Categories": "",
|
||||||
|
"Add a ": "╫Ф╫Х╫б╫г",
|
||||||
|
"Add to": "",
|
||||||
|
"Add to / Remove from Category": "",
|
||||||
|
"Add to / Remove from Playlist": "",
|
||||||
|
"All": "╫Ф╫Ы╫Ь",
|
||||||
|
"All categories already added": "",
|
||||||
|
"All tags already added": "",
|
||||||
|
"Alphabetically - A-Z": "╫Ь╫д╫Щ ╫Р-╫С - ╫Р-╫к",
|
||||||
|
"Alphabetically - Z-A": "╫Ь╫д╫Щ ╫Р-╫С - ╫к-╫Р",
|
||||||
|
"Audio": "╫Р╫Х╫У╫Щ╫Х",
|
||||||
|
"Browse your files": "╫в╫Щ╫Щ╫Я ╫С╫з╫С╫ж╫Щ╫Э ╫й╫Ь╫Ъ",
|
||||||
|
"Bulk Actions": "",
|
||||||
|
"COMMENT": "╫к╫Т╫Х╫С╫Ф",
|
||||||
|
"Cancel": "",
|
||||||
|
"Categories": "╫з╫Ш╫Т╫Х╫и╫Щ╫Х╫к",
|
||||||
|
"Category": "╫з╫Ш╫Т╫Х╫и╫Щ╫Ф",
|
||||||
|
"Change Language": "╫й╫а╫Ф ╫й╫д╫Ф",
|
||||||
|
"Change Owner": "",
|
||||||
|
"Change password": "╫й╫а╫Ф ╫б╫Щ╫б╫Ю╫Ф",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "╫Ь╫Ч╫е ╫в╫Ь '╫Ф╫к╫Ч╫Ь ╫Ф╫з╫Ь╫Ш╫Ф' ╫Х╫С╫Ч╫и ╫Р╫к ╫Ф╫Ю╫б╫Ъ ╫Р╫Х ╫Ф╫Ы╫и╫Ш╫Щ╫б╫Щ╫Щ╫Ф ╫Ь╫Ф╫з╫Ь╫Ш╫Ф. ╫Ь╫Р╫Ч╫и ╫б╫Щ╫Х╫Э ╫Ф╫Ф╫з╫Ь╫Ш╫Ф, ╫Ь╫Ч╫е ╫в╫Ь '╫в╫ж╫Х╫и ╫Ф╫з╫Ь╫Ш╫Ф', ╫Х╫Ф╫Ф╫з╫Ь╫Ш╫Ф ╫к╫Х╫в╫Ь╫Ф.",
|
||||||
|
"Co-Editors": "",
|
||||||
|
"Co-Owners": "",
|
||||||
|
"Co-Viewers": "",
|
||||||
|
"Comment": "╫к╫Т╫Х╫С╫Ф",
|
||||||
|
"Comments": "╫к╫Т╫Х╫С╫Х╫к",
|
||||||
|
"Comments are disabled": "╫Ф╫к╫Т╫Х╫С╫Х╫к ╫Ю╫Х╫й╫С╫к╫Х╫к",
|
||||||
|
"Confirm": "",
|
||||||
|
"Confirm Action": "",
|
||||||
|
"Contact": "╫ж╫Х╫и ╫з╫й╫и",
|
||||||
|
"Copy Media": "",
|
||||||
|
"Create": "",
|
||||||
|
"DELETE": "╫Ю╫Ч╫з",
|
||||||
|
"DELETE MEDIA": "╫Ю╫Ч╫з ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"DOWNLOAD": "╫Ф╫Х╫и╫У",
|
||||||
|
"DURATION": "╫Ю╫й╫Ъ",
|
||||||
|
"Delete Media": "",
|
||||||
|
"Delete media": "╫Ю╫Ч╫з ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Disable Comments": "",
|
||||||
|
"Disable Download": "",
|
||||||
|
"Drag and drop files": "╫Т╫и╫Х╫и ╫Х╫й╫Ч╫и╫и ╫з╫С╫ж╫Щ╫Э",
|
||||||
|
"EDIT MEDIA": "╫в╫и╫Х╫Ъ ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"EDIT PROFILE": "╫в╫и╫Х╫Ъ ╫д╫и╫Х╫д╫Щ╫Ь",
|
||||||
|
"EDIT SUBTITLE": "╫в╫и╫Х╫Ъ ╫Ы╫к╫Х╫С╫Щ╫Х╫к",
|
||||||
|
"Edit media": "╫в╫и╫Х╫Ъ ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Edit profile": "╫в╫и╫Х╫Ъ ╫д╫и╫Х╫д╫Щ╫Ь",
|
||||||
|
"Edit subtitle": "╫в╫и╫Х╫Ъ ╫Ы╫к╫Х╫С╫Щ╫Х╫к",
|
||||||
|
"Enable Comments": "",
|
||||||
|
"Enable Download": "",
|
||||||
|
"Enter playlist name...": "",
|
||||||
|
"Failed to add categories": "",
|
||||||
|
"Failed to add media to playlists": "",
|
||||||
|
"Failed to add tags": "",
|
||||||
|
"Failed to add users": "",
|
||||||
|
"Failed to change owner": "",
|
||||||
|
"Failed to change owner. Please try again.": "",
|
||||||
|
"Failed to copy media.": "╫Ф╫в╫к╫з╫к ╫Ф╫Ю╫У╫Щ╫Ф ╫а╫Ы╫й╫Ь╫Ф.",
|
||||||
|
"Failed to create playlist": "",
|
||||||
|
"Failed to delete media. Please try again.": "╫Ю╫Ч╫Щ╫з╫к ╫Ф╫Ю╫У╫Щ╫Ф ╫а╫Ы╫й╫Ь╫Ф. ╫Р╫а╫Р ╫а╫б╫Ф ╫й╫Х╫С.",
|
||||||
|
"Failed to disable comments.": "╫С╫Щ╫Ш╫Х╫Ь ╫Ф╫к╫Т╫Х╫С╫Х╫к ╫а╫Ы╫й╫Ь.",
|
||||||
|
"Failed to disable download.": "╫С╫Щ╫Ш╫Х╫Ь ╫Ф╫Ф╫Х╫и╫У╫Ф ╫а╫Ы╫й╫Ь.",
|
||||||
|
"Failed to enable comments.": "╫Ф╫д╫в╫Ь╫к ╫Ф╫к╫Т╫Х╫С╫Х╫к ╫а╫Ы╫й╫Ь╫Ф.",
|
||||||
|
"Failed to enable download.": "╫Ф╫д╫в╫Ь╫к ╫Ф╫Ф╫Х╫и╫У╫Ф ╫а╫Ы╫й╫Ь╫Ф.",
|
||||||
|
"Failed to fetch all categories": "",
|
||||||
|
"Failed to fetch all tags": "",
|
||||||
|
"Failed to fetch existing categories": "",
|
||||||
|
"Failed to fetch existing tags": "",
|
||||||
|
"Failed to fetch existing users": "",
|
||||||
|
"Failed to fetch playlist membership": "",
|
||||||
|
"Failed to fetch playlists": "",
|
||||||
|
"Failed to load categories": "",
|
||||||
|
"Failed to load existing permissions": "",
|
||||||
|
"Failed to load playlists": "",
|
||||||
|
"Failed to load tags": "",
|
||||||
|
"Failed to remove categories": "",
|
||||||
|
"Failed to remove media from playlists": "",
|
||||||
|
"Failed to remove tags": "",
|
||||||
|
"Failed to remove users": "",
|
||||||
|
"Failed to search users": "",
|
||||||
|
"Failed to set publish state": "",
|
||||||
|
"Failed to set publish state. Please try again.": "",
|
||||||
|
"Failed to update categories. Please try again.": "",
|
||||||
|
"Failed to update permissions. Please try again.": "",
|
||||||
|
"Failed to update playlists. Please try again.": "",
|
||||||
|
"Failed to update tags. Please try again.": "",
|
||||||
|
"Featured": "╫Ю╫Х╫Ю╫Ь╫ж╫Щ╫Э",
|
||||||
|
"Filter existing users...": "",
|
||||||
|
"Filter playlists...": "",
|
||||||
|
"Filters": "╫Ю╫б╫а╫а╫Щ╫Э",
|
||||||
|
"Go": "╫С╫ж╫в",
|
||||||
|
"History": "╫Ф╫Щ╫б╫Ш╫Х╫и╫Щ╫Ф",
|
||||||
|
"Home": "╫У╫г ╫Ф╫С╫Щ╫к",
|
||||||
|
"Image": "╫к╫Ю╫Х╫а╫Ф",
|
||||||
|
"Language": "╫й╫д╫Ф",
|
||||||
|
"Latest": "╫Ф╫в╫У╫Ы╫Х╫а╫Щ╫Э ╫Ф╫Р╫Ч╫и╫Х╫а╫Щ╫Э",
|
||||||
|
"Like count": "╫Ю╫б╫д╫и ╫Ь╫Щ╫Щ╫з╫Щ╫Э",
|
||||||
|
"Liked media": "╫Ю╫У╫Щ╫Ф ╫й╫Р╫Ф╫С╫к╫Щ",
|
||||||
|
"Likes - Least": "╫Ь╫Щ╫Щ╫з╫Щ╫Э - ╫Ф╫Ы╫Щ ╫д╫Ч╫Х╫к",
|
||||||
|
"Likes - Most": "╫Ь╫Щ╫Щ╫з╫Щ╫Э - ╫Ф╫Ы╫Щ ╫Ф╫и╫С╫Ф",
|
||||||
|
"Loading categories...": "",
|
||||||
|
"Loading existing users...": "",
|
||||||
|
"Loading playlists...": "",
|
||||||
|
"Loading tags...": "",
|
||||||
|
"MEDIA TYPE": "╫б╫Х╫Т ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Manage": "",
|
||||||
|
"Manage Playlists": "",
|
||||||
|
"Manage comments": "╫а╫Щ╫Ф╫Х╫Ь ╫к╫Т╫Х╫С╫Х╫к",
|
||||||
|
"Manage media": "╫а╫Щ╫Ф╫Х╫Ь ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Manage users": "╫а╫Щ╫Ф╫Х╫Ь ╫Ю╫й╫к╫Ю╫й╫Щ╫Э",
|
||||||
|
"Media": "╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Media I own": "",
|
||||||
|
"Media was edited": "╫Ф╫Ю╫У╫Щ╫Ф ╫а╫в╫и╫Ы╫Ф",
|
||||||
|
"Members": "╫Ю╫й╫к╫Ю╫й╫Щ╫Э",
|
||||||
|
"My media": "╫Ф╫Ю╫У╫Щ╫Ф ╫й╫Ь╫Щ",
|
||||||
|
"My playlists": "╫Ф╫д╫Ь╫Щ╫Щ╫Ь╫Щ╫б╫Ш╫Щ╫Э ╫й╫Ь╫Щ",
|
||||||
|
"No": "╫Ь╫Р",
|
||||||
|
"No categories": "",
|
||||||
|
"No comment yet": "╫в╫У╫Щ╫Щ╫Я ╫Р╫Щ╫Я ╫к╫Т╫Х╫С╫Х╫к",
|
||||||
|
"No comments yet": "╫в╫У╫Щ╫Щ╫Я ╫Р╫Щ╫Я ╫к╫Т╫Х╫С╫Х╫к",
|
||||||
|
"No existing": "",
|
||||||
|
"No playlists available": "",
|
||||||
|
"No playlists selected": "",
|
||||||
|
"No results for": "╫Р╫Щ╫Я ╫к╫Х╫ж╫Р╫Х╫к ╫в╫С╫Х╫и",
|
||||||
|
"No tags": "",
|
||||||
|
"No users to add": "",
|
||||||
|
"PLAYLISTS": "╫д╫Ь╫Щ╫Щ╫Ь╫Щ╫б╫Ш╫Щ╫Э",
|
||||||
|
"PUBLISH STATE": "╫Ю╫ж╫С ╫д╫и╫б╫Х╫Э",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "╫д╫Ь╫Щ╫Щ╫Ь╫Щ╫б╫Ш╫Щ╫Э",
|
||||||
|
"Plays - Least": "╫а╫Т╫Щ╫а╫Х╫к - ╫Ф╫Ы╫Щ ╫д╫Ч╫Х╫к",
|
||||||
|
"Plays - Most": "╫а╫Т╫Щ╫а╫Х╫к - ╫Ф╫Ы╫Щ ╫Ф╫и╫С╫Ф",
|
||||||
|
"Please select a publish state": "",
|
||||||
|
"Please select a user": "",
|
||||||
|
"Powered by": "╫Ю╫Х╫д╫в╫Ь ╫в╫Ь ╫Щ╫У╫Щ",
|
||||||
|
"Private": "╫д╫и╫Ш╫Щ",
|
||||||
|
"Proceed": "",
|
||||||
|
"Processing...": "",
|
||||||
|
"Public": "",
|
||||||
|
"Publish": "╫д╫и╫б╫Э",
|
||||||
|
"Publish State": "",
|
||||||
|
"Published": "╫д╫Х╫и╫б╫Э",
|
||||||
|
"Published on": "╫д╫Х╫и╫б╫Э ╫С╫к╫Р╫и╫Щ╫Ъ",
|
||||||
|
"Recent uploads": "╫Ф╫в╫Ь╫Р╫Х╫к ╫Р╫Ч╫и╫Х╫а╫Х╫к",
|
||||||
|
"Recommended": "╫Ю╫Х╫Ю╫Ь╫е",
|
||||||
|
"Record Screen": "╫Ф╫з╫Ь╫Ш╫к ╫Ю╫б╫Ъ",
|
||||||
|
"Register": "╫Ф╫и╫й╫Ю╫Ф",
|
||||||
|
"Remove category": "",
|
||||||
|
"Remove from list": "",
|
||||||
|
"Remove tag": "",
|
||||||
|
"Remove user": "",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "╫й╫Ю╫Х╫и",
|
||||||
|
"SEARCH": "╫Ч╫д╫й",
|
||||||
|
"SHARE": "╫й╫к╫г",
|
||||||
|
"SHOW MORE": "╫Ф╫ж╫Т ╫в╫Х╫У",
|
||||||
|
"SORT BY": "╫Ю╫Щ╫Щ╫Я ╫Ь╫д╫Щ",
|
||||||
|
"SUBMIT": "╫й╫Ь╫Ч",
|
||||||
|
"Search": "╫Ч╫д╫й",
|
||||||
|
"Search for user...": "",
|
||||||
|
"Search users to add...": "",
|
||||||
|
"Select": "╫С╫Ч╫и",
|
||||||
|
"Select Owner": "",
|
||||||
|
"Select all": "",
|
||||||
|
"Select all media": "",
|
||||||
|
"Select publish state:": "",
|
||||||
|
"Selected": "",
|
||||||
|
"Shared by me": "╫й╫Х╫к╫г ╫в╫Ь ╫Щ╫У╫Щ",
|
||||||
|
"Shared with me": "╫й╫Х╫к╫г ╫Р╫Щ╫к╫Щ",
|
||||||
|
"Sign in": "╫Ф╫к╫Ч╫С╫и",
|
||||||
|
"Sign out": "╫Ф╫к╫а╫к╫з",
|
||||||
|
"Sort By": "╫Ю╫Щ╫Щ╫Я ╫Ь╫д╫Щ",
|
||||||
|
"Start Recording": "╫Ф╫к╫Ч╫Ь ╫Ф╫з╫Ь╫Ш╫Ф",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "╫Ф╫к╫Ч╫Ь ╫Ь╫Ф╫в╫Ь╫Х╫к ╫Ю╫У╫Щ╫Ф ╫Х╫Ь╫й╫к╫г ╫Р╫к ╫в╫С╫Х╫У╫к╫Ъ. ╫Ф╫Ю╫У╫Щ╫Ф ╫й╫к╫в╫Ь╫Ф ╫к╫Х╫д╫Щ╫в ╫Ы╫Р╫Я.",
|
||||||
|
"Stop Recording": "╫в╫ж╫Х╫и ╫Ф╫з╫Ь╫Ш╫Ф",
|
||||||
|
"Submit": "",
|
||||||
|
"Subtitle was added": "╫Ф╫Ы╫к╫Х╫С╫Щ╫к ╫а╫Х╫б╫д╫Ф",
|
||||||
|
"Subtitles": "╫Ы╫к╫Х╫С╫Щ╫Х╫к",
|
||||||
|
"Successfully Copied": "╫Ф╫Х╫в╫к╫з ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully Disabled Download": "╫Ф╫Ф╫Х╫и╫У╫Ф ╫С╫Х╫Ш╫Ь╫Ф ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully Disabled comments": "╫Ф╫к╫Т╫Х╫С╫Х╫к ╫С╫Х╫Ш╫Ь╫Х ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully Enabled Download": "╫Ф╫Ф╫Х╫и╫У╫Ф ╫Ф╫Х╫д╫в╫Ь╫Ф ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully Enabled comments": "╫Ф╫к╫Т╫Х╫С╫Х╫к ╫Ф╫Х╫д╫в╫Ь╫Х ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully changed owner": "",
|
||||||
|
"Successfully deleted": "╫а╫Ю╫Ч╫з ╫С╫Ф╫ж╫Ь╫Ч╫Ф",
|
||||||
|
"Successfully updated": "",
|
||||||
|
"Successfully updated categories": "",
|
||||||
|
"Successfully updated playlist membership": "",
|
||||||
|
"Successfully updated publish state": "",
|
||||||
|
"Successfully updated tags": "",
|
||||||
|
"TAGS": "╫к╫Т╫Щ╫Х╫к",
|
||||||
|
"Tag": "╫к╫Т╫Щ╫к",
|
||||||
|
"Tags": "╫к╫Т╫Щ╫Х╫к",
|
||||||
|
"Terms": "╫к╫а╫Р╫Щ╫Э",
|
||||||
|
"The intersection of categories in the selected media is shown": "",
|
||||||
|
"The intersection of playlists in the selected media is shown": "",
|
||||||
|
"The intersection of tags in the selected media is shown": "",
|
||||||
|
"The intersection of users in the selected media is shown": "",
|
||||||
|
"The media was deleted successfully.": "╫Ф╫Ю╫У╫Щ╫Ф ╫а╫Ю╫Ч╫з╫Ф ╫С╫Ф╫ж╫Ь╫Ч╫Ф.",
|
||||||
|
"This month": "╫Ф╫Ч╫Х╫У╫й",
|
||||||
|
"This week": "╫Ф╫й╫С╫Х╫в",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "╫Ц╫Ф ╫в╫Х╫С╫У ╫С╫У╫д╫У╫д╫а╫Щ Chrome, Safari ╫Х-Edge.",
|
||||||
|
"This year": "╫Ф╫й╫а╫Ф",
|
||||||
|
"To add": "",
|
||||||
|
"Today": "╫Ф╫Щ╫Х╫Э",
|
||||||
|
"Trim": "╫Т╫Ц╫Щ╫и╫Ф",
|
||||||
|
"UPLOAD": "╫Ф╫в╫Ь╫Ф",
|
||||||
|
"UPLOAD DATE": "╫к╫Р╫и╫Щ╫Ъ ╫Ф╫в╫Ь╫Р╫Ф",
|
||||||
|
"UPLOAD MEDIA": "╫Ф╫в╫Ь╫Ф ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Undo removal": "",
|
||||||
|
"Unlisted": "╫Ь╫Р ╫и╫й╫Х╫Э",
|
||||||
|
"Up Next": "╫Ф╫С╫Р ╫С╫к╫Х╫и",
|
||||||
|
"Up next": "╫Ф╫С╫Р ╫С╫к╫Х╫и",
|
||||||
|
"Upload": "╫Ф╫в╫Ь╫Ф",
|
||||||
|
"Upload date (newest)": "╫к╫Р╫и╫Щ╫Ъ ╫Ф╫в╫Ь╫Р╫Ф (╫Ф╫Ч╫У╫й ╫С╫Щ╫Х╫к╫и)",
|
||||||
|
"Upload date (oldest)": "╫к╫Р╫и╫Щ╫Ъ ╫Ф╫в╫Ь╫Р╫Ф (╫Ф╫Щ╫й╫Я ╫С╫Щ╫Х╫к╫и)",
|
||||||
|
"Upload date - Newest": "╫к╫Р╫и╫Щ╫Ъ ╫Ф╫в╫Ь╫Р╫Ф - ╫Ф╫Ч╫У╫й ╫С╫Щ╫Х╫к╫и",
|
||||||
|
"Upload date - Oldest": "╫к╫Р╫и╫Щ╫Ъ ╫Ф╫в╫Ь╫Р╫Ф - ╫Ф╫Щ╫й╫Я ╫С╫Щ╫Х╫к╫и",
|
||||||
|
"Upload media": "╫Ф╫в╫Ь╫Ф ╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Uploads": "╫Ф╫в╫Ь╫Р╫Х╫к",
|
||||||
|
"Users": "",
|
||||||
|
"VIEW ALL": "╫Ф╫ж╫Т ╫Ф╫Ы╫Ь",
|
||||||
|
"Video": "╫Х╫Щ╫У╫Р╫Х",
|
||||||
|
"View all": "╫Ф╫ж╫Т ╫Ф╫Ы╫Ь",
|
||||||
|
"View count": "╫Ю╫б╫д╫и ╫ж╫д╫Щ╫Х╫к",
|
||||||
|
"View media": "╫ж╫д╫Ф ╫С╫Ю╫У╫Щ╫Ф",
|
||||||
|
"Welcome": "╫С╫и╫Х╫Ъ ╫Ф╫С╫Р",
|
||||||
|
"You are going to copy": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫Ф╫в╫к╫Щ╫з",
|
||||||
|
"You are going to delete": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫Ю╫Ч╫Х╫з",
|
||||||
|
"You are going to disable comments to": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫С╫Ш╫Ь ╫к╫Т╫Х╫С╫Х╫к ╫Ь",
|
||||||
|
"You are going to disable download for": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫С╫Ш╫Ь ╫Ф╫Х╫и╫У╫Ф ╫в╫С╫Х╫и",
|
||||||
|
"You are going to enable comments to": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫Ф╫д╫в╫Щ╫Ь ╫к╫Т╫Х╫С╫Х╫к ╫Ь",
|
||||||
|
"You are going to enable download for": "╫Р╫к╫Ф ╫в╫Х╫Ю╫У ╫Ь╫Ф╫д╫в╫Щ╫Ь ╫Ф╫Х╫и╫У╫Ф ╫в╫С╫Х╫и",
|
||||||
|
"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": "╫Ю╫У╫Щ╫Ф ╫С╫к╫Т╫Щ╫к",
|
||||||
|
"media, are you sure?": "╫Ю╫У╫Щ╫Ф, ╫Р╫к╫Ф ╫С╫Ш╫Х╫Ч?",
|
||||||
|
"media.": "╫Ю╫У╫Щ╫Ф.",
|
||||||
|
"or": "╫Р╫Х",
|
||||||
|
"results for": "╫к╫Х╫ж╫Р╫Х╫к ╫в╫С╫Х╫и",
|
||||||
|
"selected": "",
|
||||||
|
"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": "╫Ь╫д╫а╫Щ ╫й╫а╫Щ╫Э",
|
||||||
|
}
|
||||||
289
files/frontend_translations/hi.py
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
translation_strings = {
|
||||||
|
"+ Create Playlist": "+ рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдмрдирд╛рдПрдВ",
|
||||||
|
"00 - 20 min": "00 - 20 рдорд┐рдирдЯ",
|
||||||
|
"1 result for": "1 рдкрд░рд┐рдгрд╛рдо",
|
||||||
|
"20 - 40 min": "20 - 40 рдорд┐рдирдЯ",
|
||||||
|
"40 - 60 min": "40 - 60 рдорд┐рдирдЯ",
|
||||||
|
"60 - 120 min+": "60 - 120 рдорд┐рдирдЯ+",
|
||||||
|
"ABOUT": "рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ",
|
||||||
|
"AUTOPLAY": "рд╕реНрд╡рддрдГ рдЪрд▓рд╛рдПрдВ",
|
||||||
|
"About": "рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ",
|
||||||
|
"Add / Remove Co-Editors": "рд╕рд╣-рд╕рдВрдкрд╛рджрдХ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add / Remove Co-Owners": "рд╕рд╣-рд╕реНрд╡рд╛рдореА рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add / Remove Co-Viewers": "рд╕рд╣-рджрд░реНрд╢рдХ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add / Remove Tags": "рдЯреИрдЧ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add / Remove from Categories": "рд╢реНрд░реЗрдгрд┐рдпреЛрдВ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add a ": "рдЬреЛрдбрд╝реЗрдВ",
|
||||||
|
"Add to": "рдЗрд╕рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ",
|
||||||
|
"Add to / Remove from Category": "рд╢реНрд░реЗрдгреА рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Add to / Remove from Playlist": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ / рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"All": "рд╕рднреА",
|
||||||
|
"All categories already added": "рд╕рднреА рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рдкрд╣рд▓реЗ рд╕реЗ рдЬреЛрдбрд╝реА рдЧрдИ рд╣реИрдВ",
|
||||||
|
"All tags already added": "рд╕рднреА рдЯреИрдЧ рдкрд╣рд▓реЗ рд╕реЗ рдЬреЛрдбрд╝реЗ рдЧрдП рд╣реИрдВ",
|
||||||
|
"Alphabetically - A-Z": "рд╡рд░реНрдгрдорд╛рд▓рд╛ рдХреЗ рдЕрдиреБрд╕рд╛рд░ - A-Z",
|
||||||
|
"Alphabetically - Z-A": "рд╡рд░реНрдгрдорд╛рд▓рд╛ рдХреЗ рдЕрдиреБрд╕рд╛рд░ - Z-A",
|
||||||
|
"Audio": "рдСрдбрд┐рдпреЛ",
|
||||||
|
"Browse your files": "рдЕрдкрдиреА рдлрд╝рд╛рдЗрд▓реЗрдВ рдмреНрд░рд╛рдЙрдЬрд╝ рдХрд░реЗрдВ",
|
||||||
|
"Bulk Actions": "рдереЛрдХ рдХреНрд░рд┐рдпрд╛рдПрдВ",
|
||||||
|
"COMMENT": "рдЯрд┐рдкреНрдкрдгреА",
|
||||||
|
"Cancel": "рд░рджреНрдж рдХрд░реЗрдВ",
|
||||||
|
"Categories": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдБ",
|
||||||
|
"Category": "рд╢реНрд░реЗрдгреА",
|
||||||
|
"Change Language": "рднрд╛рд╖рд╛ рдмрджрд▓реЗрдВ",
|
||||||
|
"Change Owner": "рд╕реНрд╡рд╛рдореА рдмрджрд▓реЗрдВ",
|
||||||
|
"Change password": "рдкрд╛рд╕рд╡рд░реНрдб рдмрджрд▓реЗрдВ",
|
||||||
|
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдкреНрд░рд╛рд░рдВрдн рдХрд░реЗрдВ' рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░реЗрдВ рдФрд░ рд░рд┐рдХреЙрд░реНрдб рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрдХреНрд░реАрди рдпрд╛ рдЯреИрдм рдХрд╛ рдЪрдпрди рдХрд░реЗрдВред рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рд╕рдорд╛рдкреНрдд рд╣реЛрдиреЗ рдХреЗ рдмрд╛рдж, 'рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рд░реЛрдХреЗрдВ' рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░реЗрдВ, рдФрд░ рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдЕрдкрд▓реЛрдб рд╣реЛ рдЬрд╛рдПрдЧреАред",
|
||||||
|
"Co-Editors": "рд╕рд╣-рд╕рдВрдкрд╛рджрдХ",
|
||||||
|
"Co-Owners": "рд╕рд╣-рд╕реНрд╡рд╛рдореА",
|
||||||
|
"Co-Viewers": "рд╕рд╣-рджрд░реНрд╢рдХ",
|
||||||
|
"Comment": "рдЯрд┐рдкреНрдкрдгреА",
|
||||||
|
"Comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ",
|
||||||
|
"Comments are disabled": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ рдЕрдХреНрд╖рдо рд╣реИрдВ",
|
||||||
|
"Confirm": "рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ",
|
||||||
|
"Confirm Action": "рдХреНрд░рд┐рдпрд╛ рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ",
|
||||||
|
"Contact": "рд╕рдВрдкрд░реНрдХ рдХрд░реЗрдВ",
|
||||||
|
"Copy Media": "рдореАрдбрд┐рдпрд╛ рдХреЙрдкреА рдХрд░реЗрдВ",
|
||||||
|
"Create": "рдмрдирд╛рдПрдВ",
|
||||||
|
"DELETE": "рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"DELETE MEDIA": "рдореАрдбрд┐рдпрд╛ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"DOWNLOAD": "рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ",
|
||||||
|
"DURATION": "рдЕрд╡рдзрд┐",
|
||||||
|
"Delete Media": "рдореАрдбрд┐рдпрд╛ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Delete media": "рдореАрдбрд┐рдпрд╛ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Disable Comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рдЕрдХреНрд╖рдо рдХрд░реЗрдВ",
|
||||||
|
"Disable Download": "рдбрд╛рдЙрдирд▓реЛрдб рдЕрдХреНрд╖рдо рдХрд░реЗрдВ",
|
||||||
|
"Drag and drop files": "рдлрд╝рд╛рдЗрд▓реЗрдВ рдЦреАрдВрдЪреЗрдВ рдФрд░ рдЫреЛрдбрд╝реЗрдВ",
|
||||||
|
"EDIT MEDIA": "рдореАрдбрд┐рдпрд╛ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"EDIT PROFILE": "рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"EDIT SUBTITLE": "рдЙрдкрд╢реАрд░реНрд╖рдХ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Edit media": "рдореАрдбрд┐рдпрд╛ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Edit profile": "рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Edit subtitle": "рдЙрдкрд╢реАрд░реНрд╖рдХ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Enable Comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рд╕рдХреНрд╖рдо рдХрд░реЗрдВ",
|
||||||
|
"Enable Download": "рдбрд╛рдЙрдирд▓реЛрдб рд╕рдХреНрд╖рдо рдХрд░реЗрдВ",
|
||||||
|
"Enter playlist name...": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдХрд╛ рдирд╛рдо рджрд░реНрдЬ рдХрд░реЗрдВ...",
|
||||||
|
"Failed to add categories": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to add media to playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдореЗрдВ рдореАрдбрд┐рдпрд╛ рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to add tags": "рдЯреИрдЧ рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to add users": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЬреЛрдбрд╝рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to change owner": "рд╕реНрд╡рд╛рдореА рдмрджрд▓рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to change owner. Please try again.": "рд╕реНрд╡рд╛рдореА рдмрджрд▓рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to copy media.": "рдореАрдбрд┐рдпрд╛ рдХреЙрдкреА рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред",
|
||||||
|
"Failed to create playlist": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдмрдирд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to delete media. Please try again.": "рдореАрдбрд┐рдпрд╛ рд╣рдЯрд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to disable comments.": "рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдХреЛ рдЕрдХреНрд╖рдо рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред",
|
||||||
|
"Failed to disable download.": "рдбрд╛рдЙрдирд▓реЛрдб рдЕрдХреНрд╖рдо рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред",
|
||||||
|
"Failed to enable comments.": "рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдХреЛ рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред",
|
||||||
|
"Failed to enable download.": "рдбрд╛рдЙрдирд▓реЛрдб рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред",
|
||||||
|
"Failed to fetch all categories": "рд╕рднреА рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch all tags": "рд╕рднреА рдЯреИрдЧ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch existing categories": "рдореМрдЬреВрджрд╛ рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch existing tags": "рдореМрдЬреВрджрд╛ рдЯреИрдЧ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch existing users": "рдореМрдЬреВрджрд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch playlist membership": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд╕рджрд╕реНрдпрддрд╛ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to fetch playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд▓рд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to load categories": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to load existing permissions": "рдореМрдЬреВрджрд╛ рдЕрдиреБрдорддрд┐рдпрд╛рдВ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to load playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to load tags": "рдЯреИрдЧ рд▓реЛрдб рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to remove categories": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд╣рдЯрд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to remove media from playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд╕реЗ рдореАрдбрд┐рдпрд╛ рд╣рдЯрд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to remove tags": "рдЯреИрдЧ рд╣рдЯрд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to remove users": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рд╣рдЯрд╛рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to search users": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдЦреЛрдЬрдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to set publish state": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐ рд╕реЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓",
|
||||||
|
"Failed to set publish state. Please try again.": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐ рд╕реЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to update categories. Please try again.": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to update permissions. Please try again.": "рдЕрдиреБрдорддрд┐рдпрд╛рдВ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to update playlists. Please try again.": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Failed to update tags. Please try again.": "рдЯреИрдЧ рдЕрдкрдбреЗрдЯ рдХрд░рдиреЗ рдореЗрдВ рд╡рд┐рдлрд▓ред рдХреГрдкрдпрд╛ рдкреБрдирдГ рдкреНрд░рдпрд╛рд╕ рдХрд░реЗрдВред",
|
||||||
|
"Featured": "рд╡рд┐рд╢реЗрд╖ рд░реБрдк рд╕реЗ рдкреНрд░рджрд░реНрд╢рд┐рдд",
|
||||||
|
"Filter existing users...": "рдореМрдЬреВрджрд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░реЗрдВ...",
|
||||||
|
"Filter playlists...": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░реЗрдВ...",
|
||||||
|
"Filters": "рдлрд╝рд┐рд▓реНрдЯрд░",
|
||||||
|
"Go": "рдЬрд╛рдПрдВ",
|
||||||
|
"History": "рдЗрддрд┐рд╣рд╛рд╕",
|
||||||
|
"Home": "рдореБрдЦреНрдп рдкреГрд╖реНрда",
|
||||||
|
"Image": "рдЫрд╡рд┐",
|
||||||
|
"Language": "рднрд╛рд╖рд╛",
|
||||||
|
"Latest": "рдирд╡реАрдирддрдо",
|
||||||
|
"Like count": "рдкрд╕рдВрдж рдХреА рд╕рдВрдЦреНрдпрд╛",
|
||||||
|
"Liked media": "рдкрд╕рдВрджреАрджрд╛ рдореАрдбрд┐рдпрд╛",
|
||||||
|
"Likes - Least": "рдкрд╕рдВрдж - рдХрдо рд╕реЗ рдХрдо",
|
||||||
|
"Likes - Most": "рдкрд╕рдВрдж - рд╕рдмрд╕реЗ рдЬреНрдпрд╛рджрд╛",
|
||||||
|
"Loading categories...": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд▓реЛрдб рд╣реЛ рд░рд╣реА рд╣реИрдВ...",
|
||||||
|
"Loading existing users...": "рдореМрдЬреВрджрд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд▓реЛрдб рд╣реЛ рд░рд╣реЗ рд╣реИрдВ...",
|
||||||
|
"Loading playlists...": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд▓реЛрдб рд╣реЛ рд░рд╣реА рд╣реИрдВ...",
|
||||||
|
"Loading tags...": "рдЯреИрдЧ рд▓реЛрдб рд╣реЛ рд░рд╣реЗ рд╣реИрдВ...",
|
||||||
|
"MEDIA TYPE": "рдореАрдбрд┐рдпрд╛ рдкреНрд░рдХрд╛рд░",
|
||||||
|
"Manage": "рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Manage Playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Manage comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Manage media": "рдореАрдбрд┐рдпрд╛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Manage users": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдкреНрд░рдмрдВрдзрд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Media": "рдореАрдбрд┐рдпрд╛",
|
||||||
|
"Media I own": "рдореЗрд░реЗ рд╕реНрд╡рд╛рдорд┐рддреНрд╡ рд╡рд╛рд▓реА рдореАрдбрд┐рдпрд╛",
|
||||||
|
"Media was edited": "рдореАрдбрд┐рдпрд╛ рд╕рдВрдкрд╛рджрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛",
|
||||||
|
"Members": "рд╕рджрд╕реНрдп",
|
||||||
|
"My media": "рдореЗрд░рд╛ рдореАрдбрд┐рдпрд╛",
|
||||||
|
"My playlists": "рдореЗрд░реА рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ",
|
||||||
|
"No": "рдирд╣реАрдВ",
|
||||||
|
"No categories": "рдХреЛрдИ рд╢реНрд░реЗрдгреА рдирд╣реАрдВ",
|
||||||
|
"No comment yet": "рдЕрднреА рддрдХ рдХреЛрдИ рдЯрд┐рдкреНрдкрдгреА рдирд╣реАрдВ",
|
||||||
|
"No comments yet": "рдЕрднреА рддрдХ рдХреЛрдИ рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ рдирд╣реАрдВ",
|
||||||
|
"No existing": "рдХреЛрдИ рдореМрдЬреВрджрд╛ рдирд╣реАрдВ",
|
||||||
|
"No playlists available": "рдХреЛрдИ рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдЙрдкрд▓рдмреНрдз рдирд╣реАрдВ",
|
||||||
|
"No playlists selected": "рдХреЛрдИ рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдЪрдпрдирд┐рдд рдирд╣реАрдВ",
|
||||||
|
"No results for": "рдХреЗ рд▓рд┐рдП рдХреЛрдИ рдкрд░рд┐рдгрд╛рдо рдирд╣реАрдВ",
|
||||||
|
"No tags": "рдХреЛрдИ рдЯреИрдЧ рдирд╣реАрдВ",
|
||||||
|
"No users to add": "рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдИ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╣реАрдВ",
|
||||||
|
"PLAYLISTS": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ",
|
||||||
|
"PUBLISH STATE": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐",
|
||||||
|
"Pdf": "PDF",
|
||||||
|
"Playlists": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ",
|
||||||
|
"Plays - Least": "рдкреНрд▓реЗ - рдХрдо рд╕реЗ рдХрдо",
|
||||||
|
"Plays - Most": "рдкреНрд▓реЗ - рд╕рдмрд╕реЗ рдЬреНрдпрд╛рджрд╛",
|
||||||
|
"Please select a publish state": "рдХреГрдкрдпрд╛ рдПрдХ рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐ рдЪреБрдиреЗрдВ",
|
||||||
|
"Please select a user": "рдХреГрдкрдпрд╛ рдПрдХ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЪреБрдиреЗрдВ",
|
||||||
|
"Powered by": "рджреНрд╡рд╛рд░рд╛ рд╕рдВрдЪрд╛рд▓рд┐рдд",
|
||||||
|
"Private": "рдирд┐рдЬреА",
|
||||||
|
"Proceed": "рдЖрдЧреЗ рдмрдврд╝реЗрдВ",
|
||||||
|
"Processing...": "рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг...",
|
||||||
|
"Public": "рд╕рд╛рд░реНрд╡рдЬрдирд┐рдХ",
|
||||||
|
"Publish": "рдкреНрд░рдХрд╛рд╢рд┐рдд рдХрд░реЗрдВ",
|
||||||
|
"Publish State": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐",
|
||||||
|
"Published": "рдкреНрд░рдХрд╛рд╢рд┐рдд",
|
||||||
|
"Published on": "рдкрд░ рдкреНрд░рдХрд╛рд╢рд┐рдд",
|
||||||
|
"Recent uploads": "рд╣рд╛рд▓ рдХреЗ рдЕрдкрд▓реЛрдб",
|
||||||
|
"Recommended": "рдЕрдиреБрд╢рдВрд╕рд┐рдд",
|
||||||
|
"Record Screen": "рд╕реНрдХреНрд░реАрди рд░рд┐рдХреЙрд░реНрдб рдХрд░реЗрдВ",
|
||||||
|
"Register": "рдкрдВрдЬреАрдХрд░рдг рдХрд░реЗрдВ",
|
||||||
|
"Remove category": "рд╢реНрд░реЗрдгреА рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Remove from list": "рд╕реВрдЪреА рд╕реЗ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Remove tag": "рдЯреИрдЧ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Remove user": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рд╣рдЯрд╛рдПрдВ",
|
||||||
|
"Replace": "",
|
||||||
|
"SAVE": "рд╕рд╣реЗрдЬреЗрдВ",
|
||||||
|
"SEARCH": "рдЦреЛрдЬреЗрдВ",
|
||||||
|
"SHARE": "рд╕рд╛рдЭрд╛ рдХрд░реЗрдВ",
|
||||||
|
"SHOW MORE": "рдФрд░ рджрд┐рдЦрд╛рдПрдВ",
|
||||||
|
"SORT BY": "рдЗрд╕рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдХреНрд░рдордмрджреНрдз рдХрд░реЗрдВ",
|
||||||
|
"SUBMIT": "рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдВ",
|
||||||
|
"Search": "рдЦреЛрдЬреЗрдВ",
|
||||||
|
"Search for user...": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЦреЛрдЬреЗрдВ...",
|
||||||
|
"Search users to add...": "рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЦреЛрдЬреЗрдВ...",
|
||||||
|
"Select": "рдЪреБрдиреЗрдВ",
|
||||||
|
"Select Owner": "рд╕реНрд╡рд╛рдореА рдЪреБрдиреЗрдВ",
|
||||||
|
"Select all": "рд╕рднреА рдЪреБрдиреЗрдВ",
|
||||||
|
"Select all media": "рд╕рднреА рдореАрдбрд┐рдпрд╛ рдЪреБрдиреЗрдВ",
|
||||||
|
"Select publish state:": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐ рдЪреБрдиреЗрдВ:",
|
||||||
|
"Selected": "рдЪрдпрдирд┐рдд",
|
||||||
|
"Shared by me": "рдореЗрд░реЗ рджреНрд╡рд╛рд░рд╛ рд╕рд╛рдЭрд╛ рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Shared with me": "рдореЗрд░реЗ рд╕рд╛рде рд╕рд╛рдЭрд╛ рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Sign in": "рд╕рд╛рдЗрди рдЗрди рдХрд░реЗрдВ",
|
||||||
|
"Sign out": "рд╕рд╛рдЗрди рдЖрдЙрдЯ рдХрд░реЗрдВ",
|
||||||
|
"Sort By": "рдЗрд╕рдХреЗ рдЕрдиреБрд╕рд╛рд░ рдХреНрд░рдордмрджреНрдз рдХрд░реЗрдВ",
|
||||||
|
"Start Recording": "рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рдкреНрд░рд╛рд░рдВрдн рдХрд░реЗрдВ",
|
||||||
|
"Start uploading media and sharing your work. Media that you upload will show up here.": "рдореАрдбрд┐рдпрд╛ рдЕрдкрд▓реЛрдб рдХрд░рдирд╛ рдФрд░ рдЕрдкрдирд╛ рдХрд╛рдо рд╕рд╛рдЭрд╛ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВред рдЖрдкрдХреЗ рджреНрд╡рд╛рд░рд╛ рдЕрдкрд▓реЛрдб рдХрд┐рдпрд╛ рдЧрдпрд╛ рдореАрдбрд┐рдпрд╛ рдпрд╣рд╛рдВ рджрд┐рдЦрд╛рдИ рджреЗрдЧрд╛ред",
|
||||||
|
"Stop Recording": "рд░рд┐рдХреЙрд░реНрдбрд┐рдВрдЧ рд░реЛрдХреЗрдВ",
|
||||||
|
"Submit": "рдкреНрд░рд╕реНрддреБрдд рдХрд░реЗрдВ",
|
||||||
|
"Subtitle was added": "рдЙрдкрд╢реАрд░реНрд╖рдХ рдЬреЛрдбрд╝рд╛ рдЧрдпрд╛",
|
||||||
|
"Subtitles": "рдЙрдкрд╢реАрд░реНрд╖рдХ",
|
||||||
|
"Successfully Copied": "рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдХреЙрдкреА рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully Disabled Download": "рдбрд╛рдЙрдирд▓реЛрдб рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдХреНрд╖рдо рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully Disabled comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдХреНрд╖рдо рдХреА рдЧрдИрдВ",
|
||||||
|
"Successfully Enabled Download": "рдбрд╛рдЙрдирд▓реЛрдб рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕рдХреНрд╖рдо рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully Enabled comments": "рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдВ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╕рдХреНрд╖рдо рдХреА рдЧрдИрдВ",
|
||||||
|
"Successfully changed owner": "рд╕реНрд╡рд╛рдореА рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдмрджрд▓рд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully deleted": "рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╣рдЯрд╛рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully updated": "рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛",
|
||||||
|
"Successfully updated categories": "рд╢реНрд░реЗрдгрд┐рдпрд╛рдВ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХреА рдЧрдИрдВ",
|
||||||
|
"Successfully updated playlist membership": "рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рд╕рджрд╕реНрдпрддрд╛ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХреА рдЧрдИ",
|
||||||
|
"Successfully updated publish state": "рдкреНрд░рдХрд╛рд╢рди рд╕реНрдерд┐рддрд┐ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХреА рдЧрдИ",
|
||||||
|
"Successfully updated tags": "рдЯреИрдЧ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХрд┐рдП рдЧрдП",
|
||||||
|
"TAGS": "рдЯреИрдЧ",
|
||||||
|
"Tag": "рдЯреИрдЧ",
|
||||||
|
"Tags": "рдЯреИрдЧ",
|
||||||
|
"Terms": "рд╢рд░реНрддреЗрдВ",
|
||||||
|
"The intersection of categories in the selected media is shown": "рдЪрдпрдирд┐рдд рдореАрдбрд┐рдпрд╛ рдореЗрдВ рд╢реНрд░реЗрдгрд┐рдпреЛрдВ рдХрд╛ рдкреНрд░рддрд┐рдЪреНрдЫреЗрджрди рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ",
|
||||||
|
"The intersection of playlists in the selected media is shown": "рдЪрдпрдирд┐рдд рдореАрдбрд┐рдпрд╛ рдореЗрдВ рдкреНрд▓реЗрд▓рд┐рд╕реНрдЯ рдХрд╛ рдкреНрд░рддрд┐рдЪреНрдЫреЗрджрди рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ",
|
||||||
|
"The intersection of tags in the selected media is shown": "рдЪрдпрдирд┐рдд рдореАрдбрд┐рдпрд╛ рдореЗрдВ рдЯреИрдЧ рдХрд╛ рдкреНрд░рддрд┐рдЪреНрдЫреЗрджрди рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ",
|
||||||
|
"The intersection of users in the selected media is shown": "рдЪрдпрдирд┐рдд рдореАрдбрд┐рдпрд╛ рдореЗрдВ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХрд╛ рдкреНрд░рддрд┐рдЪреНрдЫреЗрджрди рджрд┐рдЦрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ",
|
||||||
|
"The media was deleted successfully.": "рдореАрдбрд┐рдпрд╛ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рд╣рдЯрд╛рдпрд╛ рдЧрдпрд╛ред",
|
||||||
|
"This month": "рдЗрд╕ рдорд╣реАрдиреЗ",
|
||||||
|
"This week": "рдЗрд╕ рд╕рдкреНрддрд╛рд╣",
|
||||||
|
"This works in Chrome, Safari and Edge browsers.": "рдпрд╣ рдХреНрд░реЛрдо, рд╕рдлрд╛рд░реА рдФрд░ рдПрдЬ рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред",
|
||||||
|
"This year": "рдЗрд╕ рд╕рд╛рд▓",
|
||||||
|
"To add": "рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП",
|
||||||
|
"Today": "рдЖрдЬ",
|
||||||
|
"Trim": "рдЫрд╛рдВрдЯреЗрдВ",
|
||||||
|
"UPLOAD": "рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ",
|
||||||
|
"UPLOAD DATE": "рдЕрдкрд▓реЛрдб рддрд┐рдерд┐",
|
||||||
|
"UPLOAD MEDIA": "рдореАрдбрд┐рдпрд╛ рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ",
|
||||||
|
"Undo removal": "рд╣рдЯрд╛рдирд╛ рдкреВрд░реНрд╡рд╡рдд рдХрд░реЗрдВ",
|
||||||
|
"Unlisted": "рд╕реВрдЪреАрдмрджреНрдз рдирд╣реАрдВ",
|
||||||
|
"Up Next": "рдЕрдЧрд▓рд╛",
|
||||||
|
"Up next": "рдЕрдЧрд▓рд╛",
|
||||||
|
"Upload": "рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ",
|
||||||
|
"Upload date (newest)": "рдЕрдкрд▓реЛрдб рддрд┐рдерд┐ (рдирд╡реАрдирддрдо)",
|
||||||
|
"Upload date (oldest)": "рдЕрдкрд▓реЛрдб рддрд┐рдерд┐ (рдкреБрд░рд╛рдиреА)",
|
||||||
|
"Upload date - Newest": "рдЕрдкрд▓реЛрдб рддрд┐рдерд┐ - рдирд╡реАрдирддрдо",
|
||||||
|
"Upload date - Oldest": "рдЕрдкрд▓реЛрдб рддрд┐рдерд┐ - рдкреБрд░рд╛рдиреА",
|
||||||
|
"Upload media": "рдореАрдбрд┐рдпрд╛ рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ",
|
||||||
|
"Uploads": "рдЕрдкрд▓реЛрдб",
|
||||||
|
"Users": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛",
|
||||||
|
"VIEW ALL": "рд╕рднреА рджреЗрдЦреЗрдВ",
|
||||||
|
"Video": "рд╡реАрдбрд┐рдпреЛ",
|
||||||
|
"View all": "рд╕рднреА рджреЗрдЦреЗрдВ",
|
||||||
|
"View count": "рджреЗрдЦрдиреЗ рдХреА рд╕рдВрдЦреНрдпрд╛",
|
||||||
|
"View media": "рдореАрдбрд┐рдпрд╛ рджреЗрдЦреЗрдВ",
|
||||||
|
"Welcome": "рд╕реНрд╡рд╛рдЧрдд рд╣реИ",
|
||||||
|
"You are going to copy": "рдЖрдк рдХреЙрдкреА рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"You are going to delete": "рдЖрдк рд╣рдЯрд╛рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"You are going to disable comments to": "рдЖрдк рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдХреЛ рдЕрдХреНрд╖рдо рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"You are going to disable download for": "рдЖрдк рдбрд╛рдЙрдирд▓реЛрдб рдХреЛ рдЕрдХреНрд╖рдо рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"You are going to enable comments to": "рдЖрдк рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдХреЛ рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"You are going to enable download for": "рдЖрдк рдбрд╛рдЙрдирд▓реЛрдб рдХреЛ рд╕рдХреНрд╖рдо рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ",
|
||||||
|
"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": "рдЯреИрдЧ рдореЗрдВ рдореАрдбрд┐рдпрд╛",
|
||||||
|
"media, are you sure?": "рдореАрдбрд┐рдпрд╛, рдХреНрдпрд╛ рдЖрдк рдирд┐рд╢реНрдЪрд┐рдд рд╣реИрдВ?",
|
||||||
|
"media.": "рдореАрдбрд┐рдпрд╛ред",
|
||||||
|
"or": "рдпрд╛",
|
||||||
|
"results for": "рдкрд░рд┐рдгрд╛рдо",
|
||||||
|
"selected": "рдЪрдпрдирд┐рдд",
|
||||||
|
"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": "рд╕рд╛рд▓ рдкрд╣рд▓реЗ",
|
||||||
|
}
|
||||||