Compare commits

...

163 Commits

Author SHA1 Message Date
Markos Gogoulos
48537515cb changes 2026-01-30 16:14:55 +02:00
Markos Gogoulos
e6db138d11 xif 2026-01-30 15:17:22 +02:00
Markos Gogoulos
2f2d32f0db fix 2026-01-30 13:36:41 +02:00
Markos Gogoulos
f4d3439246 fix 2026-01-30 13:31:47 +02:00
Markos Gogoulos
7fe9891942 wtv 2026-01-30 13:30:32 +02:00
Markos Gogoulos
9eb8a1ad62 plug 2026-01-30 13:04:22 +02:00
Markos Gogoulos
23ee0dc7cc new 2026-01-30 13:01:21 +02:00
Markos Gogoulos
e5be39f392 feat: make S3 bucket for Terraform unique 2026-01-30 11:58:57 +02:00
Markos Gogoulos
f0c084fa53 wtv 2026-01-30 11:55:15 +02:00
Markos Gogoulos
571bfcc4ce wtv 2026-01-29 16:36:57 +02:00
Markos Gogoulos
c04380af47 w 2026-01-29 16:09:01 +02:00
Markos Gogoulos
97741f780e wtv 2026-01-29 16:03:35 +02:00
Markos Gogoulos
78cce0eb10 wtv 2026-01-29 16:01:34 +02:00
Markos Gogoulos
472b3029c4 wtv 2026-01-29 15:58:07 +02:00
Markos Gogoulos
343f1e7009 wtv 2026-01-29 15:49:11 +02:00
Markos Gogoulos
8c78b67b0c wtv 2026-01-29 15:40:33 +02:00
Markos Gogoulos
29fc7fb861 wtv 2026-01-29 15:37:41 +02:00
Markos Gogoulos
b03a33d93e wtv 2026-01-29 15:31:44 +02:00
Markos Gogoulos
64472be406 wtv 2026-01-29 15:27:49 +02:00
Markos Gogoulos
cc0f4d4645 new 2026-01-29 15:18:15 +02:00
Markos Gogoulos
095e4d2cb4 latest 2026-01-29 14:58:44 +02:00
Markos Gogoulos
5c8978453e a 2026-01-29 14:52:48 +02:00
Markos Gogoulos
83189076e4 a 2026-01-29 14:42:44 +02:00
Markos Gogoulos
ca6dbf3740 rev 2026-01-29 12:57:40 +02:00
Markos Gogoulos
8646bd70dc wtv 2026-01-29 12:49:45 +02:00
Markos Gogoulos
1f493c8e15 wtv 2026-01-29 12:49:37 +02:00
Markos Gogoulos
e11cb7ea6e wtv 2026-01-29 12:45:04 +02:00
Markos Gogoulos
3131e76ef7 this 2026-01-29 12:30:57 +02:00
Markos Gogoulos
809cdccc42 remove 2026-01-29 12:30:06 +02:00
Markos Gogoulos
ed36240f45 wtv 2026-01-28 22:46:27 +02:00
Markos Gogoulos
77bafff6f6 wtv 2026-01-28 22:44:22 +02:00
Markos Gogoulos
f6252f4f77 wtv 2026-01-28 22:40:48 +02:00
Markos Gogoulos
764580287f tv 2026-01-28 22:36:47 +02:00
Markos Gogoulos
ce6c9a0a3c tv 2026-01-28 22:35:02 +02:00
Markos Gogoulos
1ced023a07 tv 2026-01-28 22:30:13 +02:00
Markos Gogoulos
981fec296c tv 2026-01-28 22:27:19 +02:00
Markos Gogoulos
40cd7916e7 this 2026-01-28 22:25:33 +02:00
Yiannis Christodoulou
bcef59c3a9 Update select_media.html 2026-01-28 17:19:15 +02:00
Markos Gogoulos
e93c8225c4 d 2026-01-25 11:37:26 +02:00
Markos Gogoulos
5c3c33ca84 this 2026-01-25 10:26:58 +02:00
Markos Gogoulos
7a954e7a3d this 2026-01-25 10:18:00 +02:00
Markos Gogoulos
8610df0c2b this 2026-01-16 13:45:06 +02:00
Markos Gogoulos
8ab9030d14 this 2026-01-16 13:44:46 +02:00
Markos Gogoulos
15c8dec041 you 2026-01-16 13:44:04 +02:00
Markos Gogoulos
9af4686bd4 static 2026-01-10 15:59:44 +02:00
Markos Gogoulos
bcc8a0858c droplist for actions 2026-01-10 15:58:16 +02:00
Markos Gogoulos
549b672d48 add friendly_token in indexing 2026-01-10 15:30:03 +02:00
Markos Gogoulos
abe950f1da w 2026-01-09 13:42:36 +02:00
Markos Gogoulos
5fecda02d6 w 2026-01-09 13:40:20 +02:00
Markos Gogoulos
3c6f8c102c same window 2026-01-09 13:37:18 +02:00
Markos Gogoulos
2d28520cd4 Merge branch 'main' into feat-lti-integration 2026-01-09 13:29:52 +02:00
Markos Gogoulos
4bd56da2d8 push 2026-01-09 13:29:18 +02:00
Markos Gogoulos
fdfa857794 fix 2026-01-08 17:44:44 +02:00
Markos Gogoulos
2c1f27c0be remove 2026-01-08 17:30:33 +02:00
Markos Gogoulos
2f0bbd2533 gunicorn 2026-01-08 17:29:41 +02:00
Yiannis Stergiou
1c15880ae3 feat: frontend unit tests 2026-01-07 19:47:54 +02:00
Markos Gogoulos
54336f6c31 fix 2026-01-06 17:19:47 +02:00
Markos Gogoulos
37e21f7ebf this 2025-12-30 19:57:27 +02:00
Markos Gogoulos
3deee80dd0 fix 2025-12-30 19:53:30 +02:00
Markos Gogoulos
2e57164831 req 2025-12-30 17:55:23 +02:00
Markos Gogoulos
de0c16729b a 2025-12-30 15:43:42 +02:00
Markos Gogoulos
2c0bba1427 a 2025-12-30 15:30:08 +02:00
Markos Gogoulos
54a8e41f6d a 2025-12-30 15:19:36 +02:00
Markos Gogoulos
78fb19b464 a 2025-12-30 14:58:01 +02:00
Markos Gogoulos
8e5e7991b7 a 2025-12-30 14:49:40 +02:00
Markos Gogoulos
5cf435eca0 f 2025-12-30 13:50:37 +02:00
Markos Gogoulos
5026ce73da wtv 2025-12-29 20:45:48 +02:00
Markos Gogoulos
8b2ebe2415 wtv 2025-12-29 20:40:48 +02:00
Markos Gogoulos
8df320e134 wtv 2025-12-29 20:40:36 +02:00
Markos Gogoulos
8c8f737460 wtv 2025-12-29 20:32:56 +02:00
Markos Gogoulos
995faedb08 wtv 2025-12-29 20:16:21 +02:00
Markos Gogoulos
bde300b4bd all 2025-12-29 20:14:32 +02:00
Markos Gogoulos
fd5c0a2908 all 2025-12-29 20:04:41 +02:00
Markos Gogoulos
9c145da2e2 all 2025-12-29 20:02:55 +02:00
Markos Gogoulos
e9e5d44c3e wtv 2025-12-29 19:47:03 +02:00
Markos Gogoulos
a624c2e5b8 wtv 2025-12-29 19:46:36 +02:00
Markos Gogoulos
748d3b39ba wtv 2025-12-29 19:42:17 +02:00
Markos Gogoulos
ddc6bf9e67 wtv 2025-12-29 19:36:53 +02:00
Markos Gogoulos
aa7dbfe534 wtv 2025-12-29 19:34:53 +02:00
Markos Gogoulos
5cc72357c6 wtv 2025-12-29 19:33:24 +02:00
Markos Gogoulos
01b061a47b wtv 2025-12-29 19:29:41 +02:00
Markos Gogoulos
fbc78e7944 wtv 2025-12-29 19:23:55 +02:00
Markos Gogoulos
9e7a8afdda wtv 2025-12-29 19:21:26 +02:00
Markos Gogoulos
5572a67019 wtv 2025-12-29 19:16:29 +02:00
Markos Gogoulos
610590972f wtv 2025-12-29 19:15:10 +02:00
Markos Gogoulos
bdf7d3c2d0 wtv 2025-12-29 19:14:50 +02:00
Markos Gogoulos
a47bf5a3f8 wtv 2025-12-29 19:11:02 +02:00
Markos Gogoulos
38caea3c7c wtv 2025-12-29 19:08:44 +02:00
Markos Gogoulos
30491bf420 wtv 2025-12-29 19:06:58 +02:00
Markos Gogoulos
d0ebe19c2a wtv 2025-12-29 19:03:31 +02:00
Markos Gogoulos
59be9f16c0 wtv 2025-12-29 19:01:47 +02:00
Markos Gogoulos
a2d898c54e wtv 2025-12-29 18:59:24 +02:00
Markos Gogoulos
9733d53c0b wtv 2025-12-29 18:57:39 +02:00
Markos Gogoulos
70e2c67f3d wtv 2025-12-29 18:55:44 +02:00
Markos Gogoulos
77721d9c0e wtv 2025-12-29 18:48:35 +02:00
Markos Gogoulos
06bc64b2c4 all 2025-12-29 18:21:44 +02:00
Markos Gogoulos
b9899476b9 this 2025-12-29 17:45:12 +02:00
Markos Gogoulos
107750406e this 2025-12-29 17:43:54 +02:00
Markos Gogoulos
ae4ae5a07e this 2025-12-29 17:41:16 +02:00
Markos Gogoulos
f346a5604c this 2025-12-29 17:34:47 +02:00
Markos Gogoulos
56026a1a96 this 2025-12-29 17:26:19 +02:00
Markos Gogoulos
a88413ce14 this 2025-12-29 17:10:38 +02:00
Markos Gogoulos
9dab3ad858 this 2025-12-29 16:53:23 +02:00
Markos Gogoulos
dfe7e8fab0 this 2025-12-29 16:46:29 +02:00
Markos Gogoulos
1181d16ab9 this 2025-12-29 16:36:53 +02:00
Markos Gogoulos
d032ee3baa this 2025-12-29 16:35:47 +02:00
Markos Gogoulos
93f66d206b this 2025-12-29 14:17:30 +02:00
Markos Gogoulos
0585513439 this 2025-12-29 14:13:45 +02:00
Markos Gogoulos
9667e6b0ad this 2025-12-29 13:57:40 +02:00
Markos Gogoulos
f56948a4a2 this 2025-12-28 16:51:03 +02:00
Markos Gogoulos
8b3e76b554 this 2025-12-28 16:44:43 +02:00
Markos Gogoulos
dc417de628 this 2025-12-28 16:43:00 +02:00
Markos Gogoulos
35cd56c85c this 2025-12-28 16:34:59 +02:00
Markos Gogoulos
f0b2451815 this 2025-12-28 16:18:32 +02:00
Markos Gogoulos
7696251394 doc 2025-12-28 16:13:37 +02:00
Markos Gogoulos
b95725660b notes 2025-12-28 16:07:42 +02:00
Markos Gogoulos
d6bf98b30e this 2025-12-28 15:47:26 +02:00
Markos Gogoulos
3baa8ef7d7 this 2025-12-28 15:41:23 +02:00
Markos Gogoulos
45246eac4f this 2025-12-28 15:41:01 +02:00
Markos Gogoulos
9685c1b5d4 this 2025-12-28 15:39:47 +02:00
Markos Gogoulos
20a1da22bb this 2025-12-28 15:37:35 +02:00
Markos Gogoulos
f9a94321ad this 2025-12-28 15:35:29 +02:00
Markos Gogoulos
f85299a600 this 2025-12-28 15:30:37 +02:00
Markos Gogoulos
29ab2a715b this 2025-12-28 15:23:51 +02:00
Markos Gogoulos
43ce685f08 this 2025-12-28 15:22:08 +02:00
Markos Gogoulos
8c682a76af this 2025-12-28 15:19:49 +02:00
Markos Gogoulos
ec6b6daa81 this 2025-12-28 15:18:55 +02:00
Markos Gogoulos
cf90169240 this 2025-12-28 15:17:52 +02:00
Markos Gogoulos
fb3f377e27 this 2025-12-28 15:16:23 +02:00
Markos Gogoulos
f5f9a7beac this 2025-12-28 15:14:14 +02:00
Markos Gogoulos
726a5b74a1 this 2025-12-28 15:12:49 +02:00
Markos Gogoulos
40c31f295a this 2025-12-28 15:11:24 +02:00
Markos Gogoulos
1d77293afc this 2025-12-28 15:10:19 +02:00
Markos Gogoulos
5c702387ca this 2025-12-28 15:09:22 +02:00
Markos Gogoulos
0001f370a9 this 2025-12-28 15:05:40 +02:00
Markos Gogoulos
af71d4c906 this 2025-12-28 15:03:58 +02:00
Markos Gogoulos
eb7503125d this 2025-12-28 15:02:57 +02:00
Markos Gogoulos
f897d0ba2b this 2025-12-28 15:00:14 +02:00
Markos Gogoulos
545cca154e this 2025-12-28 14:39:04 +02:00
Markos Gogoulos
ef4ff9cb1d this 2025-12-28 14:33:51 +02:00
Markos Gogoulos
3a40fc6d88 this 2025-12-28 14:31:19 +02:00
Markos Gogoulos
f67d2a4d78 erq 2025-12-24 17:28:27 +02:00
Markos Gogoulos
295578dae2 lti 2025-12-24 17:28:12 +02:00
Markos Gogoulos
ed5cfa1a84 add icon on media profile page 2025-12-24 17:18:30 +02:00
Markos Gogoulos
2fe48d8522 fix formatting 2025-12-24 12:29:25 +02:00
Josh Preston
90331f3b4a Fix: Add regex denoter and improve celerybeat gitignore (#1446)
* (bugfix): Added celerybeat extras to gitignore

* (bugfix): fixed missing regex denoter

* Fix .dockerignore node_modules pattern and add comprehensive exclusions

- Fix #1398: Change 'node_modules' to '**/node_modules' to exclude all nested directories
- Add patterns for Python bytecode, IDE files, logs, and build artifacts
- Consolidate node_modules patterns in .gitignore to use **/node_modules/
2025-12-24 12:28:55 +02:00
Josh Preston
c57f528ab1 Add missing migration for Meta options on Subtitle, TranscriptionRequest, and VideoTrimRequest (#1448)
Fixes #1447

This migration adds the missing AlterModelOptions operations for:
- Subtitle model (verbose_name: 'Caption', verbose_name_plural: 'Captions')
- TranscriptionRequest model (verbose_name: 'Caption Request', verbose_name_plural: 'Caption Requests')
- VideoTrimRequest model (verbose_name: 'Trim Request', verbose_name_plural: 'Trim Requests')

These Meta options were defined in the models but never migrated, causing
makemigrations --dry-run to show pending migrations on fresh clones.
2025-12-24 12:18:48 +02:00
Markos Gogoulos
fa67ffffb4 replace media, shared state, better category options 2025-12-24 12:14:01 +02:00
Markos Gogoulos
872571350f static files 2025-12-22 11:14:35 +02:00
Markos Gogoulos
665971856b version bump 2025-12-22 11:12:37 +02:00
Yiannis Christodoulou
d9b1d6cab1 feat: Improve Visual Distinction Between Trim and Chapters Editors (#1445)
* Update .gitignore

* feat: Improve Visual Distinction Between Trim and Chapters Editors

* fix: Convert timeline header styles to CSS classes

Moved inline styles for timeline headers in chapters and video editors to dedicated CSS classes for better maintainability and consistency.

* Bump version to 7.3.0

Update the VERSION in cms/version.py to 7.3.0 for the new release.

* build assets

* Update segment color schemes in video and chapters editor.

* build assets

* build assets

* fix: Prevent Safari from resetting segments after drag operations

Prevent Safari from resetting segments when loadedmetadata fires multiple times and fix stale state issues in click handlers by using refs instead of closure variables.

* build assets

* Bump version to 7.3.0-beta.3

Update the VERSION string in cms/version.py to reflect the new pre-release version 7.3.0-beta.3.
2025-12-22 11:12:19 +02:00
Markos Gogoulos
aeef8284bf docs: update page link 2025-12-01 11:29:58 +02:00
Markos Gogoulos
a90fcbf8dd version bump 2025-11-21 12:30:12 +02:00
Markos Gogoulos
1b3cdfd302 fix: add delay to task creation 2025-11-21 12:30:05 +02:00
Yiannis Christodoulou
cd7dd4f72c fix: Chapter numbering and preserve custom titles on segment reorder (#1435)
* FIX: Preserve custom chapter titles when renumbering (151)

Updated the renumberAllSegments function to only update chapter titles that match the default 'Chapter X' pattern, preserving any custom titles. Also ensured segments are renumbered after updates for consistent chronological naming.

* build assets (chapters editor)
2025-11-21 12:29:19 +02:00
Markos Gogoulos
9b3d9fe1e7 trim (#1431) 2025-11-13 12:42:48 +02:00
Markos Gogoulos
ea340b6a2e V7 f4 (#1430) 2025-11-13 12:30:25 +02:00
Markos Gogoulos
ba2c31b1e6 fix: static files (#1429) 2025-11-12 14:08:02 +02:00
Yiannis Christodoulou
5eb6fafb8c fix: Show default chapter names in textarea instead of placeholder text (#1428)
* Refactor chapter filtering and auto-save logic

Simplified chapter filtering to only exclude empty titles, allowing default chapter names. Updated auto-save logic to skip saving when there are no chapters or mediaId. Removed unused helper function and improved debug logging.

* Show default chapter title in editor and set initial title

The chapter title is now always displayed in the textarea, including default names like 'Chapter 1'. Also, the initial segment is created with 'Chapter 1' as its title instead of an empty string for better clarity.

* build assets
2025-11-12 14:04:07 +02:00
Markos Gogoulos
c035bcddf5 small 7.2.x fixes 2025-11-11 19:51:42 +02:00
Markos Gogoulos
01912ea1f9 fix: adjust poster url for audio 2025-11-11 13:21:10 +02:00
Markos Gogoulos
d9f299af4d V7 small fixes (#1426) 2025-11-11 13:15:36 +02:00
Markos Gogoulos
e80590a3aa Bulk actions support (#1418) 2025-11-11 11:32:54 +02:00
297 changed files with 25108 additions and 2611 deletions

View File

@@ -1,2 +1,69 @@
node_modules
npm-debug.log
# Node.js/JavaScript dependencies and artifacts
**/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/**

View 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

8
.gitignore vendored
View File

@@ -6,8 +6,9 @@ media_files/hls/
media_files/chunks/
media_files/uploads/
media_files/tinymce_media/
media_files/userlogos/
postgres_data/
celerybeat-schedule
celerybeat-schedule*
logs/
pids/
static/admin/
@@ -19,8 +20,8 @@ static/drf-yasg
cms/local_settings.py
deploy/docker/local_settings.py
yt.readme.md
/frontend-tools/video-editor/node_modules
/frontend-tools/video-editor/client/node_modules
# Node.js dependencies (covers all node_modules directories, including frontend-tools)
**/node_modules/
/static_collected
/frontend-tools/video-editor-v1
frontend-tools/.DS_Store
@@ -35,3 +36,4 @@ 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

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/isort

View File

@@ -1,23 +0,0 @@
# History
## 3.0.0
### Features
- Updates Python/Django requirements and Dockerfile to use latest 3.11 Python - https://github.com/mediacms-io/mediacms/pull/826/files. This update requires some manual steps, for existing (not new) installations. Check the update section under the [Admin docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#2-server-installation), either for single server or for Docker Compose installations
- Upgrade postgres on Docker Compose - https://github.com/mediacms-io/mediacms/pull/749
### Fixes
- video player options for HLS - https://github.com/mediacms-io/mediacms/pull/832
- AVI videos not correctly recognised as videos - https://github.com/mediacms-io/mediacms/pull/833
## 2.1.0
### Fixes
- Increase uwsgi buffer-size parameter. This prevents an error by uwsgi with large headers - [#5b60](https://github.com/mediacms-io/mediacms/commit/5b601698a41ad97f08c1830e14b1c18f73ab8315)
- Fix issues with comments. These were not reported on the tracker but it is certain that they would not show comments on media files (non videos but also videos). Unfortunately this reverts work done with Timestamps on comments + Mentions on comments, more on PR [#802](https://github.com/mediacms-io/mediacms/pull/802)
### Features
- Allow tags to contains other characters too, not only English alphabet ones [#801](https://github.com/mediacms-io/mediacms/pull/801)
- Add simple cookie consent code [#799](https://github.com/mediacms-io/mediacms/pull/799)
- Allow password reset & email verify pages on global login required [#790](https://github.com/mediacms-io/mediacms/pull/790)
- Add api_url field to search api [#692](https://github.com/mediacms-io/mediacms/pull/692)

253
LTI_SETUP.md Executable file
View 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

View File

@@ -69,7 +69,7 @@ Copyright Markos Gogoulos.
## Support and paid services
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
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**
@@ -108,7 +108,7 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
## 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

View File

@@ -24,6 +24,7 @@ INSTALLED_APPS = [
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",

View File

@@ -100,6 +100,9 @@ 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_L10N = True
USE_TZ = True
@@ -297,6 +300,7 @@ INSTALLED_APPS = [
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",
@@ -552,6 +556,7 @@ DJANGO_ADMIN_URL = "admin/"
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
@@ -560,13 +565,19 @@ ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False
# Whether to allow anonymous users to list all users
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
@@ -641,3 +652,19 @@ if USERS_NEEDS_TO_BE_APPROVED:
)
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

View File

@@ -25,6 +25,7 @@ urlpatterns = [
re_path(r"^", include("files.urls")),
re_path(r"^", include("users.urls")),
re_path(r"^accounts/", include("allauth.urls")),
re_path(r"^lti/", include("lti.urls")),
re_path(r"^api-auth/", include("rest_framework.urls")),
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),

View File

@@ -1 +1 @@
VERSION = "7.0.1-beta.8"
VERSION = "7.8124"

View File

@@ -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 {
listen 80 ;
@@ -28,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-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
proxy_pass http://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;
}
}

View File

@@ -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-enabled/default
cp deploy/docker/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
cp deploy/docker/nginx.conf /etc/nginx/
#### 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
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
echo "Enabling uwsgi app server"
cp deploy/docker/supervisord/supervisord-uwsgi.conf /etc/supervisor/conf.d/supervisord-uwsgi.conf
echo "Enabling gunicorn app server"
cp deploy/docker/supervisord/supervisord-gunicorn.conf /etc/supervisor/conf.d/supervisord-gunicorn.conf
fi
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
fi

View File

@@ -11,7 +11,7 @@ else
echo "There is no script $PRE_START_PATH"
fi
# Start Supervisor, with Nginx and uWSGI
# Start Supervisor, with Nginx and Gunicorn
echo "Starting server using supervisord..."
exec /usr/bin/supervisord

View 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

View File

@@ -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

View File

@@ -1,24 +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
buffer-size=32768

View File

@@ -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;

View File

@@ -1,22 +0,0 @@
[Unit]
Description=MediaCMS celery beat
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
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"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms beat --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -1,29 +0,0 @@
[Unit]
Description=MediaCMS celery long queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERYD_NODES="long1"
Environment=CELERY_QUEUE="long_tasks"
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
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"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -1,39 +0,0 @@
[Unit]
Description=MediaCMS celery short queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
WorkingDirectory=/home/mediacms.io/mediacms
Environment=CELERYD_NODES="short1 short2"
Environment=CELERY_QUEUE="short_tasks"
# Absolute or relative path to the 'celery' command:
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
# App instance to use
# comment out this line if you don't use an app
# 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"
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -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-----

View File

@@ -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;
}
}

View File

@@ -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-----

View File

@@ -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-----

View File

@@ -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

View File

@@ -1,7 +0,0 @@
/home/mediacms.io/mediacms/logs/*.log {
weekly
missingok
rotate 7
compress
notifempty
}

View File

@@ -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/*;
}

View File

@@ -1,34 +0,0 @@
module selinux-mediacms 1.0;
require {
type init_t;
type var_t;
type redis_port_t;
type postgresql_port_t;
type httpd_t;
type httpd_sys_content_t;
type httpd_sys_rw_content_t;
class file { append create execute execute_no_trans getattr ioctl lock open read rename setattr unlink write };
class dir { add_name remove_name rmdir };
class tcp_socket name_connect;
class lnk_file read;
}
#============= httpd_t ==============
allow httpd_t var_t:file { getattr open read };
#============= init_t ==============
allow init_t postgresql_port_t:tcp_socket name_connect;
allow init_t redis_port_t:tcp_socket name_connect;
allow init_t httpd_sys_content_t:dir rmdir;
allow init_t httpd_sys_content_t:file { append create execute execute_no_trans ioctl lock open read rename setattr unlink write };
allow init_t httpd_sys_content_t:lnk_file read;
allow init_t httpd_sys_rw_content_t:dir { add_name remove_name rmdir };
allow init_t httpd_sys_rw_content_t:file { create ioctl lock open read setattr unlink write };

View File

@@ -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
buffer-size=32768

View File

@@ -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;

View File

@@ -533,11 +533,49 @@ By default `CAN_SEE_MEMBERS_PAGE = "all"` means that all registered users can se
- **admins**, only MediaCMS admins can view the page
### 5.28 Require user approval on registration
### 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
to be written

View File

@@ -23,7 +23,7 @@ and will start all services required for MediaCMS, as Celery/Redis for asynchron
For Django, the changes from the image produced by docker-compose.yaml are these:
* Django runs in debug mode, with `python manage.py runserver`
* uwsgi and nginx are not run
* 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

View File

@@ -65,6 +65,7 @@ class CategoryAdminForm(forms.ModelForm):
class Meta:
model = Category
# LTI fields will be shown as read-only when USE_LTI is enabled
fields = '__all__'
def clean(self):
@@ -135,7 +136,7 @@ class CategoryAdmin(admin.ModelAdmin):
list_display = ["title", "user", "add_date", "media_count"]
list_filter = []
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):
@@ -167,6 +168,14 @@ class CategoryAdmin(admin.ModelAdmin):
),
]
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'}),
@@ -177,9 +186,9 @@ class CategoryAdmin(admin.ModelAdmin):
('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'}),
]
return basic_fieldset + rbac_fieldset
else:
return basic_fieldset
additional_fieldsets.extend(rbac_fieldset)
return basic_fieldset + additional_fieldsets
class TagAdmin(admin.ModelAdmin):
@@ -201,11 +210,18 @@ class LanguageAdmin(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):
pass
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):
@@ -224,7 +240,11 @@ class EncodingAdmin(admin.ModelAdmin):
class TranscriptionRequestAdmin(admin.ModelAdmin):
pass
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):

View File

@@ -57,9 +57,17 @@ def stuff(request):
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

View File

@@ -6,6 +6,7 @@ from django.conf import settings
from .methods import get_next_state, is_mediacms_editor
from .models import MEDIA_STATES, Category, Media, Subtitle
from .widgets import CategoryModalWidget
class CustomField(Field):
@@ -121,13 +122,18 @@ class MediaPublishForm(forms.ModelForm):
fields = ("category", "state", "featured", "reported_times", "is_reviewed", "allow_download")
widgets = {
"category": MultipleSelect(),
"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
@@ -140,6 +146,13 @@ class MediaPublishForm(forms.ModelForm):
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
@@ -178,34 +191,76 @@ class MediaPublishForm(forms.ModelForm):
state = cleaned_data.get("state")
categories = cleaned_data.get("category")
if getattr(settings, 'USE_RBAC', False) and 'category' in self.fields:
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 and state in ['private', 'unlisted']:
# Make the confirm_state field visible and add it to the layout
if rbac_categories or custom_permissions:
self.fields['confirm_state'].widget = forms.CheckboxInput()
# add it after the state field
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:
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'):
error_message = f"I understand that although media state is {state}, the media is also shared with users that have access to the following categories: {', '.join(rbac_categories)}"
self.add_error('confirm_state', error_message)
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):
data = self.cleaned_data
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"]:
self.instance.state = get_next_state(self.user, self.initial["state"], self.instance.state)
@@ -332,3 +387,35 @@ class ContactForm(forms.Form):
if user.is_authenticated:
self.fields.pop("name")
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

View File

@@ -1,21 +1,57 @@
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": "تعديل الملف الشخصي",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "بعد",

View File

@@ -1,21 +1,57 @@
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": "প্রোফাইল সম্পাদনা করুন",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "এখনও",

View File

@@ -1,21 +1,57 @@
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",
@@ -23,63 +59,200 @@ translation_strings = {
"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",

View File

@@ -1,21 +1,57 @@
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",
@@ -23,63 +59,200 @@ translation_strings = {
"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",

View File

@@ -1,21 +1,57 @@
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": "ΕΠΕΞΕΡΓΑΣΙΑ ΠΡΟΦΙΛ",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "ακόμα",

View File

@@ -1,88 +1,261 @@
translation_strings = {
"+ Create Playlist": "",
"00 - 20 min": "",
"1 result for": "",
"20 - 40 min": "",
"40 - 60 min": "",
"60 - 120 min+": "",
"ABOUT": "",
"AUTOPLAY": "",
"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": "",
"COMMENT": "",
"Bulk Actions": "",
"Cancel": "",
"Categories": "",
"Category": "",
"Change Language": "",
"Change Owner": "",
"Change password": "",
"About": "",
"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": "",
"Drag and drop files": "",
"Delete media": "",
"Delete Media": "",
"Disable Comments": "",
"Disable Download": "",
"DOWNLOAD": "",
"Drag and drop files": "",
"DURATION": "",
"EDIT MEDIA": "",
"EDIT PROFILE": "",
"EDIT SUBTITLE": "",
"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": "",
"SHARE": "",
"SHOW MORE": "",
"SUBMIT": "",
"Subtitles": "",
"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": "",
"UPLOAD": "",
"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": "",
"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": "",
"or": "",
"view": "",
"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 = {

View File

@@ -1,21 +1,57 @@
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",
@@ -23,63 +59,200 @@ translation_strings = {
"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",

View File

@@ -1,22 +1,58 @@
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",
@@ -24,63 +60,200 @@ translation_strings = {
"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",

View File

@@ -1,21 +1,57 @@
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": "ערוך פרופיל",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "עדיין",

View File

@@ -1,21 +1,57 @@
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": "प्रोफ़ाइल संपादित करें",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "अभी तक",

View File

@@ -1,21 +1,57 @@
translation_strings = {
"+ Create Playlist": "+ Buat Daftar Putar",
"00 - 20 min": "00 - 20 menit",
"1 result for": "1 hasil untuk",
"20 - 40 min": "20 - 40 menit",
"40 - 60 min": "40 - 60 menit",
"60 - 120 min+": "60 - 120 menit+",
"ABOUT": "TENTANG",
"AUTOPLAY": "PUTAR OTOMATIS",
"About": "Tentang",
"Add / Remove Co-Editors": "Tambah / Hapus Editor Bersama",
"Add / Remove Co-Owners": "Tambah / Hapus Pemilik Bersama",
"Add / Remove Co-Viewers": "Tambah / Hapus Penonton Bersama",
"Add / Remove Tags": "Tambah / Hapus Tag",
"Add / Remove from Categories": "Tambah / Hapus dari Kategori",
"Add a ": "Tambahkan ",
"Add to": "Tambahkan ke",
"Add to / Remove from Category": "Tambah / Hapus dari Kategori",
"Add to / Remove from Playlist": "Tambah / Hapus dari Daftar Putar",
"All": "Semua",
"All categories already added": "Semua kategori sudah ditambahkan",
"All tags already added": "Semua tag sudah ditambahkan",
"Alphabetically - A-Z": "Alfabetis - A-Z",
"Alphabetically - Z-A": "Alfabetis - Z-A",
"Audio": "Audio",
"Browse your files": "Jelajahi file Anda",
"Bulk Actions": "Aksi Massal",
"COMMENT": "KOMENTAR",
"Cancel": "Batal",
"Categories": "Kategori",
"Category": "Kategori",
"Change Language": "Ganti Bahasa",
"Change Owner": "Ganti Pemilik",
"Change password": "Ganti kata sandi",
"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 'Mulai Merekam' dan pilih layar atau tab untuk merekam. Setelah perekaman selesai, klik 'Hentikan Perekaman,' dan rekaman akan diunggah.",
"Co-Editors": "Editor Bersama",
"Co-Owners": "Pemilik Bersama",
"Co-Viewers": "Penonton Bersama",
"Comment": "Komentar",
"Comments": "Komentar",
"Comments are disabled": "Komentar dinonaktifkan",
"Confirm": "Konfirmasi",
"Confirm Action": "Konfirmasi Aksi",
"Contact": "Kontak",
"Copy Media": "Salin Media",
"Create": "Buat",
"DELETE": "HAPUS",
"DELETE MEDIA": "HAPUS MEDIA",
"DOWNLOAD": "UNDUH",
"DURATION": "DURASI",
"Delete Media": "Hapus Media",
"Delete media": "Hapus media",
"Disable Comments": "Nonaktifkan Komentar",
"Disable Download": "Nonaktifkan Unduhan",
"Drag and drop files": "Seret dan lepas file",
"EDIT MEDIA": "EDIT MEDIA",
"EDIT PROFILE": "EDIT PROFIL",
@@ -23,63 +59,200 @@ translation_strings = {
"Edit media": "Edit media",
"Edit profile": "Edit profil",
"Edit subtitle": "Edit subtitle",
"Enable Comments": "Aktifkan Komentar",
"Enable Download": "Aktifkan Unduhan",
"Enter playlist name...": "Masukkan nama daftar putar...",
"Failed to add categories": "Gagal menambahkan kategori",
"Failed to add media to playlists": "Gagal menambahkan media ke daftar putar",
"Failed to add tags": "Gagal menambahkan tag",
"Failed to add users": "Gagal menambahkan pengguna",
"Failed to change owner": "Gagal mengganti pemilik",
"Failed to change owner. Please try again.": "Gagal mengganti pemilik. Silakan coba lagi.",
"Failed to copy media.": "Gagal menyalin media.",
"Failed to create playlist": "Gagal membuat daftar putar",
"Failed to delete media. Please try again.": "Gagal menghapus media. Silakan coba lagi.",
"Failed to disable comments.": "Gagal menonaktifkan komentar.",
"Failed to disable download.": "Gagal menonaktifkan unduhan.",
"Failed to enable comments.": "Gagal mengaktifkan komentar.",
"Failed to enable download.": "Gagal mengaktifkan unduhan.",
"Failed to fetch all categories": "Gagal mengambil semua kategori",
"Failed to fetch all tags": "Gagal mengambil semua tag",
"Failed to fetch existing categories": "Gagal mengambil kategori yang ada",
"Failed to fetch existing tags": "Gagal mengambil tag yang ada",
"Failed to fetch existing users": "Gagal mengambil pengguna yang ada",
"Failed to fetch playlist membership": "Gagal mengambil keanggotaan daftar putar",
"Failed to fetch playlists": "Gagal mengambil daftar putar",
"Failed to load categories": "Gagal memuat kategori",
"Failed to load existing permissions": "Gagal memuat izin yang ada",
"Failed to load playlists": "Gagal memuat daftar putar",
"Failed to load tags": "Gagal memuat tag",
"Failed to remove categories": "Gagal menghapus kategori",
"Failed to remove media from playlists": "Gagal menghapus media dari daftar putar",
"Failed to remove tags": "Gagal menghapus tag",
"Failed to remove users": "Gagal menghapus pengguna",
"Failed to search users": "Gagal mencari pengguna",
"Failed to set publish state": "Gagal mengatur status publikasi",
"Failed to set publish state. Please try again.": "Gagal mengatur status publikasi. Silakan coba lagi.",
"Failed to update categories. Please try again.": "Gagal memperbarui kategori. Silakan coba lagi.",
"Failed to update permissions. Please try again.": "Gagal memperbarui izin. Silakan coba lagi.",
"Failed to update playlists. Please try again.": "Gagal memperbarui daftar putar. Silakan coba lagi.",
"Failed to update tags. Please try again.": "Gagal memperbarui tag. Silakan coba lagi.",
"Featured": "Unggulan",
"Filter existing users...": "Filter pengguna yang ada...",
"Filter playlists...": "Filter daftar putar...",
"Filters": "Filter",
"Go": "Pergi",
"History": "Riwayat",
"Home": "Beranda",
"Image": "Gambar",
"Language": "Bahasa",
"Latest": "Terbaru",
"Like count": "Jumlah suka",
"Liked media": "Media yang disukai",
"Likes - Least": "Suka - Paling Sedikit",
"Likes - Most": "Suka - Paling Banyak",
"Loading categories...": "Memuat kategori...",
"Loading existing users...": "Memuat pengguna yang ada...",
"Loading playlists...": "Memuat daftar putar...",
"Loading tags...": "Memuat tag...",
"MEDIA TYPE": "JENIS MEDIA",
"Manage": "Kelola",
"Manage Playlists": "Kelola Daftar Putar",
"Manage comments": "Kelola komentar",
"Manage media": "Kelola media",
"Manage users": "Kelola pengguna",
"Media": "Media",
"Media I own": "Media yang saya miliki",
"Media was edited": "Media telah diedit",
"Members": "Anggota",
"My media": "Media saya",
"My playlists": "Daftar putar saya",
"No": "Tidak",
"No categories": "Tidak ada kategori",
"No comment yet": "Belum ada komentar",
"No comments yet": "Belum ada komentar",
"No existing": "Tidak ada yang sudah ada",
"No playlists available": "Tidak ada daftar putar yang tersedia",
"No playlists selected": "Tidak ada daftar putar yang dipilih",
"No results for": "Tidak ada hasil untuk",
"No tags": "Tidak ada tag",
"No users to add": "Tidak ada pengguna untuk ditambahkan",
"PLAYLISTS": "DAFTAR PUTAR",
"PUBLISH STATE": "STATUS PUBLIKASI",
"Pdf": "PDF",
"Playlists": "Daftar putar",
"Plays - Least": "Pemutaran - Paling Sedikit",
"Plays - Most": "Pemutaran - Paling Banyak",
"Please select a publish state": "Silakan pilih status publikasi",
"Please select a user": "Silakan pilih pengguna",
"Powered by": "Didukung oleh",
"Private": "Pribadi",
"Proceed": "Lanjutkan",
"Processing...": "Memproses...",
"Public": "Publik",
"Publish": "Terbitkan",
"Publish State": "Status Publikasi",
"Published": "Diterbitkan",
"Published on": "Diterbitkan pada",
"Recent uploads": "Unggahan terbaru",
"Recommended": "Direkomendasikan",
"Record Screen": "Rekam Layar",
"Register": "Daftar",
"Remove category": "Hapus kategori",
"Remove from list": "Hapus dari daftar",
"Remove tag": "Hapus tag",
"Remove user": "Hapus pengguna",
"Replace": "",
"SAVE": "SIMPAN",
"SEARCH": "CARI",
"SHARE": "BAGIKAN",
"SHOW MORE": "TAMPILKAN LEBIH BANYAK",
"SORT BY": "URUTKAN BERDASARKAN",
"SUBMIT": "KIRIM",
"Search": "Cari",
"Search for user...": "Cari pengguna...",
"Search users to add...": "Cari pengguna untuk ditambahkan...",
"Select": "Pilih",
"Select Owner": "Pilih Pemilik",
"Select all": "Pilih semua",
"Select all media": "Pilih semua media",
"Select publish state:": "Pilih status publikasi:",
"Selected": "Dipilih",
"Shared by me": "Dibagikan oleh saya",
"Shared with me": "Dibagikan dengan saya",
"Sign in": "Masuk",
"Sign out": "Keluar",
"Sort By": "Urutkan Berdasarkan",
"Start Recording": "Mulai Merekam",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Mulai mengunggah media dan berbagi karya Anda. Media yang Anda unggah akan muncul di sini.",
"Stop Recording": "Hentikan Perekaman",
"Submit": "Kirim",
"Subtitle was added": "Subtitle telah ditambahkan",
"Subtitles": "Subtitel",
"Successfully Copied": "Berhasil Disalin",
"Successfully Disabled Download": "Unduhan Berhasil Dinonaktifkan",
"Successfully Disabled comments": "Komentar berhasil dinonaktifkan",
"Successfully Enabled Download": "Unduhan Berhasil Diaktifkan",
"Successfully Enabled comments": "Komentar berhasil diaktifkan",
"Successfully changed owner": "Berhasil mengganti pemilik",
"Successfully deleted": "Berhasil dihapus",
"Successfully updated": "Berhasil diperbarui",
"Successfully updated categories": "Kategori berhasil diperbarui",
"Successfully updated playlist membership": "Keanggotaan daftar putar berhasil diperbarui",
"Successfully updated publish state": "Status publikasi berhasil diperbarui",
"Successfully updated tags": "Tag berhasil diperbarui",
"TAGS": "TAG",
"Tag": "Tag",
"Tags": "Tag",
"Terms": "Ketentuan",
"The intersection of categories in the selected media is shown": "Irisan kategori dalam media yang dipilih ditampilkan",
"The intersection of playlists in the selected media is shown": "Irisan daftar putar dalam media yang dipilih ditampilkan",
"The intersection of tags in the selected media is shown": "Irisan tag dalam media yang dipilih ditampilkan",
"The intersection of users in the selected media is shown": "Irisan pengguna dalam media yang dipilih ditampilkan",
"The media was deleted successfully.": "Media berhasil dihapus.",
"This month": "Bulan ini",
"This week": "Minggu ini",
"This works in Chrome, Safari and Edge browsers.": "Ini berfungsi di browser Chrome, Safari, dan Edge.",
"This year": "Tahun ini",
"To add": "Untuk ditambahkan",
"Today": "Hari ini",
"Trim": "Potong",
"UPLOAD": "UNGGAH",
"UPLOAD DATE": "TANGGAL UNGGAH",
"UPLOAD MEDIA": "UNGGAH MEDIA",
"Undo removal": "Batalkan penghapusan",
"Unlisted": "Tidak terdaftar",
"Up Next": "Selanjutnya",
"Up next": "Selanjutnya",
"Upload": "Unggah",
"Upload date (newest)": "Tanggal unggah (terbaru)",
"Upload date (oldest)": "Tanggal unggah (terlama)",
"Upload date - Newest": "Tanggal unggah - Terbaru",
"Upload date - Oldest": "Tanggal unggah - Terlama",
"Upload media": "Unggah media",
"Uploads": "Unggahan",
"Users": "Pengguna",
"VIEW ALL": "LIHAT SEMUA",
"Video": "Video",
"View all": "Lihat semua",
"View count": "Jumlah tampilan",
"View media": "Lihat media",
"Welcome": "Selamat datang",
"You are going to copy": "Anda akan menyalin",
"You are going to delete": "Anda akan menghapus",
"You are going to disable comments to": "Anda akan menonaktifkan komentar untuk",
"You are going to disable download for": "Anda akan menonaktifkan unduhan untuk",
"You are going to enable comments to": "Anda akan mengaktifkan komentar untuk",
"You are going to enable download for": "Anda akan mengaktifkan unduhan untuk",
"comment": "komentar",
"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": "adalah CMS video dan media open source yang modern dan lengkap. Ini dikembangkan untuk memenuhi kebutuhan platform web modern untuk menonton dan berbagi media",
"media in category": "media dalam kategori",
"media in tag": "media dalam tag",
"media, are you sure?": "media, apakah Anda yakin?",
"media.": "media.",
"or": "atau",
"results for": "hasil untuk",
"selected": "dipilih",
"view": "lihat",
"views": "tampilan",
"yet": "belum",

View File

@@ -1,22 +1,58 @@
translation_strings = {
"+ Create Playlist": "+ Crea Playlist",
"00 - 20 min": "00 - 20 min",
"1 result for": "1 risultato per",
"20 - 40 min": "20 - 40 min",
"40 - 60 min": "40 - 60 min",
"60 - 120 min+": "60 - 120 min+",
"ABOUT": "SU DI NOI",
"AUTOPLAY": "RIPRODUZIONE AUTOMATICA",
"About": "Su di noi",
"Add / Remove Co-Editors": "Aggiungi / Rimuovi Co-Editor",
"Add / Remove Co-Owners": "Aggiungi / Rimuovi Co-Proprietari",
"Add / Remove Co-Viewers": "Aggiungi / Rimuovi Co-Visualizzatori",
"Add / Remove Tags": "Aggiungi / Rimuovi Tag",
"Add / Remove from Categories": "Aggiungi / Rimuovi dalle Categorie",
"Add a": "Aggiungi un",
"Add a ": "Aggiungi un ",
"Add to": "Aggiungi a",
"Add to / Remove from Category": "Aggiungi / Rimuovi dalla Categoria",
"Add to / Remove from Playlist": "Aggiungi / Rimuovi dalla Playlist",
"All": "Tutti",
"All categories already added": "Tutte le categorie già aggiunte",
"All tags already added": "Tutti i tag già aggiunti",
"Alphabetically - A-Z": "Alfabeticamente - A-Z",
"Alphabetically - Z-A": "Alfabeticamente - Z-A",
"Audio": "Audio",
"Browse your files": "Sfoglia i tuoi file",
"Bulk Actions": "Azioni di Massa",
"COMMENT": "COMMENTA",
"Cancel": "Annulla",
"Categories": "Categorie",
"Category": "Categoria",
"Change Language": "Cambia lingua",
"Change Owner": "Cambia Proprietario",
"Change password": "Cambia 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.": "Fai clic su 'Avvia registrazione' e seleziona lo schermo o la scheda da registrare. Una volta terminata la registrazione, fai clic su 'Interrompi registrazione' e la registrazione verrà caricata.",
"Co-Editors": "Co-Editor",
"Co-Owners": "Co-Proprietari",
"Co-Viewers": "Co-Visualizzatori",
"Comment": "Commento",
"Comments": "Commenti",
"Comments are disabled": "I commenti sono disabilitati",
"Confirm": "Conferma",
"Confirm Action": "Conferma Azione",
"Contact": "Contatti",
"Copy Media": "Copia Media",
"Create": "Crea",
"DELETE": "ELIMINA",
"DELETE MEDIA": "ELIMINA MEDIA",
"DOWNLOAD": "SCARICA",
"DURATION": "DURATA",
"Delete Media": "Elimina Media",
"Delete media": "Elimina media",
"Disable Comments": "Disabilita Commenti",
"Disable Download": "Disabilita Download",
"Drag and drop files": "Trascina e rilascia i file",
"EDIT MEDIA": "MODIFICA IL MEDIA",
"EDIT PROFILE": "MODIFICA IL PROFILO",
@@ -24,63 +60,200 @@ translation_strings = {
"Edit media": "Modifica il media",
"Edit profile": "Modifica il profilo",
"Edit subtitle": "Modifica i sottotitoli",
"Enable Comments": "Abilita Commenti",
"Enable Download": "Abilita Download",
"Enter playlist name...": "Inserisci nome playlist...",
"Failed to add categories": "Impossibile aggiungere categorie",
"Failed to add media to playlists": "Impossibile aggiungere media alle playlist",
"Failed to add tags": "Impossibile aggiungere tag",
"Failed to add users": "Impossibile aggiungere utenti",
"Failed to change owner": "Impossibile cambiare proprietario",
"Failed to change owner. Please try again.": "Impossibile cambiare proprietario. Riprova.",
"Failed to copy media.": "Impossibile copiare il media.",
"Failed to create playlist": "Impossibile creare playlist",
"Failed to delete media. Please try again.": "Impossibile eliminare il media. Riprova.",
"Failed to disable comments.": "Impossibile disabilitare i commenti.",
"Failed to disable download.": "Impossibile disabilitare il download.",
"Failed to enable comments.": "Impossibile abilitare i commenti.",
"Failed to enable download.": "Impossibile abilitare il download.",
"Failed to fetch all categories": "Impossibile recuperare tutte le categorie",
"Failed to fetch all tags": "Impossibile recuperare tutti i tag",
"Failed to fetch existing categories": "Impossibile recuperare le categorie esistenti",
"Failed to fetch existing tags": "Impossibile recuperare i tag esistenti",
"Failed to fetch existing users": "Impossibile recuperare gli utenti esistenti",
"Failed to fetch playlist membership": "Impossibile recuperare l'appartenenza alla playlist",
"Failed to fetch playlists": "Impossibile recuperare le playlist",
"Failed to load categories": "Impossibile caricare le categorie",
"Failed to load existing permissions": "Impossibile caricare i permessi esistenti",
"Failed to load playlists": "Impossibile caricare le playlist",
"Failed to load tags": "Impossibile caricare i tag",
"Failed to remove categories": "Impossibile rimuovere le categorie",
"Failed to remove media from playlists": "Impossibile rimuovere media dalle playlist",
"Failed to remove tags": "Impossibile rimuovere i tag",
"Failed to remove users": "Impossibile rimuovere gli utenti",
"Failed to search users": "Impossibile cercare utenti",
"Failed to set publish state": "Impossibile impostare lo stato di pubblicazione",
"Failed to set publish state. Please try again.": "Impossibile impostare lo stato di pubblicazione. Riprova.",
"Failed to update categories. Please try again.": "Impossibile aggiornare le categorie. Riprova.",
"Failed to update permissions. Please try again.": "Impossibile aggiornare i permessi. Riprova.",
"Failed to update playlists. Please try again.": "Impossibile aggiornare le playlist. Riprova.",
"Failed to update tags. Please try again.": "Impossibile aggiornare i tag. Riprova.",
"Featured": "In evidenza",
"Filter existing users...": "Filtra utenti esistenti...",
"Filter playlists...": "Filtra playlist...",
"Filters": "Filtri",
"Go": "Vai",
"History": "Cronologia",
"Home": "Home",
"Image": "Immagine",
"Language": "Lingua",
"Latest": "Ultimi",
"Like count": "Numero di mi piace",
"Liked media": "Piaciuti",
"Likes - Least": "Mi piace - Meno",
"Likes - Most": "Mi piace - Più",
"Loading categories...": "Caricamento categorie...",
"Loading existing users...": "Caricamento utenti esistenti...",
"Loading playlists...": "Caricamento playlist...",
"Loading tags...": "Caricamento tag...",
"MEDIA TYPE": "TIPO DI MEDIA",
"Manage": "Gestisci",
"Manage Playlists": "Gestisci Playlist",
"Manage comments": "Gestisci i commenti",
"Manage media": "Gestisci i media",
"Manage users": "Gestisci gli utenti",
"Media": "Media",
"Media I own": "Media di mia proprietà",
"Media was edited": "Il media è stato modificato",
"Members": "Membri",
"My media": "I miei media",
"My playlists": "Le mie playlist",
"No": "No",
"No categories": "Nessuna categoria",
"No comment yet": "Ancora nessun commento",
"No comments yet": "Ancora nessun commento",
"No existing": "Nessun esistente",
"No playlists available": "Nessuna playlist disponibile",
"No playlists selected": "Nessuna playlist selezionata",
"No results for": "Nessun risultato per",
"No tags": "Nessun tag",
"No users to add": "Nessun utente da aggiungere",
"PLAYLISTS": "PLAYLIST",
"PUBLISH STATE": "STATO DI PUBBLICAZIONE",
"Pdf": "PDF",
"Playlists": "Playlist",
"Plays - Least": "Riproduzioni - Meno",
"Plays - Most": "Riproduzioni - Più",
"Please select a publish state": "Seleziona uno stato di pubblicazione",
"Please select a user": "Seleziona un utente",
"Powered by": "Powered by",
"Private": "Privato",
"Proceed": "Procedi",
"Processing...": "Elaborazione...",
"Public": "Pubblico",
"Publish": "Pubblica",
"Publish State": "Stato di Pubblicazione",
"Published": "Pubblicato",
"Published on": "Pubblicato il",
"Recent uploads": "Caricamenti recenti",
"Recommended": "Raccomandati",
"Record Screen": "Registra schermo",
"Register": "Registrati",
"Remove category": "Rimuovi categoria",
"Remove from list": "Rimuovi dalla lista",
"Remove tag": "Rimuovi tag",
"Remove user": "Rimuovi utente",
"Replace": "",
"SAVE": "SALVA",
"SEARCH": "CERCA",
"SHARE": "CONDIVIDI",
"SHOW MORE": "MOSTRA DI PIÙ",
"SORT BY": "ORDINA PER",
"SUBMIT": "INVIA",
"Search": "Cerca",
"Search for user...": "Cerca utente...",
"Search users to add...": "Cerca utenti da aggiungere...",
"Select": "Seleziona",
"Select Owner": "Seleziona Proprietario",
"Select all": "Seleziona tutto",
"Select all media": "Seleziona tutti i media",
"Select publish state:": "Seleziona stato di pubblicazione:",
"Selected": "Selezionato",
"Shared by me": "Condiviso da me",
"Shared with me": "Condiviso con me",
"Sign in": "Login",
"Sign out": "Logout",
"Sort By": "Ordina per",
"Start Recording": "Inizia registrazione",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Inizia a caricare media e condividere il tuo lavoro. I media caricati appariranno qui.",
"Stop Recording": "Interrompi registrazione",
"Submit": "Invia",
"Subtitle was added": "I sottotitoli sono stati aggiunti",
"Subtitles": "Sottotitoli",
"Successfully Copied": "Copiato con successo",
"Successfully Disabled Download": "Download disabilitato con successo",
"Successfully Disabled comments": "Commenti disabilitati con successo",
"Successfully Enabled Download": "Download abilitato con successo",
"Successfully Enabled comments": "Commenti abilitati con successo",
"Successfully changed owner": "Proprietario cambiato con successo",
"Successfully deleted": "Eliminato con successo",
"Successfully updated": "Aggiornato con successo",
"Successfully updated categories": "Categorie aggiornate con successo",
"Successfully updated playlist membership": "Appartenenza alla playlist aggiornata con successo",
"Successfully updated publish state": "Stato di pubblicazione aggiornato con successo",
"Successfully updated tags": "Tag aggiornati con successo",
"TAGS": "TAG",
"Tag": "Tag",
"Tags": "Tag",
"Terms": "Termini e condizioni",
"The intersection of categories in the selected media is shown": "Viene mostrata l'intersezione delle categorie nei media selezionati",
"The intersection of playlists in the selected media is shown": "Viene mostrata l'intersezione delle playlist nei media selezionati",
"The intersection of tags in the selected media is shown": "Viene mostrata l'intersezione dei tag nei media selezionati",
"The intersection of users in the selected media is shown": "Viene mostrata l'intersezione degli utenti nei media selezionati",
"The media was deleted successfully.": "Il media è stato eliminato con successo.",
"This month": "Questo mese",
"This week": "Questa settimana",
"This works in Chrome, Safari and Edge browsers.": "Questo funziona nei browser Chrome, Safari e Edge.",
"This year": "Quest'anno",
"To add": "Da aggiungere",
"Today": "Oggi",
"Trim": "Taglia",
"UPLOAD": "CARICA",
"UPLOAD DATE": "DATA DI CARICAMENTO",
"UPLOAD MEDIA": "CARICA MEDIA",
"Undo removal": "Annulla rimozione",
"Unlisted": "Non in elenco",
"Up Next": "A seguire",
"Up next": "A seguire",
"Upload": "Carica",
"Upload date (newest)": "Data di caricamento (più recente)",
"Upload date (oldest)": "Data di caricamento (più vecchia)",
"Upload date - Newest": "Data di caricamento - Più recente",
"Upload date - Oldest": "Data di caricamento - Più vecchia",
"Upload media": "Carica i media",
"Uploads": "Caricamenti",
"Users": "Utenti",
"VIEW ALL": "MOSTRA TUTTI",
"Video": "Video",
"View all": "Mostra tutti",
"View count": "Numero di visualizzazioni",
"View media": "Visualizza media",
"Welcome": "Benvenuto",
"You are going to copy": "Stai per copiare",
"You are going to delete": "Stai per eliminare",
"You are going to disable comments to": "Stai per disabilitare i commenti di",
"You are going to disable download for": "Stai per disabilitare il download di",
"You are going to enable comments to": "Stai per abilitare i commenti di",
"You are going to enable download for": "Stai per abilitare il download di",
"comment": "commento",
"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": "è un CMS per media open source moderno e completo. È stato sviluppato per rispondere per venire incontro alle esigenze delle moderne piattaforme web di visualizzazione e condivisione media",
"media in category": "media nella categoria",
"media in tag": "media con tag",
"media, are you sure?": "media, sei sicuro?",
"media.": "media.",
"or": "o",
"results for": "risultati per",
"selected": "selezionato",
"view": "visualizzazione",
"views": "visualizzazioni",
"yet": "ancora",

View File

@@ -1,21 +1,57 @@
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": "プロフィールを編集",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "は、現代のウェブプラットフォームのニーズに応えるために開発された、最新のフル機能のオープンソースビデオおよびメディアCMSです。",
"media in category": "カテゴリー内のメディア",
"media in tag": "タグ内のメディア",
"media, are you sure?": "メディア、よろしいですか?",
"media.": "メディア。",
"or": "または",
"results for": "件の結果",
"selected": "選択済み",
"view": "ビュー",
"views": "ビュー",
"yet": "まだ",

View File

@@ -1,21 +1,57 @@
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": "프로필 편집",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "현대적인, 완전한 기능을 갖춘 오픈 소스 비디오 및 미디어 CMS입니다. 미디어를 시청하고 공유하기 위한 현대 웹 플랫폼의 요구를 충족시키기 위해 개발되었습니다",
"media in category": "카테고리의 미디어",
"media in tag": "태그의 미디어",
"media, are you sure?": "미디어, 확실합니까?",
"media.": "미디어.",
"or": "또는",
"results for": "개 결과",
"selected": "선택됨",
"view": "보기",
"views": "조회수",
"yet": "아직",

View File

@@ -1,21 +1,57 @@
translation_strings = {
"+ Create Playlist": "+ Afspeellijst maken",
"00 - 20 min": "00 - 20 min",
"1 result for": "1 resultaat voor",
"20 - 40 min": "20 - 40 min",
"40 - 60 min": "40 - 60 min",
"60 - 120 min+": "60 - 120 min+",
"ABOUT": "OVER",
"AUTOPLAY": "AUTOMATISCH AFSPELEN",
"About": "Over",
"Add / Remove Co-Editors": "Co-Editors toevoegen / verwijderen",
"Add / Remove Co-Owners": "Co-Eigenaren toevoegen / verwijderen",
"Add / Remove Co-Viewers": "Co-Kijkers toevoegen / verwijderen",
"Add / Remove Tags": "Tags toevoegen / verwijderen",
"Add / Remove from Categories": "Toevoegen / verwijderen uit categorieën",
"Add a ": "Voeg een ",
"Add to": "Toevoegen aan",
"Add to / Remove from Category": "Toevoegen / verwijderen uit categorie",
"Add to / Remove from Playlist": "Toevoegen / verwijderen uit afspeellijst",
"All": "Alles",
"All categories already added": "Alle categorieën al toegevoegd",
"All tags already added": "Alle tags al toegevoegd",
"Alphabetically - A-Z": "Alfabetisch - A-Z",
"Alphabetically - Z-A": "Alfabetisch - Z-A",
"Audio": "Audio",
"Browse your files": "Blader door uw bestanden",
"Bulk Actions": "Bulkacties",
"COMMENT": "REACTIE",
"Cancel": "Annuleren",
"Categories": "Categorieën",
"Category": "Categorie",
"Change Language": "Taal wijzigen",
"Change Owner": "Eigenaar wijzigen",
"Change password": "Wachtwoord wijzigen",
"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 op 'Opname starten' en selecteer het scherm of tabblad dat u wilt opnemen. Zodra de opname is voltooid, klikt u op 'Opname stoppen' en de opname wordt geüpload.",
"Co-Editors": "Co-Editors",
"Co-Owners": "Co-Eigenaren",
"Co-Viewers": "Co-Kijkers",
"Comment": "Reactie",
"Comments": "Reacties",
"Comments are disabled": "Reacties zijn uitgeschakeld",
"Confirm": "Bevestigen",
"Confirm Action": "Actie bevestigen",
"Contact": "Contact",
"Copy Media": "Media kopiëren",
"Create": "Aanmaken",
"DELETE": "VERWIJDEREN",
"DELETE MEDIA": "MEDIA VERWIJDEREN",
"DOWNLOAD": "DOWNLOADEN",
"DURATION": "DUUR",
"Delete Media": "Media verwijderen",
"Delete media": "Media verwijderen",
"Disable Comments": "Reacties uitschakelen",
"Disable Download": "Download uitschakelen",
"Drag and drop files": "Sleep bestanden en zet ze neer",
"EDIT MEDIA": "MEDIA BEWERKEN",
"EDIT PROFILE": "PROFIEL BEWERKEN",
@@ -23,63 +59,200 @@ translation_strings = {
"Edit media": "Media bewerken",
"Edit profile": "Profiel bewerken",
"Edit subtitle": "Ondertitel bewerken",
"Enable Comments": "Reacties inschakelen",
"Enable Download": "Download inschakelen",
"Enter playlist name...": "Voer afspeellijstnaam in...",
"Failed to add categories": "Categorieën toevoegen mislukt",
"Failed to add media to playlists": "Media toevoegen aan afspeellijsten mislukt",
"Failed to add tags": "Tags toevoegen mislukt",
"Failed to add users": "Gebruikers toevoegen mislukt",
"Failed to change owner": "Eigenaar wijzigen mislukt",
"Failed to change owner. Please try again.": "Eigenaar wijzigen mislukt. Probeer het opnieuw.",
"Failed to copy media.": "Media kopiëren mislukt.",
"Failed to create playlist": "Afspeellijst maken mislukt",
"Failed to delete media. Please try again.": "Media verwijderen mislukt. Probeer het opnieuw.",
"Failed to disable comments.": "Reacties uitschakelen mislukt.",
"Failed to disable download.": "Download uitschakelen mislukt.",
"Failed to enable comments.": "Reacties inschakelen mislukt.",
"Failed to enable download.": "Download inschakelen mislukt.",
"Failed to fetch all categories": "Alle categorieën ophalen mislukt",
"Failed to fetch all tags": "Alle tags ophalen mislukt",
"Failed to fetch existing categories": "Bestaande categorieën ophalen mislukt",
"Failed to fetch existing tags": "Bestaande tags ophalen mislukt",
"Failed to fetch existing users": "Bestaande gebruikers ophalen mislukt",
"Failed to fetch playlist membership": "Afspeellijstlidmaatschap ophalen mislukt",
"Failed to fetch playlists": "Afspeellijsten ophalen mislukt",
"Failed to load categories": "Categorieën laden mislukt",
"Failed to load existing permissions": "Bestaande machtigingen laden mislukt",
"Failed to load playlists": "Afspeellijsten laden mislukt",
"Failed to load tags": "Tags laden mislukt",
"Failed to remove categories": "Categorieën verwijderen mislukt",
"Failed to remove media from playlists": "Media verwijderen uit afspeellijsten mislukt",
"Failed to remove tags": "Tags verwijderen mislukt",
"Failed to remove users": "Gebruikers verwijderen mislukt",
"Failed to search users": "Gebruikers zoeken mislukt",
"Failed to set publish state": "Publicatiestatus instellen mislukt",
"Failed to set publish state. Please try again.": "Publicatiestatus instellen mislukt. Probeer het opnieuw.",
"Failed to update categories. Please try again.": "Categorieën bijwerken mislukt. Probeer het opnieuw.",
"Failed to update permissions. Please try again.": "Machtigingen bijwerken mislukt. Probeer het opnieuw.",
"Failed to update playlists. Please try again.": "Afspeellijsten bijwerken mislukt. Probeer het opnieuw.",
"Failed to update tags. Please try again.": "Tags bijwerken mislukt. Probeer het opnieuw.",
"Featured": "Aanbevolen",
"Filter existing users...": "Filter bestaande gebruikers...",
"Filter playlists...": "Filter afspeellijsten...",
"Filters": "Filters",
"Go": "Ga",
"History": "Geschiedenis",
"Home": "Home",
"Image": "Afbeelding",
"Language": "Taal",
"Latest": "Laatste",
"Like count": "Aantal likes",
"Liked media": "Leuke media",
"Likes - Least": "Likes - Minst",
"Likes - Most": "Likes - Meest",
"Loading categories...": "Categorieën laden...",
"Loading existing users...": "Bestaande gebruikers laden...",
"Loading playlists...": "Afspeellijsten laden...",
"Loading tags...": "Tags laden...",
"MEDIA TYPE": "MEDIATYPE",
"Manage": "Beheren",
"Manage Playlists": "Afspeellijsten beheren",
"Manage comments": "Reacties beheren",
"Manage media": "Media beheren",
"Manage users": "Gebruikers beheren",
"Media": "Media",
"Media I own": "Media die ik bezit",
"Media was edited": "Media is bewerkt",
"Members": "Leden",
"My media": "Mijn media",
"My playlists": "Mijn afspeellijsten",
"No": "Nee",
"No categories": "Geen categorieën",
"No comment yet": "Nog geen reactie",
"No comments yet": "Nog geen reacties",
"No existing": "Geen bestaande",
"No playlists available": "Geen afspeellijsten beschikbaar",
"No playlists selected": "Geen afspeellijsten geselecteerd",
"No results for": "Geen resultaten voor",
"No tags": "Geen tags",
"No users to add": "Geen gebruikers om toe te voegen",
"PLAYLISTS": "AFSPEELLIJSTEN",
"PUBLISH STATE": "PUBLICATIESTATUS",
"Pdf": "PDF",
"Playlists": "Afspeellijsten",
"Plays - Least": "Afspelingen - Minst",
"Plays - Most": "Afspelingen - Meest",
"Please select a publish state": "Selecteer een publicatiestatus",
"Please select a user": "Selecteer een gebruiker",
"Powered by": "Aangedreven door",
"Private": "Privé",
"Proceed": "Doorgaan",
"Processing...": "Verwerken...",
"Public": "Openbaar",
"Publish": "Publiceren",
"Publish State": "Publicatiestatus",
"Published": "Gepubliceerd",
"Published on": "Gepubliceerd op",
"Recent uploads": "Recente uploads",
"Recommended": "Aanbevolen",
"Record Screen": "Scherm opnemen",
"Register": "Registreren",
"Remove category": "Categorie verwijderen",
"Remove from list": "Verwijderen uit lijst",
"Remove tag": "Tag verwijderen",
"Remove user": "Gebruiker verwijderen",
"Replace": "",
"SAVE": "OPSLAAN",
"SEARCH": "ZOEKEN",
"SHARE": "DELEN",
"SHOW MORE": "MEER WEERGEVEN",
"SORT BY": "SORTEER OP",
"SUBMIT": "INDIENEN",
"Search": "Zoeken",
"Search for user...": "Zoek naar gebruiker...",
"Search users to add...": "Zoek gebruikers om toe te voegen...",
"Select": "Selecteer",
"Select Owner": "Selecteer eigenaar",
"Select all": "Alles selecteren",
"Select all media": "Alle media selecteren",
"Select publish state:": "Selecteer publicatiestatus:",
"Selected": "Geselecteerd",
"Shared by me": "Gedeeld door mij",
"Shared with me": "Gedeeld met mij",
"Sign in": "Inloggen",
"Sign out": "Uitloggen",
"Sort By": "Sorteer op",
"Start Recording": "Opname starten",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Begin met het uploaden van media en het delen van uw werk. Media die u uploadt, verschijnt hier.",
"Stop Recording": "Opname stoppen",
"Submit": "Indienen",
"Subtitle was added": "Ondertitel is toegevoegd",
"Subtitles": "Ondertitels",
"Successfully Copied": "Succesvol gekopieerd",
"Successfully Disabled Download": "Download succesvol uitgeschakeld",
"Successfully Disabled comments": "Reacties succesvol uitgeschakeld",
"Successfully Enabled Download": "Download succesvol ingeschakeld",
"Successfully Enabled comments": "Reacties succesvol ingeschakeld",
"Successfully changed owner": "Eigenaar succesvol gewijzigd",
"Successfully deleted": "Succesvol verwijderd",
"Successfully updated": "Succesvol bijgewerkt",
"Successfully updated categories": "Categorieën succesvol bijgewerkt",
"Successfully updated playlist membership": "Afspeellijstlidmaatschap succesvol bijgewerkt",
"Successfully updated publish state": "Publicatiestatus succesvol bijgewerkt",
"Successfully updated tags": "Tags succesvol bijgewerkt",
"TAGS": "TAGS",
"Tag": "Tag",
"Tags": "Tags",
"Terms": "Voorwaarden",
"The intersection of categories in the selected media is shown": "De doorsnede van categorieën in de geselecteerde media wordt getoond",
"The intersection of playlists in the selected media is shown": "De doorsnede van afspeellijsten in de geselecteerde media wordt getoond",
"The intersection of tags in the selected media is shown": "De doorsnede van tags in de geselecteerde media wordt getoond",
"The intersection of users in the selected media is shown": "De doorsnede van gebruikers in de geselecteerde media wordt getoond",
"The media was deleted successfully.": "De media is succesvol verwijderd.",
"This month": "Deze maand",
"This week": "Deze week",
"This works in Chrome, Safari and Edge browsers.": "Dit werkt in Chrome, Safari en Edge browsers.",
"This year": "Dit jaar",
"To add": "Toe te voegen",
"Today": "Vandaag",
"Trim": "Bijsnijden",
"UPLOAD": "UPLOADEN",
"UPLOAD DATE": "UPLOADDATUM",
"UPLOAD MEDIA": "MEDIA UPLOADEN",
"Undo removal": "Verwijdering ongedaan maken",
"Unlisted": "Niet vermeld",
"Up Next": "Hierna",
"Up next": "Hierna",
"Upload": "Uploaden",
"Upload date (newest)": "Uploaddatum (nieuwste)",
"Upload date (oldest)": "Uploaddatum (oudste)",
"Upload date - Newest": "Uploaddatum - Nieuwste",
"Upload date - Oldest": "Uploaddatum - Oudste",
"Upload media": "Media uploaden",
"Uploads": "Uploads",
"Users": "Gebruikers",
"VIEW ALL": "BEKIJK ALLES",
"Video": "Video",
"View all": "Bekijk alles",
"View count": "Aantal weergaven",
"View media": "Media bekijken",
"Welcome": "Welkom",
"You are going to copy": "Je gaat kopiëren",
"You are going to delete": "Je gaat verwijderen",
"You are going to disable comments to": "Je gaat reacties uitschakelen voor",
"You are going to disable download for": "Je gaat download uitschakelen voor",
"You are going to enable comments to": "Je gaat reacties inschakelen voor",
"You are going to enable download for": "Je gaat download inschakelen voor",
"comment": "reactie",
"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": "is een modern, volledig uitgerust open source video- en media-CMS. Het is ontwikkeld om te voldoen aan de behoeften van moderne webplatforms voor het bekijken en delen van media",
"media in category": "media in categorie",
"media in tag": "media in tag",
"media, are you sure?": "media, weet je het zeker?",
"media.": "media.",
"or": "of",
"results for": "resultaten voor",
"selected": "geselecteerd",
"view": "bekijk",
"views": "weergaven",
"yet": "nog",

View File

@@ -1,21 +1,57 @@
translation_strings = {
"+ Create Playlist": "+ Criar playlist",
"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": "SOBRE",
"AUTOPLAY": "REPRODUÇÃO AUTOMÁTICA",
"About": "Sobre",
"Add / Remove Co-Editors": "Adicionar / Remover coeditores",
"Add / Remove Co-Owners": "Adicionar / Remover coproprietários",
"Add / Remove Co-Viewers": "Adicionar / Remover covisuais",
"Add / Remove Tags": "Adicionar / Remover tags",
"Add / Remove from Categories": "Adicionar / Remover das categorias",
"Add a ": "Adicionar um ",
"Add to": "Adicionar a",
"Add to / Remove from Category": "Adicionar / Remover da categoria",
"Add to / Remove from Playlist": "Adicionar / Remover da playlist",
"All": "Todos",
"All categories already added": "Todas as categorias já foram adicionadas",
"All tags already added": "Todas as tags já foram adicionadas",
"Alphabetically - A-Z": "Alfabeticamente - A-Z",
"Alphabetically - Z-A": "Alfabeticamente - Z-A",
"Audio": "Áudio",
"Browse your files": "Procurar seus arquivos",
"Bulk Actions": "Ações em massa",
"COMMENT": "COMENTÁRIO",
"Cancel": "Cancelar",
"Categories": "Categorias",
"Category": "Categoria",
"Change Language": "Mudar idioma",
"Change Owner": "Mudar proprietário",
"Change password": "Mudar senha",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Clique em 'Iniciar gravação' e selecione a tela ou guia para gravar. Quando a gravação terminar, clique em 'Parar gravação' e a gravação será enviada.",
"Co-Editors": "Coeditores",
"Co-Owners": "Coproprietários",
"Co-Viewers": "Covisuais",
"Comment": "Comentário",
"Comments": "Comentários",
"Comments are disabled": "Comentários estão desativados",
"Confirm": "Confirmar",
"Confirm Action": "Confirmar ação",
"Contact": "Contato",
"Copy Media": "Copiar mídia",
"Create": "Criar",
"DELETE": "EXCLUIR",
"DELETE MEDIA": "EXCLUIR MÍDIA",
"DOWNLOAD": "BAIXAR",
"DURATION": "DURAÇÃO",
"Delete Media": "Excluir mídia",
"Delete media": "Excluir mídia",
"Disable Comments": "Desativar comentários",
"Disable Download": "Desativar download",
"Drag and drop files": "Arraste e solte arquivos",
"EDIT MEDIA": "EDITAR MÍDIA",
"EDIT PROFILE": "EDITAR PERFIL",
@@ -23,63 +59,200 @@ translation_strings = {
"Edit media": "Editar mídia",
"Edit profile": "Editar perfil",
"Edit subtitle": "Editar legenda",
"Enable Comments": "Ativar comentários",
"Enable Download": "Ativar download",
"Enter playlist name...": "Digite o nome da playlist...",
"Failed to add categories": "Falha ao adicionar categorias",
"Failed to add media to playlists": "Falha ao adicionar mídia às playlists",
"Failed to add tags": "Falha ao adicionar tags",
"Failed to add users": "Falha ao adicionar usuários",
"Failed to change owner": "Falha ao mudar proprietário",
"Failed to change owner. Please try again.": "Falha ao mudar proprietário. Por favor, tente novamente.",
"Failed to copy media.": "Falha ao copiar mídia.",
"Failed to create playlist": "Falha ao criar playlist",
"Failed to delete media. Please try again.": "Falha ao excluir mídia. Por favor, tente novamente.",
"Failed to disable comments.": "Falha ao desativar comentários.",
"Failed to disable download.": "Falha ao desativar download.",
"Failed to enable comments.": "Falha ao ativar comentários.",
"Failed to enable download.": "Falha ao ativar download.",
"Failed to fetch all categories": "Falha ao carregar todas as categorias",
"Failed to fetch all tags": "Falha ao carregar todas as tags",
"Failed to fetch existing categories": "Falha ao carregar categorias existentes",
"Failed to fetch existing tags": "Falha ao carregar tags existentes",
"Failed to fetch existing users": "Falha ao carregar usuários existentes",
"Failed to fetch playlist membership": "Falha ao carregar associação de playlist",
"Failed to fetch playlists": "Falha ao carregar playlists",
"Failed to load categories": "Falha ao carregar categorias",
"Failed to load existing permissions": "Falha ao carregar permissões existentes",
"Failed to load playlists": "Falha ao carregar playlists",
"Failed to load tags": "Falha ao carregar tags",
"Failed to remove categories": "Falha ao remover categorias",
"Failed to remove media from playlists": "Falha ao remover mídia das playlists",
"Failed to remove tags": "Falha ao remover tags",
"Failed to remove users": "Falha ao remover usuários",
"Failed to search users": "Falha ao pesquisar usuários",
"Failed to set publish state": "Falha ao definir estado de publicação",
"Failed to set publish state. Please try again.": "Falha ao definir estado de publicação. Por favor, tente novamente.",
"Failed to update categories. Please try again.": "Falha ao atualizar categorias. Por favor, tente novamente.",
"Failed to update permissions. Please try again.": "Falha ao atualizar permissões. Por favor, tente novamente.",
"Failed to update playlists. Please try again.": "Falha ao atualizar playlists. Por favor, tente novamente.",
"Failed to update tags. Please try again.": "Falha ao atualizar tags. Por favor, tente novamente.",
"Featured": "Destaque",
"Filter existing users...": "Filtrar usuários existentes...",
"Filter playlists...": "Filtrar playlists...",
"Filters": "Filtros",
"Go": "Ir",
"History": "Histórico",
"Home": "Início",
"Image": "Imagem",
"Language": "Idioma",
"Latest": "Últimos",
"Like count": "Contagem de curtidas",
"Liked media": "Mídia curtida",
"Likes - Least": "Curtidas - Menos",
"Likes - Most": "Curtidas - Mais",
"Loading categories...": "Carregando categorias...",
"Loading existing users...": "Carregando usuários existentes...",
"Loading playlists...": "Carregando playlists...",
"Loading tags...": "Carregando tags...",
"MEDIA TYPE": "TIPO DE MÍDIA",
"Manage": "Gerenciar",
"Manage Playlists": "Gerenciar playlists",
"Manage comments": "Gerenciar comentários",
"Manage media": "Gerenciar mídia",
"Manage users": "Gerenciar usuários",
"Media": "Mídia",
"Media I own": "Mídia que possuo",
"Media was edited": "Mídia foi editada",
"Members": "Membros",
"My media": "Minhas mídias",
"My playlists": "Minhas playlists",
"No": "Não",
"No categories": "Nenhuma categoria",
"No comment yet": "Nenhum comentário ainda",
"No comments yet": "Nenhum comentário ainda",
"No existing": "Nenhum existente",
"No playlists available": "Nenhuma playlist disponível",
"No playlists selected": "Nenhuma playlist selecionada",
"No results for": "Nenhum resultado para",
"No tags": "Nenhuma tag",
"No users to add": "Nenhum usuário para adicionar",
"PLAYLISTS": "PLAYLISTS",
"PUBLISH STATE": "ESTADO DE PUBLICAÇÃO",
"Pdf": "PDF",
"Playlists": "Playlists",
"Plays - Least": "Reproduções - Menos",
"Plays - Most": "Reproduções - Mais",
"Please select a publish state": "Por favor, selecione um estado de publicação",
"Please select a user": "Por favor, selecione um usuário",
"Powered by": "Desenvolvido por",
"Private": "Privado",
"Proceed": "Prosseguir",
"Processing...": "Processando...",
"Public": "Público",
"Publish": "Publicar",
"Publish State": "Estado de publicação",
"Published": "Publicado",
"Published on": "Publicado em",
"Recent uploads": "Uploads recentes",
"Recommended": "Recomendado",
"Record Screen": "Gravar tela",
"Register": "Registrar",
"Remove category": "Remover categoria",
"Remove from list": "Remover da lista",
"Remove tag": "Remover tag",
"Remove user": "Remover usuário",
"Replace": "",
"SAVE": "SALVAR",
"SEARCH": "PESQUISAR",
"SHARE": "COMPARTILHAR",
"SHOW MORE": "MOSTRAR MAIS",
"SORT BY": "ORDENAR POR",
"SUBMIT": "ENVIAR",
"Search": "Pesquisar",
"Search for user...": "Pesquisar usuário...",
"Search users to add...": "Pesquisar usuários para adicionar...",
"Select": "Selecionar",
"Select Owner": "Selecionar proprietário",
"Select all": "Selecionar todos",
"Select all media": "Selecionar todas as mídias",
"Select publish state:": "Selecionar estado de publicação:",
"Selected": "Selecionado",
"Shared by me": "Compartilhado por mim",
"Shared with me": "Compartilhado comigo",
"Sign in": "Entrar",
"Sign out": "Sair",
"Start Recording": "Iniciar Gravação",
"Stop Recording": "Parar Gravação",
"Sort By": "Ordenar por",
"Start Recording": "Iniciar gravação",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Comece a fazer upload de mídia e compartilhar seu trabalho. A mídia que você fizer upload aparecerá aqui.",
"Stop Recording": "Parar gravação",
"Submit": "Enviar",
"Subtitle was added": "Legenda foi adicionada",
"Subtitles": "Legendas",
"Successfully Copied": "Copiado com sucesso",
"Successfully Disabled Download": "Download desativado com sucesso",
"Successfully Disabled comments": "Comentários desativados com sucesso",
"Successfully Enabled Download": "Download ativado com sucesso",
"Successfully Enabled comments": "Comentários ativados com sucesso",
"Successfully changed owner": "Proprietário alterado com sucesso",
"Successfully deleted": "Excluído com sucesso",
"Successfully updated": "Atualizado com sucesso",
"Successfully updated categories": "Categorias atualizadas com sucesso",
"Successfully updated playlist membership": "Associação da playlist atualizada com sucesso",
"Successfully updated publish state": "Estado de publicação atualizado com sucesso",
"Successfully updated tags": "Tags atualizadas com sucesso",
"TAGS": "TAGS",
"Tag": "Tag",
"Tags": "Tags",
"Terms": "Termos",
"The intersection of categories in the selected media is shown": "A interseção das categorias da mídia selecionada é exibida",
"The intersection of playlists in the selected media is shown": "A interseção das playlists da mídia selecionada é exibida",
"The intersection of tags in the selected media is shown": "A interseção das tags da mídia selecionada é exibida",
"The intersection of users in the selected media is shown": "A interseção dos usuários da mídia selecionada é exibida",
"The media was deleted successfully.": "A mídia foi excluída com sucesso.",
"This month": "Este mês",
"This week": "Esta semana",
"This works in Chrome, Safari and Edge browsers.": "Isso funciona nos navegadores Chrome, Safari e Edge.",
"This year": "Este ano",
"To add": "Para adicionar",
"Today": "Hoje",
"Trim": "Cortar",
"UPLOAD": "CARREGAR",
"UPLOAD DATE": "DATA DE UPLOAD",
"UPLOAD MEDIA": "FAZER UPLOAD DE MÍDIA",
"Undo removal": "Desfazer remoção",
"Unlisted": "Não listado",
"Up Next": "A seguir",
"Up next": "A seguir",
"Upload": "Carregar",
"Upload date (newest)": "Data de upload (mais recente)",
"Upload date (oldest)": "Data de upload (mais antiga)",
"Upload date - Newest": "Data de upload - Mais recente",
"Upload date - Oldest": "Data de upload - Mais antiga",
"Upload media": "Carregar mídia",
"Uploads": "Uploads",
"Users": "Usuários",
"VIEW ALL": "VER TODOS",
"Video": "Vídeo",
"View all": "Ver todos",
"View count": "Contagem de visualizações",
"View media": "Ver mídia",
"Welcome": "Bem-vindo",
"You are going to copy": "Você vai copiar",
"You are going to delete": "Você vai excluir",
"You are going to disable comments to": "Você vai desativar comentários de",
"You are going to disable download for": "Você vai desativar download de",
"You are going to enable comments to": "Você vai ativar comentários de",
"You are going to enable download for": "Você vai ativar download de",
"comment": "comentário",
"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": "é um CMS de vídeo e mídia de código aberto, moderno e completo. Foi desenvolvido para atender às necessidades das plataformas web modernas para visualização e compartilhamento de mídia",
"media in category": "mídia na categoria",
"media in tag": "mídia na tag",
"media, are you sure?": "mídia, tem certeza?",
"media.": "mídia.",
"or": "ou",
"results for": "resultados para",
"selected": "selecionado",
"view": "visualização",
"views": "visualizações",
"yet": "ainda",

View File

@@ -1,21 +1,57 @@
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": "РЕДАКТИРОВАТЬ ПРОФИЛЬ",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "еще",

View File

@@ -1,21 +1,57 @@
translation_strings = {
"+ Create Playlist": "+ Ustvari Seznam Predvajanja",
"00 - 20 min": "00 - 20 min",
"1 result for": "1 rezultat za",
"20 - 40 min": "20 - 40 min",
"40 - 60 min": "40 - 60 min",
"60 - 120 min+": "60 - 120 min+",
"ABOUT": "O NAS",
"AUTOPLAY": "SAMODEJNO PREDVAJANJE",
"About": "O nas",
"Add / Remove Co-Editors": "Dodaj / Odstrani Sourednike",
"Add / Remove Co-Owners": "Dodaj / Odstrani Solastnike",
"Add / Remove Co-Viewers": "Dodaj / Odstrani Sogledovalce",
"Add / Remove Tags": "Dodaj / Odstrani Oznake",
"Add / Remove from Categories": "Dodaj / Odstrani iz Kategorij",
"Add a ": "Dodaj ",
"Add to": "Dodaj v",
"Add to / Remove from Category": "Dodaj / Odstrani iz Kategorije",
"Add to / Remove from Playlist": "Dodaj / Odstrani iz Seznama Predvajanja",
"All": "Vse",
"All categories already added": "Vse kategorije že dodane",
"All tags already added": "Vse oznake že dodane",
"Alphabetically - A-Z": "Po abecedi - A-Ž",
"Alphabetically - Z-A": "Po abecedi - Ž-A",
"Audio": "Zvok",
"Browse your files": "Prebrskaj datoteke",
"Bulk Actions": "Množična Dejanja",
"COMMENT": "KOMENTAR",
"Cancel": "Prekliči",
"Categories": "Kategorije",
"Category": "Kategorija",
"Change Language": "Spremeni jezik",
"Change Owner": "Spremeni Lastnika",
"Change password": "Spremeni geslo",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Kliknite 'Začni snemanje' in izberite zaslon ali zavihek za snemanje. Ko je snemanje končano, kliknite 'Ustavi snemanje' in posnetek bo naložen.",
"Co-Editors": "Sourednik",
"Co-Owners": "Solastniki",
"Co-Viewers": "Sogledovalci",
"Comment": "Komentar",
"Comments": "Komentarji",
"Comments are disabled": "Komentarji so onemogočeni",
"Confirm": "Potrdi",
"Confirm Action": "Potrdi Dejanje",
"Contact": "Kontakt",
"Copy Media": "Kopiraj Medij",
"Create": "Ustvari",
"DELETE": "IZBRIŠI",
"DELETE MEDIA": "IZBRIŠI MEDIJ",
"DOWNLOAD": "PRENESI",
"DURATION": "TRAJANJE",
"Delete Media": "Izbriši Medij",
"Delete media": "Izbriši medij",
"Disable Comments": "Onemogoči Komentarje",
"Disable Download": "Onemogoči Prenos",
"Drag and drop files": "Povleci in spusti datoteke",
"EDIT MEDIA": "UREDI MEDIJ",
"EDIT PROFILE": "UREDI PROFIL",
@@ -23,63 +59,200 @@ translation_strings = {
"Edit media": "Uredi medij",
"Edit profile": "Uredi profil",
"Edit subtitle": "Uredi podnapise",
"Enable Comments": "Omogoči Komentarje",
"Enable Download": "Omogoči Prenos",
"Enter playlist name...": "Vnesite ime seznama predvajanja...",
"Failed to add categories": "Dodajanje kategorij ni uspelo",
"Failed to add media to playlists": "Dodajanje medija na sezname predvajanja ni uspelo",
"Failed to add tags": "Dodajanje oznak ni uspelo",
"Failed to add users": "Dodajanje uporabnikov ni uspelo",
"Failed to change owner": "Spreminjanje lastnika ni uspelo",
"Failed to change owner. Please try again.": "Spreminjanje lastnika ni uspelo. Prosim poskusite ponovno.",
"Failed to copy media.": "Kopiranje medija ni uspelo.",
"Failed to create playlist": "Ustvarjanje seznama predvajanja ni uspelo",
"Failed to delete media. Please try again.": "Brisanje medija ni uspelo. Prosim poskusite ponovno.",
"Failed to disable comments.": "Onemogočanje komentarjev ni uspelo.",
"Failed to disable download.": "Onemogočanje prenosa ni uspelo.",
"Failed to enable comments.": "Omogočanje komentarjev ni uspelo.",
"Failed to enable download.": "Omogočanje prenosa ni uspelo.",
"Failed to fetch all categories": "Pridobivanje vseh kategorij ni uspelo",
"Failed to fetch all tags": "Pridobivanje vseh oznak ni uspelo",
"Failed to fetch existing categories": "Pridobivanje obstoječih kategorij ni uspelo",
"Failed to fetch existing tags": "Pridobivanje obstoječih oznak ni uspelo",
"Failed to fetch existing users": "Pridobivanje obstoječih uporabnikov ni uspelo",
"Failed to fetch playlist membership": "Pridobivanje članstva seznama predvajanja ni uspelo",
"Failed to fetch playlists": "Pridobivanje seznamov predvajanja ni uspelo",
"Failed to load categories": "Nalaganje kategorij ni uspelo",
"Failed to load existing permissions": "Nalaganje obstoječih dovoljenj ni uspelo",
"Failed to load playlists": "Nalaganje seznamov predvajanja ni uspelo",
"Failed to load tags": "Nalaganje oznak ni uspelo",
"Failed to remove categories": "Odstranjevanje kategorij ni uspelo",
"Failed to remove media from playlists": "Odstranjevanje medija iz seznamov predvajanja ni uspelo",
"Failed to remove tags": "Odstranjevanje oznak ni uspelo",
"Failed to remove users": "Odstranjevanje uporabnikov ni uspelo",
"Failed to search users": "Iskanje uporabnikov ni uspelo",
"Failed to set publish state": "Nastavljanje stanja objave ni uspelo",
"Failed to set publish state. Please try again.": "Nastavljanje stanja objave ni uspelo. Prosim poskusite ponovno.",
"Failed to update categories. Please try again.": "Posodabljanje kategorij ni uspelo. Prosim poskusite ponovno.",
"Failed to update permissions. Please try again.": "Posodabljanje dovoljenj ni uspelo. Prosim poskusite ponovno.",
"Failed to update playlists. Please try again.": "Posodabljanje seznamov predvajanja ni uspelo. Prosim poskusite ponovno.",
"Failed to update tags. Please try again.": "Posodabljanje oznak ni uspelo. Prosim poskusite ponovno.",
"Featured": "Izbrani",
"Filter existing users...": "Filtriraj obstoječe uporabnike...",
"Filter playlists...": "Filtriraj sezname predvajanja...",
"Filters": "Filtri",
"Go": "Pojdi",
"History": "Zgodovina",
"Home": "Domov",
"Image": "Slika",
"Language": "Jezik",
"Latest": "Najnovejši",
"Like count": "Število všečkov",
"Liked media": "Všečkani mediji",
"Likes - Least": "Všečki - Najmanj",
"Likes - Most": "Všečki - Največ",
"Loading categories...": "Nalaganje kategorij...",
"Loading existing users...": "Nalaganje obstoječih uporabnikov...",
"Loading playlists...": "Nalaganje seznamov predvajanja...",
"Loading tags...": "Nalaganje oznak...",
"MEDIA TYPE": "TIP MEDIJA",
"Manage": "Upravljaj",
"Manage Playlists": "Upravljaj Seznam Predvajanja",
"Manage comments": "Upravljaj komentarje",
"Manage media": "Upravljaj medije",
"Manage users": "Upravljaj uporabnike",
"Media": "Mediji",
"Media I own": "Mediji, ki jih posedujam",
"Media was edited": "Medij je bil urejen",
"Members": "Člani",
"My media": "Moji mediji",
"My playlists": "Moji seznami predvajanja",
"No": "Ne",
"No categories": "Brez kategorij",
"No comment yet": "Brez komentarja",
"No comments yet": "Brez komentarjev",
"No existing": "Brez obstoječih",
"No playlists available": "Ni razpoložljivih seznamov predvajanja",
"No playlists selected": "Ni izbranih seznamov predvajanja",
"No results for": "Ni rezultatov za",
"No tags": "Brez oznak",
"No users to add": "Ni uporabnikov za dodajanje",
"PLAYLISTS": "SEZNAMI PREDVAJANJA",
"PUBLISH STATE": "STANJE OBJAVE",
"Pdf": "PDF",
"Playlists": "Seznami predvajanja",
"Plays - Least": "Predvajanja - Najmanj",
"Plays - Most": "Predvajanja - Največ",
"Please select a publish state": "Prosim izberite stanje objave",
"Please select a user": "Prosim izberite uporabnika",
"Powered by": "Poganja",
"Private": "Zasebno",
"Proceed": "Nadaljuj",
"Processing...": "Obdelava...",
"Public": "Javno",
"Publish": "Objavi",
"Publish State": "Stanje Objave",
"Published": "Objavljeno",
"Published on": "Objavljeno",
"Recent uploads": "Nedavne naložitve",
"Recommended": "Priporočeno",
"Record Screen": "Snemanje zaslona",
"Register": "Registracija",
"Remove category": "Odstrani kategorijo",
"Remove from list": "Odstrani s seznama",
"Remove tag": "Odstrani oznako",
"Remove user": "Odstrani uporabnika",
"Replace": "",
"SAVE": "SHRANI",
"SEARCH": "ISKANJE",
"SHARE": "DELI",
"SHOW MORE": "PRIKAŽI VEČ",
"SORT BY": "RAZVRSTI PO",
"SUBMIT": "POŠLJI",
"Search": "Iskanje",
"Search for user...": "Išči uporabnika...",
"Search users to add...": "Išči uporabnike za dodajanje...",
"Select": "Izberi",
"Select Owner": "Izberi Lastnika",
"Select all": "Izberi vse",
"Select all media": "Izberi vse medije",
"Select publish state:": "Izberi stanje objave:",
"Selected": "Izbrano",
"Shared by me": "Deljeno z moje strani",
"Shared with me": "Deljeno z mano",
"Sign in": "Prijava",
"Sign out": "Odjava",
"Sort By": "Razvrsti po",
"Start Recording": "Začni snemanje",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Začnite nalagati medije in deliti svoje delo. Mediji, ki jih naložite, bodo prikazani tukaj.",
"Stop Recording": "Ustavi snemanje",
"Submit": "Pošlji",
"Subtitle was added": "Podnapisi so bili dodani",
"Subtitles": "Podnapisi",
"Successfully Copied": "Uspešno kopirano",
"Successfully Disabled Download": "Prenos uspešno onemogočen",
"Successfully Disabled comments": "Komentarji uspešno onemogočeni",
"Successfully Enabled Download": "Prenos uspešno omogočen",
"Successfully Enabled comments": "Komentarji uspešno omogočeni",
"Successfully changed owner": "Lastnik uspešno spremenjen",
"Successfully deleted": "Uspešno izbrisano",
"Successfully updated": "Uspešno posodobljeno",
"Successfully updated categories": "Kategorije uspešno posodobljene",
"Successfully updated playlist membership": "Članstvo seznama predvajanja uspešno posodobljeno",
"Successfully updated publish state": "Stanje objave uspešno posodobljeno",
"Successfully updated tags": "Oznake uspešno posodobljene",
"TAGS": "OZNAKE",
"Tag": "Oznaka",
"Tags": "Oznake",
"Terms": "Pogoji",
"The intersection of categories in the selected media is shown": "Prikazane so skupne kategorije v izbranih medijih",
"The intersection of playlists in the selected media is shown": "Prikazani so skupni seznami predvajanja v izbranih medijih",
"The intersection of tags in the selected media is shown": "Prikazane so skupne oznake v izbranih medijih",
"The intersection of users in the selected media is shown": "Prikazani so skupni uporabniki v izbranih medijih",
"The media was deleted successfully.": "Medij je bil uspešno izbrisan.",
"This month": "Ta mesec",
"This week": "Ta teden",
"This works in Chrome, Safari and Edge browsers.": "To deluje v brskalnikih Chrome, Safari in Edge.",
"This year": "Letos",
"To add": "Za dodajanje",
"Today": "Danes",
"Trim": "Obreži",
"UPLOAD": "NALOŽI",
"UPLOAD DATE": "DATUM NALAGANJA",
"UPLOAD MEDIA": "NALOŽI MEDIJ",
"Undo removal": "Razveljavi odstranitev",
"Unlisted": "Neuvrščeno",
"Up Next": "Naslednji",
"Up next": "Naslednji",
"Upload": "Naloži",
"Upload date (newest)": "Datum nalaganja (najnovejše)",
"Upload date (oldest)": "Datum nalaganja (najstarejše)",
"Upload date - Newest": "Datum nalaganja - Najnovejše",
"Upload date - Oldest": "Datum nalaganja - Najstarejše",
"Upload media": "Naloži medij",
"Uploads": "Naloženi",
"Users": "Uporabniki",
"VIEW ALL": "PRIKAŽI VSE",
"Video": "Video",
"View all": "Prikaži vse",
"View count": "Število ogledov",
"View media": "Ogled medija",
"Welcome": "Dobrodošli",
"You are going to copy": "Kopirate",
"You are going to delete": "Brišete",
"You are going to disable comments to": "Onemogočate komentarje za",
"You are going to disable download for": "Onemogočate prenos za",
"You are going to enable comments to": "Omogočate komentarje za",
"You are going to enable download for": "Omogočate prenos za",
"comment": "komentar",
"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": "je moderni, popolnoma opremljen odprtokodni video in medijski CMS. Razvit je za potrebe sodobnih spletnih platform za ogled in deljenje medijev",
"media in category": "mediji v kategoriji",
"media in tag": "mediji z oznako",
"media, are you sure?": "medij, ste prepričani?",
"media.": "medij.",
"or": "ali",
"results for": "rezultatov za",
"selected": "izbrano",
"view": "ogled",
"views": "ogledi",
"yet": "še",

View File

@@ -1,21 +1,57 @@
translation_strings = {
"+ Create Playlist": "+ Çalma Listesi Oluştur",
"00 - 20 min": "00 - 20 dk",
"1 result for": "1 sonuç",
"20 - 40 min": "20 - 40 dk",
"40 - 60 min": "40 - 60 dk",
"60 - 120 min+": "60 - 120 dk+",
"ABOUT": "HAKKINDA",
"AUTOPLAY": "OTOMATİK OYNATMA",
"About": "Hakkında",
"Add / Remove Co-Editors": "Ortak Editör Ekle / Kaldır",
"Add / Remove Co-Owners": "Ortak Sahip Ekle / Kaldır",
"Add / Remove Co-Viewers": "Ortak İzleyici Ekle / Kaldır",
"Add / Remove Tags": "Etiket Ekle / Kaldır",
"Add / Remove from Categories": "Kategorilerden Ekle / Kaldır",
"Add a ": "Ekle ",
"Add to": "Ekle",
"Add to / Remove from Category": "Kategoriye Ekle / Kaldır",
"Add to / Remove from Playlist": "Çalma Listesine Ekle / Kaldır",
"All": "Tümü",
"All categories already added": "Tüm kategoriler zaten eklendi",
"All tags already added": "Tüm etiketler zaten eklendi",
"Alphabetically - A-Z": "Alfabetik - A-Z",
"Alphabetically - Z-A": "Alfabetik - Z-A",
"Audio": "Ses",
"Browse your files": "Dosyalarınıza göz atın",
"Bulk Actions": "Toplu İşlemler",
"COMMENT": "YORUM",
"Cancel": "İptal",
"Categories": "Kategoriler",
"Category": "Kategori",
"Change Language": "Dili Değiştir",
"Change Owner": "Sahip Değiştir",
"Change password": "Şifreyi Değiştir",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'Kaydı Başlat'a tıklayın ve kaydedilecek ekranı veya sekmeyi seçin. Kayıt bittiğinde, 'Kaydı Durdur'a tıklayın ve kayıt yüklenecektir.",
"Co-Editors": "Ortak Editörler",
"Co-Owners": "Ortak Sahipler",
"Co-Viewers": "Ortak İzleyiciler",
"Comment": "Yorum",
"Comments": "Yorumlar",
"Comments are disabled": "Yorumlar devre dışı",
"Confirm": "Onayla",
"Confirm Action": "İşlemi Onayla",
"Contact": "İletişim",
"Copy Media": "Medyayı Kopyala",
"Create": "Oluştur",
"DELETE": "SİL",
"DELETE MEDIA": "MEDYAYI SİL",
"DOWNLOAD": "İNDİR",
"DURATION": "SÜRE",
"Delete Media": "Medyayı Sil",
"Delete media": "Medyayı sil",
"Disable Comments": "Yorumları Devre Dışı Bırak",
"Disable Download": "İndirmeyi Devre Dışı Bırak",
"Drag and drop files": "Dosyaları sürükleyip bırakın",
"EDIT MEDIA": "MEDYAYI DÜZENLE",
"EDIT PROFILE": "PROFİLİ DÜZENLE",
@@ -23,63 +59,200 @@ translation_strings = {
"Edit media": "Medyayı düzenle",
"Edit profile": "Profili düzenle",
"Edit subtitle": "Alt yazıyı düzenle",
"Enable Comments": "Yorumları Etkinleştir",
"Enable Download": "İndirmeyi Etkinleştir",
"Enter playlist name...": "Çalma listesi adını girin...",
"Failed to add categories": "Kategoriler eklenemedi",
"Failed to add media to playlists": "Çalma listelerine medya eklenemedi",
"Failed to add tags": "Etiketler eklenemedi",
"Failed to add users": "Kullanıcılar eklenemedi",
"Failed to change owner": "Sahip değiştirilemedi",
"Failed to change owner. Please try again.": "Sahip değiştirilemedi. Lütfen tekrar deneyin.",
"Failed to copy media.": "Medya kopyalanamadı.",
"Failed to create playlist": "Çalma listesi oluşturulamadı",
"Failed to delete media. Please try again.": "Medya silinemedi. Lütfen tekrar deneyin.",
"Failed to disable comments.": "Yorumlar devre dışı bırakılamadı.",
"Failed to disable download.": "İndirme devre dışı bırakılamadı.",
"Failed to enable comments.": "Yorumlar etkinleştirilemedi.",
"Failed to enable download.": "İndirme etkinleştirilemedi.",
"Failed to fetch all categories": "Tüm kategoriler alınamadı",
"Failed to fetch all tags": "Tüm etiketler alınamadı",
"Failed to fetch existing categories": "Mevcut kategoriler alınamadı",
"Failed to fetch existing tags": "Mevcut etiketler alınamadı",
"Failed to fetch existing users": "Mevcut kullanıcılar alınamadı",
"Failed to fetch playlist membership": "Çalma listesi üyeliği alınamadı",
"Failed to fetch playlists": "Çalma listeleri alınamadı",
"Failed to load categories": "Kategoriler yüklenemedi",
"Failed to load existing permissions": "Mevcut izinler yüklenemedi",
"Failed to load playlists": "Çalma listeleri yüklenemedi",
"Failed to load tags": "Etiketler yüklenemedi",
"Failed to remove categories": "Kategoriler kaldırılamadı",
"Failed to remove media from playlists": "Çalma listelerinden medya kaldırılamadı",
"Failed to remove tags": "Etiketler kaldırılamadı",
"Failed to remove users": "Kullanıcılar kaldırılamadı",
"Failed to search users": "Kullanıcılar aranamadı",
"Failed to set publish state": "Yayınlanma durumu ayarlanamadı",
"Failed to set publish state. Please try again.": "Yayınlanma durumu ayarlanamadı. Lütfen tekrar deneyin.",
"Failed to update categories. Please try again.": "Kategoriler güncellenemedi. Lütfen tekrar deneyin.",
"Failed to update permissions. Please try again.": "İzinler güncellenemedi. Lütfen tekrar deneyin.",
"Failed to update playlists. Please try again.": "Çalma listeleri güncellenemedi. Lütfen tekrar deneyin.",
"Failed to update tags. Please try again.": "Etiketler güncellenemedi. Lütfen tekrar deneyin.",
"Featured": "Öne Çıkan",
"Filter existing users...": "Mevcut kullanıcıları filtrele...",
"Filter playlists...": "Çalma listelerini filtrele...",
"Filters": "Filtreler",
"Go": "Git",
"History": "Geçmiş",
"Home": "Ana Sayfa",
"Image": "Resim",
"Language": "Dil",
"Latest": "En Son",
"Like count": "Beğeni sayısı",
"Liked media": "Beğenilen medya",
"Likes - Least": "Beğeniler - En Az",
"Likes - Most": "Beğeniler - En Çok",
"Loading categories...": "Kategoriler yükleniyor...",
"Loading existing users...": "Mevcut kullanıcılar yükleniyor...",
"Loading playlists...": "Çalma listeleri yükleniyor...",
"Loading tags...": "Etiketler yükleniyor...",
"MEDIA TYPE": "MEDYA TÜRÜ",
"Manage": "Yönet",
"Manage Playlists": "Çalma Listelerini Yönet",
"Manage comments": "Yorumları yönet",
"Manage media": "Medyayı yönet",
"Manage users": "Kullanıcıları yönet",
"Media": "Medya",
"Media I own": "Sahip olduğum medya",
"Media was edited": "Medya düzenlendi",
"Members": "Üyeler",
"My media": "Medyam",
"My playlists": "Çalma listelerim",
"No": "Hayır",
"No categories": "Kategori yok",
"No comment yet": "Henüz yorum yok",
"No comments yet": "Henüz yorum yok",
"No existing": "Mevcut yok",
"No playlists available": "Kullanılabilir çalma listesi yok",
"No playlists selected": "Seçili çalma listesi yok",
"No results for": "Sonuç bulunamadı",
"No tags": "Etiket yok",
"No users to add": "Eklenecek kullanıcı yok",
"PLAYLISTS": "ÇALMA LİSTELERİ",
"PUBLISH STATE": "YAYINLANMA DURUMU",
"Pdf": "PDF",
"Playlists": "Çalma listeleri",
"Plays - Least": "Oynatmalar - En Az",
"Plays - Most": "Oynatmalar - En Çok",
"Please select a publish state": "Lütfen bir yayınlanma durumu seçin",
"Please select a user": "Lütfen bir kullanıcı seçin",
"Powered by": "Tarafından desteklenmektedir",
"Private": "Özel",
"Proceed": "Devam Et",
"Processing...": "İşleniyor...",
"Public": "Genel",
"Publish": "Yayınla",
"Publish State": "Yayınlanma Durumu",
"Published": "Yayınlandı",
"Published on": "Yayınlanma tarihi",
"Recent uploads": "Son yüklemeler",
"Recommended": "Önerilen",
"Record Screen": "Ekranı Kaydet",
"Register": "Kayıt Ol",
"Remove category": "Kategoriyi kaldır",
"Remove from list": "Listeden kaldır",
"Remove tag": "Etiketi kaldır",
"Remove user": "Kullanıcıyı kaldır",
"Replace": "",
"SAVE": "KAYDET",
"SEARCH": "ARA",
"SHARE": "PAYLAŞ",
"SHOW MORE": "DAHA FAZLA GÖSTER",
"SORT BY": "SIRALA",
"SUBMIT": "GÖNDER",
"Search": "Ara",
"Search for user...": "Kullanıcı ara...",
"Search users to add...": "Eklenecek kullanıcıları ara...",
"Select": "Seç",
"Select Owner": "Sahip Seç",
"Select all": "Tümünü seç",
"Select all media": "Tüm medyayı seç",
"Select publish state:": "Yayınlanma durumunu seç:",
"Selected": "Seçildi",
"Shared by me": "Paylaştıklarım",
"Shared with me": "Benimle paylaşılanlar",
"Sign in": "Giriş Yap",
"Sign out": "Çıkış Yap",
"Sort By": "Sırala",
"Start Recording": "Kaydı Başlat",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Medya yüklemeye ve çalışmanızı paylaşmaya başlayın. Yüklediğiniz medya burada görünecektir.",
"Stop Recording": "Kaydı Durdur",
"Submit": "Gönder",
"Subtitle was added": "Alt yazı eklendi",
"Subtitles": "Altyazılar",
"Successfully Copied": "Başarıyla kopyalandı",
"Successfully Disabled Download": "İndirme başarıyla devre dışı bırakıldı",
"Successfully Disabled comments": "Yorumlar başarıyla devre dışı bırakıldı",
"Successfully Enabled Download": "İndirme başarıyla etkinleştirildi",
"Successfully Enabled comments": "Yorumlar başarıyla etkinleştirildi",
"Successfully changed owner": "Sahip başarıyla değiştirildi",
"Successfully deleted": "Başarıyla silindi",
"Successfully updated": "Başarıyla güncellendi",
"Successfully updated categories": "Kategoriler başarıyla güncellendi",
"Successfully updated playlist membership": "Çalma listesi üyeliği başarıyla güncellendi",
"Successfully updated publish state": "Yayınlanma durumu başarıyla güncellendi",
"Successfully updated tags": "Etiketler başarıyla güncellendi",
"TAGS": "ETİKETLER",
"Tag": "Etiket",
"Tags": "Etiketler",
"Terms": "Şartlar",
"The intersection of categories in the selected media is shown": "Seçili medyadaki kategorilerin kesişimi gösterilir",
"The intersection of playlists in the selected media is shown": "Seçili medyadaki çalma listelerinin kesişimi gösterilir",
"The intersection of tags in the selected media is shown": "Seçili medyadaki etiketlerin kesişimi gösterilir",
"The intersection of users in the selected media is shown": "Seçili medyadaki kullanıcıların kesişimi gösterilir",
"The media was deleted successfully.": "Medya başarıyla silindi.",
"This month": "Bu ay",
"This week": "Bu hafta",
"This works in Chrome, Safari and Edge browsers.": "Bu, Chrome, Safari ve Edge tarayıcılarında çalışır.",
"This year": "Bu yıl",
"To add": "Eklenecek",
"Today": "Bugün",
"Trim": "Kırp",
"UPLOAD": "YÜKLE",
"UPLOAD DATE": "YÜKLEME TARİHİ",
"UPLOAD MEDIA": "MEDYA YÜKLE",
"Undo removal": "Kaldırmayı geri al",
"Unlisted": "Listelenmemiş",
"Up Next": "Sıradaki",
"Up next": "Sıradaki",
"Upload": "Yükle",
"Upload date (newest)": "Yükleme tarihi (en yeni)",
"Upload date (oldest)": "Yükleme tarihi (en eski)",
"Upload date - Newest": "Yükleme tarihi - En yeni",
"Upload date - Oldest": "Yükleme tarihi - En eski",
"Upload media": "Medya yükle",
"Uploads": "Yüklemeler",
"Users": "Kullanıcılar",
"VIEW ALL": "HEPSİNİ GÖR",
"Video": "Video",
"View all": "Hepsini gör",
"View count": "Görüntülenme sayısı",
"View media": "Medyayı Görüntüle",
"Welcome": "Hoş geldiniz",
"You are going to copy": "Kopyalayacaksınız",
"You are going to delete": "Sileceksiniz",
"You are going to disable comments to": "Yorumları devre dışı bırakacaksınız",
"You are going to disable download for": "İndirmeyi devre dışı bırakacaksınız",
"You are going to enable comments to": "Yorumları etkinleştireceksiniz",
"You are going to enable download for": "İndirmeyi etkinleştireceksiniz",
"comment": "yorum",
"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": "modern, tam özellikli açık kaynaklı bir video ve medya CMS'sidir. Medya izleme ve paylaşma ihtiyaçlarını karşılamak için geliştirilmiştir",
"media in category": "kategorideki medya",
"media in tag": "etiketteki medya",
"media, are you sure?": "medya, emin misiniz?",
"media.": "medya.",
"or": "veya",
"results for": "sonuç",
"selected": "seçildi",
"view": "görünüm",
"views": "görünümler",
"yet": "henüz",

View File

@@ -1,21 +1,57 @@
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": "پروفائل ترمیم کریں",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "ابھی تک",

View File

@@ -1,21 +1,57 @@
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": "编辑个人资料",
@@ -23,63 +59,200 @@ translation_strings = {
"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": "是一个现代化、功能齐全的开源视频和媒体CMS。它是为了满足现代网络平台观看和分享媒体的需求而开发的",
"media in category": "类别中的媒体",
"media in tag": "标签中的媒体",
"media, are you sure?": "媒体,您确定吗?",
"media.": "媒体。",
"or": "",
"results for": "个结果",
"selected": "",
"view": "查看",
"views": "查看",
"yet": "",

View File

@@ -1,21 +1,57 @@
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": "編輯個人資料",
@@ -23,63 +59,200 @@ translation_strings = {
"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": " ",

View File

@@ -910,7 +910,9 @@ def trim_video_method(media_file_path, timestamps_list):
return False
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
output_file = os.path.join(temp_dir, "output.mp4")
# Detect input file extension to preserve original format
_, input_ext = os.path.splitext(media_file_path)
output_file = os.path.join(temp_dir, f"output{input_ext}")
segment_files = []
for i, item in enumerate(timestamps_list):
@@ -920,7 +922,7 @@ def trim_video_method(media_file_path, timestamps_list):
# For single timestamp, we can use the output file directly
# For multiple timestamps, we need to create segment files
segment_file = output_file if len(timestamps_list) == 1 else os.path.join(temp_dir, f"segment_{i}.mp4")
segment_file = output_file if len(timestamps_list) == 1 else os.path.join(temp_dir, f"segment_{i}{input_ext}")
cmd = [settings.FFMPEG_COMMAND, "-y", "-ss", str(item['startTime']), "-i", media_file_path, "-t", str(duration), "-c", "copy", "-avoid_negative_ts", "1", segment_file]
@@ -963,3 +965,13 @@ def get_alphanumeric_only(string):
"""
string = "".join([char for char in string if char.isalnum()])
return string.lower()
def get_alphanumeric_and_spaces(string):
"""Returns a query that contains only alphanumeric characters and spaces
This include characters other than the English alphabet too
"""
string = "".join([char for char in string if char.isalnum() or char.isspace()])
# Replace multiple spaces with single space and strip
string = " ".join(string.split())
return string

View File

@@ -3,6 +3,7 @@
import itertools
import logging
import os
import random
import re
import subprocess
@@ -271,12 +272,16 @@ def show_related_media_content(media, request, limit):
category = media.category.first()
if category:
q_category = Q(listable=True, category=category)
q_res = models.Media.objects.filter(q_category).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[: limit - media.user.media_count]
# Fix: Ensure slice index is never negative
remaining = max(0, limit - len(m))
q_res = models.Media.objects.filter(q_category).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[:remaining]
m = list(itertools.chain(m, q_res))
if len(m) < limit:
q_generic = Q(listable=True)
q_res = models.Media.objects.filter(q_generic).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[: limit - media.user.media_count]
# Fix: Ensure slice index is never negative
remaining = max(0, limit - len(m))
q_res = models.Media.objects.filter(q_generic).order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]).prefetch_related("user")[:remaining]
m = list(itertools.chain(m, q_res))
m = list(set(m[:limit])) # remove duplicates
@@ -470,22 +475,29 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
New Media object
"""
while True:
friendly_token = helpers.produce_friendly_token()
if not models.Media.objects.filter(friendly_token=friendly_token).exists():
break
with open(original_media.media_file.path, "rb") as f:
myfile = File(f)
new_media = models.Media(
media_file=myfile,
friendly_token=friendly_token,
title=f"{original_media.title} {title_suffix}",
description=original_media.description,
user=original_media.user,
media_type="video",
media_type=original_media.media_type,
enable_comments=original_media.enable_comments,
allow_download=original_media.allow_download,
state=original_media.state,
state=helpers.get_default_state(user=original_media.user),
is_reviewed=original_media.is_reviewed,
encoding_status=original_media.encoding_status,
listable=original_media.listable,
add_date=timezone.now(),
video_height=original_media.video_height,
size=original_media.size,
duration=original_media.duration,
media_info=original_media.media_info,
)
models.Media.objects.bulk_create([new_media])
@@ -497,7 +509,7 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
with open(encoding.media_file.path, "rb") as f:
myfile = File(f)
new_encoding = models.Encoding(
media_file=myfile, media=new_media, profile=encoding.profile, status="success", progress=100, chunk=False, logs=f"Copied from encoding {encoding.id}"
media_file=myfile, media=new_media, profile=encoding.profile, size=encoding.size, status="success", progress=100, chunk=False, logs=f"Copied from encoding {encoding.id}"
)
models.Encoding.objects.bulk_create([new_encoding])
# avoids calling signals as this is still not ready
@@ -519,6 +531,33 @@ def copy_video(original_media, copy_encodings=True, title_suffix="(Trimmed)"):
poster_name = helpers.get_file_name(original_media.poster.path)
new_media.poster.save(poster_name, File(f))
if original_media.uploaded_thumbnail:
with open(original_media.uploaded_thumbnail.path, 'rb') as f:
thumbnail_name = helpers.get_file_name(original_media.uploaded_thumbnail.path)
new_media.uploaded_thumbnail.save(thumbnail_name, File(f))
if original_media.uploaded_poster:
with open(original_media.uploaded_poster.path, 'rb') as f:
poster_name = helpers.get_file_name(original_media.uploaded_poster.path)
new_media.uploaded_poster.save(poster_name, File(f))
if original_media.sprites:
with open(original_media.sprites.path, 'rb') as f:
sprites_name = helpers.get_file_name(original_media.sprites.path)
new_media.sprites.save(sprites_name, File(f))
if original_media.hls_file and os.path.exists(original_media.hls_file):
p = os.path.dirname(original_media.hls_file)
if os.path.exists(p):
new_hls_file = original_media.hls_file.replace(original_media.uid.hex, new_media.uid.hex)
models.Media.objects.filter(id=new_media.id).update(hls_file=new_hls_file)
new_p = p.replace(original_media.uid.hex, new_media.uid.hex)
if not os.path.exists(new_p):
os.makedirs(new_p, exist_ok=True)
cmd = f"cp -r {p}/* {new_p}/"
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True)
return new_media
@@ -622,28 +661,69 @@ def change_media_owner(media_id, new_user):
return None
# Change the owner
# previous_user = media.user
# keep original user as owner by adding a models.MediaPermission entry with permission=owner
# if not models.MediaPermission.objects.filter(media=media, user=previous_user, permission="owner").exists():
# models.MediaPermission.objects.create(media=media, user=previous_user, owner_user=new_user, permission="owner")
media.user = new_user
media.save(update_fields=["user"])
# Update any related permissions
media_permissions = models.MediaPermission.objects.filter(media=media)
for permission in media_permissions:
permission.owner_user = new_user
permission.save(update_fields=["owner_user"])
# Optimize: Update any related permissions in bulk instead of loop
models.MediaPermission.objects.filter(media=media).update(owner_user=new_user)
# remove any existing permissions for the new user, since they are now owner
models.MediaPermission.objects.filter(media=media, user=new_user).delete()
return media
def copy_media(media_id):
def copy_media(media):
"""Create a copy of a media
Args:
media_id: ID of the media to copy
media: Media object to copy
Returns:
None
"""
pass
if media.media_type in ["video", "audio"]:
new_media = copy_video(media, title_suffix="(Copy)")
else:
# check if media.media_file.path exists actually in disk
if not os.path.exists(media.media_file.path):
return None
while True:
friendly_token = helpers.produce_friendly_token()
if not models.Media.objects.filter(friendly_token=friendly_token).exists():
break
with open(media.media_file.path, "rb") as f:
myfile = File(f)
new_media = models.Media.objects.create(
media_file=myfile,
friendly_token=friendly_token,
title=f"{media.title} (Copy)",
description=media.description,
user=media.user,
media_type=media.media_type,
enable_comments=media.enable_comments,
allow_download=media.allow_download,
state=helpers.get_default_state(user=media.user),
is_reviewed=media.is_reviewed,
encoding_status=media.encoding_status,
add_date=timezone.now(),
)
# Copy categories and tags
for category in media.category.all():
new_media.category.add(category)
for tag in media.tags.all():
new_media.tags.add(tag)
return new_media
def is_media_allowed_type(media):

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.2.6 on 2025-12-16 14:05
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('files', '0013_page_tinymcemedia'),
]
operations = [
migrations.AlterModelOptions(
name='subtitle',
options={'ordering': ['language__title'], 'verbose_name': 'Caption', 'verbose_name_plural': 'Captions'},
),
migrations.AlterModelOptions(
name='transcriptionrequest',
options={'verbose_name': 'Caption Request', 'verbose_name_plural': 'Caption Requests'},
),
migrations.AlterModelOptions(
name='videotrimrequest',
options={'verbose_name': 'Trim Request', 'verbose_name_plural': 'Trim Requests'},
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.6 on 2025-12-29 16:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0014_alter_subtitle_options_and_more'),
]
operations = [
migrations.AddField(
model_name='category',
name='is_lms_course',
field=models.BooleanField(db_index=True, default=False, help_text='Whether this category represents an LMS course'),
),
migrations.AddField(
model_name='category',
name='lti_context_id',
field=models.CharField(blank=True, db_index=True, help_text='LTI context ID from platform', max_length=255),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 5.2.6 on 2025-12-29 16:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0015_category_is_lms_course_category_lti_context_id'),
('lti', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='category',
name='lti_platform',
field=models.ForeignKey(
blank=True, help_text='LTI Platform if this is an LTI course', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='categories', to='lti.ltiplatform'
),
),
]

View File

@@ -47,6 +47,13 @@ class Category(models.Model):
verbose_name='IDP Config Name',
)
# LTI/LMS integration fields
is_lms_course = models.BooleanField(default=False, db_index=True, help_text='Whether this category represents an LMS course')
lti_platform = models.ForeignKey('lti.LTIPlatform', blank=True, null=True, on_delete=models.SET_NULL, related_name='categories', help_text='LTI Platform if this is an LTI course')
lti_context_id = models.CharField(max_length=255, blank=True, db_index=True, help_text='LTI context ID from platform')
def __str__(self):
return self.title
@@ -91,10 +98,10 @@ class Category(models.Model):
if self.listings_thumbnail:
return self.listings_thumbnail
if Media.objects.filter(category=self, state="public").exists():
media = Media.objects.filter(category=self, state="public").order_by("-views").first()
if media:
return media.thumbnail_url
# Optimize: Use first() directly instead of exists() + first() (saves one query)
media = Media.objects.filter(category=self, state="public").order_by("-views").first()
if media:
return media.thumbnail_url
return None
@@ -137,7 +144,7 @@ class Tag(models.Model):
return True
def save(self, *args, **kwargs):
self.title = helpers.get_alphanumeric_only(self.title)
self.title = helpers.get_alphanumeric_and_spaces(self.title)
self.title = self.title[:100]
super(Tag, self).save(*args, **kwargs)

View File

@@ -270,7 +270,9 @@ class Media(models.Model):
if self.media_file != self.__original_media_file:
# set this otherwise gets to infinite loop
self.__original_media_file = self.media_file
self.media_init()
from .. import tasks
tasks.media_init.apply_async(args=[self.friendly_token], countdown=5)
# for video files, if user specified a different time
# to automatically grub thumbnail
@@ -282,7 +284,7 @@ class Media(models.Model):
self.allow_whisper_transcribe != self.__original_allow_whisper_transcribe or self.allow_whisper_transcribe_and_translate != self.__original_allow_whisper_transcribe_and_translate
)
if transcription_changed and self.media_type == "video":
if transcription_changed and self.media_type in ["video", "audio"]:
self.transcribe_function()
# Update the original values for next comparison
@@ -329,10 +331,17 @@ class Media(models.Model):
if to_transcribe:
TranscriptionRequest.objects.create(media=self, translate_to_english=False)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=False)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, False],
countdown=10,
)
if to_transcribe_and_translate:
TranscriptionRequest.objects.create(media=self, translate_to_english=True)
tasks.whisper_transcribe.delay(self.friendly_token, translate_to_english=True)
tasks.whisper_transcribe.apply_async(
args=[self.friendly_token, True],
countdown=10,
)
def update_search_vector(self):
"""
@@ -343,20 +352,15 @@ class Media(models.Model):
# first get anything interesting out of the media
# that needs to be search able
a_tags = b_tags = ""
a_tags = ""
if self.id:
a_tags = " ".join([tag.title for tag in self.tags.all()])
b_tags = " ".join([tag.title.replace("-", " ") for tag in self.tags.all()])
items = [
self.title,
self.user.username,
self.user.email,
self.user.name,
self.description,
a_tags,
b_tags,
]
items = [self.friendly_token, self.title, self.user.username, self.user.email, self.user.name, self.description, a_tags]
for subtitle in self.subtitles.all():
items.append(subtitle.subtitle_text)
items = [item for item in items if item]
text = " ".join(items)
text = " ".join([token for token in text.lower().split(" ") if token not in STOP_WORDS])
@@ -406,11 +410,16 @@ class Media(models.Model):
self.media_type = "image"
elif kind == "pdf":
self.media_type = "pdf"
elif kind == "audio":
self.media_type = "audio"
elif kind == "video":
self.media_type = "video"
if self.media_type in ["audio", "image", "pdf"]:
if self.media_type in ["image", "pdf"]:
self.encoding_status = "success"
else:
ret = helpers.media_file_info(self.media_file.path)
if ret.get("fail"):
self.media_type = ""
self.encoding_status = "fail"
@@ -759,6 +768,8 @@ class Media(models.Model):
return helpers.url_from_path(self.uploaded_thumbnail.path)
if self.thumbnail:
return helpers.url_from_path(self.thumbnail.path)
if self.media_type == "audio":
return helpers.url_from_path("userlogos/poster_audio.jpg")
return None
@property
@@ -772,6 +783,9 @@ class Media(models.Model):
return helpers.url_from_path(self.uploaded_poster.path)
if self.poster:
return helpers.url_from_path(self.poster.path)
if self.media_type == "audio":
return helpers.url_from_path("userlogos/poster_audio.jpg")
return None
@property

View File

@@ -1,8 +1,11 @@
import os
import tempfile
import pysubs2
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse
from .. import helpers
@@ -42,6 +45,8 @@ class Subtitle(models.Model):
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
class Meta:
verbose_name = "Caption"
verbose_name_plural = "Captions"
ordering = ["language__title"]
def __str__(self):
@@ -71,6 +76,17 @@ class Subtitle(models.Model):
raise Exception("Could not convert to srt")
return True
@property
def subtitle_text(self):
sub = pysubs2.load(self.subtitle_file.path, encoding="utf-8")
text = ' '.join([line.text for line in sub])
text = text.replace("\\N", " ")
text = text.replace("-", " ")
text = text.replace(".", " ")
text = text.replace(" ", " ")
return text
class TranscriptionRequest(models.Model):
# Whisper transcription request
@@ -80,5 +96,19 @@ class TranscriptionRequest(models.Model):
translate_to_english = models.BooleanField(default=False)
logs = models.TextField(blank=True, null=True)
class Meta:
verbose_name = "Caption Request"
verbose_name_plural = "Caption Requests"
def __str__(self):
return f"Transcription request for {self.media.title} - {self.status}"
@receiver(post_save, sender=Subtitle)
def subtitle_save(sender, instance, created, **kwargs):
from .. import tasks
tasks.update_search_vector.apply_async(
args=[instance.media.friendly_token],
countdown=10,
)

View File

@@ -56,6 +56,10 @@ class VideoTrimRequest(models.Model):
media_trim_style = models.CharField(max_length=20, choices=TRIM_STYLE_CHOICES, default="no_encoding")
timestamps = models.JSONField(null=False, blank=False, help_text="Timestamps for trimming")
class Meta:
verbose_name = "Trim Request"
verbose_name_plural = "Trim Requests"
def __str__(self):
return f"Trim request for {self.media.title} ({self.status})"

View File

@@ -101,10 +101,17 @@ class MediaSerializer(serializers.ModelSerializer):
class SingleMediaSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source="user.username")
url = serializers.SerializerMethodField()
is_shared = serializers.SerializerMethodField()
def get_url(self, obj):
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
def get_is_shared(self, obj):
"""Check if media has custom permissions or RBAC categories"""
custom_permissions = obj.permissions.exists()
rbac_categories = obj.category.filter(is_rbac_category=True).exists()
return custom_permissions or rbac_categories
class Meta:
model = Media
read_only_fields = (
@@ -133,6 +140,7 @@ class SingleMediaSerializer(serializers.ModelSerializer):
"edit_date",
"media_type",
"state",
"is_shared",
"duration",
"thumbnail_url",
"poster_url",
@@ -212,6 +220,7 @@ class CategorySerializer(serializers.ModelSerializer):
model = Category
fields = (
"title",
"uid",
"description",
"is_global",
"media_count",
@@ -232,7 +241,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
read_only_fields = ("add_date", "user")
fields = ("add_date", "title", "description", "user", "media_count", "url", "api_url", "thumbnail_url")
fields = ("id", "add_date", "title", "description", "user", "media_count", "url", "api_url", "thumbnail_url", "friendly_token")
class PlaylistDetailSerializer(serializers.ModelSerializer):

View File

@@ -528,6 +528,17 @@ def whisper_transcribe(friendly_token, translate_to_english=False):
return False
@task(name="update_search_vector", queue="short_tasks")
def update_search_vector(friendly_token):
try:
media = Media.objects.get(friendly_token=friendly_token)
media.update_search_vector()
except: # noqa
return False
return True
@task(name="produce_sprite_from_video", queue="long_tasks")
def produce_sprite_from_video(friendly_token):
"""Produces a sprites file for a video, uses ffmpeg"""
@@ -614,6 +625,18 @@ def create_hls(friendly_token):
return True
@task(name="media_init", queue="short_tasks")
def media_init(friendly_token):
try:
media = Media.objects.get(friendly_token=friendly_token)
except: # noqa
logger.info("failed to get media with friendly_token %s" % friendly_token)
return False
media.media_init()
return True
@task(name="check_running_states", queue="short_tasks")
def check_running_states():
# Experimental - unused
@@ -980,10 +1003,10 @@ def post_trim_action(friendly_token):
produce_sprite_from_video.delay(friendly_token)
create_hls.delay(friendly_token)
vt_request = VideoTrimRequest.objects.filter(media=media, status="running").first()
if vt_request:
vt_request.status = "success"
vt_request.save(update_fields=["status"])
vt_request = VideoTrimRequest.objects.filter(media=media, status="running").first()
if vt_request:
vt_request.status = "success"
vt_request.save(update_fields=["status"])
return True
@@ -1003,7 +1026,6 @@ def video_trim_task(self, trim_request_id):
timestamps_encodings = get_trim_timestamps(trim_request.media.trim_video_path, trim_request.timestamps)
timestamps_original = get_trim_timestamps(trim_request.media.media_file.path, trim_request.timestamps)
if not timestamps_encodings:
trim_request.status = "fail"
trim_request.save(update_fields=["status"])

View File

@@ -20,6 +20,7 @@ urlpatterns = [
re_path(r"^contact$", views.contact, name="contact"),
re_path(r"^publish", views.publish_media, name="publish_media"),
re_path(r"^edit_chapters", views.edit_chapters, name="edit_chapters"),
re_path(r"^replace_media", views.replace_media, name="replace_media"),
re_path(r"^edit_video", views.edit_video, name="edit_video"),
re_path(r"^edit", views.edit_media, name="edit_media"),
re_path(r"^embed", views.embed_media, name="get_embed"),
@@ -79,6 +80,7 @@ urlpatterns = [
views.trim_video,
),
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
re_path(r"^api/v1/categories/contributor$", views.CategoryListContributor.as_view()),
re_path(r"^api/v1/tags$", views.TagList.as_view()),
re_path(r"^api/v1/comments$", views.CommentList.as_view()),
re_path(
@@ -110,7 +112,7 @@ urlpatterns = [
re_path(r"^manage/users$", views.manage_users, name="manage_users"),
# Media uploads in ADMIN created pages
re_path(r"^tinymce/upload/", tinymce_handlers.upload_image, name="tinymce_upload_image"),
re_path("^(?P<slug>[\w.-]*)$", views.get_page, name="get_page"), # noqa: W605
re_path(r"^(?P<slug>[\w.-]*)$", views.get_page, name="get_page"), # noqa: W605
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,7 +1,7 @@
# Import all views for backward compatibility
from .auth import custom_login_view, saml_metadata # noqa: F401
from .categories import CategoryList, TagList # noqa: F401
from .categories import CategoryList, CategoryListContributor, TagList # noqa: F401
from .comments import CommentDetail, CommentList # noqa: F401
from .encoding import EncodeProfileList, EncodingDetail # noqa: F401
from .media import MediaActions # noqa: F401
@@ -32,6 +32,7 @@ from .pages import members # noqa: F401
from .pages import publish_media # noqa: F401
from .pages import recommended_media # noqa: F401
from .pages import record_screen # noqa: F401
from .pages import replace_media # noqa: F401
from .pages import search # noqa: F401
from .pages import setlanguage # noqa: F401
from .pages import sitemap # noqa: F401

View File

@@ -43,6 +43,40 @@ class CategoryList(APIView):
return Response(ret)
class CategoryListContributor(APIView):
"""List categories where user has contributor access"""
@swagger_auto_schema(
manual_parameters=[],
tags=['Categories'],
operation_summary='Lists Categories for Contributors',
operation_description='Lists all categories where the user has contributor access',
responses={
200: openapi.Response('response description', CategorySerializer),
},
)
def get(self, request, format=None):
if not request.user.is_authenticated:
return Response([])
categories = Category.objects.none()
# Get global/public categories (non-RBAC)
public_categories = Category.objects.filter(is_rbac_category=False).prefetch_related("user")
# Get RBAC categories where user has contributor access
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_contributor()
categories = public_categories.union(rbac_categories)
else:
categories = public_categories
categories = categories.order_by("title")
serializer = CategorySerializer(categories, many=True, context={"request": request})
return Response(serializer.data)
class TagList(APIView):
"""List tags"""

View File

@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from django.conf import settings
from django.contrib.postgres.search import SearchQuery
from django.db.models import Q
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
@@ -33,7 +33,15 @@ from ..methods import (
show_related_media,
update_user_ratings,
)
from ..models import EncodeProfile, Media, MediaPermission, Playlist, PlaylistMedia
from ..models import (
Category,
EncodeProfile,
Media,
MediaPermission,
Playlist,
PlaylistMedia,
Tag,
)
from ..serializers import MediaSearchSerializer, MediaSerializer, SingleMediaSerializer
from ..stop_words import STOP_WORDS
from ..tasks import save_user_action
@@ -61,15 +69,13 @@ class MediaList(APIView):
if user:
base_filters &= Q(user=user)
base_queryset = Media.objects.prefetch_related("user")
base_queryset = Media.objects.prefetch_related("user", "tags")
if not request.user.is_authenticated:
return base_queryset.filter(base_filters).order_by("-add_date")
return base_queryset.filter(base_filters)
# Build OR conditions for authenticated users
conditions = base_filters # Start with listable media
conditions = base_filters
# Add user permissions
permission_filter = {'user': request.user}
if user:
permission_filter['owner_user'] = user
@@ -80,7 +86,6 @@ class MediaList(APIView):
perm_conditions &= Q(user=user)
conditions |= perm_conditions
# Add RBAC conditions
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
rbac_conditions = Q(category__in=rbac_categories)
@@ -88,10 +93,9 @@ class MediaList(APIView):
rbac_conditions &= Q(user=user)
conditions |= rbac_conditions
return base_queryset.filter(conditions).distinct().order_by("-add_date")[:1000]
return base_queryset.filter(conditions).distinct()
def get(self, request, format=None):
# Show media
# authenticated users can see:
# All listable media (public access)
@@ -100,51 +104,156 @@ class MediaList(APIView):
params = self.request.query_params
show_param = params.get("show", "")
author_param = params.get("author", "").strip()
tag = params.get("t", "").strip()
ordering = params.get("ordering", "").strip()
sort_by = params.get("sort_by", "").strip()
media_type = params.get("media_type", "").strip()
upload_date = params.get('upload_date', '').strip()
duration = params.get('duration', '').strip()
publish_state = params.get('publish_state', '').strip()
query = params.get("q", "").strip().lower()
parsed_combined = False
if sort_by and '_' in sort_by:
parts = sort_by.rsplit('_', 1)
if len(parts) == 2 and parts[1] in ['asc', 'desc']:
field, direction = parts
if field in ["title", "add_date", "edit_date", "views", "likes"]:
sort_by = field
ordering = "" if direction == "asc" else "-"
parsed_combined = True
# Fall back to legacy handling only if we didn't parse a combined option
if not parsed_combined:
sort_by_options = ["title", "add_date", "edit_date", "views", "likes"]
if sort_by not in sort_by_options:
sort_by = "add_date"
if ordering == "asc":
ordering = ""
else:
ordering = "-"
if media_type not in ["video", "image", "audio", "pdf"]:
media_type = None
gte = None
if upload_date:
if upload_date == 'today':
gte = datetime.now().date()
if upload_date == 'this_week':
gte = datetime.now() - timedelta(days=7)
if upload_date == 'this_month':
year = datetime.now().date().year
month = datetime.now().date().month
gte = datetime(year, month, 1)
if upload_date == 'this_year':
year = datetime.now().date().year
gte = datetime(year, 1, 1)
already_sorted = False
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
if show_param == "recommended":
pagination_class = FastPaginationWithoutCount
media = show_recommended_media(request, limit=50)
already_sorted = True
elif show_param == "featured":
media = Media.objects.filter(listable=True, featured=True).prefetch_related("user").order_by("-add_date")
media = Media.objects.filter(listable=True, featured=True).prefetch_related("user", "tags")
elif show_param == "shared_by_me":
if not self.request.user.is_authenticated:
media = Media.objects.none()
else:
media = Media.objects.filter(permissions__owner_user=self.request.user).prefetch_related("user")
media = Media.objects.filter(permissions__owner_user=self.request.user).prefetch_related("user", "tags").distinct()
elif show_param == "shared_with_me":
if not self.request.user.is_authenticated:
media = Media.objects.none()
else:
base_queryset = Media.objects.prefetch_related("user")
user_media_filters = {'permissions__user': request.user}
media = base_queryset.filter(**user_media_filters)
base_queryset = Media.objects.prefetch_related("user", "tags")
# Build OR conditions similar to _get_media_queryset
conditions = Q(permissions__user=request.user)
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
rbac_filters = {'category__in': rbac_categories}
conditions |= Q(category__in=rbac_categories)
rbac_media = base_queryset.filter(**rbac_filters)
media = media.union(rbac_media)
media = media.order_by("-add_date")[:1000] # limit to 1000 results
media = base_queryset.filter(conditions).distinct()
elif author_param:
user_queryset = User.objects.all()
user = get_object_or_404(user_queryset, username=author_param)
if self.request.user == user or is_mediacms_editor(self.request.user):
media = Media.objects.filter(user=user).prefetch_related("user").order_by("-add_date")
media = Media.objects.filter(user=user).prefetch_related("user", "tags")
else:
media = self._get_media_queryset(request, user)
already_sorted = True
else:
media = self._get_media_queryset(request)
if is_mediacms_editor(self.request.user):
media = Media.objects.prefetch_related("user", "tags")
else:
media = self._get_media_queryset(request)
already_sorted = True
if query:
query = helpers.clean_query(query)
q_parts = [q_part.rstrip("y") for q_part in query.split() if q_part not in STOP_WORDS]
if q_parts:
query = SearchQuery(q_parts[0] + ":*", search_type="raw")
for part in q_parts[1:]:
query &= SearchQuery(part + ":*", search_type="raw")
else:
query = None
if query:
media = media.filter(search=query)
if tag:
media = media.filter(tags__title=tag)
if media_type:
media = media.filter(media_type=media_type)
if upload_date and gte:
media = media.filter(add_date__gte=gte)
if duration:
if duration == '0-20':
media = media.filter(duration__gte=0, duration__lt=1200)
elif duration == '20-40':
media = media.filter(duration__gte=1200, duration__lt=2400)
elif duration == '40-60':
media = media.filter(duration__gte=2400, duration__lt=3600)
elif duration == '60-120':
media = media.filter(duration__gte=3600)
if publish_state:
if publish_state == 'shared':
# Filter media that have custom permissions OR RBAC categories
shared_conditions = Q(permissions__isnull=False) | Q(category__is_rbac_category=True)
media = media.filter(shared_conditions).distinct()
elif publish_state in ['private', 'public', 'unlisted']:
media = media.filter(state=publish_state)
if not already_sorted:
media = media.order_by(f"{ordering}{sort_by}")
media = media[:1000]
paginator = pagination_class()
page = paginator.paginate_queryset(media, request)
serializer = MediaSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
tags_set = set()
for media_obj in page:
for tag in media_obj.tags.all():
tags_set.add(tag.title)
tags = ", ".join(sorted(tags_set))
response = paginator.get_paginated_response(serializer.data)
response.data['tags'] = tags
return response
@swagger_auto_schema(
manual_parameters=[
@@ -194,6 +303,16 @@ class MediaBulkUserActions(APIView):
"set_state",
"change_owner",
"copy_media",
"get_ownership",
"set_ownership",
"remove_ownership",
"playlist_membership",
"category_membership",
"tag_membership",
"add_to_category",
"remove_from_category",
"add_tags",
"remove_tags",
],
),
'playlist_ids': openapi.Schema(
@@ -201,8 +320,28 @@ class MediaBulkUserActions(APIView):
items=openapi.Items(type=openapi.TYPE_INTEGER),
description="List of playlist IDs (required for add_to_playlist and remove_from_playlist actions)",
),
'category_uids': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_STRING),
description="List of category UIDs (required for add_to_category and remove_from_category actions)",
),
'tag_titles': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_STRING),
description="List of tag titles (required for add_tags and remove_tags actions)",
),
'state': openapi.Schema(type=openapi.TYPE_STRING, description="State to set (required for set_state action)", enum=["private", "public", "unlisted"]),
'owner': openapi.Schema(type=openapi.TYPE_STRING, description="New owner username (required for change_owner action)"),
'ownership_type': openapi.Schema(
type=openapi.TYPE_STRING,
description="Ownership type to filter/set/remove (required for get_ownership, set_ownership, and remove_ownership actions)",
enum=["viewer", "editor", "owner"],
),
'users': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_STRING),
description="List of usernames (required for set_ownership and remove_ownership actions)",
),
},
),
tags=['Media'],
@@ -215,28 +354,23 @@ class MediaBulkUserActions(APIView):
},
)
def post(self, request, format=None):
# Check if user is authenticated
if not request.user.is_authenticated:
return Response({"detail": "Authentication required"}, status=status.HTTP_401_UNAUTHORIZED)
# Get required parameters
media_ids = request.data.get('media_ids', [])
action = request.data.get('action')
# Validate required parameters
if not media_ids:
return Response({"detail": "media_ids is required"}, status=status.HTTP_400_BAD_REQUEST)
if not action:
return Response({"detail": "action is required"}, status=status.HTTP_400_BAD_REQUEST)
# Get media objects owned by the user
media = Media.objects.filter(user=request.user, friendly_token__in=media_ids)
if not media:
return Response({"detail": "No matching media found"}, status=status.HTTP_400_BAD_REQUEST)
# Process based on action
if action == "enable_comments":
media.update(enable_comments=True)
return Response({"detail": f"Comments enabled for {media.count()} media items"})
@@ -307,12 +441,10 @@ class MediaBulkUserActions(APIView):
if state not in valid_states:
return Response({"detail": f"state must be one of {valid_states}"}, status=status.HTTP_400_BAD_REQUEST)
# Check if user can set public state
if not is_mediacms_editor(request.user) and settings.PORTAL_WORKFLOW != "public":
if state == "public":
return Response({"detail": "You are not allowed to set media to public state"}, status=status.HTTP_400_BAD_REQUEST)
# Update media state
for m in media:
m.state = state
if m.state == "public" and m.encoding_status == "success" and m.is_reviewed is True:
@@ -343,10 +475,175 @@ class MediaBulkUserActions(APIView):
elif action == "copy_media":
for m in media:
copy_media(m.id)
copy_media(m)
return Response({"detail": f"{media.count()} media items copied"})
elif action == "get_ownership":
ownership_type = request.data.get('ownership_type')
if not ownership_type:
return Response({"detail": "ownership_type is required for get_ownership action"}, status=status.HTTP_400_BAD_REQUEST)
valid_ownership_types = ["viewer", "editor", "owner"]
if ownership_type not in valid_ownership_types:
return Response({"detail": f"ownership_type must be one of {valid_ownership_types}"}, status=status.HTTP_400_BAD_REQUEST)
media_count = media.count()
users = (
MediaPermission.objects.filter(media__in=media, permission=ownership_type)
.values('user__name', 'user__username')
.annotate(media_count=Count('media', distinct=True))
.filter(media_count=media_count)
)
results = [f"{user['user__name']} - {user['user__username']}" for user in users]
return Response({'results': results})
elif action == "set_ownership":
ownership_type = request.data.get('ownership_type')
if not ownership_type:
return Response({"detail": "ownership_type is required for set_ownership action"}, status=status.HTTP_400_BAD_REQUEST)
valid_ownership_types = ["viewer", "editor", "owner"]
if ownership_type not in valid_ownership_types:
return Response({"detail": f"ownership_type must be one of {valid_ownership_types}"}, status=status.HTTP_400_BAD_REQUEST)
usernames = request.data.get('users', [])
if not usernames:
return Response({"detail": "users is required for set_ownership action"}, status=status.HTTP_400_BAD_REQUEST)
users = User.objects.filter(username__in=usernames)
if not users.exists():
return Response({"detail": "No valid users found"}, status=status.HTTP_400_BAD_REQUEST)
for m in media:
for user in users:
# Create or update MediaPermission
MediaPermission.objects.update_or_create(user=user, media=m, defaults={'owner_user': request.user, 'permission': ownership_type})
return Response({"detail": "Action succeeded"})
elif action == "remove_ownership":
ownership_type = request.data.get('ownership_type')
if not ownership_type:
return Response({"detail": "ownership_type is required for remove_ownership action"}, status=status.HTTP_400_BAD_REQUEST)
valid_ownership_types = ["viewer", "editor", "owner"]
if ownership_type not in valid_ownership_types:
return Response({"detail": f"ownership_type must be one of {valid_ownership_types}"}, status=status.HTTP_400_BAD_REQUEST)
usernames = request.data.get('users', [])
if not usernames:
return Response({"detail": "users is required for remove_ownership action"}, status=status.HTTP_400_BAD_REQUEST)
users = User.objects.filter(username__in=usernames)
if not users.exists():
return Response({"detail": "No valid users found"}, status=status.HTTP_400_BAD_REQUEST)
MediaPermission.objects.filter(media__in=media, permission=ownership_type, user__in=users).delete()
return Response({"detail": "Action succeeded"})
elif action == "playlist_membership":
media_count = media.count()
results = list(
Playlist.objects.filter(user=request.user, playlistmedia__media__in=media)
.values('id', 'friendly_token', 'title')
.annotate(media_count=Count('playlistmedia__media', distinct=True))
.filter(media_count=media_count)
)
return Response({'results': results})
elif action == "category_membership":
media_count = media.count()
results = list(Category.objects.filter(media__in=media).values('title', 'uid').annotate(media_count=Count('media', distinct=True)).filter(media_count=media_count))
return Response({'results': results})
elif action == "tag_membership":
media_count = media.count()
results = list(Tag.objects.filter(media__in=media).values('title').annotate(media_count=Count('media', distinct=True)).filter(media_count=media_count))
return Response({'results': results})
elif action == "add_to_category":
category_uids = request.data.get('category_uids', [])
if not category_uids:
return Response({"detail": "category_uids is required for add_to_category action"}, status=status.HTTP_400_BAD_REQUEST)
categories = Category.objects.filter(uid__in=category_uids)
if not categories:
return Response({"detail": "No matching categories found"}, status=status.HTTP_400_BAD_REQUEST)
added_count = 0
for category in categories:
for m in media:
if not m.category.filter(uid=category.uid).exists():
m.category.add(category)
added_count += 1
return Response({"detail": f"Added {added_count} media items to {categories.count()} categories"})
elif action == "remove_from_category":
category_uids = request.data.get('category_uids', [])
if not category_uids:
return Response({"detail": "category_uids is required for remove_from_category action"}, status=status.HTTP_400_BAD_REQUEST)
categories = Category.objects.filter(uid__in=category_uids)
if not categories:
return Response({"detail": "No matching categories found"}, status=status.HTTP_400_BAD_REQUEST)
removed_count = 0
for category in categories:
for m in media:
if m.category.filter(uid=category.uid).exists():
m.category.remove(category)
removed_count += 1
return Response({"detail": f"Removed {removed_count} media items from {categories.count()} categories"})
elif action == "add_tags":
tag_titles = request.data.get('tag_titles', [])
if not tag_titles:
return Response({"detail": "tag_titles is required for add_tags action"}, status=status.HTTP_400_BAD_REQUEST)
tags = Tag.objects.filter(title__in=tag_titles)
if not tags:
return Response({"detail": "No matching tags found"}, status=status.HTTP_400_BAD_REQUEST)
added_count = 0
for tag in tags:
for m in media:
if not m.tags.filter(title=tag.title).exists():
m.tags.add(tag)
added_count += 1
return Response({"detail": f"Added {added_count} media items to {tags.count()} tags"})
elif action == "remove_tags":
tag_titles = request.data.get('tag_titles', [])
if not tag_titles:
return Response({"detail": "tag_titles is required for remove_tags action"}, status=status.HTTP_400_BAD_REQUEST)
tags = Tag.objects.filter(title__in=tag_titles)
if not tags:
return Response({"detail": "No matching tags found"}, status=status.HTTP_400_BAD_REQUEST)
removed_count = 0
for tag in tags:
for m in media:
if m.tags.filter(title=tag.title).exists():
m.tags.remove(tag)
removed_count += 1
return Response({"detail": f"Removed {removed_count} media items from {tags.count()} tags"})
else:
return Response({"detail": f"Unknown action: {action}"}, status=status.HTTP_400_BAD_REQUEST)
@@ -507,13 +804,14 @@ class MediaDetail(APIView):
serializer = MediaSerializer(media, data=request.data, context={"request": request})
if serializer.is_valid():
serializer.save(user=request.user)
# no need to update the media file itself, only the metadata
# if request.data.get('media_file'):
# media_file = request.data["media_file"]
# serializer.save(user=request.user, media_file=media_file)
# media_file = request.data["media_file"]
# media.state = helpers.get_default_state(request.user)
# media.listable = False
# serializer.save(user=request.user, media_file=media_file)
# else:
# serializer.save(user=request.user)
# serializer.save(user=request.user)
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -673,13 +971,26 @@ class MediaSearch(APIView):
author = params.get("author", "").strip()
upload_date = params.get('upload_date', '').strip()
sort_by_options = ["title", "add_date", "edit_date", "views", "likes"]
if sort_by not in sort_by_options:
sort_by = "add_date"
if ordering == "asc":
ordering = ""
else:
ordering = "-"
# Handle combined sort options (e.g., title_asc, views_desc)
parsed_combined = False
if sort_by and '_' in sort_by:
parts = sort_by.rsplit('_', 1)
if len(parts) == 2 and parts[1] in ['asc', 'desc']:
field, direction = parts
if field in ["title", "add_date", "edit_date", "views", "likes"]:
sort_by = field
ordering = "" if direction == "asc" else "-"
parsed_combined = True
# Fall back to legacy handling only if we didn't parse a combined option
if not parsed_combined:
sort_by_options = ["title", "add_date", "edit_date", "views", "likes"]
if sort_by not in sort_by_options:
sort_by = "add_date"
if ordering == "asc":
ordering = ""
else:
ordering = "-"
if media_type not in ["video", "image", "audio", "pdf"]:
media_type = None
@@ -689,11 +1000,15 @@ class MediaSearch(APIView):
return Response(ret, status=status.HTTP_200_OK)
if request.user.is_authenticated:
basic_query = Q(listable=True) | Q(permissions__user=request.user)
if is_mediacms_editor(self.request.user):
media = Media.objects.prefetch_related("user", "tags")
basic_query = Q()
else:
basic_query = Q(listable=True) | Q(permissions__user=request.user) | Q(user=request.user)
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
basic_query |= Q(category__in=rbac_categories)
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
basic_query |= Q(category__in=rbac_categories)
else:
basic_query = Q(listable=True)

View File

@@ -1,4 +1,5 @@
import json
import os
from django.conf import settings
from django.contrib import messages
@@ -18,11 +19,12 @@ from ..forms import (
EditSubtitleForm,
MediaMetadataForm,
MediaPublishForm,
ReplaceMediaForm,
SubtitleForm,
WhisperSubtitlesForm,
)
from ..frontend_translations import translate_string
from ..helpers import get_alphanumeric_only
from ..helpers import get_alphanumeric_and_spaces
from ..methods import (
can_transcribe_video,
create_video_trim_request,
@@ -94,7 +96,7 @@ def add_subtitle(request):
if not media:
return HttpResponseRedirect("/")
if not (request.user == media.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
# Initialize variables
@@ -146,7 +148,7 @@ def edit_subtitle(request):
if not subtitle:
return HttpResponseRedirect("/")
if not (request.user == subtitle.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(subtitle.media)):
return HttpResponseRedirect("/")
context = {"subtitle": subtitle, "action": action}
@@ -252,7 +254,7 @@ def video_chapters(request, friendly_token):
if not media:
return HttpResponseRedirect("/")
if not (request.user == media.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
try:
@@ -308,8 +310,8 @@ def edit_media(request):
media.tags.remove(tag)
if form.cleaned_data.get("new_tags"):
for tag in form.cleaned_data.get("new_tags").split(","):
tag = get_alphanumeric_only(tag)
tag = tag[:99]
tag = get_alphanumeric_and_spaces(tag)
tag = tag[:100]
if tag:
try:
tag = Tag.objects.get(title=tag)
@@ -343,6 +345,10 @@ def publish_media(request):
if not (request.user.has_contributor_access_to_media(media) or is_mediacms_editor(request.user)):
return HttpResponseRedirect("/")
if not (request.user.has_owner_access_to_media(media) or is_mediacms_editor(request.user)):
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, f"Permission to publish is not grated by the owner: {media.user.name}"))
return HttpResponseRedirect(media.get_absolute_url())
if request.method == "POST":
form = MediaPublishForm(request.user, request.POST, request.FILES, instance=media)
if form.is_valid():
@@ -359,6 +365,76 @@ def publish_media(request):
)
@login_required
def replace_media(request):
"""Replace media file"""
if not getattr(settings, 'ALLOW_MEDIA_REPLACEMENT', False):
return HttpResponseRedirect("/")
friendly_token = request.GET.get("m", "").strip()
if not friendly_token:
return HttpResponseRedirect("/")
media = Media.objects.filter(friendly_token=friendly_token).first()
if not media:
return HttpResponseRedirect("/")
if not (request.user.has_contributor_access_to_media(media) or is_mediacms_editor(request.user)):
return HttpResponseRedirect("/")
if not is_media_allowed_type(media):
return HttpResponseRedirect(media.get_absolute_url())
if request.method == "POST":
form = ReplaceMediaForm(media, request.POST, request.FILES)
if form.is_valid():
new_media_file = form.cleaned_data.get("new_media_file")
media.encodings.all().delete()
if media.thumbnail:
helpers.rm_file(media.thumbnail.path)
media.thumbnail = None
if media.poster:
helpers.rm_file(media.poster.path)
media.poster = None
if media.uploaded_thumbnail:
helpers.rm_file(media.uploaded_thumbnail.path)
media.uploaded_thumbnail = None
if media.uploaded_poster:
helpers.rm_file(media.uploaded_poster.path)
media.uploaded_poster = None
if media.sprites:
helpers.rm_file(media.sprites.path)
media.sprites = None
if media.preview_file_path:
helpers.rm_file(media.preview_file_path)
media.preview_file_path = ""
if media.hls_file:
hls_dir = os.path.dirname(media.hls_file)
helpers.rm_dir(hls_dir)
media.hls_file = ""
media.media_file = new_media_file
media.listable = False
media.state = helpers.get_default_state(request.user)
media.save()
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media file was replaced successfully"))
return HttpResponseRedirect(media.get_absolute_url())
else:
form = ReplaceMediaForm(media)
return render(
request,
"cms/replace_media.html",
{"form": form, "media_object": media, "add_subtitle_url": media.add_subtitle_url},
)
@login_required
def edit_chapters(request):
"""Edit chapters"""
@@ -370,7 +446,7 @@ def edit_chapters(request):
if not media:
return HttpResponseRedirect("/")
if not (request.user == media.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
chapters = media.chapter_data
@@ -395,7 +471,7 @@ def trim_video(request, friendly_token):
if not media:
return HttpResponseRedirect("/")
if not (request.user == media.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
existing_requests = VideoTrimRequest.objects.filter(media=media, status__in=["initial", "running"]).exists()
@@ -426,11 +502,11 @@ def edit_video(request):
if not media:
return HttpResponseRedirect("/")
if not (request.user == media.user or is_mediacms_editor(request.user)):
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
if media.media_type not in ["video", "audio"]:
messages.add_message(request, messages.INFO, "Media is not video")
messages.add_message(request, messages.INFO, "Media is not video or audio")
return HttpResponseRedirect(media.get_absolute_url())
if not settings.ALLOW_VIDEO_TRIMMER:
@@ -629,10 +705,12 @@ def view_media(request):
if request.user.is_authenticated:
if request.user.has_contributor_access_to_media(media) or is_mediacms_editor(request.user):
context["CAN_DELETE_MEDIA"] = True
context["CAN_EDIT_MEDIA"] = True
context["CAN_DELETE_COMMENTS"] = True
if request.user == media.user or is_mediacms_editor(request.user):
context["CAN_DELETE_MEDIA"] = True
# in case media is video and is processing (eg the case a video was just uploaded)
# attempt to show it (rather than showing a blank video player)
if media.media_type == 'video':

View File

@@ -1,4 +1,5 @@
from django.conf import settings
from django.db.models import Q
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import permissions, status
@@ -94,7 +95,11 @@ class PlaylistDetail(APIView):
serializer = PlaylistDetailSerializer(playlist, context={"request": request})
playlist_media = PlaylistMedia.objects.filter(playlist=playlist, media__state="public").prefetch_related("media__user")
# If user is the author, show all media; otherwise, show only public and unlisted media
if request.user.is_authenticated and request.user == playlist.user:
playlist_media = PlaylistMedia.objects.filter(playlist=playlist).prefetch_related("media__user").select_related("media")
else:
playlist_media = PlaylistMedia.objects.filter(playlist=playlist).filter(Q(media__state="public") | Q(media__state="unlisted")).prefetch_related("media__user").select_related("media")
playlist_media = [c.media for c in playlist_media]

39
files/widgets.py Normal file
View File

@@ -0,0 +1,39 @@
import json
from django import forms
from django.utils.safestring import mark_safe
class CategoryModalWidget(forms.SelectMultiple):
"""Two-panel category selector with modal"""
class Media:
css = {'all': ('css/category_modal.css',)}
js = ('js/category_modal.js',)
def render(self, name, value, attrs=None, renderer=None):
# Get all categories as JSON
categories = []
for opt_value, opt_label in self.choices:
if opt_value: # Skip empty choice
categories.append({'id': str(opt_value), 'title': str(opt_label)})
all_categories_json = json.dumps(categories)
selected_ids_json = json.dumps([str(v) for v in (value or [])])
html = f'''<div class="category-widget" data-name="{name}">
<div class="category-content">
<div class="category-panel">
<input type="text" class="category-search" placeholder="Search categories...">
<div class="category-list scrollable" data-panel="left"></div>
</div>
<div class="category-panel">
<h3>Selected Categories</h3>
<div class="category-list scrollable" data-panel="right"></div>
</div>
</div>
<div class="hidden-inputs"></div>
<script type="application/json" class="category-data">{{"all":{all_categories_json},"selected":{selected_ids_json}}}</script>
</div>'''
return mark_safe(html)

View File

@@ -150,6 +150,11 @@ const App = () => {
canRedo={historyPosition < history.length - 1}
/>
{/* Timeline Header */}
<div className="timeline-header-container">
<h2 className="timeline-header-title">Add Chapters</h2>
</div>
{/* Timeline Controls */}
<TimelineControls
currentTime={currentTime}

View File

@@ -28,9 +28,9 @@ const ClipSegments = ({ segments, selectedSegmentId }: ClipSegmentsProps) => {
// Generate the same color background for a segment as shown in the timeline
const getSegmentColorClass = (index: number) => {
// Return CSS class based on index modulo 8
// This matches the CSS nth-child selectors in the timeline
return `segment-default-color segment-color-${(index % 8) + 1}`;
// Return CSS class based on index modulo 20
// This matches the CSS classes for up to 20 segments
return `segment-default-color segment-color-${(index % 20) + 1}`;
};
// Get selected segment
@@ -65,8 +65,8 @@ const ClipSegments = ({ segments, selectedSegmentId }: ClipSegmentsProps) => {
<div className="segment-actions">
<button
className="delete-button"
aria-label="Delete Segment"
data-tooltip="Delete this segment"
aria-label="Delete Chapter"
data-tooltip="Delete this chapter"
onClick={() => handleDeleteSegment(segment.id)}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">

View File

@@ -13,6 +13,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
const [videoUrl, setVideoUrl] = useState<string>('');
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
const [isAudioFile, setIsAudioFile] = useState(false);
// Refs for hold-to-continue functionality
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -41,12 +42,13 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
setVideoUrl(url);
// Check if the media is an audio file and set poster image
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
const audioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
setIsAudioFile(audioFile);
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
setPosterImage(isValidPoster ? mediaPosterUrl : (isAudioFile ? AUDIO_POSTER_URL : undefined));
setPosterImage(isValidPoster ? mediaPosterUrl : (audioFile ? AUDIO_POSTER_URL : undefined));
}, [videoRef]);
// Function to jump 15 seconds backward
@@ -128,22 +130,34 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
</span>
</div>
{/* iOS-optimized Video Element with Native Controls */}
<video
ref={(ref) => setIosVideoRef(ref)}
className="w-full rounded-md"
src={videoUrl}
controls
playsInline
webkit-playsinline="true"
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>
</video>
{/* Video container with persistent background for audio files */}
<div className="ios-video-wrapper">
{/* Persistent background image for audio files (Safari fix) */}
{isAudioFile && posterImage && (
<div
className="ios-audio-poster-background"
style={{ backgroundImage: `url(${posterImage})` }}
aria-hidden="true"
/>
)}
{/* iOS-optimized Video Element with Native Controls */}
<video
ref={(ref) => setIosVideoRef(ref)}
className={`w-full rounded-md ${isAudioFile && posterImage ? 'audio-with-poster' : ''}`}
src={videoUrl}
controls
playsInline
webkit-playsinline="true"
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>
</video>
</div>
{/* iOS Video Skip Controls */}
<div className="ios-skip-controls mt-3 flex justify-center gap-4">

View File

@@ -177,7 +177,16 @@ const TimelineControls = ({
const [isAutoSaving, setIsAutoSaving] = useState(false);
const autoSaveTimerRef = useRef<NodeJS.Timeout | null>(null);
const clipSegmentsRef = useRef(clipSegments);
// Track when a drag just ended to prevent Safari from triggering clicks after drag
const dragJustEndedRef = useRef<boolean>(false);
const dragEndTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Helper function to detect Safari browser
const isSafari = () => {
if (typeof window === 'undefined') return false;
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
return /Safari/.test(userAgent) && !/Chrome/.test(userAgent) && !/Chromium/.test(userAgent);
};
// Keep clipSegmentsRef updated
useEffect(() => {
@@ -268,13 +277,8 @@ const TimelineControls = ({
// Update editing title when selected segment changes
useEffect(() => {
if (selectedSegment) {
// Check if the chapter title is a default generated name (e.g., "Chapter 1", "Chapter 2", etc.)
const isDefaultChapterName = selectedSegment.chapterTitle &&
/^Chapter \d+$/.test(selectedSegment.chapterTitle);
// If it's a default name, show empty string so placeholder appears
// If it's a custom title, show the actual title
setEditingChapterTitle(isDefaultChapterName ? '' : (selectedSegment.chapterTitle || ''));
// Always show the chapter title in the textarea, whether it's default or custom
setEditingChapterTitle(selectedSegment.chapterTitle || '');
} else {
setEditingChapterTitle('');
}
@@ -872,6 +876,12 @@ const TimelineControls = ({
logger.debug('Clearing auto-save timer in cleanup:', autoSaveTimerRef.current);
clearTimeout(autoSaveTimerRef.current);
}
// Clear any pending drag end timeout
if (dragEndTimeoutRef.current) {
clearTimeout(dragEndTimeoutRef.current);
dragEndTimeoutRef.current = null;
}
};
}, [scheduleAutoSave]);
@@ -1089,16 +1099,20 @@ const TimelineControls = ({
};
// Helper function to calculate available space for a new segment
const calculateAvailableSpace = (startTime: number): number => {
const calculateAvailableSpace = (startTime: number, segmentsOverride?: Segment[]): number => {
// Always return at least 0.1 seconds to ensure tooltip shows
const MIN_SPACE = 0.1;
// Use override segments if provided, otherwise use ref to get latest segments
// This ensures we always have the most up-to-date segments, especially important for Safari
const segmentsToUse = segmentsOverride || clipSegmentsRef.current;
// Determine the amount of available space:
// 1. Check remaining space until the end of video
const remainingDuration = Math.max(0, duration - startTime);
// 2. Find the next segment (if any)
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
const sortedSegments = [...segmentsToUse].sort((a, b) => a.startTime - b.startTime);
// Find the next and previous segments
const nextSegment = sortedSegments.find((seg) => seg.startTime > startTime);
@@ -1114,14 +1128,6 @@ const TimelineControls = ({
availableSpace = duration - startTime;
}
// Log the space calculation for debugging
logger.debug('Space calculation:', {
position: formatDetailedTime(startTime),
nextSegment: nextSegment ? formatDetailedTime(nextSegment.startTime) : 'none',
prevSegment: prevSegment ? formatDetailedTime(prevSegment.endTime) : 'none',
availableSpace: formatDetailedTime(Math.max(MIN_SPACE, availableSpace)),
});
// Always return at least MIN_SPACE to ensure tooltip shows
return Math.max(MIN_SPACE, availableSpace);
};
@@ -1130,8 +1136,11 @@ const TimelineControls = ({
const updateTooltipForPosition = (currentPosition: number) => {
if (!timelineRef.current) return;
// Use ref to get latest segments to avoid stale state issues
const currentSegments = clipSegmentsRef.current;
// Find if we're in a segment at the current position with a small tolerance
const segmentAtPosition = clipSegments.find((seg) => {
const segmentAtPosition = currentSegments.find((seg) => {
const isWithinSegment = currentPosition >= seg.startTime && currentPosition <= seg.endTime;
const isVeryCloseToStart = Math.abs(currentPosition - seg.startTime) < 0.001;
const isVeryCloseToEnd = Math.abs(currentPosition - seg.endTime) < 0.001;
@@ -1139,7 +1148,7 @@ const TimelineControls = ({
});
// Find the next and previous segments
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
const sortedSegments = [...currentSegments].sort((a, b) => a.startTime - b.startTime);
const nextSegment = sortedSegments.find((seg) => seg.startTime > currentPosition);
const prevSegment = [...sortedSegments].reverse().find((seg) => seg.endTime < currentPosition);
@@ -1149,21 +1158,13 @@ const TimelineControls = ({
setShowEmptySpaceTooltip(false);
} else {
// We're in a cutaway area
// Calculate available space for new segment
const availableSpace = calculateAvailableSpace(currentPosition);
// Calculate available space for new segment using current segments
const availableSpace = calculateAvailableSpace(currentPosition, currentSegments);
setAvailableSegmentDuration(availableSpace);
// Always show empty space tooltip
setSelectedSegmentId(null);
setShowEmptySpaceTooltip(true);
// Log position info for debugging
logger.debug('Cutaway position:', {
current: formatDetailedTime(currentPosition),
prevSegmentEnd: prevSegment ? formatDetailedTime(prevSegment.endTime) : 'none',
nextSegmentStart: nextSegment ? formatDetailedTime(nextSegment.startTime) : 'none',
availableSpace: formatDetailedTime(availableSpace),
});
}
// Update tooltip position
@@ -1193,6 +1194,12 @@ const TimelineControls = ({
if (!timelineRef.current || !scrollContainerRef.current) return;
// Safari-specific fix: Ignore clicks that happen immediately after a drag operation
// Safari fires click events after drag ends, which can cause issues with stale state
if (isSafari() && dragJustEndedRef.current) {
return;
}
// If on mobile device and video hasn't been initialized, don't handle timeline clicks
if (isIOSUninitialized) {
return;
@@ -1200,7 +1207,6 @@ const TimelineControls = ({
// Check if video is globally playing before the click
const wasPlaying = videoRef.current && !videoRef.current.paused;
logger.debug('Video was playing before timeline click:', wasPlaying);
// Reset continuation flag when clicking on timeline - ensures proper boundary detection
setContinuePastBoundary(false);
@@ -1221,14 +1227,6 @@ const TimelineControls = ({
const newTime = position * duration;
// Log the position for debugging
logger.debug(
'Timeline clicked at:',
formatDetailedTime(newTime),
'distance from end:',
formatDetailedTime(duration - newTime)
);
// Store position globally for iOS Safari (this is critical for first-time visits)
if (typeof window !== 'undefined') {
window.lastSeekedPosition = newTime;
@@ -1241,8 +1239,12 @@ const TimelineControls = ({
setClickedTime(newTime);
setDisplayTime(newTime);
// Use ref to get latest segments to avoid stale state issues, especially in Safari
// Safari can fire click events immediately after drag before React re-renders
const currentSegments = clipSegmentsRef.current;
// Find if we clicked in a segment with a small tolerance for boundaries
const segmentAtClickedTime = clipSegments.find((seg) => {
const segmentAtClickedTime = currentSegments.find((seg) => {
// Standard check for being inside a segment
const isInside = newTime >= seg.startTime && newTime <= seg.endTime;
// Additional checks for being exactly at the start or end boundary (with small tolerance)
@@ -1263,7 +1265,7 @@ const TimelineControls = ({
if (isPlayingSegments && wasPlaying) {
// Update the current segment index if we clicked into a segment
if (segmentAtClickedTime) {
const orderedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
const orderedSegments = [...currentSegments].sort((a, b) => a.startTime - b.startTime);
const targetSegmentIndex = orderedSegments.findIndex((seg) => seg.id === segmentAtClickedTime.id);
if (targetSegmentIndex !== -1) {
@@ -1316,8 +1318,9 @@ const TimelineControls = ({
// We're in a cutaway area - always show tooltip
setSelectedSegmentId(null);
// Calculate the available space for a new segment
const availableSpace = calculateAvailableSpace(newTime);
// Calculate the available space for a new segment using current segments from ref
// This ensures we use the latest segments even if React hasn't re-rendered yet
const availableSpace = calculateAvailableSpace(newTime, currentSegments);
setAvailableSegmentDuration(availableSpace);
// Calculate and set tooltip position correctly for zoomed timeline
@@ -1339,18 +1342,6 @@ const TimelineControls = ({
// Always show the empty space tooltip in cutaway areas
setShowEmptySpaceTooltip(true);
// Log the cutaway area details
const sortedSegments = [...clipSegments].sort((a, b) => a.startTime - b.startTime);
const prevSegment = [...sortedSegments].reverse().find((seg) => seg.endTime < newTime);
const nextSegment = sortedSegments.find((seg) => seg.startTime > newTime);
logger.debug('Clicked in cutaway area:', {
position: formatDetailedTime(newTime),
availableSpace: formatDetailedTime(availableSpace),
prevSegmentEnd: prevSegment ? formatDetailedTime(prevSegment.endTime) : 'none',
nextSegmentStart: nextSegment ? formatDetailedTime(nextSegment.startTime) : 'none',
});
}
}
};
@@ -1503,6 +1494,10 @@ const TimelineControls = ({
return seg;
});
// Update the ref immediately during drag to ensure we always have latest segments
// This is critical for Safari which may fire events before React re-renders
clipSegmentsRef.current = updatedSegments;
// Create a custom event to update the segments WITHOUT recording in history during drag
const updateEvent = new CustomEvent('update-segments', {
detail: {
@@ -1587,6 +1582,26 @@ const TimelineControls = ({
return seg;
});
// CRITICAL: Update the ref immediately with the new segments
// This ensures that if Safari fires a click event before React re-renders,
// the click handler will use the updated segments instead of stale ones
clipSegmentsRef.current = finalSegments;
// Safari-specific fix: Set flag to ignore clicks immediately after drag
// Safari fires click events after drag ends, which can interfere with state updates
if (isSafari()) {
dragJustEndedRef.current = true;
// Clear the flag after a delay to allow React to re-render with updated segments
// Increased timeout to ensure state has propagated
if (dragEndTimeoutRef.current) {
clearTimeout(dragEndTimeoutRef.current);
}
dragEndTimeoutRef.current = setTimeout(() => {
dragJustEndedRef.current = false;
dragEndTimeoutRef.current = null;
}, 200); // 200ms to ensure React has processed the state update and re-rendered
}
// Now we can create a history record for the complete drag operation
const actionType = isLeft ? 'adjust_segment_start' : 'adjust_segment_end';
document.dispatchEvent(
@@ -1599,6 +1614,13 @@ const TimelineControls = ({
})
);
// Dispatch segment-drag-end event for other listeners
document.dispatchEvent(
new CustomEvent('segment-drag-end', {
detail: { segmentId },
})
);
// After drag is complete, do a final check to see if playhead is inside the segment
if (selectedSegmentId === segmentId && videoRef.current) {
const currentTime = videoRef.current.currentTime;
@@ -3948,9 +3970,7 @@ const TimelineControls = ({
<button
onClick={() => setShowSaveChaptersModal(true)}
className="save-chapters-button"
data-tooltip={clipSegments.length === 0
? "Clear all chapters"
: "Save chapters"}
{...(clipSegments.length === 0 && { 'data-tooltip': 'Clear all chapters' })}
>
{clipSegments.length === 0
? 'Clear Chapters'
@@ -4087,4 +4107,4 @@ const TimelineControls = ({
);
};
export default TimelineControls;
export default TimelineControls;

View File

@@ -353,8 +353,18 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
return (
<div className="video-player-container">
{/* Persistent background image for audio files (Safari fix) */}
{isAudioFile && posterImage && (
<div
className="audio-poster-background"
style={{ backgroundImage: `url(${posterImage})` }}
aria-hidden="true"
/>
)}
<video
ref={videoRef}
className={isAudioFile && posterImage ? 'audio-with-poster' : ''}
preload="metadata"
crossOrigin="anonymous"
onClick={handleVideoClick}

View File

@@ -20,7 +20,7 @@ const useVideoChapters = () => {
// Sort by start time to find chronological position
const sortedSegments = allSegments.sort((a, b) => a.startTime - b.startTime);
// Find the index of our new segment
const chapterIndex = sortedSegments.findIndex(seg => seg.startTime === newSegmentStartTime);
const chapterIndex = sortedSegments.findIndex((seg) => seg.startTime === newSegmentStartTime);
return `Chapter ${chapterIndex + 1}`;
};
@@ -28,12 +28,18 @@ const useVideoChapters = () => {
const renumberAllSegments = (segments: Segment[]): Segment[] => {
// Sort segments by start time
const sortedSegments = [...segments].sort((a, b) => a.startTime - b.startTime);
// Renumber each segment based on its chronological position
return sortedSegments.map((segment, index) => ({
...segment,
chapterTitle: `Chapter ${index + 1}`
}));
// Only update titles that follow the default "Chapter X" pattern to preserve custom titles
return sortedSegments.map((segment, index) => {
const currentTitle = segment.chapterTitle || '';
const isDefaultTitle = /^Chapter \d+$/.test(currentTitle);
return {
...segment,
chapterTitle: isDefaultTitle ? `Chapter ${index + 1}` : currentTitle,
};
});
};
// Helper function to parse time string (HH:MM:SS.mmm) to seconds
@@ -54,6 +60,9 @@ const useVideoChapters = () => {
const [duration, setDuration] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
// Track if editor has been initialized to prevent re-initialization on Safari metadata events
const isInitializedRef = useRef<boolean>(false);
// Timeline state
const [trimStart, setTrimStart] = useState(0);
@@ -102,11 +111,7 @@ const useVideoChapters = () => {
// Detect Safari browser
const isSafari = () => {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
const isSafariBrowser = /Safari/.test(userAgent) && !/Chrome/.test(userAgent) && !/Chromium/.test(userAgent);
if (isSafariBrowser) {
logger.debug('Safari browser detected, enabling audio support fallbacks');
}
return isSafariBrowser;
return /Safari/.test(userAgent) && !/Chrome/.test(userAgent) && !/Chromium/.test(userAgent);
};
// Initialize video event listeners
@@ -115,7 +120,15 @@ const useVideoChapters = () => {
if (!video) return;
const handleLoadedMetadata = () => {
logger.debug('Video loadedmetadata event fired, duration:', video.duration);
// CRITICAL: Prevent re-initialization if editor has already been initialized
// Safari fires loadedmetadata multiple times, which was resetting segments
if (isInitializedRef.current) {
// Still update duration and trimEnd in case they changed
setDuration(video.duration);
setTrimEnd(video.duration);
return;
}
setDuration(video.duration);
setTrimEnd(video.duration);
@@ -124,9 +137,7 @@ const useVideoChapters = () => {
let initialSegments: Segment[] = [];
// Check if we have existing chapters from the backend
const existingChapters =
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.chapters) ||
[];
const existingChapters = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.chapters) || [];
if (existingChapters.length > 0) {
// Create segments from existing chapters
@@ -150,7 +161,7 @@ const useVideoChapters = () => {
// Create a default segment that spans the entire video on first load
const initialSegment: Segment = {
id: 1,
chapterTitle: '',
chapterTitle: 'Chapter 1',
startTime: 0,
endTime: video.duration,
};
@@ -169,7 +180,7 @@ const useVideoChapters = () => {
setHistory([initialState]);
setHistoryPosition(0);
setClipSegments(initialSegments);
logger.debug('Editor initialized with segments:', initialSegments.length);
isInitializedRef.current = true; // Mark as initialized
};
initializeEditor();
@@ -177,20 +188,18 @@ const useVideoChapters = () => {
// Safari-specific fallback for audio files
const handleCanPlay = () => {
logger.debug('Video canplay event fired');
// If loadedmetadata hasn't fired yet but we have duration, trigger initialization
if (video.duration && duration === 0) {
logger.debug('Safari fallback: Using canplay event to initialize');
// Also check if already initialized to prevent re-initialization
if (video.duration && duration === 0 && !isInitializedRef.current) {
handleLoadedMetadata();
}
};
// Additional Safari fallback for audio files
const handleLoadedData = () => {
logger.debug('Video loadeddata event fired');
// If we still don't have duration, try again
if (video.duration && duration === 0) {
logger.debug('Safari fallback: Using loadeddata event to initialize');
// Also check if already initialized to prevent re-initialization
if (video.duration && duration === 0 && !isInitializedRef.current) {
handleLoadedMetadata();
}
};
@@ -222,14 +231,12 @@ const useVideoChapters = () => {
// Safari-specific fallback event listeners for audio files
if (isSafari()) {
logger.debug('Adding Safari-specific event listeners for audio support');
video.addEventListener('canplay', handleCanPlay);
video.addEventListener('loadeddata', handleLoadedData);
// Additional timeout fallback for Safari audio files
const safariTimeout = setTimeout(() => {
if (video.duration && duration === 0) {
logger.debug('Safari timeout fallback: Force initializing editor');
if (video.duration && duration === 0 && !isInitializedRef.current) {
handleLoadedMetadata();
}
}, 1000);
@@ -261,21 +268,21 @@ const useVideoChapters = () => {
useEffect(() => {
if (isSafari() && videoRef.current) {
const video = videoRef.current;
const initializeSafariOnInteraction = () => {
// Try to load video metadata by attempting to play and immediately pause
const attemptInitialization = async () => {
try {
logger.debug('Safari: Attempting auto-initialization on user interaction');
// Briefly play to trigger metadata loading, then pause
await video.play();
video.pause();
// Check if we now have duration and initialize if needed
if (video.duration > 0 && clipSegments.length === 0) {
logger.debug('Safari: Successfully initialized metadata, creating default segment');
const defaultSegment: Segment = {
id: 1,
chapterTitle: '',
@@ -286,14 +293,14 @@ const useVideoChapters = () => {
setDuration(video.duration);
setTrimEnd(video.duration);
setClipSegments([defaultSegment]);
const initialState: EditorState = {
trimStart: 0,
trimEnd: video.duration,
splitPoints: [],
clipSegments: [defaultSegment],
};
setHistory([initialState]);
setHistoryPosition(0);
}
@@ -315,7 +322,7 @@ const useVideoChapters = () => {
// Add listeners for various user interactions
document.addEventListener('click', handleUserInteraction);
document.addEventListener('keydown', handleUserInteraction);
return () => {
document.removeEventListener('click', handleUserInteraction);
document.removeEventListener('keydown', handleUserInteraction);
@@ -332,7 +339,7 @@ const useVideoChapters = () => {
// This play/pause will trigger metadata loading in Safari
await video.play();
video.pause();
// The metadata events should fire now and initialize segments
return true;
} catch (error) {
@@ -564,8 +571,11 @@ const useVideoChapters = () => {
`Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? 'true' : 'false'}`
);
// Renumber all segments to ensure proper chronological naming
const renumberedSegments = renumberAllSegments(e.detail.segments);
// Update segment state immediately for UI feedback
setClipSegments(e.detail.segments);
setClipSegments(renumberedSegments);
// Always save state to history for non-intermediate actions
if (isSignificantChange) {
@@ -573,7 +583,7 @@ const useVideoChapters = () => {
// ensure we capture the state properly
setTimeout(() => {
// Deep clone to ensure state is captured correctly
const segmentsClone = JSON.parse(JSON.stringify(e.detail.segments));
const segmentsClone = JSON.parse(JSON.stringify(renumberedSegments));
// Create a complete state snapshot
const stateWithAction: EditorState = {
@@ -919,10 +929,10 @@ const useVideoChapters = () => {
const singleChapter = backendChapters[0];
const startSeconds = parseTimeToSeconds(singleChapter.startTime);
const endSeconds = parseTimeToSeconds(singleChapter.endTime);
// Check if this single chapter spans the entire video (within 0.1 second tolerance)
const isFullVideoChapter = startSeconds <= 0.1 && Math.abs(endSeconds - duration) <= 0.1;
if (isFullVideoChapter) {
logger.debug('Manual save: Single chapter spans full video - sending empty array');
backendChapters = [];

View File

@@ -82,27 +82,24 @@
font-size: 0.875rem;
font-weight: 500;
color: var(--foreground, #333);
margin: 0;
margin-bottom: 0.75rem;
}
.save-chapters-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
color: #ffffff;
background: #059669;
border-radius: 0.25rem;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
cursor: pointer;
transition: all 0.2s ease;
border: none;
white-space: nowrap;
transition: background-color 0.2s;
min-width: fit-content;
&:hover {
background-color: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
background-color: #059669;
box-shadow: 0 4px 6px -1px rgba(5, 150, 105, 0.3);
}
&.has-changes {
@@ -205,9 +202,9 @@
}
&.selected {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
background-color: rgba(59, 130, 246, 0.05);
border-color: #059669;
box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1);
background-color: rgba(5, 150, 105, 0.05);
}
}
@@ -287,29 +284,68 @@
color: rgba(51, 51, 51, 0.7);
}
/* Generate 20 shades of #059669 (rgb(5, 150, 105)) */
/* Base color: #059669 = rgb(5, 150, 105) */
/* Creating variations from lighter to darker */
.segment-color-1 {
background-color: rgba(59, 130, 246, 0.15);
background-color: rgba(167, 243, 208, 0.2);
}
.segment-color-2 {
background-color: rgba(16, 185, 129, 0.15);
background-color: rgba(134, 239, 172, 0.2);
}
.segment-color-3 {
background-color: rgba(245, 158, 11, 0.15);
background-color: rgba(101, 235, 136, 0.2);
}
.segment-color-4 {
background-color: rgba(239, 68, 68, 0.15);
background-color: rgba(68, 231, 100, 0.2);
}
.segment-color-5 {
background-color: rgba(139, 92, 246, 0.15);
background-color: rgba(35, 227, 64, 0.2);
}
.segment-color-6 {
background-color: rgba(236, 72, 153, 0.15);
background-color: rgba(20, 207, 54, 0.2);
}
.segment-color-7 {
background-color: rgba(6, 182, 212, 0.15);
background-color: rgba(15, 187, 48, 0.2);
}
.segment-color-8 {
background-color: rgba(250, 204, 21, 0.15);
background-color: rgba(10, 167, 42, 0.2);
}
.segment-color-9 {
background-color: rgba(5, 150, 105, 0.2);
}
.segment-color-10 {
background-color: rgba(4, 135, 95, 0.2);
}
.segment-color-11 {
background-color: rgba(3, 120, 85, 0.2);
}
.segment-color-12 {
background-color: rgba(2, 105, 75, 0.2);
}
.segment-color-13 {
background-color: rgba(2, 90, 65, 0.2);
}
.segment-color-14 {
background-color: rgba(1, 75, 55, 0.2);
}
.segment-color-15 {
background-color: rgba(1, 66, 48, 0.2);
}
.segment-color-16 {
background-color: rgba(1, 57, 41, 0.2);
}
.segment-color-17 {
background-color: rgba(1, 48, 34, 0.2);
}
.segment-color-18 {
background-color: rgba(0, 39, 27, 0.2);
}
.segment-color-19 {
background-color: rgba(0, 30, 20, 0.2);
}
.segment-color-20 {
background-color: rgba(0, 21, 13, 0.2);
}
/* Responsive styles */

View File

@@ -31,7 +31,7 @@
.ios-notification-icon {
flex-shrink: 0;
color: #0066cc;
color: #059669;
margin-right: 15px;
margin-top: 3px;
}
@@ -96,7 +96,7 @@
}
.ios-desktop-mode-btn {
background-color: #0066cc;
background-color: #059669;
color: white;
border: none;
border-radius: 8px;

View File

@@ -8,12 +8,40 @@
overflow: hidden;
}
/* Video wrapper for positioning background */
.ios-video-wrapper {
position: relative;
width: 100%;
background-color: black;
border-radius: 0.5rem;
overflow: hidden;
}
/* Persistent background poster for audio files (Safari fix) */
.ios-audio-poster-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
z-index: -1;
pointer-events: none;
}
.ios-video-player-container video {
position: relative;
width: 100%;
height: auto;
max-height: 360px;
aspect-ratio: 16/9;
background-color: black;
}
/* Make video transparent only for audio files with poster so background shows through */
.ios-video-player-container video.audio-with-poster {
background-color: transparent;
}
.ios-time-display {

View File

@@ -92,12 +92,12 @@
}
.modal-button-primary {
background-color: #0066cc;
background-color: #059669;
color: white;
}
.modal-button-primary:hover {
background-color: #0055aa;
background-color: #059669;
}
.modal-button-secondary {
@@ -138,7 +138,7 @@
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid #0066cc;
border-top: 4px solid #059669;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
@@ -224,7 +224,7 @@
padding: 12px 16px;
border: none;
border-radius: 4px;
background-color: #0066cc;
background-color: #059669;
text-align: center;
cursor: pointer;
transition: all 0.2s;
@@ -258,12 +258,12 @@
margin: 0 auto;
width: auto;
min-width: 220px;
background-color: #0066cc;
background-color: #059669;
color: white;
}
.centered-choice:hover {
background-color: #0055aa;
background-color: #059669;
}
@media (max-width: 480px) {
@@ -300,7 +300,7 @@
.countdown {
font-weight: bold;
color: #0066cc;
color: #059669;
font-size: 1.1rem;
}
}

View File

@@ -1,4 +1,16 @@
#chapters-editor-root {
.timeline-header-container {
margin-left: 1rem;
margin-top: -0.5rem;
}
.timeline-header-title {
font-size: 1.125rem;
font-weight: 600;
color: #059669;
margin: 0;
}
.timeline-container-card {
background-color: white;
border-radius: 0.5rem;
@@ -11,6 +23,8 @@
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 0.5rem;
border-bottom: 2px solid rgba(16, 185, 129, 0.2);
}
.timeline-title {
@@ -20,7 +34,7 @@
}
.timeline-title-text {
font-weight: 700;
font-size: 0.875rem;
}
.current-time {
@@ -48,10 +62,11 @@
.timeline-container {
position: relative;
min-width: 100%;
background-color: #fafbfc;
background-color: #e2ede4;
height: 70px;
border-radius: 0.25rem;
overflow: visible !important;
border: 1px solid rgba(16, 185, 129, 0.2);
}
.timeline-marker {
@@ -194,7 +209,7 @@
left: 0;
right: 0;
padding: 0.4rem;
background-color: rgba(0, 0, 0, 0.4);
background-color: rgba(16, 185, 129, 0.6);
color: white;
opacity: 1;
transition: background-color 0.2s;
@@ -202,15 +217,15 @@
}
.clip-segment:hover .clip-segment-info {
background-color: rgba(0, 0, 0, 0.5);
background-color: rgba(16, 185, 129, 0.7);
}
.clip-segment.selected .clip-segment-info {
background-color: rgba(59, 130, 246, 0.5);
background-color: rgba(5, 150, 105, 0.8);
}
.clip-segment.selected:hover .clip-segment-info {
background-color: rgba(59, 130, 246, 0.4);
background-color: rgba(5, 150, 105, 0.75);
}
.clip-segment-name {
@@ -540,7 +555,7 @@
.save-copy-button,
.save-segments-button {
color: #ffffff;
background: #0066cc;
background: #059669;
border-radius: 0.25rem;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
@@ -713,7 +728,7 @@
height: 50px;
border: 5px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top-color: #0066cc;
border-top-color: #059669;
animation: spin 1s ease-in-out infinite;
}
@@ -753,7 +768,7 @@
align-items: center;
justify-content: center;
padding: 0.75rem 1.25rem;
background-color: #0066cc;
background-color: #059669;
color: white;
border-radius: 4px;
text-decoration: none;
@@ -766,7 +781,7 @@
}
.modal-choice-button:hover {
background-color: #0056b3;
background-color:rgb(7, 119, 84);
}
.modal-choice-button svg {
@@ -941,7 +956,6 @@
.save-chapters-button:hover {
background-color: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
}

View File

@@ -76,10 +76,26 @@
user-select: none;
}
/* Persistent background poster for audio files (Safari fix) */
.audio-poster-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
z-index: 1;
pointer-events: none;
}
.video-player-container video {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 2;
/* Force hardware acceleration */
transform: translateZ(0);
-webkit-transform: translateZ(0);
@@ -88,6 +104,11 @@
user-select: none;
}
/* Make video transparent only for audio files with poster so background shows through */
.video-player-container video.audio-with-poster {
background: transparent;
}
/* iOS-specific styles */
@supports (-webkit-touch-callout: none) {
.video-player-container video {
@@ -109,6 +130,7 @@
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
z-index: 3;
}
.video-player-container:hover .play-pause-indicator {
@@ -187,6 +209,7 @@
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
opacity: 0;
transition: opacity 0.3s;
z-index: 3;
}
.video-player-container:hover .video-controls {

View File

@@ -309,6 +309,11 @@ const App = () => {
canRedo={historyPosition < history.length - 1}
/>
{/* Timeline Header */}
<div className="timeline-header-container">
<h2 className="timeline-header-title">Trim or Split</h2>
</div>
{/* Timeline Controls */}
<TimelineControls
currentTime={currentTime}

View File

@@ -28,9 +28,9 @@ const ClipSegments = ({ segments }: ClipSegmentsProps) => {
// Generate the same color background for a segment as shown in the timeline
const getSegmentColorClass = (index: number) => {
// Return CSS class based on index modulo 8
// This matches the CSS nth-child selectors in the timeline
return `segment-default-color segment-color-${(index % 8) + 1}`;
// Return CSS class based on index modulo 20
// This matches the CSS classes for up to 20 segments
return `segment-default-color segment-color-${(index % 20) + 1}`;
};
return (

View File

@@ -13,6 +13,7 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
const [videoUrl, setVideoUrl] = useState<string>('');
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
const [posterImage, setPosterImage] = useState<string | undefined>(undefined);
const [isAudioFile, setIsAudioFile] = useState(false);
// Refs for hold-to-continue functionality
const incrementIntervalRef = useRef<NodeJS.Timeout | null>(null);
@@ -41,12 +42,13 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
setVideoUrl(url);
// Check if the media is an audio file and set poster image
const isAudioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
const audioFile = url.match(/\.(mp3|wav|ogg|m4a|aac|flac)$/i) !== null;
setIsAudioFile(audioFile);
// Get posterUrl from MEDIA_DATA, or use audio-poster.jpg as fallback for audio files when posterUrl is empty, null, or "None"
const mediaPosterUrl = (typeof window !== 'undefined' && (window as any).MEDIA_DATA?.posterUrl) || '';
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
setPosterImage(isValidPoster ? mediaPosterUrl : (isAudioFile ? AUDIO_POSTER_URL : undefined));
setPosterImage(isValidPoster ? mediaPosterUrl : (audioFile ? AUDIO_POSTER_URL : undefined));
}, [videoRef]);
// Function to jump 15 seconds backward
@@ -128,22 +130,34 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
</span>
</div>
{/* iOS-optimized Video Element with Native Controls */}
<video
ref={(ref) => setIosVideoRef(ref)}
className="w-full rounded-md"
src={videoUrl}
controls
playsInline
webkit-playsinline="true"
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>
</video>
{/* Video container with persistent background for audio files */}
<div className="ios-video-wrapper">
{/* Persistent background image for audio files (Safari fix) */}
{isAudioFile && posterImage && (
<div
className="ios-audio-poster-background"
style={{ backgroundImage: `url(${posterImage})` }}
aria-hidden="true"
/>
)}
{/* iOS-optimized Video Element with Native Controls */}
<video
ref={(ref) => setIosVideoRef(ref)}
className={`w-full rounded-md ${isAudioFile && posterImage ? 'audio-with-poster' : ''}`}
src={videoUrl}
controls
playsInline
webkit-playsinline="true"
x-webkit-airplay="allow"
preload="auto"
crossOrigin="anonymous"
poster={posterImage}
>
<source src={videoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>
</video>
</div>
{/* iOS Video Skip Controls */}
<div className="ios-skip-controls mt-3 flex justify-center gap-4">

View File

@@ -47,14 +47,24 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
const isValidPoster = mediaPosterUrl && mediaPosterUrl !== 'None' && mediaPosterUrl.trim() !== '';
const posterImage = isValidPoster ? mediaPosterUrl : (isAudioFile ? AUDIO_POSTER_URL : undefined);
// Detect iOS device
// Detect iOS device and Safari browser
useEffect(() => {
const checkIOS = () => {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
return /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
};
const checkSafari = () => {
const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;
return /Safari/.test(userAgent) && !/Chrome/.test(userAgent) && !/Chromium/.test(userAgent);
};
setIsIOS(checkIOS());
// Store Safari detection globally for other components
if (typeof window !== 'undefined') {
(window as any).isSafari = checkSafari();
}
// Check if video was previously initialized
if (typeof window !== 'undefined') {
@@ -343,9 +353,19 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
return (
<div className="video-player-container">
{/* Persistent background image for audio files (Safari fix) */}
{isAudioFile && posterImage && (
<div
className="audio-poster-background"
style={{ backgroundImage: `url(${posterImage})` }}
aria-hidden="true"
/>
)}
<video
ref={videoRef}
preload="auto"
className={isAudioFile && posterImage ? 'audio-with-poster' : ''}
preload="metadata"
crossOrigin="anonymous"
onClick={handleVideoClick}
playsInline
@@ -356,7 +376,10 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
poster={posterImage}
>
<source src={sampleVideoUrl} type="video/mp4" />
<p>Your browser doesn't support HTML5 video.</p>
{/* Safari fallback for audio files */}
<source src={sampleVideoUrl} type="audio/mp4" />
<source src={sampleVideoUrl} type="audio/mpeg" />
<p>Your browser doesn't support HTML5 video or audio.</p>
</video>
{/* iOS First-play indicator - only shown on first visit for iOS devices when not initialized */}

View File

@@ -99,6 +99,7 @@
}
.segment-thumbnail {
display: none;
width: 4rem;
height: 2.25rem;
background-size: cover;
@@ -129,7 +130,7 @@
margin-top: 0.25rem;
display: inline-block;
background-color: #f3f4f6;
padding: 0 0.5rem;
padding: 0;
border-radius: 0.25rem;
color: black;
}
@@ -169,28 +170,67 @@
color: rgba(51, 51, 51, 0.7);
}
/* Generate 20 shades of #2563eb (rgb(37, 99, 235)) */
/* Base color: #2563eb = rgb(37, 99, 235) */
/* Creating variations from lighter to darker */
.segment-color-1 {
background-color: rgba(59, 130, 246, 0.15);
background-color: rgba(147, 179, 247, 0.2);
}
.segment-color-2 {
background-color: rgba(16, 185, 129, 0.15);
background-color: rgba(129, 161, 243, 0.2);
}
.segment-color-3 {
background-color: rgba(245, 158, 11, 0.15);
background-color: rgba(111, 143, 239, 0.2);
}
.segment-color-4 {
background-color: rgba(239, 68, 68, 0.15);
background-color: rgba(93, 125, 237, 0.2);
}
.segment-color-5 {
background-color: rgba(139, 92, 246, 0.15);
background-color: rgba(75, 107, 235, 0.2);
}
.segment-color-6 {
background-color: rgba(236, 72, 153, 0.15);
background-color: rgba(65, 99, 235, 0.2);
}
.segment-color-7 {
background-color: rgba(6, 182, 212, 0.15);
background-color: rgba(55, 91, 235, 0.2);
}
.segment-color-8 {
background-color: rgba(250, 204, 21, 0.15);
background-color: rgba(45, 83, 235, 0.2);
}
.segment-color-9 {
background-color: rgba(37, 99, 235, 0.2);
}
.segment-color-10 {
background-color: rgba(33, 89, 215, 0.2);
}
.segment-color-11 {
background-color: rgba(29, 79, 195, 0.2);
}
.segment-color-12 {
background-color: rgba(25, 69, 175, 0.2);
}
.segment-color-13 {
background-color: rgba(21, 59, 155, 0.2);
}
.segment-color-14 {
background-color: rgba(17, 49, 135, 0.2);
}
.segment-color-15 {
background-color: rgba(15, 43, 119, 0.2);
}
.segment-color-16 {
background-color: rgba(13, 37, 103, 0.2);
}
.segment-color-17 {
background-color: rgba(11, 31, 87, 0.2);
}
.segment-color-18 {
background-color: rgba(9, 25, 71, 0.2);
}
.segment-color-19 {
background-color: rgba(7, 19, 55, 0.2);
}
.segment-color-20 {
background-color: rgba(5, 13, 39, 0.2);
}
}

View File

@@ -8,12 +8,40 @@
overflow: hidden;
}
/* Video wrapper for positioning background */
.ios-video-wrapper {
position: relative;
width: 100%;
background-color: black;
border-radius: 0.5rem;
overflow: hidden;
}
/* Persistent background poster for audio files (Safari fix) */
.ios-audio-poster-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
z-index: -1;
pointer-events: none;
}
.ios-video-player-container video {
position: relative;
width: 100%;
height: auto;
max-height: 360px;
aspect-ratio: 16/9;
background-color: black;
}
/* Make video transparent only for audio files with poster so background shows through */
.ios-video-player-container video.audio-with-poster {
background-color: transparent;
}
.ios-time-display {

View File

@@ -1,4 +1,16 @@
#video-editor-trim-root {
.timeline-header-container {
margin-left: 1rem;
margin-top: -0.5rem;
}
.timeline-header-title {
font-size: 1.125rem;
font-weight: 600;
color: #2563eb;
margin: 0;
}
.timeline-container-card {
background-color: white;
border-radius: 0.5rem;
@@ -11,6 +23,8 @@
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 0.5rem;
border-bottom: 2px solid rgba(59, 130, 246, 0.2);
}
.timeline-title {
@@ -20,7 +34,7 @@
}
.timeline-title-text {
font-weight: 700;
font-size: 0.875rem;
}
.current-time {
@@ -48,10 +62,11 @@
.timeline-container {
position: relative;
min-width: 100%;
background-color: #fafbfc;
background-color: #eff6ff;
height: 70px;
border-radius: 0.25rem;
overflow: visible !important;
border: 1px solid rgba(59, 130, 246, 0.2);
}
.timeline-marker {
@@ -194,7 +209,7 @@
left: 0;
right: 0;
padding: 0.4rem;
background-color: rgba(0, 0, 0, 0.4);
background-color: rgba(59, 130, 246, 0.6);
color: white;
opacity: 1;
transition: background-color 0.2s;
@@ -202,15 +217,15 @@
}
.clip-segment:hover .clip-segment-info {
background-color: rgba(0, 0, 0, 0.5);
background-color: rgba(59, 130, 246, 0.7);
}
.clip-segment.selected .clip-segment-info {
background-color: rgba(59, 130, 246, 0.5);
background-color: rgba(37, 99, 235, 0.8);
}
.clip-segment.selected:hover .clip-segment-info {
background-color: rgba(59, 130, 246, 0.4);
background-color: rgba(37, 99, 235, 0.75);
}
.clip-segment-name {

View File

@@ -76,10 +76,26 @@
user-select: none;
}
/* Persistent background poster for audio files (Safari fix) */
.audio-poster-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
z-index: 1;
pointer-events: none;
}
.video-player-container video {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 2;
/* Force hardware acceleration */
transform: translateZ(0);
-webkit-transform: translateZ(0);
@@ -88,6 +104,11 @@
user-select: none;
}
/* Make video transparent only for audio files with poster so background shows through */
.video-player-container video.audio-with-poster {
background: transparent;
}
/* iOS-specific styles */
@supports (-webkit-touch-callout: none) {
.video-player-container video {
@@ -109,6 +130,7 @@
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
z-index: 3;
}
.video-player-container:hover .play-pause-indicator {
@@ -187,6 +209,7 @@
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
opacity: 0;
transition: opacity 0.3s;
z-index: 3;
}
.video-player-container:hover .video-controls {

View File

@@ -20,6 +20,7 @@ class CustomChaptersOverlay extends Component {
this.touchStartTime = 0;
this.touchThreshold = 150; // ms for tap vs scroll detection
this.isSmallScreen = window.innerWidth <= 480;
this.scrollY = 0; // Track scroll position before locking
// Bind methods
this.createOverlay = this.createOverlay.bind(this);
@@ -31,6 +32,8 @@ class CustomChaptersOverlay extends Component {
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
this.setupResizeListener = this.setupResizeListener.bind(this);
this.handleResize = this.handleResize.bind(this);
this.lockBodyScroll = this.lockBodyScroll.bind(this);
this.unlockBodyScroll = this.unlockBodyScroll.bind(this);
// Initialize after player is ready
this.player().ready(() => {
@@ -65,6 +68,9 @@ class CustomChaptersOverlay extends Component {
const el = this.player().el();
if (el) el.classList.remove('chapters-open');
// Restore body scroll on mobile when closing
this.unlockBodyScroll();
}
setupResizeListener() {
@@ -164,6 +170,8 @@ class CustomChaptersOverlay extends Component {
this.overlay.style.display = 'none';
const el = this.player().el();
if (el) el.classList.remove('chapters-open');
// Restore body scroll on mobile when closing
this.unlockBodyScroll();
};
chapterClose.appendChild(closeBtn);
playlistTitle.appendChild(chapterClose);
@@ -355,6 +363,37 @@ class CustomChaptersOverlay extends Component {
}
}
lockBodyScroll() {
if (!this.isMobile) return;
// Save current scroll position
this.scrollY = window.scrollY || window.pageYOffset;
// Lock body scroll with proper iOS handling
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.top = `-${this.scrollY}px`;
document.body.style.left = '0';
document.body.style.right = '0';
document.body.style.width = '100%';
}
unlockBodyScroll() {
if (!this.isMobile) return;
// Restore body scroll
const scrollY = this.scrollY;
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.top = '';
document.body.style.left = '';
document.body.style.right = '';
document.body.style.width = '';
// Restore scroll position
window.scrollTo(0, scrollY);
}
toggleOverlay() {
if (!this.overlay) return;
@@ -369,17 +408,11 @@ class CustomChaptersOverlay extends Component {
navigator.vibrate(30);
}
// Prevent body scroll on mobile when overlay is open
if (this.isMobile) {
if (isHidden) {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
} else {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
// Lock/unlock body scroll on mobile when overlay opens/closes
if (isHidden) {
this.lockBodyScroll();
} else {
this.unlockBodyScroll();
}
try {
@@ -390,7 +423,9 @@ class CustomChaptersOverlay extends Component {
m.classList.remove('vjs-lock-showing');
m.style.display = 'none';
});
} catch (e) {}
} catch {
// Ignore errors when closing menus
}
}
updateCurrentChapter() {
@@ -406,7 +441,6 @@ class CustomChaptersOverlay extends Component {
currentTime >= chapter.startTime &&
(index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime);
const handle = item.querySelector('.playlist-drag-handle');
const dynamic = item.querySelector('.meta-dynamic');
if (isPlaying) {
currentChapterIndex = index;
@@ -463,11 +497,7 @@ class CustomChaptersOverlay extends Component {
if (el) el.classList.remove('chapters-open');
// Restore body scroll on mobile
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
this.unlockBodyScroll();
}
}
@@ -479,11 +509,7 @@ class CustomChaptersOverlay extends Component {
if (el) el.classList.remove('chapters-open');
// Restore body scroll on mobile when disposing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
this.unlockBodyScroll();
// Clean up event listeners
if (this.handleResize) {

View File

@@ -25,6 +25,7 @@ class CustomSettingsMenu extends Component {
this.isMobile = this.detectMobile();
this.isSmallScreen = window.innerWidth <= 480;
this.touchThreshold = 150; // ms for tap vs scroll detection
this.scrollY = 0; // Track scroll position before locking
// Bind methods
this.createSettingsButton = this.createSettingsButton.bind(this);
@@ -41,6 +42,8 @@ class CustomSettingsMenu extends Component {
this.detectMobile = this.detectMobile.bind(this);
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
this.setupResizeListener = this.setupResizeListener.bind(this);
this.lockBodyScroll = this.lockBodyScroll.bind(this);
this.unlockBodyScroll = this.unlockBodyScroll.bind(this);
// Initialize after player is ready
this.player().ready(() => {
@@ -656,6 +659,8 @@ class CustomSettingsMenu extends Component {
if (btnEl) {
btnEl.classList.remove('settings-clicked');
}
// Restore body scroll on mobile when closing
this.unlockBodyScroll();
};
closeButton.addEventListener('click', closeFunction);
@@ -942,6 +947,37 @@ class CustomSettingsMenu extends Component {
this.startSubtitleSync();
}
lockBodyScroll() {
if (!this.isMobile) return;
// Save current scroll position
this.scrollY = window.scrollY || window.pageYOffset;
// Lock body scroll with proper iOS handling
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.top = `-${this.scrollY}px`;
document.body.style.left = '0';
document.body.style.right = '0';
document.body.style.width = '100%';
}
unlockBodyScroll() {
if (!this.isMobile) return;
// Restore body scroll
const scrollY = this.scrollY;
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.top = '';
document.body.style.left = '';
document.body.style.right = '';
document.body.style.width = '';
// Restore scroll position
window.scrollTo(0, scrollY);
}
toggleSettings(e) {
// e.stopPropagation();
const isVisible = this.settingsOverlay.classList.contains('show');
@@ -954,11 +990,7 @@ class CustomSettingsMenu extends Component {
this.stopKeepingControlsVisible();
// Restore body scroll on mobile when closing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
this.unlockBodyScroll();
} else {
this.settingsOverlay.classList.add('show');
this.settingsOverlay.style.display = 'block';
@@ -972,11 +1004,7 @@ class CustomSettingsMenu extends Component {
}
// Prevent body scroll on mobile when overlay is open
if (this.isMobile) {
document.body.style.overflow = 'hidden';
document.body.style.position = 'fixed';
document.body.style.width = '100%';
}
this.lockBodyScroll();
}
this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
@@ -1002,6 +1030,9 @@ class CustomSettingsMenu extends Component {
this.settingsOverlay.classList.add('show');
this.settingsOverlay.style.display = 'block';
// Lock body scroll when opening
this.lockBodyScroll();
// Hide other submenus and show subtitles submenu
this.speedSubmenu.style.display = 'none';
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
@@ -1072,11 +1103,7 @@ class CustomSettingsMenu extends Component {
}
// Restore body scroll on mobile when closing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
this.unlockBodyScroll();
}
}
@@ -1417,6 +1444,8 @@ class CustomSettingsMenu extends Component {
if (btnEl) {
btnEl.classList.remove('settings-clicked');
}
// Restore body scroll on mobile when closing
this.unlockBodyScroll();
}
}
@@ -1493,11 +1522,7 @@ class CustomSettingsMenu extends Component {
}
// Restore body scroll on mobile when disposing
if (this.isMobile) {
document.body.style.overflow = '';
document.body.style.position = '';
document.body.style.width = '';
}
this.unlockBodyScroll();
// Remove DOM elements
if (this.settingsOverlay) {

View File

@@ -49,10 +49,7 @@ class EndScreenOverlay extends Component {
// Get videos to show - access directly from options during createEl
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
const videosToShow =
relatedVideos.length > 0
? relatedVideos.slice(0, maxVideos)
: this.createSampleVideos().slice(0, maxVideos);
const videosToShow = relatedVideos.slice(0, maxVideos);
if (useSwiper) {
return this.createSwiperGrid(videosToShow, itemsPerView || 2, columns, gridRows || 1);
@@ -307,8 +304,8 @@ class EndScreenOverlay extends Component {
if (this.relatedVideos && Array.isArray(this.relatedVideos) && this.relatedVideos.length > 0) {
return this.relatedVideos.slice(0, maxVideos);
}
// Fallback to sample videos for testing
return this.createSampleVideos().slice(0, maxVideos);
// Return empty array if no related videos
return [];
}
createVideoItem(video, isSwiperMode = false, itemsPerView = 2, isGridMode = false) {
@@ -745,153 +742,12 @@ class EndScreenOverlay extends Component {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
}
createSampleVideos() {
return [
{
id: 'sample1',
title: 'React Full Course - Complete Tutorial for Beginners',
author: 'Bro Code',
views: '2.1M views',
duration: 1800,
},
{
id: 'sample2',
title: 'JavaScript ES6+ Modern Features',
author: 'Tech Tutorials',
views: '850K views',
duration: 1200,
},
{
id: 'sample3',
title: 'CSS Grid Layout Masterclass',
author: 'Web Dev Academy',
views: '1.2M views',
duration: 2400,
},
{
id: 'sample4',
title: 'Node.js Backend Development',
author: 'Code Master',
views: '650K views',
duration: 3600,
},
{
id: 'sample5',
title: 'Vue.js Complete Guide',
author: 'Frontend Pro',
views: '980K views',
duration: 2800,
},
{
id: 'sample6',
title: 'Python Data Science Bootcamp',
author: 'Data Academy',
views: '1.5M views',
duration: 4200,
},
{
id: 'sample7',
title: 'TypeScript for Beginners',
author: 'Code School',
views: '750K views',
duration: 1950,
},
{
id: 'sample8',
title: 'Docker Container Tutorial',
author: 'DevOps Pro',
views: '920K views',
duration: 2700,
},
{
id: 'sample9',
title: 'MongoDB Database Design',
author: 'DB Expert',
views: '580K views',
duration: 3200,
},
{
id: 'sample10',
title: 'AWS Cloud Computing Essentials',
author: 'Cloud Master',
views: '1.8M views',
duration: 4800,
},
{
id: 'sample11',
title: 'GraphQL API Development',
author: 'API Guru',
views: '420K views',
duration: 2100,
},
{
id: 'sample12',
title: 'Kubernetes Orchestration Guide',
author: 'Container Pro',
views: '680K views',
duration: 3900,
},
{
id: 'sample13',
title: 'Redis Caching Strategies',
author: 'Cache Expert',
views: '520K views',
duration: 2250,
},
{
id: 'sample14',
title: 'Web Performance Optimization',
author: 'Speed Master',
views: '890K views',
duration: 3100,
},
{
id: 'sample15',
title: 'CI/CD Pipeline Setup',
author: 'DevOps Guide',
views: '710K views',
duration: 2900,
},
{
id: 'sample16',
title: 'Microservices Architecture',
author: 'System Design',
views: '1.3M views',
duration: 4500,
},
{
id: 'sample17',
title: 'Next.js App Router Tutorial',
author: 'Web Academy',
views: '640K views',
duration: 2650,
},
{
id: 'sample18',
title: 'Tailwind CSS Crash Course',
author: 'CSS Master',
views: '1.1M views',
duration: 1800,
},
{
id: 'sample19',
title: 'Git and GitHub Essentials',
author: 'Version Control Pro',
views: '2.3M views',
duration: 3300,
},
{
id: 'sample20',
title: 'REST API Best Practices',
author: 'API Design',
views: '780K views',
duration: 2400,
},
];
}
show() {
this.el().style.display = 'flex';
// Only show if there are related videos
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
if (relatedVideos.length > 0) {
this.el().style.display = 'flex';
}
}
hide() {

View File

@@ -139,12 +139,6 @@ video::cue {
}
}
@media (max-width: 600px) {
.video-js .vjs-control-bar .vjs-autoplay-toggle {
display: none !important;
}
}
@media (max-width: 500px) {
.video-js .vjs-control-bar .vjs-next-video-button {
display: none !important;

Some files were not shown because too many files have changed in this diff Show More