Compare commits

...

49 Commits
v6.0.0 ... main

Author SHA1 Message Date
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
Yiannis Christodoulou
2a0cb977f2
Video.js fixes and improvements after major upgrade (#1413) 2025-10-27 11:53:35 +02:00
Yiannis Christodoulou
a5e6e7b9ca
feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements 2025-10-20 15:30:00 +03:00
Markos Gogoulos
b39072c8ae feat: disapprove user 2025-10-09 17:29:29 +03:00
Markos Gogoulos
f4ab60e894 fix: extend gitignore 2025-09-26 11:33:44 +03:00
Markos Gogoulos
8656b40c5b fix: url 2025-09-25 08:51:26 +03:00
Markos Gogoulos
553a25a86f
feat: pass extra css (#1392) 2025-09-24 19:01:06 +03:00
Markos Gogoulos
1c1af489f1
feat: allow portal logo override (#1391) 2025-09-24 16:26:45 +03:00
Markos Gogoulos
c4c5ecf06a update version 2025-09-21 16:18:31 +03:00
Markos Gogoulos
725cc71960 fix migration 2025-09-21 16:17:18 +03:00
Markos Gogoulos
0c1c5bbb09 add new migration 2025-09-21 16:15:46 +03:00
Markos Gogoulos
56182f0a6d feat: allow customizable about page 2025-09-21 15:47:04 +03:00
Markos Gogoulos
208f0b338b
feat: update versions for python packages, add Pages functionality (#1386)
This PR updates Django core version and also brings html pages support (that admins can create)
2025-09-21 15:38:43 +03:00
Markos Gogoulos
cbef629baf
feat: approve users, edit users through manage users page (#1383) 2025-09-20 15:16:52 +03:00
Bret.S (AKA: CyberGladius)
8e8454d8c2
Docs Update with SAML Deployement Guide and Troubleshooting (#1377)
* Docs update with SAML deployment guide.

* Docs update with SAML deployment guide. URL Fix

---------

Co-authored-by: root <git@tdcme.com>
2025-09-16 14:51:05 +03:00
Markos Gogoulos
8d982ace92
Feat whisper opts (#1368) 2025-09-04 13:39:41 +03:00
Meet Dholakia
6cee02085c
Updated the title splicing length to 100 in playlist, category and media models (#1364) 2025-09-02 19:43:46 +03:00
Markos Gogoulos
e33aa17911 fix version 2025-09-02 11:32:02 +03:00
Markos Gogoulos
a8db23f204 docs: add whisper section 2025-09-02 11:00:17 +03:00
Markos Gogoulos
d6428e3334 version bump 2025-09-01 20:34:15 +03:00
Markos Gogoulos
fd342fd1d6 version bump 2025-09-01 20:32:11 +03:00
Markos Gogoulos
7a1b32f1ba
fix: delete media (#1366) 2025-09-01 20:06:49 +03:00
Markos Gogoulos
817e16ac60
feat: whisper STT and record screen (#1363) 2025-09-01 15:11:38 +03:00
Markos Gogoulos
8cbeb72dd2
feat: add fily type and max user media uplod limits 2025-08-19 11:35:49 +03:00
Markos Gogoulos
e9f862a0ff
feat: 3 small fixes (#1347)
* fix: datetime input

* show message on upload user only

* show all media of user for editors/managers/admins
2025-08-17 19:18:47 +03:00
Markos Gogoulos
02eac68b51
fix: swagger (#1345)
* fix: swagger

* fix: swagger
2025-08-12 12:24:26 +03:00
Markos Gogoulos
e790795bfd
feat: bulk actions API 2025-08-07 13:21:12 +03:00
Markos Gogoulos
de99d84c18 feat: revert ffmpeg install 2025-07-07 13:33:45 +03:00
Markos Gogoulos
8aa89c0958 feat: use apt for installing ffmpeg 2025-07-07 13:00:33 +03:00
Markos Gogoulos
df98b65704
feat: pass version on static files (#1318) 2025-07-07 11:54:02 +03:00
Markos Gogoulos
a607996bfa
feat: adds minimum resolution of 144p 2025-07-07 11:34:02 +03:00
Markos Gogoulos
79f2e2bb11
feat: replace format with fstrings 2025-07-07 11:26:08 +03:00
Markos Gogoulos
d54732040a
feat: add DB connection pooling 2025-07-07 11:18:40 +03:00
Andy
e8520bc7cd
fix: date picker in edit media (#1297)
By default, Django uses type=text for the date input, which respects
the locale settings. However, when changing the input type to date,
it should only accept YYYY-MM-DD format so the input field can be
properly handled.
2025-07-06 11:44:44 +03:00
Markos Gogoulos
b6e46e7b62
feat: replace login middleware (#1314) 2025-07-06 11:25:50 +03:00
Adam Stradovnik
36eab954bd
feat: Adds support for Slovenian frontend translations (#1306) 2025-07-06 11:05:07 +03:00
Markos Gogoulos
610716533b fix formatting 2025-07-01 15:46:34 +03:00
Yiannis Christodoulou
4f1c4a2b4c
fix: Disable Segment Tools and Reset Preview State During Playback (#1305)
* fix: Disable Segment Tools and Reset Preview State During Playback

* chore: remove some unnecessary comments

* chore: build assets

* fix: do not display the handles (left/right) on preview mode

* fix: Disable all tools on preview mode (undo, redo, reset, etc.)

* Update README.md

* feat: Prettier configuration for video editor

* Update README.md

* Update .prettierrc

* style: Format entire codebase (video-editor) with Prettier

* fix: During segments playback mode, disable button interactions but keep hover working

* feat: Add yarn format

* prettier format

* Update package.json

* feat: Install prettier and improve formatting

* build assets

* Update version.py 6.2.0
2025-07-01 15:33:39 +03:00
Markos Gogoulos
83f3eec940
feat: enable editing of media slug, show categories on manage media 2025-06-24 11:13:33 +03:00
Markos Gogoulos
a5acce4ab1
fix: single server install issues 2025-06-15 11:19:29 +03:00
Ofek
a4e9309350
(FEAT) Add Hebrew Translation (#1295) 2025-06-14 11:08:17 +03:00
Casper Tollund
6beaf0bbe2
Add support for Danish translation (#1293)
Co-authored-by: Casper Tollund <casto@mac.ait.clients.local>
2025-06-12 17:35:37 +03:00
753 changed files with 84424 additions and 242583 deletions

View File

@ -15,15 +15,18 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta for base image
id: meta-base
uses: docker/metadata-action@v4
with:
# List of Docker images to use as base name for tags
images: |
mediacms/mediacms
# Generate Docker tags based on the following events/attributes
# Set latest tag for default branch
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
@ -37,16 +40,39 @@ jobs:
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
- name: Docker meta for full image
id: meta-full
uses: docker/metadata-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
images: |
mediacms/mediacms
tags: |
type=raw,value=full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}-full
type=semver,pattern={{major}}.{{minor}}-full
type=semver,pattern={{major}}-full
labels: |
org.opencontainers.image.title=MediaCMS Full
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API. This is the full version with additional dependencies.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push
- name: Build and push full image
uses: docker/build-push-action@v4
with:
context: .
target: full
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
- name: Build and push base image
uses: docker/build-push-action@v4
with:
context: .
target: base
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-base.outputs.tags }}
labels: ${{ steps.meta-base.outputs.labels }}

10
.gitignore vendored
View File

@ -5,6 +5,7 @@ media_files/original/
media_files/hls/
media_files/chunks/
media_files/uploads/
media_files/tinymce_media/
postgres_data/
celerybeat-schedule
logs/
@ -25,3 +26,12 @@ yt.readme.md
frontend-tools/.DS_Store
static/video_editor/videos/sample-video-30s.mp4
static/video_editor/videos/sample-video-37s.mp4
/frontend-tools/video-editor-v2
.DS_Store
static/video_editor/videos/sample-video-10m.mp4
static/video_editor/videos/sample-video-10s.mp4
frontend-tools/video-js/public/videos/sample-video-white.mp4
frontend-tools/video-editor/client/public/videos/sample-video.mp3
frontend-tools/chapters-editor/client/public/videos/sample-video.mp3
static/chapters_editor/videos/sample-video.mp3
static/video_editor/videos/sample-video.mp3

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 +1,3 @@
*
/templates/cms/*
/templates/*.html
*.scss

21
.prettierrc Normal file
View File

@ -0,0 +1,21 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto",
"overrides": [
{
"files": ["*.css", "*.scss"],
"options": {
"singleQuote": false
}
}
]
}

View File

@ -1,4 +1,4 @@
FROM python:3.13-bookworm AS build-image
FROM python:3.13.5-slim-bookworm AS build-image
# Install system dependencies needed for downloading and extracting
RUN apt-get update -y && \
@ -7,9 +7,9 @@ RUN apt-get update -y && \
apt-get purge --auto-remove && \
apt-get clean
# Install ffmpeg
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
mkdir -p ffmpeg-tmp && \
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
RUN mkdir -p ffmpeg-tmp && \
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
@ -23,8 +23,8 @@ RUN mkdir -p /home/mediacms.io/bento4 && \
rm -rf /home/mediacms.io/bento4/docs && \
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
############ RUNTIME IMAGE ############
FROM python:3.13-bookworm AS runtime_image
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
SHELL ["/bin/bash", "-c"]
@ -34,13 +34,47 @@ ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install runtime system dependencies
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends supervisor nginx imagemagick procps libxml2-dev libxmlsec1-dev libxmlsec1-openssl -y && \
rm -rf /var/lib/apt/lists/* && \
apt-get purge --auto-remove && \
apt-get clean
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
pkg-config \
zlib1g-dev \
zlib1g \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Copy requirements files
COPY requirements.txt requirements-dev.txt ./
# Install Python dependencies using pip (within virtualenv)
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir uv && \
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
echo "Installing development dependencies..." && \
uv pip install -r requirements-dev.txt; \
fi && \
apt-get purge -y --auto-remove \
build-essential \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
libpq-dev
# Copy ffmpeg and Bento4 from build image
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
@ -48,28 +82,11 @@ COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
# Set up virtualenv
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Install Python dependencies
COPY requirements.txt requirements-dev.txt ./
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir -r requirements.txt && \
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
echo "Installing development dependencies..." && \
pip install --no-cache-dir -r requirements-dev.txt; \
fi
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Set process control environment variables
@ -86,3 +103,11 @@ RUN chmod +x ./deploy/docker/entrypoint.sh
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
COPY requirements-full.txt ./
RUN mkdir -p /root/.cache/ && \
chmod go+rwx /root/ && \
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@ -1,7 +1,7 @@
.PHONY: admin-shell build-frontend
admin-shell:
@container_id=$$(docker-compose ps -q web); \
@container_id=$$(docker compose ps -q web); \
if [ -z "$$container_id" ]; then \
echo "Web container not found"; \
exit 1; \

View File

@ -25,11 +25,12 @@ A demo is available at https://demo.mediacms.io
- **Complete control over your data**: host it yourself!
- **Modern technologies**: Django/Python/Celery, React.
- **Support for multiple publishing workflows**: public, private, unlisted and custom
- **Role-Based Access Control (RBAC)**: create RBAC categories and connect users to groups with view/edit access on their media
- **Automatic transcription**: through integration with Whisper running locally
- **Multiple media types support**: video, audio, image, pdf
- **Multiple media classification options**: categories, tags and custom
- **Multiple media sharing options**: social media share, videos embed code generation
- **Video Trimmer**: trim video, replace, save as new or create segments
- **Role-Based Access Control (RBAC)**: create RBAC categories and connect users to groups with view/edit access on their media
- **SAML support**: with ability to add mappings to system roles and groups
- **Easy media searching**: enriched with live search functionality
- **Playlists for audio and video content**: create playlists, add and reorder content
@ -38,7 +39,7 @@ A demo is available at https://demo.mediacms.io
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
- **Configuration options**: change logos, fonts, styling, add more pages
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (144p, 240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Adaptive video streaming**: possible through HLS protocol
- **Subtitles/CC**: support for multilingual subtitle files
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
@ -48,7 +49,7 @@ A demo is available at https://demo.mediacms.io
## Example cases
- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
- **Universities, schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
- **Organization sensitive content.** In cases where content is sensitive and cannot be uploaded to external sites.
- **Build a great community.** MediaCMS can be customized (URLs, logos, fonts, aesthetics) so that you create a highly customized video portal for your community!
- **Personal portal.** Organize, categorize and host your content the way you prefer.
@ -83,6 +84,7 @@ For a small to medium installation, with a few hours of video uploaded daily, an
In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365).
In order to support automatic transcriptions through Whisper, consider more CPUs.
## Installation / Maintanance
@ -93,20 +95,15 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
## Configuration
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
## Information for developers
Check out the new section on the [Developer Experience](docs/dev_exp.md) page
## Documentation
* [Users documentation](docs/user_docs.md) page
* [Administrators documentation](docs/admins_docs.md) page
* [Developers documentation](docs/developers_docs.md) page
* [Configuration](docs/admins_docs.md#5-configuration) page
* [Transcoding](docs/transcoding.md) page
* [Developer Experience](docs/dev_exp.md) page
* [Media Permissions](docs/media_permissions.md) page
## Technology
@ -115,7 +112,7 @@ This software uses the following list of awesome technologies: Python, Django, D
## Who is using it
- **Multiple Universities** for hosting educational videos
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org
- **American Association of Gynecologic Laparoscopists** - https://surgeryu.org/

10
cms/auth_backends.py Normal file
View File

@ -0,0 +1,10 @@
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
class ApprovalBackend(ModelBackend):
def user_can_authenticate(self, user):
can_authenticate = super().user_can_authenticate(user)
if can_authenticate and settings.USERS_NEEDS_TO_BE_APPROVED and not user.is_superuser:
return getattr(user, 'is_approved', False)
return can_authenticate

View File

@ -34,6 +34,7 @@ INSTALLED_APPS = [
"allauth.socialaccount.providers.saml",
"saml_auth.apps.SamlAuthConfig",
"corsheaders",
"tinymce",
]
MIDDLEWARE = [

23
cms/middleware.py Normal file
View File

@ -0,0 +1,23 @@
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
class ApprovalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if settings.USERS_NEEDS_TO_BE_APPROVED and request.user.is_authenticated and not request.user.is_superuser and not getattr(request.user, 'is_approved', False):
allowed_paths = [
reverse('approval_required'),
reverse('account_logout'),
]
if request.path not in allowed_paths:
if request.path.startswith('/api/'):
return JsonResponse({'detail': 'User account not approved.'}, status=403)
return redirect('approval_required')
response = self.get_response(request)
return response

View File

@ -1,14 +1,22 @@
from django.conf import settings
from rest_framework import permissions
from rest_framework.exceptions import PermissionDenied
from files.methods import is_mediacms_editor, is_mediacms_manager
from files.methods import (
is_mediacms_editor,
is_mediacms_manager,
user_allowed_to_upload,
)
class IsAuthorizedToAdd(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return user_allowed_to_upload(request)
if not user_allowed_to_upload(request):
raise PermissionDenied("You don't have permission to upload media, or have reached max number of media uploads.")
return True
class IsAuthorizedToAddComment(permissions.BasePermission):
@ -55,26 +63,6 @@ class IsUserOrEditor(permissions.BasePermission):
return obj.user == request.user
def user_allowed_to_upload(request):
"""Any custom logic for whether a user is allowed
to upload content lives here
"""
if request.user.is_anonymous:
return False
if request.user.is_superuser:
return True
if settings.CAN_ADD_MEDIA == "all":
return True
elif settings.CAN_ADD_MEDIA == "email_verified":
if request.user.email_is_verified:
return True
elif settings.CAN_ADD_MEDIA == "advancedUser":
if request.user.advancedUser:
return True
return False
def user_allowed_to_comment(request):
"""Any custom logic for whether a user is allowed
to comment lives here

View File

@ -100,11 +100,31 @@ 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
SITE_ID = 1
# these are the portal logos (dark and light)
# set new paths for svg or png if you want to override
# svg has priority over png, so if you want to use
# custom pngs and not svgs, remove the lines with svgs
# or set as empty strings
# example:
# PORTAL_LOGO_DARK_SVG = ""
# PORTAL_LOGO_LIGHT_SVG = ""
# place the files on static/images folder
PORTAL_LOGO_DARK_SVG = "/static/images/logo_dark.svg"
PORTAL_LOGO_DARK_PNG = "/static/images/logo_dark.png"
PORTAL_LOGO_LIGHT_SVG = "/static/images/logo_light.svg"
PORTAL_LOGO_LIGHT_PNG = "/static/images/logo_dark.png"
# paths to extra css files to be included, eg "/static/css/custom.css"
# place css inside static/css folder
EXTRA_CSS_PATHS = []
# protection agains anonymous users
# per ip address limit, for actions as like/dislike/report
TIME_TO_ACTION_ANONYMOUS = 10 * 60
@ -128,6 +148,10 @@ USERS_CAN_SELF_REGISTER = True
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
# by default users do not need to be approved. If this is set to True, then new users
# will have to be approved before they can login successfully
USERS_NEEDS_TO_BE_APPROVED = False
# Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"]
# Empty list disables.
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = []
@ -186,7 +210,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
VIDEO_CHUNKS_DURATION = 60 * 4
# always get these two, even if upscaling
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360]
MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
# default settings for notifications
# not all of them are implemented
@ -226,7 +250,7 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = ""
# only in case where unlisted workflow is used and no commentary
# exists
CANNOT_ADD_MEDIA_MESSAGE = ""
CANNOT_ADD_MEDIA_MESSAGE = "User cannot add media, or maximum number of media uploads has been reached."
# mp4hls command, part of Bento4
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
@ -285,6 +309,7 @@ INSTALLED_APPS = [
"drf_yasg",
"allauth.socialaccount.providers.saml",
"saml_auth.apps.SamlAuthConfig",
"tinymce",
]
MIDDLEWARE = [
@ -376,16 +401,7 @@ LOGGING = {
},
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mediacms",
"HOST": "127.0.0.1",
"PORT": "5432",
"USER": "mediacms",
"PASSWORD": "mediacms",
}
}
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
@ -453,6 +469,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
LANGUAGES = [
('ar', _('Arabic')),
('bn', _('Bengali')),
('da', _('Danish')),
('nl', _('Dutch')),
('en', _('English')),
('fr', _('French')),
@ -465,18 +482,60 @@ LANGUAGES = [
('pt', _('Portuguese')),
('ru', _('Russian')),
('zh-hans', _('Simplified Chinese')),
('sl', _('Slovenian')),
('zh-hant', _('Traditional Chinese')),
('es', _('Spanish')),
('tr', _('Turkish')),
('el', _('Greek')),
('ur', _('Urdu')),
('he', _('Hebrew')),
]
LANGUAGE_CODE = 'en' # default language
TINYMCE_DEFAULT_CONFIG = {
"theme": "silver",
"height": 500,
"resize": "both",
"menubar": "file edit view insert format tools table help",
"menu": {
"format": {
"title": "Format",
"items": "blocks | bold italic underline strikethrough superscript subscript code | " "fontfamily fontsize align lineheight | " "forecolor backcolor removeformat",
},
},
"plugins": "advlist,autolink,autosave,lists,link,image,charmap,print,preview,anchor,"
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,directionality,"
"code,help,wordcount,emoticons,file,image,media",
"toolbar": "undo redo | code preview | blocks | "
"bold italic | alignleft aligncenter "
"alignright alignjustify ltr rtl | bullist numlist outdent indent | "
"removeformat | restoredraft help | image media",
"branding": False, # remove branding
"promotion": False, # remove promotion
"body_class": "page-main-inner custom-page-wrapper", # class of the body element in tinymce
"block_formats": "Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3;",
"formats": { # customize h2 to always have emphasis-large class
"h2": {"block": "h2", "classes": "emphasis-large"},
},
"font_size_formats": "16px 18px 24px 32px",
"images_upload_url": "/tinymce/upload/",
"images_upload_handler": "tinymce.views.upload_image",
"automatic_uploads": True,
"file_picker_types": "image",
"paste_data_images": True,
"paste_as_text": False,
"paste_enable_default_filters": True,
"paste_word_valid_elements": "b,strong,i,em,h1,h2,h3,h4,h5,h6,p,br,a,ul,ol,li",
"paste_retain_style_properties": "all",
"paste_remove_styles": False,
"paste_merge_formats": True,
"sandbox_iframes": False,
}
SPRITE_NUM_SECS = 10
# number of seconds for sprite image.
# If you plan to change this, you must also follow the instructions on admin_docs.md
# If you plan to change this, you must also follow the instructions on admins_docs.md
# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend
# how many images will be shown on the slideshow
@ -501,6 +560,45 @@ JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
USE_ROUNDED_CORNERS = True
ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False
# Whether to allow anonymous users to list all users
ALLOW_ANONYMOUS_USER_LISTING = True
# Who can see the members page
# valid choices are all, editors, admins
CAN_SEE_MEMBERS_PAGE = "all"
# User search field setting
# valid choices are name_username, name_username_email
# this searches for users in the share media modal under my media
USER_SEARCH_FIELD = "name_username"
# Maximum number of media a user can upload
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 100
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
# If 'all' is in the list, no check is performed
ALLOWED_MEDIA_UPLOAD_TYPES = ["video", "audio", "image", "pdf"]
# transcription options
# the mediacms-full docker image needs to be used in order to be able to use transcription
# if you are using the mediacms-full image, change USE_WHISPER_TRANSCRIBE to True
USE_WHISPER_TRANSCRIBE = False
# by default all users can request a video to be transcribed. If you want to
# allow only editors, set this to False
USER_CAN_TRANSCRIBE_VIDEO = True
# Whisper transcribe options - https://github.com/openai/whisper
WHISPER_MODEL = "base"
# show a custom text in the sidebar footer, otherwise the default will be shown if this is empty
SIDEBAR_FOOTER_TEXT = ""
try:
# keep a local_settings.py file for local overrides
from .local_settings import * # noqa
@ -540,13 +638,14 @@ except ImportError:
if GLOBAL_LOGIN_REQUIRED:
# this should go after the AuthenticationMiddleware middleware
MIDDLEWARE.insert(6, "login_required.middleware.LoginRequiredMiddleware")
LOGIN_REQUIRED_IGNORE_PATHS = [
r'/accounts/login/$',
r'/accounts/logout/$',
r'/accounts/signup/$',
r'/accounts/password/.*/$',
r'/accounts/confirm-email/.*/$',
# r'/api/v[0-9]+/',
]
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(auth_index + 1, "django.contrib.auth.middleware.LoginRequiredMiddleware")
if USERS_NEEDS_TO_BE_APPROVED:
AUTHENTICATION_BACKENDS = (
'cms.auth_backends.ApprovalBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")

View File

@ -30,6 +30,7 @@ urlpatterns = [
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path("tinymce/", include("tinymce.urls")),
]
admin.site.site_header = "MediaCMS Admin"

View File

@ -1 +1 @@
VERSION = "6.0.0"
VERSION = "7.2.1"

View File

@ -30,7 +30,8 @@ fi
# We should do this only for folders that have a different owner, since it is an expensive operation
# Also ignoring .git folder to fix this issue https://github.com/mediacms-io/mediacms/issues/934
find /home/mediacms.io/mediacms ! \( -path "*.git*" \) -exec chown www-data:$TARGET_GID {} +
# Exclude package-lock.json files that may not exist or be removed during frontend setup
find /home/mediacms.io/mediacms ! \( -path "*.git*" -o -name "package-lock.json" \) -exec chown www-data:$TARGET_GID {} + 2>/dev/null || true
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh

View File

@ -13,6 +13,7 @@ DATABASES = {
"PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True},
}
}

View File

@ -1,5 +1,6 @@
#!/bin/bash
# This script builds the video editor package and deploys the frontend assets to the static directory.
# How to run: sh deploy/scripts/build_and_deploy.sh
# Exit on any error
set -e
@ -12,9 +13,21 @@ cd frontend-tools/video-editor
yarn build:django
cd ../../
# Build chapter editor package
echo "Building chapters editor package..."
cd frontend-tools/chapters-editor
yarn build:django
cd ../../
# Build video js package
echo "Building video js package..."
cd frontend-tools/video-js
yarn build:django
cd ../../
# Run npm build in the frontend container
echo "Building frontend assets..."
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
docker compose -f docker-compose/docker-compose-dev-updated.yaml exec frontend npm run dist
# Copy static assets to the static directory
echo "Copying static assets..."
@ -22,6 +35,6 @@ cp -r frontend/dist/static/* static/
# Restart the web service
echo "Restarting web service..."
docker compose -f docker-compose-dev.yaml restart web
docker compose -f docker-compose/docker-compose-dev-updated.yaml restart web
echo "Build and deployment completed successfully!"

View File

@ -5,6 +5,7 @@ services:
build:
context: .
dockerfile: ./Dockerfile
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:latest
@ -84,6 +85,5 @@ services:
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
DEVELOPMENT_MODE: True
depends_on:
- web

5
docker-compose.full.yaml Normal file
View File

@ -0,0 +1,5 @@
version: "3"
services:
celery_worker:
image: mediacms/mediacms:full

View File

@ -72,7 +72,7 @@ services:
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
@ -81,6 +81,6 @@ services:
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 30s
timeout: 10s
interval: 10s
timeout: 5s
retries: 3

View File

@ -33,55 +33,35 @@ services:
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
- frontend_node_modules:/home/mediacms.io/mediacms/frontend/node_modules
- player_node_modules:/home/mediacms.io/mediacms/frontend/packages/player/node_modules
- scripts_node_modules:/home/mediacms.io/mediacms/frontend/packages/scripts/node_modules
- npm_global:/home/node/.npm-global
- npm_cache:/home/node/.npm
working_dir: /home/mediacms.io/mediacms/frontend/
command: >
bash -c "
echo 'Setting up npm global directory...' &&
mkdir -p /home/node/.npm-global &&
chown -R node:node /home/node/.npm-global &&
echo 'Setting up permissions...' &&
chown -R node:node /home/mediacms.io/mediacms/frontend &&
echo 'Cleaning up node_modules...' &&
find /home/mediacms.io/mediacms/frontend/node_modules -mindepth 1 -delete 2>/dev/null || true &&
find /home/mediacms.io/mediacms/frontend/packages/player/node_modules -mindepth 1 -delete 2>/dev/null || true &&
find /home/mediacms.io/mediacms/frontend/packages/scripts/node_modules -mindepth 1 -delete 2>/dev/null || true &&
chown -R node:node /home/mediacms.io/mediacms/frontend/node_modules &&
chown -R node:node /home/mediacms.io/mediacms/frontend/packages/player/node_modules &&
chown -R node:node /home/mediacms.io/mediacms/frontend/packages/scripts/node_modules &&
echo 'Switching to node user...' &&
su node -c '
export NPM_CONFIG_PREFIX=/home/node/.npm-global &&
echo \"Setting up frontend...\" &&
rm -f package-lock.json &&
rm -f packages/player/package-lock.json &&
rm -f packages/scripts/package-lock.json &&
echo \"Installing dependencies...\" &&
npm install --legacy-peer-deps &&
echo \"Setting up workspaces...\" &&
npm install -g npm@latest &&
echo 'Checking dependencies...' &&
if [ ! -f node_modules/.install-complete ]; then
echo 'First-time setup or dependencies changed, installing...' &&
npm install --legacy-peer-deps --cache /home/node/.npm &&
cd packages/scripts &&
npm install --legacy-peer-deps &&
npm install rollup@2.79.1 --save-dev --legacy-peer-deps &&
npm install typescript@4.9.5 --save-dev --legacy-peer-deps &&
npm install tslib@2.6.2 --save --legacy-peer-deps &&
npm install rollup-plugin-typescript2@0.34.1 --save-dev --legacy-peer-deps &&
npm install --legacy-peer-deps &&
npm install --legacy-peer-deps --cache /home/node/.npm &&
npm run build &&
cd ../.. &&
cd packages/player &&
npm install --legacy-peer-deps &&
npm run build &&
cd ../.. &&
echo \"Starting development server...\" &&
npm run start
'"
touch node_modules/.install-complete &&
echo 'Dependencies installed successfully'
else
echo 'Dependencies already installed, skipping installation...' &&
if [ ! -d packages/scripts/dist ]; then
echo 'Building scripts package...' &&
cd packages/scripts &&
npm run build &&
cd ../..
fi
fi &&
echo 'Starting development server...' &&
npm run start
"
env_file:
- ${PWD}/frontend/.env
environment:
- NPM_CONFIG_PREFIX=/home/node/.npm-global
ports:
- "8088:8088"
depends_on:
@ -140,6 +120,5 @@ services:
volumes:
frontend_node_modules:
player_node_modules:
scripts_node_modules:
npm_global:
npm_cache:

View File

@ -2,7 +2,7 @@
## Table of contents
- [1. Welcome](#1-welcome)
- [2. Server Installaton](#2-server-installation)
- [2. Single Server Installaton](#2-single-server-installation)
- [3. Docker Installation](#3-docker-installation)
- [4. Docker Deployment options](#4-docker-deployment-options)
- [5. Configuration](#5-configuration)
@ -25,20 +25,23 @@
- [22. Role-Based Access Control](#22-role-based-access-control)
- [23. SAML setup](#23-saml-setup)
- [24. Identity Providers setup](#24-identity-providers-setup)
- [25. Custom urls](#25-custom-urls)
- [26. Allowed files](#26-allowed-files)
- [27. User upload limits](#27-user-upload-limits)
- [28. Whisper Transcribe for Automatic Subtitles](#28-whisper-transcribe-for-automatic-subtitles)
## 1. Welcome
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
## 2. Server Installation
## 2. Single Server Installation
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu (tested on versions 20, 22).
The core dependencies are python3, Django, celery, PostgreSQL, redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But the install.sh is only tested in Linux Ubuntu 24 and 22 versions.
Installation on an Ubuntu system with git utility installed should be completed in a few minutes with the following steps.
Installation on an Ubuntu 22/24 system with git utility installed should be completed in a few minutes with the following steps.
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
Automated script - tested on Ubuntu 20, Ubuntu 22 and Debian Buster
```bash
mkdir /home/mediacms.io && cd /home/mediacms.io/
@ -89,13 +92,11 @@ Database can be backed up with pg_dump and media_files on /home/mediacms.io/medi
## Installation
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
For Ubuntu 20/22 systems this is:
For Ubuntu systems this is:
```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```
Then run as root
@ -111,7 +112,7 @@ If you want to explore more options (including setup of https with letsencrypt c
Run
```bash
docker-compose up
docker compose up
```
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
@ -124,6 +125,12 @@ migrations_1 | Created admin user with password: gwg1clfkwf
or if you have set the ADMIN_PASSWORD variable on docker-compose file you have used (example `docker-compose.yaml`), that variable will be set as the admin user's password
`Note`: if you want to use the automatic transcriptions, you have to do one of the following:
* either use the docker-compose.full.yaml, so in this case run `docker-compose -f docker-compose.yaml -f docker-compose.full.yaml up`
* or edit the docker-compose.yaml file and set the image for the celery_worker service as mediacms/mediacms:full instead of mediacms/mediacms:latest
Plus set variable `USE_WHISPER_TRANSCRIBE = True` in the settings.py file
### Update
Get latest MediaCMS image and stop/start containers
@ -131,8 +138,8 @@ Get latest MediaCMS image and stop/start containers
```bash
cd /path/to/mediacms/installation
docker pull mediacms/mediacms
docker-compose down
docker-compose up
docker compose down
docker compose up
```
### Update from version 2 to version 3
@ -170,9 +177,7 @@ By default, all these services are enabled, but in order to create a scaleable d
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
See example deployments in the sections below. These example deployments have been tested on `docker-compose version 1.27.4` running on `Docker version 19.03.13`
To run, update the configs above if necessary, build the image by running `docker-compose build`, then run `docker-compose run`
To run, update the configs above if necessary, build the image by running `docker compose build`, then run `docker compose run`
### Simple Deployment, accessed as http://localhost
@ -189,7 +194,7 @@ Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
Now run docker-compose -f docker-compose-letsencrypt.yaml up, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
Now run `docker compose -f docker-compose-letsencrypt.yaml up`, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
### Advanced Deployment, accessed as http://localhost:8000
@ -230,12 +235,17 @@ Single server installation: edit `cms/local_settings.py`, make a change and rest
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
```bash
#docker-compose restart web celery_worker celery_beat
#docker compose restart web celery_worker celery_beat
```
### 5.1 Change portal logo
Set a new svg file for the white theme (`static/images/logo_dark.svg`) or the dark theme (`static/images/logo_light.svg`)
Find the default svg files for the white theme on `static/images/logo_dark.svg` and for the dark theme on `static/images/logo_light.svg`
You can specify new svg paths to override by editing the `PORTAL_LOGO_DARK_SVG` and `PORTAL_LOGO_LIGHT_SVG` variables in `settings.py`.
You can also use custom pngs, by setting the variables `PORTAL_LOGO_DARK_PNG` and `PORTAL_LOGO_LIGHT_PNG` in `settings.py`. The svg files have priority over png files, so if both are set, svg files will be used.
In any case, make sure the files are placed on the static/images folder.
### 5.2 Set global portal title
@ -504,6 +514,68 @@ By default `CAN_COMMENT = "all"` means that all registered users can add comment
- **advancedUser**, only users that are marked as advanced users can add comment. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
### 5.26 Control whether anonymous users can list all users
By default, anonymous users can view the list of all users on the platform. To restrict this to authenticated users only, set:
```
ALLOW_ANONYMOUS_USER_LISTING = False
```
When set to False, only logged-in users will be able to access the user listing API endpoint.
### 5.27 Control who can see the members page
By default `CAN_SEE_MEMBERS_PAGE = "all"` means that all registered users can see the members page. Other valid options are:
- **editors**, only MediaCMS editors can view the page
- **admins**, only MediaCMS admins can view the page
### 5.28 Configure user search fields
By default, when searching for users (e.g., in bulk actions modals or the users API), the search is performed on the user's name and username. You can configure this behavior using the `USER_SEARCH_FIELD` setting:
```
USER_SEARCH_FIELD = "name_username" # Default - searches in name and username
```
To also include email addresses in the search and display them in the user interface:
```
USER_SEARCH_FIELD = "name_username_email" # Searches in name, username, and email
```
When set to `"name_username_email"`:
- The user search will also match email addresses
- The email field will be returned in the API response
- Frontend components will display users as "Name - Email" instead of "Name - Username"
This setting is useful when you want to make it easier to find users by their email addresses, particularly in administrative interfaces like bulk action modals.
### 5.29 Require user approval on registration
By default, users do not require approval, so they can login immediately after registration (if registration is open). However, if the parameter `USERS_NEEDS_TO_BE_APPROVED` is set to `True`, they will first have to have their accounts approved by an administrator before they can successfully sign in.
Administrators can approve users through the following ways: 1. through Django administration, 2. through the users management page, 3. through editing the profile page directly. In all cases, set 'Is approved' to True.
### 5.30 Show or hide media count numbers on categories and tags pages
By default, the number of media items is displayed next to each category and tag on the `/categories` and `/tags` pages. To hide these numbers:
```
INCLUDE_LISTING_NUMBERS = False
```
To show the numbers (default behavior):
```
INCLUDE_LISTING_NUMBERS = True
```
This setting affects only the visual display on the categories and tags listing pages and does not impact the functionality of filtering by categories or tags.
## 6. Manage pages
to be written
@ -949,6 +1021,8 @@ Select the SAML Configurations tab, create a new one and set:
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS
A full SAML deployment with [EntraID guide and troubleshooting steps is available here.](./saml_entraid_setup.md). This guide can be used as reference for other IDPs too.
## 24. Identity Providers setup
A separate Django app identity_providers has been added in order to facilitate a number of configurations related to different identity providers. If this is enabled, it gives the following options:
@ -967,3 +1041,38 @@ USE_IDENTITY_PROVIDERS = True
Visiting the admin, you will see the Identity Providers tab and you can add one.
## 25. Custom urls
To enable custom urls, set `ALLOW_CUSTOM_MEDIA_URLS = True` on settings.py or local_settings.py
This will enable editing the URL of the media, while editing a media. If the URL is already taken you get a message you cannot update this.
## 26. Allowed files
MediaCMS performs identification attempts on new file uploads and only allows certain file types specified in the `ALLOWED_MEDIA_UPLOAD_TYPES` setting. By default, only ["video", "audio", "image", "pdf"] files are allowed.
When a file is not identified as one of these allowed types, the file gets removed from the system and there's an entry indicating that this is not a supported media type.
If you want to change the allowed file types, edit the `ALLOWED_MEDIA_UPLOAD_TYPES` list in your `settings.py` or `local_settings.py` file. If 'all' is specified in this list, no check is performed and all files are allowed.
## 27. User upload limits
MediaCMS allows you to set a maximum number of media files that each user can upload. This is controlled by the `NUMBER_OF_MEDIA_USER_CAN_UPLOAD` setting in `settings.py` or `local_settings.py`. By default, this is set to 100 media items per user.
When a user reaches this limit, they will no longer be able to upload new media until they delete some of their existing content. This limit applies regardless of the user's role or permissions in the system.
To change the maximum number of uploads allowed per user, modify the `NUMBER_OF_MEDIA_USER_CAN_UPLOAD` value in your settings file:
```
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 5
```
## 28. Whisper Transcribe for Automatic Subtitles
MediaCMS can integrate with OpenAI's Whisper to automatically generate subtitles for your media files. This feature is useful for making your content more accessible.
### How it works
When the whisper transcribe task is triggered for a media file, MediaCMS runs the `whisper` command-line tool to process the audio and generate a subtitle file in VTT format. The generated subtitles are then associated with the media and are available under the "automatic" language option.
### Configuration
Transcription functionality is available only for the Docker installation. To enable this feature, you must either use the `docker-compose.full.yaml` file, as it contains an image with the necessary requirements, or you can also set that celery_worker service is usine mediacms:full image instead of mediacms:latest. Then you also have to set the setting: `USE_WHISPER_TRANSCRIBE = True` in your local_settings.py file.
By default, all users have the ability to send a request for a video to be transcribed, as well as transcribed and translated to English. If you wish to change this behavior, you can edit the `settings.py` file and set `USER_CAN_TRANSCRIBE_VIDEO=False`.
The transcription uses the base model of Whisper speech-to-text by default. However, you can change the model by editing the `WHISPER_MODEL` setting in `settings.py`.

View File

@ -4,10 +4,10 @@ There is ongoing effort to provide a better developer experience and document it
## How to develop locally with Docker
First install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
Then run `docker-compose -f docker-compose-dev.yaml up`
Then run `docker compose -f docker-compose-dev.yaml up`
```
user@user:~/mediacms$ docker-compose -f docker-compose-dev.yaml up
user@user:~/mediacms$ docker compose -f docker-compose-dev.yaml up
```
In a few minutes the app will be available at http://localhost . Login via admin/admin
@ -37,7 +37,7 @@ Django starts at http://localhost and is reloading automatically. Making any cha
If Django breaks due to an error (eg SyntaxError, while editing the code), you might have to restart it
```
docker-compose -f docker-compose-dev.yaml restart web
docker compose -f docker-compose-dev.yaml restart web
```
@ -62,9 +62,9 @@ In order to make changes to React code, edit code on frontend/src and check it's
### Development workflow with the frontend
1. Edit frontend/src/ files
2. Check changes on http://localhost:8088/
3. Build frontend with `docker-compose -f docker-compose-dev.yaml exec frontend npm run dist`
3. Build frontend with `docker compose -f docker-compose-dev.yaml exec frontend npm run dist`
4. Copy static files to Django static folder with`cp -r frontend/dist/static/* static/`
5. Restart Django - `docker-compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
5. Restart Django - `docker compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
6. Commit the changes
### Helper commands
@ -81,7 +81,7 @@ Build the frontend:
```
user@user:~/mediacms$ make build-frontend
docker-compose -f docker-compose-dev.yaml exec frontend npm run dist
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
> mediacms-frontend@0.9.1 dist /home/mediacms.io/mediacms/frontend
> mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist

View File

@ -17,7 +17,7 @@ to be written
## 3. API documentation
API is documented using Swagger - checkout ot http://your_installation/swagger - example https://demo.mediacms.io/swagger/
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
An example of working with Python requests library:
@ -50,8 +50,8 @@ Checkout the [Code of conduct page](../CODE_OF_CONDUCT.md) if you want to contri
To perform the Docker installation, follow instructions to install Docker + Docker compose (docs/Docker_Compose.md) and then build/start docker-compose-dev.yaml . This will run the frontend application on port 8088 on top of all other containers (including the Django web application on port 80)
```
docker-compose -f docker-compose-dev.yaml build
docker-compose -f docker-compose-dev.yaml up
docker compose -f docker-compose-dev.yaml build
docker compose -f docker-compose-dev.yaml up
```
An `admin` user is created during the installation process. Its attributes are defined in `docker-compose-dev.yaml`:
@ -65,16 +65,16 @@ ADMIN_EMAIL: 'admin@localhost'
Eg change `frontend/src/static/js/pages/HomePage.tsx` , dev application refreshes in a number of seconds (hot reloading) and I see the changes, once I'm happy I can run
```
docker-compose -f docker-compose-dev.yaml exec -T frontend npm run dist
docker compose -f docker-compose-dev.yaml exec -T frontend npm run dist
```
And then in order for the changes to be visible on the application while served through nginx,
And then in order for the changes to be visible on the application while served through nginx,
```
cp -r frontend/dist/static/* static/
```
POST calls: cannot be performed through the dev server, you have to make through the normal application (port 80) and then see changes on the dev application on port 8088.
POST calls: cannot be performed through the dev server, you have to make through the normal application (port 80) and then see changes on the dev application on port 8088.
Make sure the urls are set on `frontend/.env` if different than localhost
@ -90,7 +90,7 @@ http://localhost:8088/manage-media.html manage_media
After I make changes to the django application (eg make a change on `files/forms.py`) in order to see the changes I have to restart the web container
```
docker-compose -f docker-compose-dev.yaml restart web
docker compose -f docker-compose-dev.yaml restart web
```
## How video is transcoded
@ -113,7 +113,7 @@ there is also an experimental small service (not commited to the repo currently)
When the Encode object is marked as success and chunk=False, and thus is available for download/stream, there is a task that gets started and saves an HLS version of the file (1 mp4-->x number of small .ts chunks). This would be FILES_C
This mechanism allows for workers that have access on the same filesystem (either localhost, or through a shared network filesystem, eg NFS/EFS) to work on the same time and produce results.
This mechanism allows for workers that have access on the same filesystem (either localhost, or through a shared network filesystem, eg NFS/EFS) to work on the same time and produce results.
## 6. Working with the automated tests
@ -122,19 +122,19 @@ This instructions assume that you're using the docker installation
1. start docker-compose
```
docker-compose up
docker compose up
```
2. Install the requirements on `requirements-dev.txt ` on web container (we'll use the web container for this)
```
docker-compose exec -T web pip install -r requirements-dev.txt
docker compose exec -T web pip install -r requirements-dev.txt
```
3. Now you can run the existing tests
```
docker-compose exec --env TESTING=True -T web pytest
docker compose exec --env TESTING=True -T web pytest
```
The `TESTING=True` is passed for Django to be aware this is a testing environment (so that it runs Celery tasks as functions for example and not as background tasks, since Celery is not started in the case of pytest)
@ -143,13 +143,13 @@ The `TESTING=True` is passed for Django to be aware this is a testing environmen
4. You may try a single test, by specifying the path, for example
```
docker-compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
docker compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
```
5. You can also see the coverage
```
docker-compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
docker compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
```
and of course...you are very welcome to help us increase it ;)

166
docs/media_permissions.md Normal file
View File

@ -0,0 +1,166 @@
# Media Permissions in MediaCMS
This document explains the permission system in MediaCMS, which controls who can view, edit, and manage media files.
## Overview
MediaCMS provides a flexible permission system that allows fine-grained control over media access. The system supports:
1. **Basic permissions** - Public, private, and unlisted media
2. **User-specific permissions** - Direct permissions granted to specific users
3. **Role-Based Access Control (RBAC)** - Category-based permissions through group membership
## Media States
Every media file has a state that determines its basic visibility:
- **Public** - Visible to everyone
- **Private** - Only visible to the owner and users with explicit permissions
- **Unlisted** - Not listed in public listings but accessible via direct link
## User Roles
MediaCMS has several user roles that affect permissions:
- **Regular User** - Can upload and manage their own media
- **Advanced User** - Additional capabilities (configurable)
- **MediaCMS Editor** - Can edit and review content across the platform
- **MediaCMS Manager** - Full management capabilities
- **Admin** - Complete system access
## Direct Media Permissions
The `MediaPermission` model allows granting specific permissions to individual users:
### Permission Levels
- **Viewer** - Can view the media even if it's private
- **Editor** - Can view and edit the media's metadata
- **Owner** - Full control, including deletion
## Role-Based Access Control (RBAC)
When RBAC is enabled (`USE_RBAC` setting), permissions can be managed through categories and groups:
1. Categories can be marked as RBAC-controlled
2. Users are assigned to RBAC groups with specific roles
3. RBAC groups are associated with categories
4. Users inherit permissions to media in those categories based on their role
### RBAC Roles
- **Member** - Can view media in the category
- **Contributor** - Can view and edit media in the category
- **Manager** - Full control over media in the category
## Permission Checking Methods
The User model provides several methods to check permissions:
```python
# From users/models.py
def has_member_access_to_media(self, media):
# Check if user can view the media
# ...
def has_contributor_access_to_media(self, media):
# Check if user can edit the media
# ...
def has_owner_access_to_media(self, media):
# Check if user has full control over the media
# ...
```
## How Permissions Are Applied
When a user attempts to access media, the system checks permissions in this order:
1. Is the media public? If yes, allow access.
2. Is the user the owner of the media? If yes, allow full access.
3. Does the user have direct permissions through MediaPermission? If yes, grant the corresponding access level.
4. If RBAC is enabled, does the user have access through category membership? If yes, grant the corresponding access level.
5. If none of the above, deny access.
## Media Sharing
Users can share media with others by:
1. Making it public or unlisted
2. Granting direct permissions to specific users
3. Adding it to a category that's accessible to an RBAC group
## Implementation Details
### Media Listing
When listing media, the system filters based on permissions:
```python
# Simplified example from files/views/media.py
def _get_media_queryset(self, request, user=None):
# 1. Public media
listable_media = Media.objects.filter(listable=True)
if not request.user.is_authenticated:
return listable_media
# 2. User permissions for authenticated users
user_media = Media.objects.filter(permissions__user=request.user)
# 3. RBAC for authenticated users
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
rbac_media = Media.objects.filter(category__in=rbac_categories)
# Combine all accessible media
return listable_media.union(user_media, rbac_media)
```
### Permission Checking
The system uses helper methods to check permissions:
```python
# From users/models.py
def has_member_access_to_media(self, media):
# First check if user is the owner
if media.user == self:
return True
# Then check RBAC permissions
if getattr(settings, 'USE_RBAC', False):
rbac_groups = RBACGroup.objects.filter(
memberships__user=self,
memberships__role__in=["member", "contributor", "manager"],
categories__in=media.category.all()
).distinct()
if rbac_groups.exists():
return True
# Then check MediaShare permissions for any access
media_permission_exists = MediaPermission.objects.filter(
user=self,
media=media,
).exists()
return media_permission_exists
```
## Best Practices
1. **Default to Private** - Consider setting new uploads to private by default
2. **Use Categories** - Organize media into categories for easier permission management
3. **RBAC for Teams** - Use RBAC for team collaboration scenarios
4. **Direct Permissions for Exceptions** - Use direct permissions for one-off sharing
## Configuration
The permission system can be configured through several settings:
- `USE_RBAC` - Enable/disable Role-Based Access Control
## Conclusion
MediaCMS provides a flexible and powerful permission system that can accommodate various use cases, from simple personal media libraries to complex team collaboration scenarios with fine-grained access control.

315
docs/saml_entraid_setup.md Normal file
View File

@ -0,0 +1,315 @@
# Integrating Microsoft Entra ID (formerly Azure AD) with MediaCMS via SAML Authentication
This guide provides step-by-step instructions on how to configure Microsoft Entra ID as a SAML Identity Provider (IdP) for MediaCMS, an open-source content management system. The goal is to enable single sign-on (SSO) authentication for users in a secure and scalable way.
## Table of Contents
1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Step 1: Configure MediaCMS for SAML](#step-1-configure-mediacms-for-saml)
4. [Step 2: Register MediaCMS as an Enterprise App in Entra ID](#step-2-register-mediacms-as-an-enterprise-app-in-entra-id)
5. [Step 3: Configure SAML Settings in Entra ID](#step-3-configure-saml-settings-in-entra-id)
6. [Step 4: Configure SAML Settings in MediaCMS](#step-4-configure-saml-settings-in-mediacms)
7. [Step 5: Allow Users or Groups to Log Into the Application](#step-5-allow-users-or-groups-to-log-into-the-application)
8. [Step 6: Test and Validate Login Flow](#step-6-test-and-validate-login-flow)
9. [Troubleshooting](#troubleshooting)
10. [Resources](#resources)
---
## Overview
MediaCMS supports SAML 2.0 authentication by acting as a Service Provider (SP). By integrating with Microsoft Entra ID, organizations can allow users to authenticate using their existing enterprise credentials.
In our particular deployment of MediaCMS, the application is hosted internally with no direct inbound access from the public Internet. As an internal company application, it was essential to integrate it with our existing authentication systems and provide a seamless single sign-on experience. This is where the SAML protocol shines.
One of the major advantages of SAML authentication is that all communication between the Identity Provider (IdP) — in this case, Microsoft Entra ID — and the Service Provider (SP) — MediaCMS — is brokered entirely by the end user's browser. The browser initiates the authentication flow, communicates securely with Microsofts login portal, receives the identity assertion, and then passes it back to the internal MediaCMS server.
This architecture enables the MediaCMS server to remain isolated from the Internet while still participating in a modern and seamless federated login experience.
Even though the deployment method outlined in this tutorial is for EntraID on an isolated MediaCMS server, the same steps and general information could be applied to another authentication SAML provider/identity provider on a non-isolated system.
> **Note**: This guide assumes you are running MediaCMS with Django backend and that the `django-allauth` library is enabled and configured.
---
## Prerequisites
Before beginning, ensure the following:
* You have administrator access to both MediaCMS and Microsoft Entra ID (Azure portal).
* MediaCMS is installed and accessible via HTTPS, with a valid SSL certificate.
* Your MediaCMS installation has SAML support enabled (via `django-allauth`).
* You have a dedicated domain or subdomain for MediaCMS (e.g., `https://<MyMediaCMS.MyDomain.com>`).
---
## Step 1: Configure MediaCMS for SAML
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./deploy/docker/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
```python
USE_RBAC = True
USE_SAML = True
USE_IDENTITY_PROVIDERS = True
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
SOCIALACCOUNT_PROVIDERS = {
"saml": {
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
}
}
```
These settings enable SAML authentication, configure MediaCMS to respect role-based access, and apply important headers and cookie policies for secure browser handling — all of which are necessary for the SAML flow to function properly.
> ⚠️ **Important**: After updating the `local_settings.py` file, you must restart your MediaCMS service (e.g., by rebooting the Docker container) in order for the changes to take effect. This step must be completed before proceeding to the next configuration stage.
---
## Step 2: Register MediaCMS as an Enterprise App in Entra ID
To begin the integration process on the Microsoft Entra ID (formerly Azure AD) side, follow the steps below to register MediaCMS as a new Enterprise Application.
### 1. Navigate to Enterprise Applications
* Log in to your [Azure Portal](https://portal.azure.com).
* Navigate to **Enterprise Applications**.
> *Note: This guide assumes you already have an existing Azure tenant and Entra ID configured with users and groups.*
### 2. Create a New Application
* Click the **+ New Application** button.
* On the next screen, choose **Create your own application**.
* Enter a name for the application (e.g., `MediaCMS`).
* Under "What are you looking to do with your application?", select **Integrate any other application you don't find in the gallery (Non-gallery)**.
* Click **Create**.
After a few moments, Azure will create the new application and redirect you to its configuration page.
---
## Step 3: Configure SAML Settings in Entra ID
### 1. Configure SAML-Based Single Sign-On
* From the application overview page, in the left-hand menu under **Manage**, click **Single sign-on**.
* You will be prompted to choose a sign-on method. Select **SAML**.
### 2. Choose a Client ID Name
Before filling out the SAML configuration, you must decide on a client ID name. This name will uniquely identify your SAML integration and appear in your login URL.
* Choose a name that is descriptive and easy to remember (e.g., `mediacms_entraid`).
* You will use this name in both MediaCMS and Entra ID configuration settings.
### 3. Fill Out Basic SAML Configuration
Now input the following values under the **Basic SAML Configuration** section:
| Field | Value |
| -------------------------- | --------------------------------------------------------------------- |
| **Identifier (Entity ID)** | `https://<MyMediaCMS.MyDomain.com>/saml/metadata/` |
| **Reply URL (ACS URL)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/` |
| **Sign-on URL** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/` |
| **Relay State (Optional)** | `https://<MyMediaCMS.MyDomain.com>/` |
| **Logout URL (Optional)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/sls/` |
> 🔐 Replace `<MyClientID>` with your own chosen client ID if different.
Once these fields are filled in, save your configuration.
Keep the Azure Enterprise single sign-on configuration window up, as we are now going to configure some of the details from this Azure page into our MediaCMS system.
---
## Step 4: Configure SAML Settings in MediaCMS
In MediaCMS, start by logging into the back-end administrative web page. You will now have new options under the left-hand menu bar.
### 1. Add Login Option
* Navigate to **Identity Providers → Login Options**.
* Click **Add Login Option**.
* Give the login option a title. This title can be anything you like but it will appear to the end-user when they select a method of logging in, so ensure the name is clear. (e.g., `EntraID-SSO`).
* Set the **Login URL** to the same Sign-on URL:
```
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/
```
* Leave the ordering at `0` if you have no other authentication methods.
* Ensure the **Active** box is checked to make this an active login method.
* Click **Save** to continue.
### 2. Add ID Provider
* Navigate to **Identity Providers → ID Providers**.
* Click **Add ID Provider**.
Back in your Azure Enterprise application configuration window (at the bottom of the Single Sign-On configuration menu), find your application-specific details. They will look like the following example:
```
Example unique AppID: 123456ab-1234-12ab-ab12-abc123abc123
The unique AppID is automatically generated when you create the application.
-- Example URLs --
Login URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
Microsoft Entra Identifier: https://sts.windows.net/123456ab-1234-12ab-ab12-abc123abc123/
Logout URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
```
Back in MediaCMS's new ID Provider window, under the **General** tab:
* **Protocol**: `saml` (all lowercase)
* **Provider ID**: The Microsoft Entra Identifier (as shown above), the whole URL.
* **IDP Configuration Name**: Any unique name (e.g., `EntraID`)
* **Client ID**: The exact same client ID you used earlier when configuring EntraID (e.g., `mediacms_entraid`).
* **Sites**: Add all the sites you want this login to appear on (e.g., all of them)
Click **Save and Continue**, then go to the **SAML Configuration** tab.
On the **SAML Configuration** tab:
* **SSO URL**: Use the same Logon URL from EntraID example listed above.
* **SLO URL**: Use the Logout URL from EntraID example listed above.
* **SP Metadata URL**:
```
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
```
* **IdP ID**: Use the same Microsoft Entra Identifier URL as listed above.
#### LDP Certificate
Back in Azure's Enterprise Application page (SAML certificates section), download the **Base64 Certificate**, open it in a text editor, and copy the contents into the **LDP Certificate** setting inside of MediaCMS.
### 3. Configure Identity Mappings
Map the identity attributes that Entra ID will provide to MediaCMS. Even though only UID is specified as mandatory, Entra ID will not work unless all of these details are filled in(YES, you must type NA in the fields; you cannot leave anything blank. You will get 500 errors if this is not done). You can use the exact settings below:
| Field | Value |
| -------------- | -------------------------------------------------------------------- |
| **Uid** | `http://schemas.microsoft.com/identity/claims/objectidentifier` |
| **Name** | `http://schemas.microsoft.com/identity/claims/displayname` |
| **Email** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` |
| **Groups** | `NA` |
| **First name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` |
| **Last name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` |
| **User logo** | `NA` |
| **Role** | `NA` |
> Groups and Role can be changed or remapped inside the Azure Enterprise Application under **Attributes and Claims**.
Check the **Verified Email** box (since EntraID will verify the user for you). While setting up, you can enable **Save SAML Response Log** for troubleshooting purposes.
Finally, click **Save** to finish adding the new ID provider.
---
## Step 5: Allow Users or Groups to Log Into the Application
Back inside Azure AD, within your MediaCMS Enterprise Application, you must assign users or groups that are allowed to use the MediaCMS authentication sign-on.
### 1. Navigate to Users and Groups
* Open the Azure Portal and go to your **MediaCMS Enterprise Application**.
* In the left-hand **Manage** menu, click **Users and Groups**.
### 2. Assign Users or Groups
* Add individual users or groups of users who are allowed to use the EntraID authentication method with MediaCMS.
* In this example, the application was provided to all registered users inside of EntraID by using the special group **All Users**, which grants any registered user in the tenant access to MediaCMS.
> ⚠️ **Important**: Nested groups will not work. All users must be directly assigned to the group you are giving permission to. If a group contains another group, the users of the nested group will not inherit the permissions to use this application from the parent group.
---
## Step 6: Test and Validate Login Flow
At this point, you should go to your MediaCMS webpage and attempt to log in using the authentication method that you have just set up.
---
## Troubleshooting
If you're experiencing logon issues, it is helpful to first review the SAML authentication data directly.
1. Go to MediaCMS's login page. It should redirect you to Microsoft's login page.
2. Before completing the Microsoft authentication, open Firefox or Chrome Developer Tools (press **F12**) and navigate to the **Network** tab.
3. Enable **Persistent Logging**.
4. Complete the Microsoft authentication steps on your page (including two-factor authentication if enabled).
On the final step of the authentication (usually after entering a code and confirming "Stay signed in?"), you will see several POST requests going back to your MediaCMS server URL. Find the POST request that is going to your MediaCMS server's Assertion Consumer Service (ACS) URL, which will look like this:
```
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/
```
Inside the request section of the Network tab, you will see a **Form Data** field labeled **SAMLResponse**, which contains a Base64-encoded XML string of your authenticated assertion from EntraID.
* Click into the data field of the SAML response so you can highlight and copy all of the Base64-encoded text.
* You can then take this Base64-encoded text to a tool like [CyberChef](https://gchq.github.io/CyberChef/) and use the **From Base64** decoder and **XML Beautify** to reveal the XML-formatted SAML response.
This decoded XML contains all the assertion and token details passed back to MediaCMS. You can use this information to troubleshoot any issues or misconfigurations that arise.
You can also confirm your MediaCMS server has the SAML authentication settings correct by opening a private browsing window and navigating to the following URL, which will output the current XML data that your MediaCMS server is configured with:
```
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
```
You can use the returned XML data from this URL to confirm that MediaCMS is configured appropriately as expected and is providing the correct information to the identity provider.
### Infinite Redirect Loop
Another issue you might encounter is an **infinite redirect loop**. This can happen when global login is enforced and local user login is disabled.
**Symptoms:** The system continuously redirects between the homepage and the login URL.
**Root Cause:** With global login required and local login disabled, Django attempts to redirect users to the default local login page. Since that login method is unavailable, users are bounced back to the homepage, triggering the same redirect logic again — resulting in a loop.
**Solution:** Specify the correct SAML authentication URL in your local settings. For example:
* "Login Option" URL configured for EntraID in MediaCMS:
```
https://<MyDomainName>/accounts/saml/mediacms_entraid/login/
```
* Add the following line to `./deploy/docker/local_settings.py`:
```python
LOGIN_URL = "/accounts/saml/mediacms_entraid/login/"
```
This change ensures Django uses the proper SAML login route, breaking the redirect loop and allowing authentication via EntraID as intended.
> **Note:** The `LOGIN_URL` setting works because we are using the Django AllAuth module to perform the SAML authentication. If you review the AllAuth Django configuration settings, you will find that this is a setting, among other settings, that you can set inside of your local settings file that Django will pick up when using the AllAuth module. You can review the module documentation at the following URL for more details and additional settings that can be set through AllAuth via `local_settings.py`: [https://django-allauth.readthedocs.io/en/latest/account/configuration.html](https://django-allauth.readthedocs.io/en/latest/account/configuration.html)
---
## Resources
* [MediaCMS SAML Docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#24-identity-providers-setup)
* [Enable SAML single sign-on for an enterprise application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal-setup-sso)
* [Django AllAuth](https://django-allauth.readthedocs.io/en/latest/index.html)
---
*This documentation is a work-in-progress and will be updated as further steps are dictated or completed.*

50
docs/transcoding.md Normal file
View File

@ -0,0 +1,50 @@
# Transcoding in MediaCMS
MediaCMS uses FFmpeg for transcoding media files. Most of the transcoding settings and configurations are defined in `files/helpers.py`.
## Configuration Options
Several transcoding parameters can be customized in `cms/settings.py`:
### FFmpeg Preset
The default FFmpeg preset is set to "medium". This setting controls the encoding speed and compression efficiency trade-off.
```python
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
```
Available presets include:
- ultrafast
- superfast
- veryfast
- faster
- fast
- medium (default)
- slow
- slower
- veryslow
Faster presets result in larger file sizes for the same quality, while slower presets provide better compression but take longer to encode.
### Other Transcoding Settings
Additional transcoding settings in `settings.py` include:
- `FFMPEG_COMMAND`: Path to the FFmpeg executable
- `FFPROBE_COMMAND`: Path to the FFprobe executable
- `DO_NOT_TRANSCODE_VIDEO`: If set to True, only the original video is shown without transcoding
- `CHUNKIZE_VIDEO_DURATION`: For videos longer than this duration (in seconds), they get split into chunks and encoded independently
- `VIDEO_CHUNKS_DURATION`: Duration of each chunk (must be smaller than CHUNKIZE_VIDEO_DURATION)
- `MINIMUM_RESOLUTIONS_TO_ENCODE`: Always encode these resolutions, even if upscaling is required
## Advanced Configuration
For more advanced transcoding settings, you may need to modify the following in `files/helpers.py`:
- Video bitrates for different codecs and resolutions
- Audio encoders and bitrates
- CRF (Constant Rate Factor) values
- Keyframe settings
- Encoding parameters for different codecs (H.264, H.265, VP9)

View File

@ -3,6 +3,7 @@ from django.conf import settings
from django.contrib import admin
from django.core.exceptions import ValidationError
from django.db import transaction
from tinymce.widgets import TinyMCE
from rbac.models import RBACGroup
@ -13,8 +14,11 @@ from .models import (
Encoding,
Language,
Media,
Page,
Subtitle,
Tag,
TinyMCEMedia,
TranscriptionRequest,
VideoTrimRequest,
)
@ -197,11 +201,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):
@ -219,14 +230,51 @@ class EncodingAdmin(admin.ModelAdmin):
has_file.short_description = "Has file"
class TranscriptionRequestAdmin(admin.ModelAdmin):
list_display = ["media", "add_date", "status", "translate_to_english"]
list_filter = ["status", "translate_to_english", "add_date"]
search_fields = ["media__title"]
readonly_fields = ("add_date", "logs")
ordering = ("-add_date",)
class PageAdminForm(forms.ModelForm):
description = forms.CharField(widget=TinyMCE())
def clean_description(self):
content = self.cleaned_data['description']
# Add sandbox attribute to all iframes
content = content.replace('<iframe ', '<iframe sandbox="allow-scripts allow-same-origin allow-presentation" ')
return content
class Meta:
model = Page
fields = "__all__"
class PageAdmin(admin.ModelAdmin):
form = PageAdminForm
@admin.register(TinyMCEMedia)
class TinyMCEMediaAdmin(admin.ModelAdmin):
list_display = ['original_filename', 'file_type', 'uploaded_at', 'user']
list_filter = ['file_type', 'uploaded_at']
search_fields = ['original_filename']
readonly_fields = ['uploaded_at']
date_hierarchy = 'uploaded_at'
admin.site.register(EncodeProfile, EncodeProfileAdmin)
admin.site.register(Comment, CommentAdmin)
admin.site.register(Media, MediaAdmin)
admin.site.register(Encoding, EncodingAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Page, PageAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Subtitle, SubtitleAdmin)
admin.site.register(Language, LanguageAdmin)
admin.site.register(VideoTrimRequest, VideoTrimRequestAdmin)
admin.site.register(TranscriptionRequest, TranscriptionRequestAdmin)
Media._meta.app_config.verbose_name = "Media"

View File

@ -1,5 +1,7 @@
from django.conf import settings
from cms.version import VERSION
from .frontend_translations import get_translation, get_translation_strings
from .methods import is_mediacms_editor, is_mediacms_manager
@ -10,6 +12,12 @@ def stuff(request):
ret["FRONTEND_HOST"] = request.build_absolute_uri('/').rstrip('/')
ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
ret["PORTAL_NAME"] = settings.PORTAL_NAME
ret["PORTAL_LOGO_DARK_SVG"] = getattr(settings, 'PORTAL_LOGO_DARK_SVG', "")
ret["PORTAL_LOGO_DARK_PNG"] = getattr(settings, 'PORTAL_LOGO_DARK_PNG', "")
ret["PORTAL_LOGO_LIGHT_SVG"] = getattr(settings, 'PORTAL_LOGO_LIGHT_SVG', "")
ret["PORTAL_LOGO_LIGHT_PNG"] = getattr(settings, 'PORTAL_LOGO_LIGHT_PNG', "")
ret["EXTRA_CSS_PATHS"] = getattr(settings, 'EXTRA_CSS_PATHS', [])
ret["PORTAL_DESCRIPTION"] = settings.PORTAL_DESCRIPTION
ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
@ -24,10 +32,22 @@ def stuff(request):
ret["UPLOAD_MAX_SIZE"] = settings.UPLOAD_MAX_SIZE
ret["UPLOAD_MAX_FILES_NUMBER"] = settings.UPLOAD_MAX_FILES_NUMBER
ret["PRE_UPLOAD_MEDIA_MESSAGE"] = settings.PRE_UPLOAD_MEDIA_MESSAGE
ret["SIDEBAR_FOOTER_TEXT"] = settings.SIDEBAR_FOOTER_TEXT
ret["POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY"] = settings.POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY
ret["IS_MEDIACMS_ADMIN"] = request.user.is_superuser
ret["IS_MEDIACMS_EDITOR"] = is_mediacms_editor(request.user)
ret["IS_MEDIACMS_MANAGER"] = is_mediacms_manager(request.user)
ret["USERS_NEEDS_TO_BE_APPROVED"] = settings.USERS_NEEDS_TO_BE_APPROVED
can_see_members_page = False
if request.user.is_authenticated:
if settings.CAN_SEE_MEMBERS_PAGE == "all":
can_see_members_page = True
elif settings.CAN_SEE_MEMBERS_PAGE == "editors" and is_mediacms_editor(request.user):
can_see_members_page = True
elif settings.CAN_SEE_MEMBERS_PAGE == "admins" and request.user.is_superuser:
can_see_members_page = True
ret["CAN_SEE_MEMBERS_PAGE"] = can_see_members_page
ret["ALLOW_RATINGS"] = settings.ALLOW_RATINGS
ret["ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY"] = settings.ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY
ret["VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE"] = settings.VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE
@ -37,6 +57,8 @@ 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["VERSION"] = VERSION
if request.user.is_superuser:
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL

View File

@ -83,7 +83,7 @@ class IndexRSSFeed(Feed):
return item.edit_date
def item_link(self, item):
return reverse("get_media") + "?m={0}".format(item.friendly_token)
return f"{reverse('get_media')}?m={item.friendly_token}"
def item_extra_kwargs(self, item):
item = {
@ -151,7 +151,7 @@ class SearchRSSFeed(Feed):
return item.edit_date
def item_link(self, item):
return reverse("get_media") + "?m={0}".format(item.friendly_token)
return f"{reverse('get_media')}?m={item.friendly_token}"
def item_extra_kwargs(self, item):
item = {

View File

@ -1,6 +1,6 @@
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field, Layout, Submit
from crispy_forms.layout import HTML, Field, Layout, Submit
from django import forms
from django.conf import settings
@ -22,6 +22,7 @@ class MediaMetadataForm(forms.ModelForm):
class Meta:
model = Media
fields = (
"friendly_token",
"title",
"new_tags",
"add_date",
@ -34,15 +35,17 @@ class MediaMetadataForm(forms.ModelForm):
widgets = {
"new_tags": MultipleSelect(),
"description": forms.Textarea(attrs={'rows': 4}),
"add_date": forms.DateInput(attrs={'type': 'date'}),
"add_date": forms.DateTimeInput(attrs={'type': 'datetime-local', 'step': '1'}, format='%Y-%m-%dT%H:%M:%S'),
"thumbnail_time": forms.NumberInput(attrs={'min': 0, 'step': 0.1}),
}
labels = {
"friendly_token": "Slug",
"uploaded_poster": "Poster Image",
"thumbnail_time": "Thumbnail Time (seconds)",
}
help_texts = {
"title": "",
"friendly_token": "Media URL slug",
"thumbnail_time": "Select the time in seconds for the video thumbnail",
"uploaded_poster": "Maximum file size: 5MB",
}
@ -50,6 +53,8 @@ class MediaMetadataForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(MediaMetadataForm, self).__init__(*args, **kwargs)
if not getattr(settings, 'ALLOW_CUSTOM_MEDIA_URLS', False):
self.fields.pop("friendly_token")
if self.instance.media_type != "video":
self.fields.pop("thumbnail_time")
if self.instance.media_type == "image":
@ -63,20 +68,37 @@ class MediaMetadataForm(forms.ModelForm):
self.helper.form_method = 'post'
self.helper.form_enctype = "multipart/form-data"
self.helper.form_show_errors = False
self.helper.layout = Layout(
layout_fields = [
CustomField('title'),
CustomField('new_tags'),
CustomField('add_date'),
CustomField('description'),
CustomField('uploaded_poster'),
CustomField('enable_comments'),
)
]
if self.instance.media_type != "image":
layout_fields.append(CustomField('uploaded_poster'))
self.helper.layout = Layout(*layout_fields)
if self.instance.media_type == "video":
self.helper.layout.append(CustomField('thumbnail_time'))
if getattr(settings, 'ALLOW_CUSTOM_MEDIA_URLS', False):
self.helper.layout.insert(0, CustomField('friendly_token'))
self.helper.layout.append(FormActions(Submit('submit', 'Update Media', css_class='primaryAction')))
def clean_friendly_token(self):
token = self.cleaned_data.get("friendly_token", "").strip()
if token:
if not all(c.isalnum() or c in "-_" for c in token):
raise forms.ValidationError("Slug can only contain alphanumeric characters, underscores, or hyphens.")
if Media.objects.filter(friendly_token=token).exclude(pk=self.instance.pk).exists():
raise forms.ValidationError("This slug is already in use. Please choose a different one.")
return token
def clean_uploaded_poster(self):
image = self.cleaned_data.get("uploaded_poster", False)
if image:
@ -96,14 +118,7 @@ class MediaPublishForm(forms.ModelForm):
class Meta:
model = Media
fields = (
"category",
"state",
"featured",
"reported_times",
"is_reviewed",
"allow_download",
)
fields = ("category", "state", "featured", "reported_times", "is_reviewed", "allow_download")
widgets = {
"category": MultipleSelect(),
@ -112,6 +127,7 @@ class MediaPublishForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(MediaPublishForm, self).__init__(*args, **kwargs)
if not is_mediacms_editor(user):
for field in ["featured", "reported_times", "is_reviewed"]:
self.fields[field].disabled = True
@ -162,14 +178,11 @@ 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 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':
@ -182,8 +195,12 @@ class MediaPublishForm(forms.ModelForm):
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)
return cleaned_data
@ -198,16 +215,95 @@ class MediaPublishForm(forms.ModelForm):
return media
class WhisperSubtitlesForm(forms.ModelForm):
class Meta:
model = Media
fields = (
"allow_whisper_transcribe",
"allow_whisper_transcribe_and_translate",
)
labels = {
"allow_whisper_transcribe": "Transcription",
"allow_whisper_transcribe_and_translate": "English Translation",
}
help_texts = {
"allow_whisper_transcribe": "",
"allow_whisper_transcribe_and_translate": "",
}
def __init__(self, user, *args, **kwargs):
self.user = user
super(WhisperSubtitlesForm, self).__init__(*args, **kwargs)
if self.instance.allow_whisper_transcribe:
self.fields['allow_whisper_transcribe'].widget.attrs['readonly'] = True
self.fields['allow_whisper_transcribe'].widget.attrs['disabled'] = True
if self.instance.allow_whisper_transcribe_and_translate:
self.fields['allow_whisper_transcribe_and_translate'].widget.attrs['readonly'] = True
self.fields['allow_whisper_transcribe_and_translate'].widget.attrs['disabled'] = True
both_readonly = self.instance.allow_whisper_transcribe and self.instance.allow_whisper_transcribe_and_translate
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'post-form'
self.helper.form_method = 'post'
self.helper.form_enctype = "multipart/form-data"
self.helper.form_show_errors = False
self.helper.layout = Layout(
CustomField('allow_whisper_transcribe'),
CustomField('allow_whisper_transcribe_and_translate'),
)
if not both_readonly:
self.helper.layout.append(FormActions(Submit('submit_whisper', 'Submit', css_class='primaryAction')))
else:
# Optional: Add a disabled button with explanatory text
self.helper.layout.append(
FormActions(Submit('submit_whisper', 'Submit', css_class='primaryAction', disabled=True), HTML('<small class="text-muted">Cannot submit - both options are already enabled</small>'))
)
def clean_allow_whisper_transcribe(self):
# Ensure the field value doesn't change if it was originally True
if self.instance and self.instance.allow_whisper_transcribe:
return self.instance.allow_whisper_transcribe
return self.cleaned_data['allow_whisper_transcribe']
def clean_allow_whisper_transcribe_and_translate(self):
# Ensure the field value doesn't change if it was originally True
if self.instance and self.instance.allow_whisper_transcribe_and_translate:
return self.instance.allow_whisper_transcribe_and_translate
return self.cleaned_data['allow_whisper_transcribe_and_translate']
class SubtitleForm(forms.ModelForm):
class Meta:
model = Subtitle
fields = ["language", "subtitle_file"]
labels = {
"subtitle_file": "Upload Caption File",
}
help_texts = {
"subtitle_file": "SubRip (.srt) and WebVTT (.vtt) are supported file formats.",
}
def __init__(self, media_item, *args, **kwargs):
super(SubtitleForm, self).__init__(*args, **kwargs)
self.instance.media = media_item
self.fields["subtitle_file"].help_text = "SubRip (.srt) and WebVTT (.vtt) are supported file formats."
self.fields["subtitle_file"].label = "Subtitle or Closed Caption File"
self.helper = FormHelper()
self.helper.form_tag = True
self.helper.form_class = 'post-form'
self.helper.form_method = 'post'
self.helper.form_enctype = "multipart/form-data"
self.helper.form_show_errors = False
self.helper.layout = Layout(
CustomField('subtitle_file'),
CustomField('language'),
)
self.helper.layout.append(FormActions(Submit('submit', 'Submit', css_class='primaryAction')))
def save(self, *args, **kwargs):
self.instance.user = self.instance.media.user

View File

@ -1,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ إنشاء قائمة تشغيل",
"00 - 20 min": "00 - 20 دقيقة",
"1 result for": "نتيجة واحدة لـ",
"20 - 40 min": "20 - 40 دقيقة",
"40 - 60 min": "40 - 60 دقيقة",
"60 - 120 min+": "60 - 120 دقيقة+",
"ABOUT": "حول",
"AUTOPLAY": "تشغيل تلقائي",
"About": "حول",
"Add / Remove Co-Editors": "إضافة / إزالة المحررين المشاركين",
"Add / Remove Co-Owners": "إضافة / إزالة المالكين المشاركين",
"Add / Remove Co-Viewers": "إضافة / إزالة المشاهدين المشاركين",
"Add / Remove Tags": "إضافة / إزالة العلامات",
"Add / Remove from Categories": "إضافة / إزالة من الفئات",
"Add a ": "أضف ",
"Add to": "إضافة إلى",
"Add to / Remove from Category": "إضافة / إزالة من الفئة",
"Add to / Remove from Playlist": "إضافة / إزالة من قائمة التشغيل",
"All": "الكل",
"All categories already added": "تمت إضافة جميع الفئات بالفعل",
"All tags already added": "تمت إضافة جميع العلامات بالفعل",
"Alphabetically - A-Z": "أبجدياً - أ-ي",
"Alphabetically - Z-A": "أبجدياً - ي-أ",
"Audio": "صوت",
"Browse your files": "تصفح ملفاتك",
"Bulk Actions": "إجراءات جماعية",
"COMMENT": "تعليق",
"Cancel": "إلغاء",
"Categories": "الفئات",
"Category": "الفئة",
"Change Language": "تغيير اللغة",
"Change Owner": "تغيير المالك",
"Change password": "تغيير كلمة المرور",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "انقر على 'بدء التسجيل' واختر الشاشة أو علامة التبويب المراد تسجيلها. بمجرد الانتهاء من التسجيل، انقر على 'إيقاف التسجيل'، وسيتم تحميل التسجيل.",
"Co-Editors": "المحررون المشاركون",
"Co-Owners": "المالكون المشاركون",
"Co-Viewers": "المشاهدون المشاركون",
"Comment": "تعليق",
"Comments": "تعليقات",
"Comments are disabled": "التعليقات معطلة",
"Confirm": "تأكيد",
"Confirm Action": "تأكيد الإجراء",
"Contact": "اتصل",
"Copy Media": "نسخ الوسائط",
"Create": "إنشاء",
"DELETE": "حذف",
"DELETE MEDIA": "حذف الوسائط",
"DOWNLOAD": "تحميل",
"DURATION": "المدة",
"Delete Media": "حذف الوسائط",
"Delete media": "حذف الوسائط",
"Disable Comments": "تعطيل التعليقات",
"Disable Download": "تعطيل التنزيل",
"Drag and drop files": "سحب وإفلات الملفات",
"EDIT MEDIA": "تعديل الوسائط",
"EDIT PROFILE": "تعديل الملف الشخصي",
"EDIT SUBTITLE": "تعديل الترجمة",
"Edit media": "تعديل الوسائط",
"Edit profile": "تعديل الملف الشخصي",
"Edit subtitle": "تعديل الترجمة",
"Enable Comments": "تفعيل التعليقات",
"Enable Download": "تفعيل التنزيل",
"Enter playlist name...": "أدخل اسم قائمة التشغيل...",
"Failed to add categories": "فشل إضافة الفئات",
"Failed to add media to playlists": "فشل إضافة الوسائط إلى قوائم التشغيل",
"Failed to add tags": "فشل إضافة العلامات",
"Failed to add users": "فشل إضافة المستخدمين",
"Failed to change owner": "فشل تغيير المالك",
"Failed to change owner. Please try again.": "فشل تغيير المالك. يرجى المحاولة مرة أخرى.",
"Failed to copy media.": "فشل نسخ الوسائط.",
"Failed to create playlist": "فشل إنشاء قائمة التشغيل",
"Failed to delete media. Please try again.": "فشل حذف الوسائط. يرجى المحاولة مرة أخرى.",
"Failed to disable comments.": "فشل تعطيل التعليقات.",
"Failed to disable download.": "فشل تعطيل التنزيل.",
"Failed to enable comments.": "فشل تفعيل التعليقات.",
"Failed to enable download.": "فشل تفعيل التنزيل.",
"Failed to fetch all categories": "فشل جلب جميع الفئات",
"Failed to fetch all tags": "فشل جلب جميع العلامات",
"Failed to fetch existing categories": "فشل جلب الفئات الموجودة",
"Failed to fetch existing tags": "فشل جلب العلامات الموجودة",
"Failed to fetch existing users": "فشل جلب المستخدمين الموجودين",
"Failed to fetch playlist membership": "فشل جلب عضوية قائمة التشغيل",
"Failed to fetch playlists": "فشل جلب قوائم التشغيل",
"Failed to load categories": "فشل تحميل الفئات",
"Failed to load existing permissions": "فشل تحميل الأذونات الموجودة",
"Failed to load playlists": "فشل تحميل قوائم التشغيل",
"Failed to load tags": "فشل تحميل العلامات",
"Failed to remove categories": "فشل إزالة الفئات",
"Failed to remove media from playlists": "فشل إزالة الوسائط من قوائم التشغيل",
"Failed to remove tags": "فشل إزالة العلامات",
"Failed to remove users": "فشل إزالة المستخدمين",
"Failed to search users": "فشل البحث عن المستخدمين",
"Failed to set publish state": "فشل تعيين حالة النشر",
"Failed to set publish state. Please try again.": "فشل تعيين حالة النشر. يرجى المحاولة مرة أخرى.",
"Failed to update categories. Please try again.": "فشل تحديث الفئات. يرجى المحاولة مرة أخرى.",
"Failed to update permissions. Please try again.": "فشل تحديث الأذونات. يرجى المحاولة مرة أخرى.",
"Failed to update playlists. Please try again.": "فشل تحديث قوائم التشغيل. يرجى المحاولة مرة أخرى.",
"Failed to update tags. Please try again.": "فشل تحديث العلامات. يرجى المحاولة مرة أخرى.",
"Featured": "مميز",
"Filter existing users...": "تصفية المستخدمين الموجودين...",
"Filter playlists...": "تصفية قوائم التشغيل...",
"Filters": "الفلاتر",
"Go": "اذهب",
"History": "التاريخ",
"Home": "الرئيسية",
"Image": "صورة",
"Language": "اللغة",
"Latest": "الأحدث",
"Like count": "عدد الإعجابات",
"Liked media": "الوسائط المفضلة",
"Likes - Least": "الإعجابات - الأقل",
"Likes - Most": "الإعجابات - الأكثر",
"Loading categories...": "جارٍ تحميل الفئات...",
"Loading existing users...": "جارٍ تحميل المستخدمين الموجودين...",
"Loading playlists...": "جارٍ تحميل قوائم التشغيل...",
"Loading tags...": "جارٍ تحميل العلامات...",
"MEDIA TYPE": "نوع الوسائط",
"Manage": "إدارة",
"Manage Playlists": "إدارة قوائم التشغيل",
"Manage comments": "إدارة التعليقات",
"Manage media": "إدارة الوسائط",
"Manage users": "إدارة المستخدمين",
"Media": "وسائط",
"Media I own": "الوسائط التي أمتلكها",
"Media was edited": "تم تعديل الوسائط",
"Members": "الأعضاء",
"My media": "وسائطي",
"My playlists": "قوائم التشغيل الخاصة بي",
"No": "لا",
"No categories": "لا توجد فئات",
"No comment yet": "لا يوجد تعليق بعد",
"No comments yet": "لا توجد تعليقات بعد",
"No existing": "لا يوجد موجود",
"No playlists available": "لا توجد قوائم تشغيل متاحة",
"No playlists selected": "لم يتم تحديد قوائم تشغيل",
"No results for": "لا توجد نتائج لـ",
"No tags": "لا توجد علامات",
"No users to add": "لا يوجد مستخدمون لإضافتهم",
"PLAYLISTS": "قوائم التشغيل",
"PUBLISH STATE": "حالة النشر",
"Pdf": "PDF",
"Playlists": "قوائم التشغيل",
"Plays - Least": "المشاهدات - الأقل",
"Plays - Most": "المشاهدات - الأكثر",
"Please select a publish state": "يرجى تحديد حالة النشر",
"Please select a user": "يرجى تحديد مستخدم",
"Powered by": "مدعوم من",
"Private": "خاص",
"Proceed": "متابعة",
"Processing...": "جارٍ المعالجة...",
"Public": "عام",
"Publish": "نشر",
"Publish State": "حالة النشر",
"Published": "منشور",
"Published on": "نشر في",
"Recent uploads": "التحميلات الأخيرة",
"Recommended": "موصى به",
"Record Screen": "تسجيل الشاشة",
"Register": "تسجيل",
"Remove category": "إزالة الفئة",
"Remove from list": "إزالة من القائمة",
"Remove tag": "إزالة العلامة",
"Remove user": "إزالة المستخدم",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "",
"00 - 20 min": "00 - 20 মিনিট",
"1 result for": "1টি ফলাফল",
"20 - 40 min": "20 - 40 মিনিট",
"40 - 60 min": "40 - 60 মিনিট",
"60 - 120 min+": "60 - 120 মিনিট+",
"ABOUT": "সম্পর্কে",
"AUTOPLAY": "স্বয়ংক্রিয় প্লে",
"About": "সম্পর্কে",
"Add / Remove Co-Editors": "",
"Add / Remove Co-Owners": "",
"Add / Remove Co-Viewers": "",
"Add / Remove Tags": "",
"Add / Remove from Categories": "",
"Add a ": "যোগ করুন",
"Add to": "",
"Add to / Remove from Category": "",
"Add to / Remove from Playlist": "",
"All": "সব",
"All categories already added": "",
"All tags already added": "",
"Alphabetically - A-Z": "বর্ণানুক্রমিক - A-Z",
"Alphabetically - Z-A": "বর্ণানুক্রমিক - Z-A",
"Audio": "অডিও",
"Browse your files": "আপনার ফাইল ব্রাউজ করুন",
"Bulk Actions": "",
"COMMENT": "মন্তব্য",
"Cancel": "",
"Categories": "বিভাগসমূহ",
"Category": "বিভাগ",
"Change Language": "ভাষা পরিবর্তন করুন",
"Change Owner": "",
"Change password": "পাসওয়ার্ড পরিবর্তন করুন",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'রেকর্ডিং শুরু করুন'-এ ক্লিক করুন এবং রেকর্ড করার জন্য স্ক্রিন বা ট্যাব নির্বাচন করুন। রেকর্ডিং শেষ হলে, 'রেকর্ডিং বন্ধ করুন'-এ ক্লিক করুন এবং রেকর্ডিং আপলোড হয়ে যাবে।",
"Co-Editors": "",
"Co-Owners": "",
"Co-Viewers": "",
"Comment": "মন্তব্য",
"Comments": "মন্তব্যসমূহ",
"Comments are disabled": "মন্তব্য নিষ্ক্রিয় করা হয়েছে",
"Confirm": "",
"Confirm Action": "",
"Contact": "যোগাযোগ",
"Copy Media": "",
"Create": "",
"DELETE": "মুছে ফেলুন",
"DELETE MEDIA": "মিডিয়া মুছুন",
"DOWNLOAD": "ডাউনলোড",
"DURATION": "সময়কাল",
"Delete Media": "",
"Delete media": "মিডিয়া মুছুন",
"Disable Comments": "",
"Disable Download": "",
"Drag and drop files": "ফাইল টেনে আনুন",
"EDIT MEDIA": "মিডিয়া সম্পাদনা করুন",
"EDIT PROFILE": "প্রোফাইল সম্পাদনা করুন",
"EDIT SUBTITLE": "সাবটাইটেল সম্পাদনা করুন",
"Edit media": "মিডিয়া সম্পাদনা করুন",
"Edit profile": "প্রোফাইল সম্পাদনা করুন",
"Edit subtitle": "সাবটাইটেল সম্পাদনা করুন",
"Enable Comments": "",
"Enable Download": "",
"Enter playlist name...": "",
"Failed to add categories": "",
"Failed to add media to playlists": "",
"Failed to add tags": "",
"Failed to add users": "",
"Failed to change owner": "",
"Failed to change owner. Please try again.": "",
"Failed to copy media.": "মিডিয়া কপি করতে ব্যর্থ হয়েছে।",
"Failed to create playlist": "",
"Failed to delete media. Please try again.": "মিডিয়া মুছতে ব্যর্থ হয়েছে। দয়া করে আবার চেষ্টা করুন।",
"Failed to disable comments.": "মন্তব্য নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
"Failed to disable download.": "ডাউনলোড নিষ্ক্রিয় করতে ব্যর্থ হয়েছে।",
"Failed to enable comments.": "মন্তব্য সক্রিয় করতে ব্যর্থ হয়েছে।",
"Failed to enable download.": "ডাউনলোড সক্রিয় করতে ব্যর্থ হয়েছে।",
"Failed to fetch all categories": "",
"Failed to fetch all tags": "",
"Failed to fetch existing categories": "",
"Failed to fetch existing tags": "",
"Failed to fetch existing users": "",
"Failed to fetch playlist membership": "",
"Failed to fetch playlists": "",
"Failed to load categories": "",
"Failed to load existing permissions": "",
"Failed to load playlists": "",
"Failed to load tags": "",
"Failed to remove categories": "",
"Failed to remove media from playlists": "",
"Failed to remove tags": "",
"Failed to remove users": "",
"Failed to search users": "",
"Failed to set publish state": "",
"Failed to set publish state. Please try again.": "",
"Failed to update categories. Please try again.": "",
"Failed to update permissions. Please try again.": "",
"Failed to update playlists. Please try again.": "",
"Failed to update tags. Please try again.": "",
"Featured": "বৈশিষ্ট্যযুক্ত",
"Filter existing users...": "",
"Filter playlists...": "",
"Filters": "ফিল্টার",
"Go": "যাও",
"History": "ইতিহাস",
"Home": "বাড়ি",
"Image": "ছবি",
"Language": "ভাষা",
"Latest": "সর্বশেষ",
"Like count": "পছন্দের সংখ্যা",
"Liked media": "পছন্দের মিডিয়া",
"Likes - Least": "পছন্দ - সবচেয়ে কম",
"Likes - Most": "পছন্দ - সবচেয়ে বেশি",
"Loading categories...": "",
"Loading existing users...": "",
"Loading playlists...": "",
"Loading tags...": "",
"MEDIA TYPE": "মিডিয়ার ধরন",
"Manage": "",
"Manage Playlists": "",
"Manage comments": "মন্তব্য পরিচালনা করুন",
"Manage media": "মিডিয়া পরিচালনা করুন",
"Manage users": "ব্যবহারকারীদের পরিচালনা করুন",
"Media": "মিডিয়া",
"Media I own": "",
"Media was edited": "মিডিয়া সম্পাদিত হয়েছে",
"Members": "সদস্যরা",
"My media": "আমার মিডিয়া",
"My playlists": "আমার প্লেলিস্ট",
"No": "না",
"No categories": "",
"No comment yet": "এখনও কোন মন্তব্য নেই",
"No comments yet": "এখনও কোন মন্তব্য নেই",
"No existing": "",
"No playlists available": "",
"No playlists selected": "",
"No results for": "এর জন্য কোন ফলাফল নেই",
"No tags": "",
"No users to add": "",
"PLAYLISTS": "প্লেলিস্ট",
"PUBLISH STATE": "প্রকাশের অবস্থা",
"Pdf": "PDF",
"Playlists": "প্লেলিস্ট",
"Plays - Least": "প্লে - সবচেয়ে কম",
"Plays - Most": "প্লে - সবচেয়ে বেশি",
"Please select a publish state": "",
"Please select a user": "",
"Powered by": "দ্বারা চালিত",
"Private": "ব্যক্তিগত",
"Proceed": "",
"Processing...": "",
"Public": "",
"Publish": "প্রকাশ করুন",
"Publish State": "",
"Published": "প্রকাশিত",
"Published on": "প্রকাশিত",
"Recent uploads": "সাম্প্রতিক আপলোড",
"Recommended": "প্রস্তাবিত",
"Record Screen": "স্ক্রিন রেকর্ড করুন",
"Register": "নিবন্ধন করুন",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"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

@ -0,0 +1,288 @@
translation_strings = {
"+ Create Playlist": "+ Opret Playliste",
"00 - 20 min": "00 - 20 min",
"1 result for": "1 resultat for",
"20 - 40 min": "20 - 40 min",
"40 - 60 min": "40 - 60 min",
"60 - 120 min+": "60 - 120 min+",
"ABOUT": "OM",
"AUTOPLAY": "Automatisk afspilning",
"About": "Om",
"Add / Remove Co-Editors": "Tilføj / Fjern Medredaktører",
"Add / Remove Co-Owners": "Tilføj / Fjern Medejere",
"Add / Remove Co-Viewers": "Tilføj / Fjern Medseere",
"Add / Remove Tags": "Tilføj / Fjern Tags",
"Add / Remove from Categories": "Tilføj / Fjern fra Kategorier",
"Add a ": "Tilføj en ",
"Add to": "Tilføj til",
"Add to / Remove from Category": "Tilføj til / Fjern fra Kategori",
"Add to / Remove from Playlist": "Tilføj til / Fjern fra Playliste",
"All": "Alle",
"All categories already added": "Alle kategorier allerede tilføjet",
"All tags already added": "Alle tags allerede tilføjet",
"Alphabetically - A-Z": "Alfabetisk - A-Å",
"Alphabetically - Z-A": "Alfabetisk - Å-A",
"Audio": "Lyd",
"Browse your files": "Gennemse dine filer",
"Bulk Actions": "Massehandlinger",
"COMMENT": "KOMMENTAR",
"Cancel": "Annuller",
"Categories": "Kategorier",
"Category": "Kategori",
"Change Language": "Skift sprog",
"Change Owner": "Skift Ejer",
"Change password": "Skift adgangskode",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Klik på 'Start optagelse' og vælg den skærm eller fane, du vil optage. Når optagelsen er færdig, skal du klikke på 'Stop optagelse', og optagelsen vil blive uploadet.",
"Co-Editors": "Medredaktører",
"Co-Owners": "Medejere",
"Co-Viewers": "Medseere",
"Comment": "Kommentar",
"Comments": "Kommentarer",
"Comments are disabled": "Kommentarer er slået fra",
"Confirm": "Bekræft",
"Confirm Action": "Bekræft Handling",
"Contact": "Kontakt",
"Copy Media": "Kopier Medie",
"Create": "Opret",
"DELETE": "SLET",
"DELETE MEDIA": "SLET MEDIE",
"DOWNLOAD": "HENT",
"DURATION": "VARIGHED",
"Delete Media": "Slet Medie",
"Delete media": "Slet medie",
"Disable Comments": "Deaktiver Kommentarer",
"Disable Download": "Deaktiver Download",
"Drag and drop files": "Træk og slip filer",
"EDIT MEDIA": "REDIGER MEDIE",
"EDIT PROFILE": "REDIGER PROFIL",
"EDIT SUBTITLE": "REDIGER UNDERTEKSTER",
"Edit media": "Rediger medie",
"Edit profile": "Rediger profil",
"Edit subtitle": "Rediger undertekster",
"Enable Comments": "Aktiver Kommentarer",
"Enable Download": "Aktiver Download",
"Enter playlist name...": "Indtast playlistenavn...",
"Failed to add categories": "Tilføjelse af kategorier mislykkedes",
"Failed to add media to playlists": "Tilføjelse af medie til playlister mislykkedes",
"Failed to add tags": "Tilføjelse af tags mislykkedes",
"Failed to add users": "Tilføjelse af brugere mislykkedes",
"Failed to change owner": "Ændring af ejer mislykkedes",
"Failed to change owner. Please try again.": "Ændring af ejer mislykkedes. Prøv venligst igen.",
"Failed to copy media.": "Kopiering af medie mislykkedes.",
"Failed to create playlist": "Oprettelse af playliste mislykkedes",
"Failed to delete media. Please try again.": "Sletning af medie mislykkedes. Prøv venligst igen.",
"Failed to disable comments.": "Deaktivering af kommentarer mislykkedes.",
"Failed to disable download.": "Deaktivering af download mislykkedes.",
"Failed to enable comments.": "Aktivering af kommentarer mislykkedes.",
"Failed to enable download.": "Aktivering af download mislykkedes.",
"Failed to fetch all categories": "Hentning af alle kategorier mislykkedes",
"Failed to fetch all tags": "Hentning af alle tags mislykkedes",
"Failed to fetch existing categories": "Hentning af eksisterende kategorier mislykkedes",
"Failed to fetch existing tags": "Hentning af eksisterende tags mislykkedes",
"Failed to fetch existing users": "Hentning af eksisterende brugere mislykkedes",
"Failed to fetch playlist membership": "Hentning af playlistemedlemskab mislykkedes",
"Failed to fetch playlists": "Hentning af playlister mislykkedes",
"Failed to load categories": "Indlæsning af kategorier mislykkedes",
"Failed to load existing permissions": "Indlæsning af eksisterende tilladelser mislykkedes",
"Failed to load playlists": "Indlæsning af playlister mislykkedes",
"Failed to load tags": "Indlæsning af tags mislykkedes",
"Failed to remove categories": "Fjernelse af kategorier mislykkedes",
"Failed to remove media from playlists": "Fjernelse af medie fra playlister mislykkedes",
"Failed to remove tags": "Fjernelse af tags mislykkedes",
"Failed to remove users": "Fjernelse af brugere mislykkedes",
"Failed to search users": "Søgning af brugere mislykkedes",
"Failed to set publish state": "Indstilling af publiceringsstatus mislykkedes",
"Failed to set publish state. Please try again.": "Indstilling af publiceringsstatus mislykkedes. Prøv venligst igen.",
"Failed to update categories. Please try again.": "Opdatering af kategorier mislykkedes. Prøv venligst igen.",
"Failed to update permissions. Please try again.": "Opdatering af tilladelser mislykkedes. Prøv venligst igen.",
"Failed to update playlists. Please try again.": "Opdatering af playlister mislykkedes. Prøv venligst igen.",
"Failed to update tags. Please try again.": "Opdatering af tags mislykkedes. Prøv venligst igen.",
"Featured": "Fremhævede",
"Filter existing users...": "Filtrer eksisterende brugere...",
"Filter playlists...": "Filtrer playlister...",
"Filters": "Filtre",
"Go": "Vælg",
"History": "Historik",
"Home": "Hjem",
"Image": "Billede",
"Language": "Sprog",
"Latest": "Nyeste",
"Like count": "Antal likes",
"Liked media": "Medier du har liket",
"Likes - Least": "Likes - Færrest",
"Likes - Most": "Likes - Flest",
"Loading categories...": "Indlæser kategorier...",
"Loading existing users...": "Indlæser eksisterende brugere...",
"Loading playlists...": "Indlæser playlister...",
"Loading tags...": "Indlæser tags...",
"MEDIA TYPE": "MEDIETYPE",
"Manage": "Administrer",
"Manage Playlists": "Administrer Playlister",
"Manage comments": "Administrer kommentarer",
"Manage media": "Administrer medier",
"Manage users": "Administrer brugere",
"Media": "Medier",
"Media I own": "Medier jeg ejer",
"Media was edited": "Mediet er blevet redigeret",
"Members": "Medlemmer",
"My media": "Mine medier",
"My playlists": "Mine playlister",
"No": "Nej",
"No categories": "Ingen kategorier",
"No comment yet": "Ingen kommentar endnu",
"No comments yet": "Ingen komentarer endnu",
"No existing": "Ingen eksisterende",
"No playlists available": "Ingen playlister tilgængelige",
"No playlists selected": "Ingen playlister valgt",
"No results for": "Ingen resultater for",
"No tags": "Ingen tags",
"No users to add": "Ingen brugere at tilføje",
"PLAYLISTS": "PLAYLISTER",
"PUBLISH STATE": "PUBLICERINGSSTATUS",
"Pdf": "PDF",
"Playlists": "Playlister",
"Plays - Least": "Afspilninger - Færrest",
"Plays - Most": "Afspilninger - Flest",
"Please select a publish state": "Vælg venligst en publiceringsstatus",
"Please select a user": "Vælg venligst en bruger",
"Powered by": "Drevet af",
"Private": "Privat",
"Proceed": "Fortsæt",
"Processing...": "Behandler...",
"Public": "Offentlig",
"Publish": "Udgiv",
"Publish State": "Publiceringsstatus",
"Published": "Publiceret",
"Published on": "Udgivet på",
"Recent uploads": "Nylige uploads",
"Recommended": "Anbefalet",
"Record Screen": "Optag skærm",
"Register": "Registrer",
"Remove category": "Fjern kategori",
"Remove from list": "Fjern fra liste",
"Remove tag": "Fjern tag",
"Remove user": "Fjern bruger",
"SAVE": "GEM",
"SEARCH": "SØG",
"SHARE": "DEL",
"SHOW MORE": "VIS MERE",
"SORT BY": "SORTER EFTER",
"SUBMIT": "INDSEND",
"Search": "Søg",
"Search for user...": "Søg efter bruger...",
"Search users to add...": "Søg efter brugere at tilføje...",
"Select": "Vælg",
"Select Owner": "Vælg Ejer",
"Select all": "Vælg alle",
"Select all media": "Vælg alle medier",
"Select publish state:": "Vælg publiceringsstatus:",
"Selected": "Valgt",
"Shared by me": "Delt af mig",
"Shared with me": "Delt med mig",
"Sign in": "Log ind",
"Sign out": "Log ud",
"Sort By": "Sorter efter",
"Start Recording": "Start optagelse",
"Start uploading media and sharing your work. Media that you upload will show up here.": "Begynd at uploade medier og dele dit arbejde. Medier, du uploader, vil blive vist her.",
"Stop Recording": "Stop optagelse",
"Submit": "Indsend",
"Subtitle was added": "Undertekster tilføjet",
"Subtitles": "Undertekster",
"Successfully Copied": "Kopieret med succes",
"Successfully Disabled Download": "Download deaktiveret med succes",
"Successfully Disabled comments": "Kommentarer deaktiveret med succes",
"Successfully Enabled Download": "Download aktiveret med succes",
"Successfully Enabled comments": "Kommentarer aktiveret med succes",
"Successfully changed owner": "Ejer ændret med succes",
"Successfully deleted": "Slettet med succes",
"Successfully updated": "Opdateret med succes",
"Successfully updated categories": "Kategorier opdateret med succes",
"Successfully updated playlist membership": "Playlistemedlemskab opdateret med succes",
"Successfully updated publish state": "Publiceringsstatus opdateret med succes",
"Successfully updated tags": "Tags opdateret med succes",
"TAGS": "TAGS",
"Tag": "Tag",
"Tags": "Tags",
"Terms": "Vilkår",
"The intersection of categories in the selected media is shown": "Fælles kategorier i de valgte medier vises",
"The intersection of playlists in the selected media is shown": "Fælles playlister i de valgte medier vises",
"The intersection of tags in the selected media is shown": "Fælles tags i de valgte medier vises",
"The intersection of users in the selected media is shown": "Fælles brugere i de valgte medier vises",
"The media was deleted successfully.": "Mediet blev slettet med succes.",
"This month": "Denne måned",
"This week": "Denne uge",
"This works in Chrome, Safari and Edge browsers.": "Dette virker i Chrome, Safari og Edge browsere.",
"This year": "Dette år",
"To add": "At tilføje",
"Today": "I dag",
"Trim": "Beskær",
"UPLOAD": "UPLOAD",
"UPLOAD DATE": "UPLOADDATO",
"UPLOAD MEDIA": "UPLOAD MEDIE",
"Undo removal": "Fortryd fjernelse",
"Unlisted": "Ikke listet",
"Up Next": "Næste",
"Up next": "Næste",
"Upload": "Upload",
"Upload date (newest)": "Uploaddato (nyeste)",
"Upload date (oldest)": "Uploaddato (ældste)",
"Upload date - Newest": "Uploaddato - Nyeste",
"Upload date - Oldest": "Uploaddato - Ældste",
"Upload media": "Upload medie",
"Uploads": "Uploads",
"Users": "Brugere",
"VIEW ALL": "SE ALLE",
"Video": "Video",
"View all": "Se alle",
"View count": "Antal visninger",
"View media": "Se medie",
"Welcome": "Velkommen",
"You are going to copy": "Du er ved at kopiere",
"You are going to delete": "Du er ved at slette",
"You are going to disable comments to": "Du er ved at deaktivere kommentarer til",
"You are going to disable download for": "Du er ved at deaktivere download for",
"You are going to enable comments to": "Du er ved at aktivere kommentarer til",
"You are going to enable download for": "Du er ved at aktivere download for",
"comment": "kommentar",
"is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media": "er et moderne, fuldt udstyret open source video og medie CMS. Det er udviklet til at imødekomme behovene for moderne webplatforme til visning og deling af medier.",
"media in category": "medier i kategori",
"media in tag": "medier i tag",
"media, are you sure?": "medie, er du sikker?",
"media.": "medie.",
"or": "eller",
"results for": "resultater for",
"selected": "valgt",
"view": "visning",
"views": "visninger",
"yet": "endnu",
}
replacement_strings = {
"Apr": "Apr",
"Aug": "Aug",
"Dec": "Dec",
"Feb": "Feb",
"Jan": "Jan",
"Jul": "Jul",
"Jun": "Jun",
"Mar": "Mar",
"May": "Maj",
"Nov": "Nov",
"Oct": "Okt",
"Sep": "Sep",
"day ago": "dag siden",
"days ago": "dage siden",
"hour ago": "time siden",
"hours ago": "timer siden",
"just now": "lige nu",
"minute ago": "minut siden",
"minutes ago": "minutter siden",
"month ago": "måned siden",
"months ago": "måneder siden",
"second ago": "sekund siden",
"seconds ago": "sekunder siden",
"week ago": "uge siden",
"weeks ago": "uger siden",
"year ago": "år siden",
"years ago": "år siden",
}

View File

@ -1,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ Playlist erstellen",
"00 - 20 min": "00 - 20 Min",
"1 result for": "1 Ergebnis für",
"20 - 40 min": "20 - 40 Min",
"40 - 60 min": "40 - 60 Min",
"60 - 120 min+": "60 - 120 Min+",
"ABOUT": "Über",
"AUTOPLAY": "Automatische Wiedergabe",
"About": "Über",
"Add / Remove Co-Editors": "Co-Editoren hinzufügen/entfernen",
"Add / Remove Co-Owners": "Co-Eigentümer hinzufügen/entfernen",
"Add / Remove Co-Viewers": "Co-Zuschauer hinzufügen/entfernen",
"Add / Remove Tags": "Tags hinzufügen/entfernen",
"Add / Remove from Categories": "Zu/aus Kategorien hinzufügen/entfernen",
"Add a ": "Hinzufügen eines ",
"Add to": "Hinzufügen zu",
"Add to / Remove from Category": "Zu/aus Kategorie hinzufügen/entfernen",
"Add to / Remove from Playlist": "Zu/aus Playlist hinzufügen/entfernen",
"All": "Alle",
"All categories already added": "Alle Kategorien bereits hinzugefügt",
"All tags already added": "Alle Tags bereits hinzugefügt",
"Alphabetically - A-Z": "Alphabetisch - A-Z",
"Alphabetically - Z-A": "Alphabetisch - Z-A",
"Audio": "Audio",
"Browse your files": "Durchsuchen Sie Ihre Dateien",
"Bulk Actions": "Massenaktionen",
"COMMENT": "KOMMENTAR",
"Cancel": "Abbrechen",
"Categories": "Kategorien",
"Category": "Kategorie",
"Change Language": "Sprache ändern",
"Change Owner": "Eigentümer ändern",
"Change password": "Passwort ändern",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Klicken Sie auf 'Aufnahme starten' und wählen Sie den Bildschirm oder Tab aus, den Sie aufnehmen möchten. Sobald die Aufnahme beendet ist, klicken Sie auf 'Aufnahme beenden', und die Aufnahme wird hochgeladen.",
"Co-Editors": "Co-Editoren",
"Co-Owners": "Co-Eigentümer",
"Co-Viewers": "Co-Zuschauer",
"Comment": "Kommentar",
"Comments": "Kommentare",
"Comments are disabled": "Kommentare sind deaktiviert",
"Confirm": "Bestätigen",
"Confirm Action": "Aktion bestätigen",
"Contact": "Kontakt",
"Copy Media": "Medien kopieren",
"Create": "Erstellen",
"DELETE": "LÖSCHEN",
"DELETE MEDIA": "MEDIEN LÖSCHEN",
"DOWNLOAD": "HERUNTERLADEN",
"DURATION": "DAUER",
"Delete Media": "Medien löschen",
"Delete media": "Medien löschen",
"Disable Comments": "Kommentare deaktivieren",
"Disable Download": "Download deaktivieren",
"Drag and drop files": "Dateien per Drag & Drop verschieben",
"EDIT MEDIA": "MEDIEN BEARBEITEN",
"EDIT PROFILE": "PROFIL BEARBEITEN",
"EDIT SUBTITLE": "UNTERTITEL BEARBEITEN",
"Edit media": "Medien bearbeiten",
"Edit profile": "Profil bearbeiten",
"Edit subtitle": "Untertitel bearbeiten",
"Enable Comments": "Kommentare aktivieren",
"Enable Download": "Download aktivieren",
"Enter playlist name...": "Playlist-Namen eingeben...",
"Failed to add categories": "Fehler beim Hinzufügen der Kategorien",
"Failed to add media to playlists": "Fehler beim Hinzufügen der Medien zu Playlists",
"Failed to add tags": "Fehler beim Hinzufügen der Tags",
"Failed to add users": "Fehler beim Hinzufügen der Benutzer",
"Failed to change owner": "Fehler beim Ändern des Eigentümers",
"Failed to change owner. Please try again.": "Fehler beim Ändern des Eigentümers. Bitte versuchen Sie es erneut.",
"Failed to copy media.": "Fehler beim Kopieren der Medien.",
"Failed to create playlist": "Fehler beim Erstellen der Playlist",
"Failed to delete media. Please try again.": "Fehler beim Löschen der Medien. Bitte versuchen Sie es erneut.",
"Failed to disable comments.": "Fehler beim Deaktivieren der Kommentare.",
"Failed to disable download.": "Fehler beim Deaktivieren des Downloads.",
"Failed to enable comments.": "Fehler beim Aktivieren der Kommentare.",
"Failed to enable download.": "Fehler beim Aktivieren des Downloads.",
"Failed to fetch all categories": "Fehler beim Laden aller Kategorien",
"Failed to fetch all tags": "Fehler beim Laden aller Tags",
"Failed to fetch existing categories": "Fehler beim Laden vorhandener Kategorien",
"Failed to fetch existing tags": "Fehler beim Laden vorhandener Tags",
"Failed to fetch existing users": "Fehler beim Laden vorhandener Benutzer",
"Failed to fetch playlist membership": "Fehler beim Laden der Playlist-Mitgliedschaft",
"Failed to fetch playlists": "Fehler beim Laden der Playlists",
"Failed to load categories": "Fehler beim Laden der Kategorien",
"Failed to load existing permissions": "Fehler beim Laden vorhandener Berechtigungen",
"Failed to load playlists": "Fehler beim Laden der Playlists",
"Failed to load tags": "Fehler beim Laden der Tags",
"Failed to remove categories": "Fehler beim Entfernen der Kategorien",
"Failed to remove media from playlists": "Fehler beim Entfernen der Medien aus Playlists",
"Failed to remove tags": "Fehler beim Entfernen der Tags",
"Failed to remove users": "Fehler beim Entfernen der Benutzer",
"Failed to search users": "Fehler bei der Benutzersuche",
"Failed to set publish state": "Fehler beim Festlegen des Veröffentlichungsstatus",
"Failed to set publish state. Please try again.": "Fehler beim Festlegen des Veröffentlichungsstatus. Bitte versuchen Sie es erneut.",
"Failed to update categories. Please try again.": "Fehler beim Aktualisieren der Kategorien. Bitte versuchen Sie es erneut.",
"Failed to update permissions. Please try again.": "Fehler beim Aktualisieren der Berechtigungen. Bitte versuchen Sie es erneut.",
"Failed to update playlists. Please try again.": "Fehler beim Aktualisieren der Playlists. Bitte versuchen Sie es erneut.",
"Failed to update tags. Please try again.": "Fehler beim Aktualisieren der Tags. Bitte versuchen Sie es erneut.",
"Featured": "Empfohlen",
"Filter existing users...": "Vorhandene Benutzer filtern...",
"Filter playlists...": "Playlists filtern...",
"Filters": "Filter",
"Go": "Los",
"History": "Verlauf",
"Home": "Startseite",
"Image": "Bild",
"Language": "Sprache",
"Latest": "Neueste",
"Like count": "Anzahl der Likes",
"Liked media": "Beliebte Medien",
"Likes - Least": "Likes - Wenigste",
"Likes - Most": "Likes - Meiste",
"Loading categories...": "Kategorien werden geladen...",
"Loading existing users...": "Vorhandene Benutzer werden geladen...",
"Loading playlists...": "Playlists werden geladen...",
"Loading tags...": "Tags werden geladen...",
"MEDIA TYPE": "MEDIENTYP",
"Manage": "Verwalten",
"Manage Playlists": "Playlists verwalten",
"Manage comments": "Kommentare verwalten",
"Manage media": "Medien verwalten",
"Manage users": "Benutzer verwalten",
"Media": "Medien",
"Media I own": "Medien, die mir gehören",
"Media was edited": "Medien wurden bearbeitet",
"Members": "Mitglieder",
"My media": "Meine Medien",
"My playlists": "Meine Playlists",
"No": "Nein",
"No categories": "Keine Kategorien",
"No comment yet": "Noch kein Kommentar",
"No comments yet": "Noch keine Kommentare",
"No existing": "Keine vorhanden",
"No playlists available": "Keine Playlists verfügbar",
"No playlists selected": "Keine Playlists ausgewählt",
"No results for": "Keine Ergebnisse für",
"No tags": "Keine Tags",
"No users to add": "Keine Benutzer hinzuzufügen",
"PLAYLISTS": "PLAYLISTS",
"PUBLISH STATE": "VERÖFFENTLICHUNGSSTATUS",
"Pdf": "PDF",
"Playlists": "Playlists",
"Plays - Least": "Wiedergaben - Wenigste",
"Plays - Most": "Wiedergaben - Meiste",
"Please select a publish state": "Bitte wählen Sie einen Veröffentlichungsstatus aus",
"Please select a user": "Bitte wählen Sie einen Benutzer aus",
"Powered by": "Bereitgestellt von",
"Private": "Privat",
"Proceed": "Fortfahren",
"Processing...": "Wird verarbeitet...",
"Public": "Öffentlich",
"Publish": "Veröffentlichen",
"Publish State": "Veröffentlichungsstatus",
"Published": "Veröffentlicht",
"Published on": "Veröffentlicht am",
"Recent uploads": "Neue Uploads",
"Recommended": "Empfohlen",
"Record Screen": "Bildschirm aufnehmen",
"Register": "Registrieren",
"Remove category": "Kategorie entfernen",
"Remove from list": "Aus Liste entfernen",
"Remove tag": "Tag entfernen",
"Remove user": "Benutzer entfernen",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ Δημιουργία Λίστας",
"00 - 20 min": "00 - 20 λεπτά",
"1 result for": "1 αποτέλεσμα για",
"20 - 40 min": "20 - 40 λεπτά",
"40 - 60 min": "40 - 60 λεπτά",
"60 - 120 min+": "60 - 120 λεπτά+",
"ABOUT": "ΣΧΕΤΙΚΑ",
"AUTOPLAY": "Αυτόματη αναπαραγωγή",
"About": "Σχετικά",
"Add / Remove Co-Editors": "Προσθήκη / Αφαίρεση Συν-Συντακτών",
"Add / Remove Co-Owners": "Προσθήκη / Αφαίρεση Συν-Ιδιοκτητών",
"Add / Remove Co-Viewers": "Προσθήκη / Αφαίρεση Συν-Θεατών",
"Add / Remove Tags": "Προσθήκη / Αφαίρεση Ετικετών",
"Add / Remove from Categories": "Προσθήκη / Αφαίρεση από Κατηγορίες",
"Add a ": "Προσθέστε ένα ",
"Add to": "Προσθήκη σε",
"Add to / Remove from Category": "Προσθήκη / Αφαίρεση από Κατηγορία",
"Add to / Remove from Playlist": "Προσθήκη / Αφαίρεση από Λίστα",
"All": "Όλα",
"All categories already added": "Όλες οι κατηγορίες έχουν ήδη προστεθεί",
"All tags already added": "Όλες οι ετικέτες έχουν ήδη προστεθεί",
"Alphabetically - A-Z": "Αλφαβητικά - Α",
"Alphabetically - Z-A": "Αλφαβητικά - Ω-Α",
"Audio": "Ήχος",
"Browse your files": "Περιήγηση στα αρχεία σας",
"Bulk Actions": "Μαζικές Ενέργειες",
"COMMENT": "ΣΧΟΛΙΟ",
"Cancel": "Ακύρωση",
"Categories": "Κατηγορίες",
"Category": "Κατηγορία",
"Change Language": "Αλλαγή Γλώσσας",
"Change Owner": "Αλλαγή Ιδιοκτήτη",
"Change password": "Αλλαγή κωδικού",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Κάντε κλικ στο 'Έναρξη εγγραφής' και επιλέξτε την οθόνη ή την καρτέλα για εγγραφή. Μόλις ολοκληρωθεί η εγγραφή, κάντε κλικ στο 'Διακοπή εγγραφής' και η εγγραφή θα μεταφορτωθεί.",
"Co-Editors": "Συν-Συντάκτες",
"Co-Owners": "Συν-Ιδιοκτήτες",
"Co-Viewers": "Συν-Θεατές",
"Comment": "Σχόλιο",
"Comments": "Σχόλια",
"Comments are disabled": "Τα σχόλια είναι απενεργοποιημένα",
"Confirm": "Επιβεβαίωση",
"Confirm Action": "Επιβεβαίωση Ενέργειας",
"Contact": "Επικοινωνία",
"Copy Media": "Αντιγραφή Αρχείου",
"Create": "Δημιουργία",
"DELETE": "ΔΙΑΓΡΑΦΗ",
"DELETE MEDIA": "ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΟΥ",
"DOWNLOAD": "ΚΑΤΕΒΑΣΜΑ",
"DURATION": "ΔΙΑΡΚΕΙΑ",
"Delete Media": "Διαγραφή Αρχείου",
"Delete media": "Διαγραφή αρχείου",
"Disable Comments": "Απενεργοποίηση Σχολίων",
"Disable Download": "Απενεργοποίηση Λήψης",
"Drag and drop files": "Σύρετε και αποθέστε αρχεία",
"EDIT MEDIA": "ΕΠΕΞΕΡΓΑΣΙΑ ΑΡΧΕΙΟΥ",
"EDIT PROFILE": "ΕΠΕΞΕΡΓΑΣΙΑ ΠΡΟΦΙΛ",
"EDIT SUBTITLE": "ΕΠΕΞΕΡΓΑΣΙΑ ΥΠΟΤΙΤΛΩΝ",
"Edit media": "Επεξεργασία αρχείου",
"Edit profile": "Επεξεργασία προφιλ",
"Edit 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": "Επιλεγμένα",
"Go": "Πήγαινε",
"Filter existing users...": "Φιλτράρισμα υπαρχόντων χρηστών...",
"Filter playlists...": "Φιλτράρισμα λιστών...",
"Filters": "Φίλτρα",
"Go": "Μετάβαση",
"History": "Ιστορικό",
"Home": "Αρχική",
"Image": "Εικόνα",
"Language": "Γλώσσα",
"Latest": "Πρόσφατα",
"Liked media": "Αγαπημένα",
"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": "Αφαίρεση χρήστη",
"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": "Ανέβασμα",
"Upload date (newest)": "Ημερομηνία ανεβάσματος (νεότερα)",
"Upload date (oldest)": "Ημερομηνία ανεβάσματος (παλαιότερα)",
"Upload date - Newest": "Ημερομηνία ανεβάσματος - Νεότερα",
"Upload date - Oldest": "Ημερομηνία ανεβάσματος - Παλαιότερα",
"Upload media": "Ανέβασμα αρχείων",
"Uploads": "Ανεβάσματα",
"Users": "Χρήστες",
"VIEW ALL": "ΔΕΣ ΤΑ ΟΛΑ",
"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": "ακόμα",
@ -82,10 +266,10 @@ replacement_strings = {
"Jul": "Ιουλ",
"Jun": "Ιουν",
"Mar": "Μαρ",
"May": "Μαϊ",
"May": "Μάι",
"Nov": "Νοε",
"Oct": "Οκτ",
"Sep": "Σεπτ",
"Sep": "Σεπ",
"day ago": "μέρα πριν",
"days ago": "μέρες πριν",
"hour ago": "ώρα πριν",

View File

@ -1,76 +1,260 @@
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 ": "",
"COMMENT": "",
"Add to": "",
"Add to / Remove from Category": "",
"Add to / Remove from Playlist": "",
"All": "",
"All categories already added": "",
"All tags already added": "",
"Alphabetically - A-Z": "",
"Alphabetically - Z-A": "",
"Audio": "",
"AUTOPLAY": "",
"Browse your files": "",
"Bulk Actions": "",
"Cancel": "",
"Categories": "",
"Category": "",
"Change Language": "",
"Change Owner": "",
"Change password": "",
"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": "",
"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": "",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"results for": "",
"SAVE": "",
"SEARCH": "",
"SHARE": "",
"SHOW MORE": "",
"SUBMIT": "",
"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": "",
"UPLOAD": "",
"The intersection of categories in the selected media is shown": "",
"The intersection of playlists in the selected media is shown": "",
"The intersection of tags in the selected media is shown": "",
"The intersection of users in the selected media is shown": "",
"The media was deleted successfully.": "",
"This month": "",
"This week": "",
"This works in Chrome, Safari and Edge browsers.": "",
"This year": "",
"To add": "",
"Today": "",
"Trim": "",
"Undo removal": "",
"Unlisted": "",
"Up Next": "",
"Up next": "",
"UPLOAD": "",
"Upload": "",
"UPLOAD DATE": "",
"Upload date (newest)": "",
"Upload date (oldest)": "",
"Upload date - Newest": "",
"Upload date - Oldest": "",
"UPLOAD MEDIA": "",
"Upload media": "",
"Uploads": "",
"Users": "",
"Video": "",
"view": "",
"VIEW ALL": "",
"View all": "",
"comment": "",
"is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media": "",
"media in category": "",
"media in tag": "",
"view": "",
"View count": "",
"View media": "",
"views": "",
"Welcome": "",
"yet": "",
"You are going to copy": "",
"You are going to delete": "",
"You are going to disable comments to": "",
"You are going to disable download for": "",
"You are going to enable comments to": "",
"You are going to enable download for": "",
}
replacement_strings = {

View File

@ -1,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ Crear Lista de Reproducción",
"00 - 20 min": "00 - 20 min",
"1 result for": "1 resultado para",
"20 - 40 min": "20 - 40 min",
"40 - 60 min": "40 - 60 min",
"60 - 120 min+": "60 - 120 min+",
"ABOUT": "Acerca de",
"AUTOPLAY": "Reproducción automática",
"About": "Acerca de",
"Add / Remove Co-Editors": "Agregar / Eliminar Coeditores",
"Add / Remove Co-Owners": "Agregar / Eliminar Copropietarios",
"Add / Remove Co-Viewers": "Agregar / Eliminar Covisores",
"Add / Remove Tags": "Agregar / Eliminar Etiquetas",
"Add / Remove from Categories": "Agregar / Eliminar de Categorías",
"Add a ": "Agregar un ",
"Add to": "Agregar a",
"Add to / Remove from Category": "Agregar / Eliminar de Categoría",
"Add to / Remove from Playlist": "Agregar / Eliminar de Lista de Reproducción",
"All": "Todos",
"All categories already added": "Todas las categorías ya agregadas",
"All tags already added": "Todas las etiquetas ya agregadas",
"Alphabetically - A-Z": "Alfabéticamente - A-Z",
"Alphabetically - Z-A": "Alfabéticamente - Z-A",
"Audio": "Audio",
"Browse your files": "Explorar sus archivos",
"Bulk Actions": "Acciones Masivas",
"COMMENT": "COMENTARIO",
"Cancel": "Cancelar",
"Categories": "Categorías",
"Category": "Categoría",
"Change Language": "Cambiar idioma",
"Change Owner": "Cambiar Propietario",
"Change password": "Cambiar contraseña",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Haga clic en 'Iniciar grabación' y seleccione la pantalla o pestaña para grabar. Una vez finalizada la grabación, haga clic en 'Detener grabación' y la grabación se subirá.",
"Co-Editors": "Coeditores",
"Co-Owners": "Copropietarios",
"Co-Viewers": "Covisores",
"Comment": "Comentario",
"Comments": "Comentarios",
"Comments are disabled": "Los comentarios están deshabilitados",
"Confirm": "Confirmar",
"Confirm Action": "Confirmar Acción",
"Contact": "Contacto",
"Copy Media": "Copiar Medio",
"Create": "Crear",
"DELETE": "ELIMINAR",
"DELETE MEDIA": "ELIMINAR MEDIOS",
"DOWNLOAD": "DESCARGAR",
"DURATION": "DURACIÓN",
"Delete Media": "Eliminar Medio",
"Delete media": "Eliminar medios",
"Disable Comments": "Deshabilitar Comentarios",
"Disable Download": "Deshabilitar Descarga",
"Drag and drop files": "Arrastre y suelte archivos",
"EDIT MEDIA": "EDITAR MEDIOS",
"EDIT PROFILE": "EDITAR PERFIL",
"EDIT SUBTITLE": "EDITAR SUBTÍTULO",
"Edit media": "Editar medios",
"Edit profile": "Editar perfil",
"Edit subtitle": "Editar subtítulo",
"Enable Comments": "Habilitar Comentarios",
"Enable Download": "Habilitar Descarga",
"Enter playlist name...": "Ingrese nombre de lista de reproducción...",
"Failed to add categories": "Error al agregar categorías",
"Failed to add media to playlists": "Error al agregar medios a listas de reproducción",
"Failed to add tags": "Error al agregar etiquetas",
"Failed to add users": "Error al agregar usuarios",
"Failed to change owner": "Error al cambiar propietario",
"Failed to change owner. Please try again.": "Error al cambiar propietario. Por favor, inténtelo de nuevo.",
"Failed to copy media.": "Error al copiar medios.",
"Failed to create playlist": "Error al crear lista de reproducción",
"Failed to delete media. Please try again.": "Error al eliminar medios. Por favor, inténtelo de nuevo.",
"Failed to disable comments.": "Error al deshabilitar comentarios.",
"Failed to disable download.": "Error al deshabilitar descarga.",
"Failed to enable comments.": "Error al habilitar comentarios.",
"Failed to enable download.": "Error al habilitar descarga.",
"Failed to fetch all categories": "Error al obtener todas las categorías",
"Failed to fetch all tags": "Error al obtener todas las etiquetas",
"Failed to fetch existing categories": "Error al obtener categorías existentes",
"Failed to fetch existing tags": "Error al obtener etiquetas existentes",
"Failed to fetch existing users": "Error al obtener usuarios existentes",
"Failed to fetch playlist membership": "Error al obtener membresía de lista de reproducción",
"Failed to fetch playlists": "Error al obtener listas de reproducción",
"Failed to load categories": "Error al cargar categorías",
"Failed to load existing permissions": "Error al cargar permisos existentes",
"Failed to load playlists": "Error al cargar listas de reproducción",
"Failed to load tags": "Error al cargar etiquetas",
"Failed to remove categories": "Error al eliminar categorías",
"Failed to remove media from playlists": "Error al eliminar medios de listas de reproducción",
"Failed to remove tags": "Error al eliminar etiquetas",
"Failed to remove users": "Error al eliminar usuarios",
"Failed to search users": "Error al buscar usuarios",
"Failed to set publish state": "Error al establecer estado de publicación",
"Failed to set publish state. Please try again.": "Error al establecer estado de publicación. Por favor, inténtelo de nuevo.",
"Failed to update categories. Please try again.": "Error al actualizar categorías. Por favor, inténtelo de nuevo.",
"Failed to update permissions. Please try again.": "Error al actualizar permisos. Por favor, inténtelo de nuevo.",
"Failed to update playlists. Please try again.": "Error al actualizar listas de reproducción. Por favor, inténtelo de nuevo.",
"Failed to update tags. Please try again.": "Error al actualizar etiquetas. Por favor, inténtelo de nuevo.",
"Featured": "Destacado",
"Filter existing users...": "Filtrar usuarios existentes...",
"Filter playlists...": "Filtrar listas de reproducción...",
"Filters": "Filtros",
"Go": "Ir",
"History": "Historial",
"Home": "Inicio",
"Image": "Imagen",
"Language": "Idioma",
"Latest": "Último",
"Like count": "Cantidad de me gusta",
"Liked media": "Medios que me gustan",
"Likes - Least": "Me gusta - Menos",
"Likes - Most": "Me gusta - Más",
"Loading categories...": "Cargando categorías...",
"Loading existing users...": "Cargando usuarios existentes...",
"Loading playlists...": "Cargando listas de reproducción...",
"Loading tags...": "Cargando etiquetas...",
"MEDIA TYPE": "TIPO DE MEDIO",
"Manage": "Gestionar",
"Manage Playlists": "Gestionar Listas de Reproducción",
"Manage comments": "Gestionar comentarios",
"Manage media": "Gestionar medios",
"Manage users": "Gestionar usuarios",
"Media": "Medios",
"Media I own": "Medios que poseo",
"Media was edited": "El medio fue editado",
"Members": "Miembros",
"My media": "Mis medios",
"My playlists": "Mis listas de reproducción",
"No": "No",
"No categories": "Sin categorías",
"No comment yet": "Aún no hay comentarios",
"No comments yet": "Aún no hay comentarios",
"No existing": "No existente",
"No playlists available": "No hay listas de reproducción disponibles",
"No playlists selected": "No hay listas de reproducción seleccionadas",
"No results for": "No hay resultados para",
"No tags": "Sin etiquetas",
"No users to add": "No hay usuarios para agregar",
"PLAYLISTS": "LISTAS DE REPRODUCCIÓN",
"PUBLISH STATE": "ESTADO DE PUBLICACIÓN",
"Pdf": "PDF",
"Playlists": "Listas de reproducción",
"Plays - Least": "Reproducciones - Menos",
"Plays - Most": "Reproducciones - Más",
"Please select a publish state": "Por favor seleccione un estado de publicación",
"Please select a user": "Por favor seleccione un usuario",
"Powered by": "Desarrollado por",
"Private": "Privado",
"Proceed": "Continuar",
"Processing...": "Procesando...",
"Public": "Público",
"Publish": "Publicar",
"Publish State": "Estado de Publicación",
"Published": "Publicado",
"Published on": "Publicado en",
"Recent uploads": "Subidas recientes",
"Recommended": "Recomendado",
"Record Screen": "Grabar pantalla",
"Register": "Registrarse",
"Remove category": "Eliminar categoría",
"Remove from list": "Eliminar de la lista",
"Remove tag": "Eliminar etiqueta",
"Remove user": "Eliminar usuario",
"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,74 +1,258 @@
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 ": "",
"Add a ": "Ajouter un ",
"Add to": "Ajouter à",
"Add to / Remove from Category": "Ajouter / Supprimer de la catégorie",
"Add to / Remove from Playlist": "Ajouter / Supprimer de la playlist",
"All": "Tout",
"All categories already added": "Toutes les catégories déjà ajoutées",
"All tags already added": "Tous les tags déjà ajoutés",
"Alphabetically - A-Z": "Alphabétiquement - A-Z",
"Alphabetically - Z-A": "Alphabétiquement - Z-A",
"Audio": "Audio",
"Browse your files": "Parcourir vos fichiers",
"Bulk Actions": "Actions groupées",
"COMMENT": "COMMENTAIRE",
"Cancel": "Annuler",
"Categories": "Catégories",
"Category": "Catégorie",
"Change Language": "Changer de langue",
"Change Owner": "Changer de propriétaire",
"Change password": "Changer le mot de passe",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Cliquez sur 'Démarrer l'enregistrement' et sélectionnez l'écran ou l'onglet à enregistrer. Une fois l'enregistrement terminé, cliquez sur 'Arrêter l'enregistrement', et l'enregistrement sera téléversé.",
"Co-Editors": "Co-éditeurs",
"Co-Owners": "Co-propriétaires",
"Co-Viewers": "Co-visualisateurs",
"Comment": "Commentaire",
"Comments": "Commentaires",
"Comments are disabled": "Les commentaires sont désactivés",
"Confirm": "Confirmer",
"Confirm Action": "Confirmer l'action",
"Contact": "Contact",
"Copy Media": "Copier le média",
"Create": "Créer",
"DELETE": "SUPPRIMER",
"DELETE MEDIA": "SUPPRIMER LE MÉDIA",
"DOWNLOAD": "TÉLÉCHARGER",
"DURATION": "DURÉE",
"Delete Media": "Supprimer le média",
"Delete media": "Supprimer le média",
"Disable Comments": "Désactiver les commentaires",
"Disable Download": "Désactiver le téléchargement",
"Drag and drop files": "Glisser-déposer des fichiers",
"EDIT MEDIA": "MODIFIER LE MÉDIA",
"EDIT PROFILE": "MODIFIER LE PROFIL",
"EDIT SUBTITLE": "MODIFIER LE SOUS-TITRE",
"Edit media": "Modifier le média",
"Edit profile": "Modifier le profil",
"Edit subtitle": "Modifier le sous-titre",
"Enable Comments": "Activer les commentaires",
"Enable Download": "Activer le téléchargement",
"Enter playlist name...": "Entrez le nom de la playlist...",
"Failed to add categories": "Échec de l'ajout des catégories",
"Failed to add media to playlists": "Échec de l'ajout du média aux playlists",
"Failed to add tags": "Échec de l'ajout des tags",
"Failed to add users": "Échec de l'ajout des utilisateurs",
"Failed to change owner": "Échec du changement de propriétaire",
"Failed to change owner. Please try again.": "Échec du changement de propriétaire. Veuillez réessayer.",
"Failed to copy media.": "Échec de la copie du média.",
"Failed to create playlist": "Échec de la création de la playlist",
"Failed to delete media. Please try again.": "Échec de la suppression du média. Veuillez réessayer.",
"Failed to disable comments.": "Échec de la désactivation des commentaires.",
"Failed to disable download.": "Échec de la désactivation du téléchargement.",
"Failed to enable comments.": "Échec de l'activation des commentaires.",
"Failed to enable download.": "Échec de l'activation du téléchargement.",
"Failed to fetch all categories": "Échec de la récupération de toutes les catégories",
"Failed to fetch all tags": "Échec de la récupération de tous les tags",
"Failed to fetch existing categories": "Échec de la récupération des catégories existantes",
"Failed to fetch existing tags": "Échec de la récupération des tags existants",
"Failed to fetch existing users": "Échec de la récupération des utilisateurs existants",
"Failed to fetch playlist membership": "Échec de la récupération de l'adhésion à la playlist",
"Failed to fetch playlists": "Échec de la récupération des playlists",
"Failed to load categories": "Échec du chargement des catégories",
"Failed to load existing permissions": "Échec du chargement des permissions existantes",
"Failed to load playlists": "Échec du chargement des playlists",
"Failed to load tags": "Échec du chargement des tags",
"Failed to remove categories": "Échec de la suppression des catégories",
"Failed to remove media from playlists": "Échec de la suppression du média des playlists",
"Failed to remove tags": "Échec de la suppression des tags",
"Failed to remove users": "Échec de la suppression des utilisateurs",
"Failed to search users": "Échec de la recherche d'utilisateurs",
"Failed to set publish state": "Échec de la définition de l'état de publication",
"Failed to set publish state. Please try again.": "Échec de la définition de l'état de publication. Veuillez réessayer.",
"Failed to update categories. Please try again.": "Échec de la mise à jour des catégories. Veuillez réessayer.",
"Failed to update permissions. Please try again.": "Échec de la mise à jour des permissions. Veuillez réessayer.",
"Failed to update playlists. Please try again.": "Échec de la mise à jour des playlists. Veuillez réessayer.",
"Failed to update tags. Please try again.": "Échec de la mise à jour des tags. Veuillez réessayer.",
"Featured": "En vedette",
"Filter existing users...": "Filtrer les utilisateurs existants...",
"Filter playlists...": "Filtrer les playlists...",
"Filters": "Filtres",
"Go": "Aller",
"History": "Historique",
"Home": "Accueil",
"Image": "Image",
"Language": "Langue",
"Latest": "Dernier",
"Like count": "Nombre de j'aime",
"Liked media": "Médias aimés",
"Likes - Least": "J'aime - Moins",
"Likes - Most": "J'aime - Plus",
"Loading categories...": "Chargement des catégories...",
"Loading existing users...": "Chargement des utilisateurs existants...",
"Loading playlists...": "Chargement des playlists...",
"Loading tags...": "Chargement des tags...",
"MEDIA TYPE": "TYPE DE MÉDIA",
"Manage": "Gérer",
"Manage Playlists": "Gérer les playlists",
"Manage comments": "Gérer les commentaires",
"Manage media": "Gérer les médias",
"Manage users": "Gérer les utilisateurs",
"Media": "Média",
"Media I own": "Médias que je possède",
"Media was edited": "Le média a été modifié",
"Members": "Membres",
"My media": "Mes médias",
"My playlists": "Mes playlists",
"No": "Non",
"No categories": "Aucune catégorie",
"No comment yet": "Pas encore de commentaire",
"No comments yet": "Pas encore de commentaires",
"No existing": "Aucun existant",
"No playlists available": "Aucune playlist disponible",
"No playlists selected": "Aucune playlist sélectionnée",
"No results for": "Aucun résultat pour",
"No tags": "Aucun tag",
"No users to add": "Aucun utilisateur à ajouter",
"PLAYLISTS": "PLAYLISTS",
"PUBLISH STATE": "ÉTAT DE PUBLICATION",
"Pdf": "PDF",
"Playlists": "Playlists",
"Plays - Least": "Lectures - Moins",
"Plays - Most": "Lectures - Plus",
"Please select a publish state": "Veuillez sélectionner un état de publication",
"Please select a user": "Veuillez sélectionner un utilisateur",
"Powered by": "Propulsé par",
"Private": "Privé",
"Proceed": "Continuer",
"Processing...": "Traitement en cours...",
"Public": "Public",
"Publish": "Publier",
"Publish State": "État de publication",
"Published": "Publié",
"Published on": "Publié le",
"Recent uploads": "Téléchargements récents",
"Recommended": "Recommandé",
"Record Screen": "Enregistrer l'écran",
"Register": "S'inscrire",
"Remove category": "Supprimer la catégorie",
"Remove from list": "Supprimer de la liste",
"Remove tag": "Supprimer le tag",
"Remove user": "Supprimer l'utilisateur",
"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

@ -0,0 +1,288 @@
translation_strings = {
"+ Create Playlist": "",
"00 - 20 min": "00 - 20 דקות",
"1 result for": "תוצאה אחת עבור",
"20 - 40 min": "20 - 40 דקות",
"40 - 60 min": "40 - 60 דקות",
"60 - 120 min+": "60 - 120 דקות+",
"ABOUT": "על אודות",
"AUTOPLAY": "ניגון אוטומטי",
"About": "על אודות",
"Add / Remove Co-Editors": "",
"Add / Remove Co-Owners": "",
"Add / Remove Co-Viewers": "",
"Add / Remove Tags": "",
"Add / Remove from Categories": "",
"Add a ": "הוסף",
"Add to": "",
"Add to / Remove from Category": "",
"Add to / Remove from Playlist": "",
"All": "הכל",
"All categories already added": "",
"All tags already added": "",
"Alphabetically - A-Z": "לפי א-ב - א-ת",
"Alphabetically - Z-A": "לפי א-ב - ת-א",
"Audio": "אודיו",
"Browse your files": "עיין בקבצים שלך",
"Bulk Actions": "",
"COMMENT": "תגובה",
"Cancel": "",
"Categories": "קטגוריות",
"Category": "קטגוריה",
"Change Language": "שנה שפה",
"Change Owner": "",
"Change password": "שנה סיסמה",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "לחץ על 'התחל הקלטה' ובחר את המסך או הכרטיסייה להקלטה. לאחר סיום ההקלטה, לחץ על 'עצור הקלטה', וההקלטה תועלה.",
"Co-Editors": "",
"Co-Owners": "",
"Co-Viewers": "",
"Comment": "תגובה",
"Comments": "תגובות",
"Comments are disabled": "התגובות מושבתות",
"Confirm": "",
"Confirm Action": "",
"Contact": "צור קשר",
"Copy Media": "",
"Create": "",
"DELETE": "מחק",
"DELETE MEDIA": "מחק מדיה",
"DOWNLOAD": "הורד",
"DURATION": "משך",
"Delete Media": "",
"Delete media": "מחק מדיה",
"Disable Comments": "",
"Disable Download": "",
"Drag and drop files": "גרור ושחרר קבצים",
"EDIT MEDIA": "ערוך מדיה",
"EDIT PROFILE": "ערוך פרופיל",
"EDIT SUBTITLE": "ערוך כתוביות",
"Edit media": "ערוך מדיה",
"Edit profile": "ערוך פרופיל",
"Edit subtitle": "ערוך כתוביות",
"Enable Comments": "",
"Enable Download": "",
"Enter playlist name...": "",
"Failed to add categories": "",
"Failed to add media to playlists": "",
"Failed to add tags": "",
"Failed to add users": "",
"Failed to change owner": "",
"Failed to change owner. Please try again.": "",
"Failed to copy media.": "העתקת המדיה נכשלה.",
"Failed to create playlist": "",
"Failed to delete media. Please try again.": "מחיקת המדיה נכשלה. אנא נסה שוב.",
"Failed to disable comments.": "ביטול התגובות נכשל.",
"Failed to disable download.": "ביטול ההורדה נכשל.",
"Failed to enable comments.": "הפעלת התגובות נכשלה.",
"Failed to enable download.": "הפעלת ההורדה נכשלה.",
"Failed to fetch all categories": "",
"Failed to fetch all tags": "",
"Failed to fetch existing categories": "",
"Failed to fetch existing tags": "",
"Failed to fetch existing users": "",
"Failed to fetch playlist membership": "",
"Failed to fetch playlists": "",
"Failed to load categories": "",
"Failed to load existing permissions": "",
"Failed to load playlists": "",
"Failed to load tags": "",
"Failed to remove categories": "",
"Failed to remove media from playlists": "",
"Failed to remove tags": "",
"Failed to remove users": "",
"Failed to search users": "",
"Failed to set publish state": "",
"Failed to set publish state. Please try again.": "",
"Failed to update categories. Please try again.": "",
"Failed to update permissions. Please try again.": "",
"Failed to update playlists. Please try again.": "",
"Failed to update tags. Please try again.": "",
"Featured": "מומלצים",
"Filter existing users...": "",
"Filter playlists...": "",
"Filters": "מסננים",
"Go": "בצע",
"History": "היסטוריה",
"Home": "דף הבית",
"Image": "תמונה",
"Language": "שפה",
"Latest": "העדכונים האחרונים",
"Like count": "מספר לייקים",
"Liked media": "מדיה שאהבתי",
"Likes - Least": "לייקים - הכי פחות",
"Likes - Most": "לייקים - הכי הרבה",
"Loading categories...": "",
"Loading existing users...": "",
"Loading playlists...": "",
"Loading tags...": "",
"MEDIA TYPE": "סוג מדיה",
"Manage": "",
"Manage Playlists": "",
"Manage comments": "ניהול תגובות",
"Manage media": "ניהול מדיה",
"Manage users": "ניהול משתמשים",
"Media": "מדיה",
"Media I own": "",
"Media was edited": "המדיה נערכה",
"Members": "משתמשים",
"My media": "המדיה שלי",
"My playlists": "הפלייליסטים שלי",
"No": "לא",
"No categories": "",
"No comment yet": "עדיין אין תגובות",
"No comments yet": "עדיין אין תגובות",
"No existing": "",
"No playlists available": "",
"No playlists selected": "",
"No results for": "אין תוצאות עבור",
"No tags": "",
"No users to add": "",
"PLAYLISTS": "פלייליסטים",
"PUBLISH STATE": "מצב פרסום",
"Pdf": "PDF",
"Playlists": "פלייליסטים",
"Plays - Least": "נגינות - הכי פחות",
"Plays - Most": "נגינות - הכי הרבה",
"Please select a publish state": "",
"Please select a user": "",
"Powered by": "מופעל על ידי",
"Private": "פרטי",
"Proceed": "",
"Processing...": "",
"Public": "",
"Publish": "פרסם",
"Publish State": "",
"Published": "פורסם",
"Published on": "פורסם בתאריך",
"Recent uploads": "העלאות אחרונות",
"Recommended": "מומלץ",
"Record Screen": "הקלטת מסך",
"Register": "הרשמה",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"SAVE": "שמור",
"SEARCH": "חפש",
"SHARE": "שתף",
"SHOW MORE": "הצג עוד",
"SORT BY": "מיין לפי",
"SUBMIT": "שלח",
"Search": "חפש",
"Search for user...": "",
"Search users to add...": "",
"Select": "בחר",
"Select Owner": "",
"Select all": "",
"Select all media": "",
"Select publish state:": "",
"Selected": "",
"Shared by me": "שותף על ידי",
"Shared with me": "שותף איתי",
"Sign in": "התחבר",
"Sign out": "התנתק",
"Sort By": "מיין לפי",
"Start Recording": "התחל הקלטה",
"Start uploading media and sharing your work. Media that you upload will show up here.": "התחל להעלות מדיה ולשתף את עבודתך. המדיה שתעלה תופיע כאן.",
"Stop Recording": "עצור הקלטה",
"Submit": "",
"Subtitle was added": "הכתובית נוספה",
"Subtitles": "כתוביות",
"Successfully Copied": "הועתק בהצלחה",
"Successfully Disabled Download": "ההורדה בוטלה בהצלחה",
"Successfully Disabled comments": "התגובות בוטלו בהצלחה",
"Successfully Enabled Download": "ההורדה הופעלה בהצלחה",
"Successfully Enabled comments": "התגובות הופעלו בהצלחה",
"Successfully changed owner": "",
"Successfully deleted": "נמחק בהצלחה",
"Successfully updated": "",
"Successfully updated categories": "",
"Successfully updated playlist membership": "",
"Successfully updated publish state": "",
"Successfully updated tags": "",
"TAGS": "תגיות",
"Tag": "תגית",
"Tags": "תגיות",
"Terms": "תנאים",
"The intersection of categories in the selected media is shown": "",
"The intersection of playlists in the selected media is shown": "",
"The intersection of tags in the selected media is shown": "",
"The intersection of users in the selected media is shown": "",
"The media was deleted successfully.": "המדיה נמחקה בהצלחה.",
"This month": "החודש",
"This week": "השבוע",
"This works in Chrome, Safari and Edge browsers.": "זה עובד בדפדפני Chrome, Safari ו-Edge.",
"This year": "השנה",
"To add": "",
"Today": "היום",
"Trim": "גזירה",
"UPLOAD": "העלה",
"UPLOAD DATE": "תאריך העלאה",
"UPLOAD MEDIA": "העלה מדיה",
"Undo removal": "",
"Unlisted": "לא רשום",
"Up Next": "הבא בתור",
"Up next": "הבא בתור",
"Upload": "העלה",
"Upload date (newest)": "תאריך העלאה (החדש ביותר)",
"Upload date (oldest)": "תאריך העלאה (הישן ביותר)",
"Upload date - Newest": "תאריך העלאה - החדש ביותר",
"Upload date - Oldest": "תאריך העלאה - הישן ביותר",
"Upload media": "העלה מדיה",
"Uploads": "העלאות",
"Users": "",
"VIEW ALL": "הצג הכל",
"Video": "וידאו",
"View all": "הצג הכל",
"View count": "מספר צפיות",
"View media": "צפה במדיה",
"Welcome": "ברוך הבא",
"You are going to copy": "אתה עומד להעתיק",
"You are going to delete": "אתה עומד למחוק",
"You are going to disable comments to": "אתה עומד לבטל תגובות ל",
"You are going to disable download for": "אתה עומד לבטל הורדה עבור",
"You are going to enable comments to": "אתה עומד להפעיל תגובות ל",
"You are going to enable download for": "אתה עומד להפעיל הורדה עבור",
"comment": "תגובה",
"is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media": "מערכת ניהול מדיה ווידאו מודרנית, פתוחה ומלאה בפיצ׳רים. פותחה כדי לענות על הצרכים של פלטפורמות אינטרנט מודרניות לצפייה ושיתוף מדיה.",
"media in category": "מדיה בקטגוריה",
"media in tag": "מדיה בתגית",
"media, are you sure?": "מדיה, אתה בטוח?",
"media.": "מדיה.",
"or": "או",
"results for": "תוצאות עבור",
"selected": "",
"view": "צפיות",
"views": "צפיות",
"yet": "עדיין",
}
replacement_strings = {
"Apr": "אפריל",
"Aug": "אוגוסט",
"Dec": "דצמבר",
"Feb": "פברואר",
"Jan": "ינואר",
"Jul": "יולי",
"Jun": "יוני",
"Mar": "מרץ",
"May": "מאי",
"Nov": "נובמבר",
"Oct": "אוקטובר",
"Sep": "ספטמבר",
"day ago": "לפני יום",
"days ago": "לפני ימים",
"hour ago": "לפני שעה",
"hours ago": "לפני שעות",
"just now": "הרגע",
"minute ago": "לפני דקה",
"minutes ago": "לפני דקות",
"month ago": "לפני חודש",
"months ago": "לפני חודשים",
"second ago": "לפני שנייה",
"seconds ago": "לפני שניות",
"week ago": "לפני שבוע",
"weeks ago": "לפני שבועות",
"year ago": "לפני שנה",
"years ago": "לפני שנים",
}

View File

@ -1,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ प्लेलिस्ट बनाएं",
"00 - 20 min": "00 - 20 मिनट",
"1 result for": "1 परिणाम",
"20 - 40 min": "20 - 40 मिनट",
"40 - 60 min": "40 - 60 मिनट",
"60 - 120 min+": "60 - 120 मिनट+",
"ABOUT": "के बारे में",
"AUTOPLAY": "स्वतः चलाएं",
"About": "के बारे में",
"Add / Remove Co-Editors": "सह-संपादक जोड़ें / हटाएं",
"Add / Remove Co-Owners": "सह-स्वामी जोड़ें / हटाएं",
"Add / Remove Co-Viewers": "सह-दर्शक जोड़ें / हटाएं",
"Add / Remove Tags": "टैग जोड़ें / हटाएं",
"Add / Remove from Categories": "श्रेणियों में जोड़ें / हटाएं",
"Add a ": "जोड़ें",
"Add to": "इसमें जोड़ें",
"Add to / Remove from Category": "श्रेणी में जोड़ें / हटाएं",
"Add to / Remove from Playlist": "प्लेलिस्ट में जोड़ें / हटाएं",
"All": "सभी",
"All categories already added": "सभी श्रेणियां पहले से जोड़ी गई हैं",
"All tags already added": "सभी टैग पहले से जोड़े गए हैं",
"Alphabetically - A-Z": "वर्णमाला के अनुसार - A-Z",
"Alphabetically - Z-A": "वर्णमाला के अनुसार - Z-A",
"Audio": "ऑडियो",
"Browse your files": "अपनी फ़ाइलें ब्राउज़ करें",
"Bulk Actions": "थोक क्रियाएं",
"COMMENT": "टिप्पणी",
"Cancel": "रद्द करें",
"Categories": "श्रेणियाँ",
"Category": "श्रेणी",
"Change Language": "भाषा बदलें",
"Change Owner": "स्वामी बदलें",
"Change password": "पासवर्ड बदलें",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'रिकॉर्डिंग प्रारंभ करें' पर क्लिक करें और रिकॉर्ड करने के लिए स्क्रीन या टैब का चयन करें। रिकॉर्डिंग समाप्त होने के बाद, 'रिकॉर्डिंग रोकें' पर क्लिक करें, और रिकॉर्डिंग अपलोड हो जाएगी।",
"Co-Editors": "सह-संपादक",
"Co-Owners": "सह-स्वामी",
"Co-Viewers": "सह-दर्शक",
"Comment": "टिप्पणी",
"Comments": "टिप्पणियाँ",
"Comments are disabled": "टिप्पणियाँ अक्षम हैं",
"Confirm": "पुष्टि करें",
"Confirm Action": "क्रिया की पुष्टि करें",
"Contact": "संपर्क करें",
"Copy Media": "मीडिया कॉपी करें",
"Create": "बनाएं",
"DELETE": "हटाएं",
"DELETE MEDIA": "मीडिया हटाएं",
"DOWNLOAD": "डाउनलोड करें",
"DURATION": "अवधि",
"Delete Media": "मीडिया हटाएं",
"Delete media": "मीडिया हटाएं",
"Disable Comments": "टिप्पणियां अक्षम करें",
"Disable Download": "डाउनलोड अक्षम करें",
"Drag and drop files": "फ़ाइलें खींचें और छोड़ें",
"EDIT MEDIA": "मीडिया संपादित करें",
"EDIT PROFILE": "प्रोफ़ाइल संपादित करें",
"EDIT SUBTITLE": "उपशीर्षक संपादित करें",
"Edit media": "मीडिया संपादित करें",
"Edit profile": "प्रोफ़ाइल संपादित करें",
"Edit subtitle": "उपशीर्षक संपादित करें",
"Enable Comments": "टिप्पणियां सक्षम करें",
"Enable Download": "डाउनलोड सक्षम करें",
"Enter playlist name...": "प्लेलिस्ट का नाम दर्ज करें...",
"Failed to add categories": "श्रेणियां जोड़ने में विफल",
"Failed to add media to playlists": "प्लेलिस्ट में मीडिया जोड़ने में विफल",
"Failed to add tags": "टैग जोड़ने में विफल",
"Failed to add users": "उपयोगकर्ता जोड़ने में विफल",
"Failed to change owner": "स्वामी बदलने में विफल",
"Failed to change owner. Please try again.": "स्वामी बदलने में विफल। कृपया पुनः प्रयास करें।",
"Failed to copy media.": "मीडिया कॉपी करने में विफल।",
"Failed to create playlist": "प्लेलिस्ट बनाने में विफल",
"Failed to delete media. Please try again.": "मीडिया हटाने में विफल। कृपया पुनः प्रयास करें।",
"Failed to disable comments.": "टिप्पणियों को अक्षम करने में विफल।",
"Failed to disable download.": "डाउनलोड अक्षम करने में विफल।",
"Failed to enable comments.": "टिप्पणियों को सक्षम करने में विफल।",
"Failed to enable download.": "डाउनलोड सक्षम करने में विफल।",
"Failed to fetch all categories": "सभी श्रेणियां लाने में विफल",
"Failed to fetch all tags": "सभी टैग लाने में विफल",
"Failed to fetch existing categories": "मौजूदा श्रेणियां लाने में विफल",
"Failed to fetch existing tags": "मौजूदा टैग लाने में विफल",
"Failed to fetch existing users": "मौजूदा उपयोगकर्ताओं को लाने में विफल",
"Failed to fetch playlist membership": "प्लेलिस्ट सदस्यता लाने में विफल",
"Failed to fetch playlists": "प्लेलिस्ट लाने में विफल",
"Failed to load categories": "श्रेणियां लोड करने में विफल",
"Failed to load existing permissions": "मौजूदा अनुमतियां लोड करने में विफल",
"Failed to load playlists": "प्लेलिस्ट लोड करने में विफल",
"Failed to load tags": "टैग लोड करने में विफल",
"Failed to remove categories": "श्रेणियां हटाने में विफल",
"Failed to remove media from playlists": "प्लेलिस्ट से मीडिया हटाने में विफल",
"Failed to remove tags": "टैग हटाने में विफल",
"Failed to remove users": "उपयोगकर्ताओं को हटाने में विफल",
"Failed to search users": "उपयोगकर्ताओं को खोजने में विफल",
"Failed to set publish state": "प्रकाशन स्थिति सेट करने में विफल",
"Failed to set publish state. Please try again.": "प्रकाशन स्थिति सेट करने में विफल। कृपया पुनः प्रयास करें।",
"Failed to update categories. Please try again.": "श्रेणियां अपडेट करने में विफल। कृपया पुनः प्रयास करें।",
"Failed to update permissions. Please try again.": "अनुमतियां अपडेट करने में विफल। कृपया पुनः प्रयास करें।",
"Failed to update playlists. Please try again.": "प्लेलिस्ट अपडेट करने में विफल। कृपया पुनः प्रयास करें।",
"Failed to update tags. Please try again.": "टैग अपडेट करने में विफल। कृपया पुनः प्रयास करें।",
"Featured": "विशेष रुप से प्रदर्शित",
"Filter existing users...": "मौजूदा उपयोगकर्ताओं को फ़िल्टर करें...",
"Filter playlists...": "प्लेलिस्ट फ़िल्टर करें...",
"Filters": "फ़िल्टर",
"Go": "जाएं",
"History": "इतिहास",
"Home": "मुख्य पृष्ठ",
"Image": "छवि",
"Language": "भाषा",
"Latest": "नवीनतम",
"Like count": "पसंद की संख्या",
"Liked media": "पसंदीदा मीडिया",
"Likes - Least": "पसंद - कम से कम",
"Likes - Most": "पसंद - सबसे ज्यादा",
"Loading categories...": "श्रेणियां लोड हो रही हैं...",
"Loading existing users...": "मौजूदा उपयोगकर्ता लोड हो रहे हैं...",
"Loading playlists...": "प्लेलिस्ट लोड हो रही हैं...",
"Loading tags...": "टैग लोड हो रहे हैं...",
"MEDIA TYPE": "मीडिया प्रकार",
"Manage": "प्रबंधित करें",
"Manage Playlists": "प्लेलिस्ट प्रबंधित करें",
"Manage comments": "टिप्पणियाँ प्रबंधित करें",
"Manage media": "मीडिया प्रबंधित करें",
"Manage users": "उपयोगकर्ताओं को प्रबंधित करें",
"Media": "मीडिया",
"Media I own": "मेरे स्वामित्व वाली मीडिया",
"Media was edited": "मीडिया संपादित किया गया था",
"Members": "सदस्य",
"My media": "मेरा मीडिया",
"My playlists": "मेरी प्लेलिस्ट",
"No": "नहीं",
"No categories": "कोई श्रेणी नहीं",
"No comment yet": "अभी तक कोई टिप्पणी नहीं",
"No comments yet": "अभी तक कोई टिप्पणियाँ नहीं",
"No existing": "कोई मौजूदा नहीं",
"No playlists available": "कोई प्लेलिस्ट उपलब्ध नहीं",
"No playlists selected": "कोई प्लेलिस्ट चयनित नहीं",
"No results for": "के लिए कोई परिणाम नहीं",
"No tags": "कोई टैग नहीं",
"No users to add": "जोड़ने के लिए कोई उपयोगकर्ता नहीं",
"PLAYLISTS": "प्लेलिस्ट",
"PUBLISH STATE": "प्रकाशन स्थिति",
"Pdf": "PDF",
"Playlists": "प्लेलिस्ट",
"Plays - Least": "प्ले - कम से कम",
"Plays - Most": "प्ले - सबसे ज्यादा",
"Please select a publish state": "कृपया एक प्रकाशन स्थिति चुनें",
"Please select a user": "कृपया एक उपयोगकर्ता चुनें",
"Powered by": "द्वारा संचालित",
"Private": "निजी",
"Proceed": "आगे बढ़ें",
"Processing...": "प्रसंस्करण...",
"Public": "सार्वजनिक",
"Publish": "प्रकाशित करें",
"Publish State": "प्रकाशन स्थिति",
"Published": "प्रकाशित",
"Published on": "पर प्रकाशित",
"Recent uploads": "हाल के अपलोड",
"Recommended": "अनुशंसित",
"Record Screen": "स्क्रीन रिकॉर्ड करें",
"Register": "पंजीकरण करें",
"Remove category": "श्रेणी हटाएं",
"Remove from list": "सूची से हटाएं",
"Remove tag": "टैग हटाएं",
"Remove user": "उपयोगकर्ता हटाएं",
"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,73 +1,257 @@
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",
"EDIT SUBTITLE": "EDIT SUBTITLE",
"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",
"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,74 +1,258 @@
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",
"EDIT SUBTITLE": "MODIFICA I SOTTOTITOLI",
"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",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "プレイリストを作成",
"00 - 20 min": "00 - 20分",
"1 result for": "1件の結果",
"20 - 40 min": "20 - 40分",
"40 - 60 min": "40 - 60分",
"60 - 120 min+": "60 - 120分+",
"ABOUT": "",
"AUTOPLAY": "自動再生",
"About": "",
"Add / Remove Co-Editors": "共同編集者を追加/削除",
"Add / Remove Co-Owners": "共同所有者を追加/削除",
"Add / Remove Co-Viewers": "共同閲覧者を追加/削除",
"Add / Remove Tags": "タグを追加/削除",
"Add / Remove from Categories": "カテゴリーから追加/削除",
"Add a ": "追加",
"Add to": "追加",
"Add to / Remove from Category": "カテゴリーに追加/削除",
"Add to / Remove from Playlist": "プレイリストに追加/削除",
"All": "すべて",
"All categories already added": "すべてのカテゴリーは既に追加されています",
"All tags already added": "すべてのタグは既に追加されています",
"Alphabetically - A-Z": "アルファベット順 - A-Z",
"Alphabetically - Z-A": "アルファベット順 - Z-A",
"Audio": "オーディオ",
"Browse your files": "ファイルを参照",
"Bulk Actions": "一括操作",
"COMMENT": "コメント",
"Cancel": "キャンセル",
"Categories": "カテゴリー",
"Category": "カテゴリー",
"Change Language": "言語を変更",
"Change Owner": "所有者を変更",
"Change password": "パスワードを変更",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "「録画開始」をクリックして、録画する画面またはタブを選択します。録画が終了したら、「録画停止」をクリックすると、録画がアップロードされます。",
"Co-Editors": "共同編集者",
"Co-Owners": "共同所有者",
"Co-Viewers": "共同閲覧者",
"Comment": "コメント",
"Comments": "コメント",
"Comments are disabled": "コメントは無効です",
"Confirm": "確認",
"Confirm Action": "操作を確認",
"Contact": "連絡先",
"Copy Media": "メディアをコピー",
"Create": "作成",
"DELETE": "削除",
"DELETE MEDIA": "メディアを削除",
"DOWNLOAD": "ダウンロード",
"DURATION": "期間",
"Delete Media": "メディアを削除",
"Delete media": "メディアを削除",
"Disable Comments": "コメントを無効化",
"Disable Download": "ダウンロードを無効化",
"Drag and drop files": "ファイルをドラッグアンドドロップ",
"EDIT MEDIA": "メディアを編集",
"EDIT PROFILE": "プロフィールを編集",
"EDIT SUBTITLE": "字幕を編集",
"Edit media": "メディアを編集",
"Edit profile": "プロフィールを編集",
"Edit subtitle": "字幕を編集",
"Enable Comments": "コメントを有効化",
"Enable Download": "ダウンロードを有効化",
"Enter playlist name...": "プレイリスト名を入力...",
"Failed to add categories": "カテゴリーの追加に失敗しました",
"Failed to add media to playlists": "プレイリストへのメディア追加に失敗しました",
"Failed to add tags": "タグの追加に失敗しました",
"Failed to add users": "ユーザーの追加に失敗しました",
"Failed to change owner": "所有者の変更に失敗しました",
"Failed to change owner. Please try again.": "所有者の変更に失敗しました。もう一度お試しください。",
"Failed to copy media.": "メディアのコピーに失敗しました。",
"Failed to create playlist": "プレイリストの作成に失敗しました",
"Failed to delete media. Please try again.": "メディアの削除に失敗しました。もう一度お試しください。",
"Failed to disable comments.": "コメントの無効化に失敗しました。",
"Failed to disable download.": "ダウンロードの無効化に失敗しました。",
"Failed to enable comments.": "コメントの有効化に失敗しました。",
"Failed to enable download.": "ダウンロードの有効化に失敗しました。",
"Failed to fetch all categories": "すべてのカテゴリーの取得に失敗しました",
"Failed to fetch all tags": "すべてのタグの取得に失敗しました",
"Failed to fetch existing categories": "既存のカテゴリー取得に失敗しました",
"Failed to fetch existing tags": "既存のタグ取得に失敗しました",
"Failed to fetch existing users": "既存のユーザー取得に失敗しました",
"Failed to fetch playlist membership": "プレイリストメンバー情報の取得に失敗しました",
"Failed to fetch playlists": "プレイリストの取得に失敗しました",
"Failed to load categories": "カテゴリーの読み込みに失敗しました",
"Failed to load existing permissions": "既存の権限読み込みに失敗しました",
"Failed to load playlists": "プレイリストの読み込みに失敗しました",
"Failed to load tags": "タグの読み込みに失敗しました",
"Failed to remove categories": "カテゴリーの削除に失敗しました",
"Failed to remove media from playlists": "プレイリストからメディア削除に失敗しました",
"Failed to remove tags": "タグの削除に失敗しました",
"Failed to remove users": "ユーザーの削除に失敗しました",
"Failed to search users": "ユーザー検索に失敗しました",
"Failed to set publish state": "公開状態の設定に失敗しました",
"Failed to set publish state. Please try again.": "公開状態の設定に失敗しました。もう一度お試しください。",
"Failed to update categories. Please try again.": "カテゴリーの更新に失敗しました。もう一度お試しください。",
"Failed to update permissions. Please try again.": "権限の更新に失敗しました。もう一度お試しください。",
"Failed to update playlists. Please try again.": "プレイリストの更新に失敗しました。もう一度お試しください。",
"Failed to update tags. Please try again.": "タグの更新に失敗しました。もう一度お試しください。",
"Featured": "注目",
"Filter existing users...": "既存ユーザーをフィルター...",
"Filter playlists...": "プレイリストをフィルター...",
"Filters": "フィルター",
"Go": "行く",
"History": "履歴",
"Home": "ホーム",
"Image": "画像",
"Language": "言語",
"Latest": "最新",
"Like count": "いいね数",
"Liked media": "いいねしたメディア",
"Likes - Least": "いいね - 少ない順",
"Likes - Most": "いいね - 多い順",
"Loading categories...": "カテゴリーを読み込み中...",
"Loading existing users...": "既存ユーザーを読み込み中...",
"Loading playlists...": "プレイリストを読み込み中...",
"Loading tags...": "タグを読み込み中...",
"MEDIA TYPE": "メディアタイプ",
"Manage": "管理",
"Manage Playlists": "プレイリストを管理",
"Manage comments": "コメントを管理",
"Manage media": "メディアを管理",
"Manage users": "ユーザーを管理",
"Media": "メディア",
"Media I own": "自分が所有するメディア",
"Media was edited": "メディアが編集されました",
"Members": "メンバー",
"My media": "私のメディア",
"My playlists": "私のプレイリスト",
"No": "いいえ",
"No categories": "カテゴリーなし",
"No comment yet": "まだコメントはありません",
"No comments yet": "まだコメントはありません",
"No existing": "既存なし",
"No playlists available": "利用可能なプレイリストはありません",
"No playlists selected": "プレイリストが選択されていません",
"No results for": "の結果はありません",
"No tags": "タグなし",
"No users to add": "追加するユーザーなし",
"PLAYLISTS": "プレイリスト",
"PUBLISH STATE": "公開状態",
"Pdf": "PDF",
"Playlists": "プレイリスト",
"Plays - Least": "再生 - 少ない順",
"Plays - Most": "再生 - 多い順",
"Please select a publish state": "公開状態を選択してください",
"Please select a user": "ユーザーを選択してください",
"Powered by": "提供",
"Private": "非公開",
"Proceed": "進む",
"Processing...": "処理中...",
"Public": "公開",
"Publish": "公開",
"Publish State": "公開状態",
"Published": "公開済み",
"Published on": "公開日",
"Recent uploads": "最近のアップロード",
"Recommended": "おすすめ",
"Record Screen": "画面を録画",
"Register": "登録",
"Remove category": "カテゴリーを削除",
"Remove from list": "リストから削除",
"Remove tag": "タグを削除",
"Remove user": "ユーザーを削除",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ 재생 목록 만들기",
"00 - 20 min": "00 - 20분",
"1 result for": "1개 결과",
"20 - 40 min": "20 - 40분",
"40 - 60 min": "40 - 60분",
"60 - 120 min+": "60 - 120분+",
"ABOUT": "정보",
"AUTOPLAY": "자동 재생",
"About": "정보",
"Add / Remove Co-Editors": "공동 편집자 추가 / 제거",
"Add / Remove Co-Owners": "공동 소유자 추가 / 제거",
"Add / Remove Co-Viewers": "공동 시청자 추가 / 제거",
"Add / Remove Tags": "태그 추가 / 제거",
"Add / Remove from Categories": "카테고리에 추가 / 제거",
"Add a ": "추가",
"Add to": "추가",
"Add to / Remove from Category": "카테고리에 추가 / 제거",
"Add to / Remove from Playlist": "재생 목록에 추가 / 제거",
"All": "전체",
"All categories already added": "모든 카테고리가 이미 추가되었습니다",
"All tags already added": "모든 태그가 이미 추가되었습니다",
"Alphabetically - A-Z": "알파벳순 - A-Z",
"Alphabetically - Z-A": "알파벳순 - Z-A",
"Audio": "오디오",
"Browse your files": "파일 찾아보기",
"Bulk Actions": "일괄 작업",
"COMMENT": "댓글",
"Cancel": "취소",
"Categories": "카테고리",
"Category": "카테고리",
"Change Language": "언어 변경",
"Change Owner": "소유자 변경",
"Change password": "비밀번호 변경",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'녹화 시작'을 클릭하고 녹화할 화면이나 탭을 선택하세요. 녹화가 끝나면 '녹화 중지'를 클릭하면 녹화 파일이 업로드됩니다.",
"Co-Editors": "공동 편집자",
"Co-Owners": "공동 소유자",
"Co-Viewers": "공동 시청자",
"Comment": "댓글",
"Comments": "댓글",
"Comments are disabled": "댓글이 비활성화되었습니다",
"Confirm": "확인",
"Confirm Action": "작업 확인",
"Contact": "연락처",
"Copy Media": "미디어 복사",
"Create": "만들기",
"DELETE": "삭제",
"DELETE MEDIA": "미디어 삭제",
"DOWNLOAD": "다운로드",
"DURATION": "재생 시간",
"Delete Media": "미디어 삭제",
"Delete media": "미디어 삭제",
"Disable Comments": "댓글 비활성화",
"Disable Download": "다운로드 비활성화",
"Drag and drop files": "파일을 끌어다 놓기",
"EDIT MEDIA": "미디어 편집",
"EDIT PROFILE": "프로필 편집",
"EDIT SUBTITLE": "자막 편집",
"Edit media": "미디어 편집",
"Edit profile": "프로필 편집",
"Edit subtitle": "자막 편집",
"Enable Comments": "댓글 활성화",
"Enable Download": "다운로드 활성화",
"Enter playlist name...": "재생 목록 이름 입력...",
"Failed to add categories": "카테고리 추가 실패",
"Failed to add media to playlists": "재생 목록에 미디어 추가 실패",
"Failed to add tags": "태그 추가 실패",
"Failed to add users": "사용자 추가 실패",
"Failed to change owner": "소유자 변경 실패",
"Failed to change owner. Please try again.": "소유자 변경에 실패했습니다. 다시 시도해주세요.",
"Failed to copy media.": "미디어 복사에 실패했습니다.",
"Failed to create playlist": "재생 목록 만들기 실패",
"Failed to delete media. Please try again.": "미디어 삭제에 실패했습니다. 다시 시도해주세요.",
"Failed to disable comments.": "댓글 비활성화에 실패했습니다.",
"Failed to disable download.": "다운로드 비활성화에 실패했습니다.",
"Failed to enable comments.": "댓글 활성화에 실패했습니다.",
"Failed to enable download.": "다운로드 활성화에 실패했습니다.",
"Failed to fetch all categories": "모든 카테고리 가져오기 실패",
"Failed to fetch all tags": "모든 태그 가져오기 실패",
"Failed to fetch existing categories": "기존 카테고리 가져오기 실패",
"Failed to fetch existing tags": "기존 태그 가져오기 실패",
"Failed to fetch existing users": "기존 사용자 가져오기 실패",
"Failed to fetch playlist membership": "재생 목록 멤버십 가져오기 실패",
"Failed to fetch playlists": "재생 목록 가져오기 실패",
"Failed to load categories": "카테고리 로드 실패",
"Failed to load existing permissions": "기존 권한 로드 실패",
"Failed to load playlists": "재생 목록 로드 실패",
"Failed to load tags": "태그 로드 실패",
"Failed to remove categories": "카테고리 제거 실패",
"Failed to remove media from playlists": "재생 목록에서 미디어 제거 실패",
"Failed to remove tags": "태그 제거 실패",
"Failed to remove users": "사용자 제거 실패",
"Failed to search users": "사용자 검색 실패",
"Failed to set publish state": "게시 상태 설정 실패",
"Failed to set publish state. Please try again.": "게시 상태 설정에 실패했습니다. 다시 시도해주세요.",
"Failed to update categories. Please try again.": "카테고리 업데이트에 실패했습니다. 다시 시도해주세요.",
"Failed to update permissions. Please try again.": "권한 업데이트에 실패했습니다. 다시 시도해주세요.",
"Failed to update playlists. Please try again.": "재생 목록 업데이트에 실패했습니다. 다시 시도해주세요.",
"Failed to update tags. Please try again.": "태그 업데이트에 실패했습니다. 다시 시도해주세요.",
"Featured": "추천",
"Filter existing users...": "기존 사용자 필터링...",
"Filter playlists...": "재생 목록 필터링...",
"Filters": "필터",
"Go": "이동",
"History": "기록",
"Home": "",
"Image": "이미지",
"Language": "언어",
"Latest": "최신",
"Like count": "좋아요 수",
"Liked media": "좋아한 미디어",
"Likes - Least": "좋아요 - 적은순",
"Likes - Most": "좋아요 - 많은순",
"Loading categories...": "카테고리 로드 중...",
"Loading existing users...": "기존 사용자 로드 중...",
"Loading playlists...": "재생 목록 로드 중...",
"Loading tags...": "태그 로드 중...",
"MEDIA TYPE": "미디어 유형",
"Manage": "관리",
"Manage Playlists": "재생 목록 관리",
"Manage comments": "댓글 관리",
"Manage media": "미디어 관리",
"Manage users": "사용자 관리",
"Media": "미디어",
"Media I own": "내가 소유한 미디어",
"Media was edited": "미디어가 편집되었습니다",
"Members": "회원",
"My media": "내 미디어",
"My playlists": "내 재생 목록",
"No": "아니요",
"No categories": "카테고리 없음",
"No comment yet": "아직 댓글이 없습니다",
"No comments yet": "아직 댓글이 없습니다",
"No existing": "기존 항목 없음",
"No playlists available": "사용 가능한 재생 목록 없음",
"No playlists selected": "선택된 재생 목록 없음",
"No results for": "결과 없음",
"No tags": "태그 없음",
"No users to add": "추가할 사용자 없음",
"PLAYLISTS": "재생 목록",
"PUBLISH STATE": "게시 상태",
"Pdf": "PDF",
"Playlists": "재생 목록",
"Plays - Least": "재생 - 적은순",
"Plays - Most": "재생 - 많은순",
"Please select a publish state": "게시 상태를 선택하세요",
"Please select a user": "사용자를 선택하세요",
"Powered by": "제공",
"Private": "비공개",
"Proceed": "계속",
"Processing...": "처리 중...",
"Public": "공개",
"Publish": "게시",
"Publish State": "게시 상태",
"Published": "게시됨",
"Published on": "게시일",
"Recent uploads": "최근 업로드",
"Recommended": "추천",
"Record Screen": "화면 녹화",
"Register": "등록",
"Remove category": "카테고리 제거",
"Remove from list": "목록에서 제거",
"Remove tag": "태그 제거",
"Remove user": "사용자 제거",
"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,73 +1,257 @@
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",
"EDIT SUBTITLE": "ONDERTITEL BEWERKEN",
"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",
"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,73 +1,257 @@
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",
"EDIT SUBTITLE": "EDITAR LEGENDA",
"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",
"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",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ Создать плейлист",
"00 - 20 min": "00 - 20 мин",
"1 result for": "1 результат для",
"20 - 40 min": "20 - 40 мин",
"40 - 60 min": "40 - 60 мин",
"60 - 120 min+": "60 - 120 мин+",
"ABOUT": "О",
"AUTOPLAY": "Автовоспроизведение",
"About": "О",
"Add / Remove Co-Editors": "Добавить / Удалить соредакторов",
"Add / Remove Co-Owners": "Добавить / Удалить совладельцев",
"Add / Remove Co-Viewers": "Добавить / Удалить созрителей",
"Add / Remove Tags": "Добавить / Удалить теги",
"Add / Remove from Categories": "Добавить / Удалить из категорий",
"Add a ": "Добавить ",
"Add to": "Добавить в",
"Add to / Remove from Category": "Добавить / Удалить из категории",
"Add to / Remove from Playlist": "Добавить / Удалить из плейлиста",
"All": "Все",
"All categories already added": "Все категории уже добавлены",
"All tags already added": "Все теги уже добавлены",
"Alphabetically - A-Z": "По алфавиту - А",
"Alphabetically - Z-A": "По алфавиту - Я-А",
"Audio": "Аудио",
"Browse your files": "Просмотреть файлы",
"Bulk Actions": "Массовые действия",
"COMMENT": "КОММЕНТАРИЙ",
"Cancel": "Отмена",
"Categories": "Категории",
"Category": "Категория",
"Change Language": "Изменить язык",
"Change Owner": "Изменить владельца",
"Change password": "Изменить пароль",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "Нажмите 'Начать запись' и выберите экран или вкладку для записи. После окончания записи нажмите 'Остановить запись', и запись будет загружена.",
"Co-Editors": "Соредакторы",
"Co-Owners": "Совладельцы",
"Co-Viewers": "Созрители",
"Comment": "Комментарий",
"Comments": "Комментарии",
"Comments are disabled": "Комментарии отключены",
"Confirm": "Подтвердить",
"Confirm Action": "Подтвердить действие",
"Contact": "Контакт",
"Copy Media": "Копировать медиа",
"Create": "Создать",
"DELETE": "УДАЛИТЬ",
"DELETE MEDIA": "УДАЛИТЬ МЕДИА",
"DOWNLOAD": "СКАЧАТЬ",
"DURATION": "ДЛИТЕЛЬНОСТЬ",
"Delete Media": "Удалить медиа",
"Delete media": "Удалить медиа",
"Disable Comments": "Отключить комментарии",
"Disable Download": "Отключить загрузку",
"Drag and drop files": "Перетащите файлы",
"EDIT MEDIA": "РЕДАКТИРОВАТЬ МЕДИА",
"EDIT PROFILE": "РЕДАКТИРОВАТЬ ПРОФИЛЬ",
"EDIT SUBTITLE": "РЕДАКТИРОВАТЬ СУБТИТРЫ",
"Edit media": "Редактировать медиа",
"Edit profile": "Редактировать профиль",
"Edit subtitle": "Редактировать субтитры",
"Enable Comments": "Включить комментарии",
"Enable Download": "Включить загрузку",
"Enter playlist name...": "Введите название плейлиста...",
"Failed to add categories": "Не удалось добавить категории",
"Failed to add media to playlists": "Не удалось добавить медиа в плейлисты",
"Failed to add tags": "Не удалось добавить теги",
"Failed to add users": "Не удалось добавить пользователей",
"Failed to change owner": "Не удалось изменить владельца",
"Failed to change owner. Please try again.": "Не удалось изменить владельца. Пожалуйста, попробуйте снова.",
"Failed to copy media.": "Не удалось скопировать медиа.",
"Failed to create playlist": "Не удалось создать плейлист",
"Failed to delete media. Please try again.": "Не удалось удалить медиа. Пожалуйста, попробуйте снова.",
"Failed to disable comments.": "Не удалось отключить комментарии.",
"Failed to disable download.": "Не удалось отключить загрузку.",
"Failed to enable comments.": "Не удалось включить комментарии.",
"Failed to enable download.": "Не удалось включить загрузку.",
"Failed to fetch all categories": "Не удалось получить все категории",
"Failed to fetch all tags": "Не удалось получить все теги",
"Failed to fetch existing categories": "Не удалось получить существующие категории",
"Failed to fetch existing tags": "Не удалось получить существующие теги",
"Failed to fetch existing users": "Не удалось получить существующих пользователей",
"Failed to fetch playlist membership": "Не удалось получить членство в плейлисте",
"Failed to fetch playlists": "Не удалось получить плейлисты",
"Failed to load categories": "Не удалось загрузить категории",
"Failed to load existing permissions": "Не удалось загрузить существующие разрешения",
"Failed to load playlists": "Не удалось загрузить плейлисты",
"Failed to load tags": "Не удалось загрузить теги",
"Failed to remove categories": "Не удалось удалить категории",
"Failed to remove media from playlists": "Не удалось удалить медиа из плейлистов",
"Failed to remove tags": "Не удалось удалить теги",
"Failed to remove users": "Не удалось удалить пользователей",
"Failed to search users": "Не удалось найти пользователей",
"Failed to set publish state": "Не удалось установить состояние публикации",
"Failed to set publish state. Please try again.": "Не удалось установить состояние публикации. Пожалуйста, попробуйте снова.",
"Failed to update categories. Please try again.": "Не удалось обновить категории. Пожалуйста, попробуйте снова.",
"Failed to update permissions. Please try again.": "Не удалось обновить разрешения. Пожалуйста, попробуйте снова.",
"Failed to update playlists. Please try again.": "Не удалось обновить плейлисты. Пожалуйста, попробуйте снова.",
"Failed to update tags. Please try again.": "Не удалось обновить теги. Пожалуйста, попробуйте снова.",
"Featured": "Рекомендуемое",
"Filter existing users...": "Фильтровать существующих пользователей...",
"Filter playlists...": "Фильтровать плейлисты...",
"Filters": "Фильтры",
"Go": "Перейти",
"History": "История",
"Home": "Главная",
"Image": "Изображение",
"Language": "Язык",
"Latest": "Последние",
"Like count": "Количество лайков",
"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": "Удалить пользователя",
"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

@ -0,0 +1,288 @@
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",
"EDIT SUBTITLE": "UREDI PODNAPISE",
"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",
"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",
}
replacement_strings = {
"Apr": "Apr",
"Aug": "Avg",
"Dec": "Dec",
"Feb": "Feb",
"Jan": "Jan",
"Jul": "Jul",
"Jun": "Jun",
"Mar": "Mar",
"May": "Maj",
"Nov": "Nov",
"Oct": "Okt",
"Sep": "Sep",
"day ago": "dan nazaj",
"days ago": "dni nazaj",
"hour ago": "ura nazaj",
"hours ago": "ur nazaj",
"just now": "pravkar",
"minute ago": "minuta nazaj",
"minutes ago": "minut nazaj",
"month ago": "mesec nazaj",
"months ago": "mesecev nazaj",
"second ago": "sekunda nazaj",
"seconds ago": "sekund nazaj",
"week ago": "teden nazaj",
"weeks ago": "tednov nazaj",
"year ago": "leto nazaj",
"years ago": "let nazaj",
}

View File

@ -1,73 +1,257 @@
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",
"EDIT SUBTITLE": "ALT YAZIYI DÜZENLE",
"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",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "+ پلے لسٹ بنائیں",
"00 - 20 min": "00 - 20 منٹ",
"1 result for": "1 نتیجہ",
"20 - 40 min": "20 - 40 منٹ",
"40 - 60 min": "40 - 60 منٹ",
"60 - 120 min+": "60 - 120 منٹ+",
"ABOUT": "کے بارے میں",
"AUTOPLAY": "خودکار پلے",
"About": "کے بارے میں",
"Add / Remove Co-Editors": "شریک ایڈیٹرز شامل / ہٹائیں",
"Add / Remove Co-Owners": "شریک مالکان شامل / ہٹائیں",
"Add / Remove Co-Viewers": "شریک ناظرین شامل / ہٹائیں",
"Add / Remove Tags": "ٹیگز شامل / ہٹائیں",
"Add / Remove from Categories": "اقسام میں شامل / ہٹائیں",
"Add a ": "شامل کریں",
"Add to": "میں شامل کریں",
"Add to / Remove from Category": "قسم میں شامل / ہٹائیں",
"Add to / Remove from Playlist": "پلے لسٹ میں شامل / ہٹائیں",
"All": "تمام",
"All categories already added": "تمام اقسام پہلے ہی شامل ہیں",
"All tags already added": "تمام ٹیگز پہلے ہی شامل ہیں",
"Alphabetically - A-Z": "حروف تہجی کے مطابق - A-Z",
"Alphabetically - Z-A": "حروف تہجی کے مطابق - Z-A",
"Audio": "آڈیو",
"Browse your files": "اپنی فائلیں براؤز کریں",
"Bulk Actions": "بلک ایکشنز",
"COMMENT": "تبصرہ",
"Cancel": "منسوخ کریں",
"Categories": "اقسام",
"Category": "قسم",
"Change Language": "زبان تبدیل کریں",
"Change Owner": "مالک تبدیل کریں",
"Change password": "پاس ورڈ تبدیل کریں",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "'ریکارڈنگ شروع کریں' پر کلک کریں اور ریکارڈ کرنے کے لیے اسکرین یا ٹیب منتخب کریں۔ ریکارڈنگ مکمل ہونے کے بعد، 'ریکارڈنگ بند کریں' پر کلک کریں، اور ریکارڈنگ اپ لوڈ ہو جائے گی۔",
"Co-Editors": "شریک ایڈیٹرز",
"Co-Owners": "شریک مالکان",
"Co-Viewers": "شریک ناظرین",
"Comment": "تبصرہ",
"Comments": "تبصرے",
"Comments are disabled": "تبصرے غیر فعال ہیں",
"Confirm": "تصدیق کریں",
"Confirm Action": "ایکشن کی تصدیق کریں",
"Contact": "رابطہ کریں",
"Copy Media": "میڈیا کاپی کریں",
"Create": "بنائیں",
"DELETE": "حذف کریں",
"DELETE MEDIA": "میڈیا حذف کریں",
"DOWNLOAD": "ڈاؤن لوڈ",
"DURATION": "دورانیہ",
"Delete Media": "میڈیا حذف کریں",
"Delete media": "میڈیا حذف کریں",
"Disable Comments": "تبصرے غیر فعال کریں",
"Disable Download": "ڈاؤن لوڈ غیر فعال کریں",
"Drag and drop files": "فائلیں گھسیٹیں اور چھوڑیں",
"EDIT MEDIA": "میڈیا ترمیم کریں",
"EDIT PROFILE": "پروفائل ترمیم کریں",
"EDIT SUBTITLE": "سب ٹائٹل ترمیم کریں",
"Edit media": "میڈیا ترمیم کریں",
"Edit profile": "پروفائل ترمیم کریں",
"Edit subtitle": "سب ٹائٹل ترمیم کریں",
"Enable Comments": "تبصرے فعال کریں",
"Enable Download": "ڈاؤن لوڈ فعال کریں",
"Enter playlist name...": "پلے لسٹ کا نام درج کریں...",
"Failed to add categories": "اقسام شامل کرنے میں ناکام",
"Failed to add media to playlists": "پلے لسٹس میں میڈیا شامل کرنے میں ناکام",
"Failed to add tags": "ٹیگز شامل کرنے میں ناکام",
"Failed to add users": "صارفین شامل کرنے میں ناکام",
"Failed to change owner": "مالک تبدیل کرنے میں ناکام",
"Failed to change owner. Please try again.": "مالک تبدیل کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to copy media.": "میڈیا کاپی کرنے میں ناکام۔",
"Failed to create playlist": "پلے لسٹ بنانے میں ناکام",
"Failed to delete media. Please try again.": "میڈیا حذف کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to disable comments.": "تبصرے غیر فعال کرنے میں ناکام۔",
"Failed to disable download.": "ڈاؤن لوڈ غیر فعال کرنے میں ناکام۔",
"Failed to enable comments.": "تبصرے فعال کرنے میں ناکام۔",
"Failed to enable download.": "ڈاؤن لوڈ فعال کرنے میں ناکام۔",
"Failed to fetch all categories": "تمام اقسام حاصل کرنے میں ناکام",
"Failed to fetch all tags": "تمام ٹیگز حاصل کرنے میں ناکام",
"Failed to fetch existing categories": "موجودہ اقسام حاصل کرنے میں ناکام",
"Failed to fetch existing tags": "موجودہ ٹیگز حاصل کرنے میں ناکام",
"Failed to fetch existing users": "موجودہ صارفین حاصل کرنے میں ناکام",
"Failed to fetch playlist membership": "پلے لسٹ ممبرشپ حاصل کرنے میں ناکام",
"Failed to fetch playlists": "پلے لسٹس حاصل کرنے میں ناکام",
"Failed to load categories": "اقسام لوڈ کرنے میں ناکام",
"Failed to load existing permissions": "موجودہ اجازات لوڈ کرنے میں ناکام",
"Failed to load playlists": "پلے لسٹس لوڈ کرنے میں ناکام",
"Failed to load tags": "ٹیگز لوڈ کرنے میں ناکام",
"Failed to remove categories": "اقسام ہٹانے میں ناکام",
"Failed to remove media from playlists": "پلے لسٹس سے میڈیا ہٹانے میں ناکام",
"Failed to remove tags": "ٹیگز ہٹانے میں ناکام",
"Failed to remove users": "صارفین ہٹانے میں ناکام",
"Failed to search users": "صارفین تلاش کرنے میں ناکام",
"Failed to set publish state": "اشاعت کی حالت سیٹ کرنے میں ناکام",
"Failed to set publish state. Please try again.": "اشاعت کی حالت سیٹ کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to update categories. Please try again.": "اقسام اپ ڈیٹ کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to update permissions. Please try again.": "اجازات اپ ڈیٹ کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to update playlists. Please try again.": "پلے لسٹس اپ ڈیٹ کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Failed to update tags. Please try again.": "ٹیگز اپ ڈیٹ کرنے میں ناکام۔ براہ کرم دوبارہ کوشش کریں۔",
"Featured": "نمایاں",
"Filter existing users...": "موجودہ صارفین فلٹر کریں...",
"Filter playlists...": "پلے لسٹس فلٹر کریں...",
"Filters": "فلٹرز",
"Go": "جائیں",
"History": "تاریخ",
"Home": "ہوم",
"Image": "تصویر",
"Language": "زبان",
"Latest": "تازہ ترین",
"Like count": "پسند کی تعداد",
"Liked media": "پسندیدہ میڈیا",
"Likes - Least": "پسند - کم سے کم",
"Likes - Most": "پسند - سب سے زیادہ",
"Loading categories...": "اقسام لوڈ ہو رہی ہیں...",
"Loading existing users...": "موجودہ صارفین لوڈ ہو رہے ہیں...",
"Loading playlists...": "پلے لسٹس لوڈ ہو رہی ہیں...",
"Loading tags...": "ٹیگز لوڈ ہو رہے ہیں...",
"MEDIA TYPE": "میڈیا کی قسم",
"Manage": "منظم کریں",
"Manage Playlists": "پلے لسٹس منظم کریں",
"Manage comments": "تبصرے منظم کریں",
"Manage media": "میڈیا منظم کریں",
"Manage users": "صارفین منظم کریں",
"Media": "میڈیا",
"Media I own": "",
"Media was edited": "میڈیا ترمیم کیا گیا",
"Members": "اراکین",
"My media": "میرا میڈیا",
"My playlists": "میری پلے لسٹس",
"No": "نہیں",
"No categories": "کوئی اقسام نہیں",
"No comment yet": "ابھی تک کوئی تبصرہ نہیں",
"No comments yet": "ابھی تک کوئی تبصرے نہیں",
"No existing": "کوئی موجودہ نہیں",
"No playlists available": "کوئی پلے لسٹ دستیاب نہیں",
"No playlists selected": "کوئی پلے لسٹ منتخب نہیں",
"No results for": "کے لئے کوئی نتائج نہیں",
"No tags": "کوئی ٹیگز نہیں",
"No users to add": "شامل کرنے کے لیے کوئی صارف نہیں",
"PLAYLISTS": "پلے لسٹس",
"PUBLISH STATE": "اشاعت کی حالت",
"Pdf": "PDF",
"Playlists": "پلے لسٹس",
"Plays - Least": "پلے - کم سے کم",
"Plays - Most": "پلے - سب سے زیادہ",
"Please select a publish state": "براہ کرم اشاعت کی حالت منتخب کریں",
"Please select a user": "براہ کرم صارف منتخب کریں",
"Powered by": "کے ذریعہ تقویت یافتہ",
"Private": "نجی",
"Proceed": "آگے بڑھیں",
"Processing...": "پروسیسنگ...",
"Public": "عوامی",
"Publish": "شائع کریں",
"Publish State": "اشاعت کی حالت",
"Published": "شائع شدہ",
"Published on": "پر شائع ہوا",
"Recent uploads": "حالیہ اپ لوڈز",
"Recommended": "تجویز کردہ",
"Record Screen": "اسکرین ریکارڈ کریں",
"Register": "رجسٹر کریں",
"Remove category": "قسم ہٹائیں",
"Remove from list": "فہرست سے ہٹائیں",
"Remove tag": "ٹیگ ہٹائیں",
"Remove user": "صارف ہٹائیں",
"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,73 +1,257 @@
translation_strings = {
"+ Create Playlist": "",
"00 - 20 min": "00 - 20分钟",
"1 result for": "1个结果",
"20 - 40 min": "20 - 40分钟",
"40 - 60 min": "40 - 60分钟",
"60 - 120 min+": "60 - 120分钟+",
"ABOUT": "关于",
"AUTOPLAY": "自动播放",
"About": "关于",
"Add / Remove Co-Editors": "",
"Add / Remove Co-Owners": "",
"Add / Remove Co-Viewers": "",
"Add / Remove Tags": "",
"Add / Remove from Categories": "",
"Add a ": "添加一个",
"Add to": "",
"Add to / Remove from Category": "",
"Add to / Remove from Playlist": "",
"All": "全部",
"All categories already added": "",
"All tags already added": "",
"Alphabetically - A-Z": "按字母顺序 - A-Z",
"Alphabetically - Z-A": "按字母顺序 - Z-A",
"Audio": "音频",
"Browse your files": "浏览文件",
"Bulk Actions": "",
"COMMENT": "评论",
"Cancel": "",
"Categories": "分类",
"Category": "类别",
"Change Language": "更改语言",
"Change Owner": "",
"Change password": "更改密码",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "点击“开始录制”并选择要录制的屏幕或标签页。录制完成后,点击“停止录制”,录制内容将被上传。",
"Co-Editors": "",
"Co-Owners": "",
"Co-Viewers": "",
"Comment": "评论",
"Comments": "评论",
"Comments are disabled": "评论已禁用",
"Confirm": "",
"Confirm Action": "",
"Contact": "联系",
"Copy Media": "",
"Create": "",
"DELETE": "删除",
"DELETE MEDIA": "删除媒体",
"DOWNLOAD": "下载",
"DURATION": "时长",
"Delete Media": "",
"Delete media": "删除媒体",
"Disable Comments": "",
"Disable Download": "",
"Drag and drop files": "拖放文件",
"EDIT MEDIA": "编辑媒体",
"EDIT PROFILE": "编辑个人资料",
"EDIT SUBTITLE": "编辑字幕",
"Edit media": "编辑媒体",
"Edit profile": "编辑个人资料",
"Edit subtitle": "编辑字幕",
"Enable Comments": "",
"Enable Download": "",
"Enter playlist name...": "",
"Failed to add categories": "",
"Failed to add media to playlists": "",
"Failed to add tags": "",
"Failed to add users": "",
"Failed to change owner": "",
"Failed to change owner. Please try again.": "",
"Failed to copy media.": "复制媒体失败。",
"Failed to create playlist": "",
"Failed to delete media. Please try again.": "删除媒体失败。请重试。",
"Failed to disable comments.": "禁用评论失败。",
"Failed to disable download.": "禁用下载失败。",
"Failed to enable comments.": "启用评论失败。",
"Failed to enable download.": "启用下载失败。",
"Failed to fetch all categories": "",
"Failed to fetch all tags": "",
"Failed to fetch existing categories": "",
"Failed to fetch existing tags": "",
"Failed to fetch existing users": "",
"Failed to fetch playlist membership": "",
"Failed to fetch playlists": "",
"Failed to load categories": "",
"Failed to load existing permissions": "",
"Failed to load playlists": "",
"Failed to load tags": "",
"Failed to remove categories": "",
"Failed to remove media from playlists": "",
"Failed to remove tags": "",
"Failed to remove users": "",
"Failed to search users": "",
"Failed to set publish state": "",
"Failed to set publish state. Please try again.": "",
"Failed to update categories. Please try again.": "",
"Failed to update permissions. Please try again.": "",
"Failed to update playlists. Please try again.": "",
"Failed to update tags. Please try again.": "",
"Featured": "精选",
"Filter existing users...": "",
"Filter playlists...": "",
"Filters": "筛选",
"Go": "",
"History": "历史",
"Home": "主页",
"Image": "图片",
"Language": "语言",
"Latest": "最新",
"Like count": "点赞数",
"Liked media": "喜欢的媒体",
"Likes - Least": "点赞 - 最少",
"Likes - Most": "点赞 - 最多",
"Loading categories...": "",
"Loading existing users...": "",
"Loading playlists...": "",
"Loading tags...": "",
"MEDIA TYPE": "媒体类型",
"Manage": "",
"Manage Playlists": "",
"Manage comments": "管理评论",
"Manage media": "管理媒体",
"Manage users": "管理用户",
"Media": "媒体",
"Media I own": "",
"Media was edited": "媒体已编辑",
"Members": "成员",
"My media": "我的媒体",
"My playlists": "我的播放列表",
"No": "",
"No categories": "",
"No comment yet": "还没有评论",
"No comments yet": "还没有评论",
"No existing": "",
"No playlists available": "",
"No playlists selected": "",
"No results for": "没有结果",
"No tags": "",
"No users to add": "",
"PLAYLISTS": "播放列表",
"PUBLISH STATE": "发布状态",
"Pdf": "PDF",
"Playlists": "播放列表",
"Plays - Least": "播放 - 最少",
"Plays - Most": "播放 - 最多",
"Please select a publish state": "",
"Please select a user": "",
"Powered by": "由...提供技术支持",
"Private": "私密",
"Proceed": "",
"Processing...": "",
"Public": "",
"Publish": "发布",
"Publish State": "",
"Published": "已发布",
"Published on": "发布于",
"Recent uploads": "最近上传",
"Recommended": "推荐",
"Record Screen": "录制屏幕",
"Register": "注册",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"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,104 +1,288 @@
translation_strings = {
'ABOUT': '關於',
'AUTOPLAY': '自動播放',
'About': '關於',
'Add a ': '新增',
'COMMENT': '留言',
'Categories': '分類',
'Category': '分類',
'Change Language': '切換語言',
'Change password': '變更密碼',
'Comment': '留言',
'Comments': '留言',
'Comments are disabled': '留言功能已關閉',
'Contact': '聯絡資訊',
'DELETE MEDIA': '刪除影片',
'DOWNLOAD': '下載',
'EDIT MEDIA': '編輯影片',
'EDIT PROFILE': '編輯個人資料',
'EDIT SUBTITLE': '編輯字幕',
'Edit media': '編輯影片',
'Edit profile': '編輯個人資料',
'Edit subtitle': '編輯字幕',
'Featured': '精選內容',
'Go': '執行', # in context of "execution"
'History': '觀看紀錄',
'Home': '首頁',
'Language': '語言',
'Latest': '最新內容',
'Liked media': '我喜歡的影片',
'Manage comments': '留言管理',
'Manage media': '媒體管理',
'Manage users': '使用者管理',
'Media': '媒體',
'Media was edited': '媒體已更新',
'Members': '會員',
'My media': '我的媒體',
'My playlists': '我的播放清單',
'No': '', # in context of "no comments", etc.
'No comment yet': '尚無留言',
'No comments yet': '尚未有留言',
'No results for': '查無相關結果:',
'PLAYLISTS': '播放清單',
'Playlists': '播放清單',
'Powered by': '技術提供為',
'Published on': '發布日期為',
'Recommended': '推薦內容',
'Register': '註冊',
'SAVE': '儲存',
'SEARCH': '搜尋',
'SHARE': '分享',
'SHOW MORE': '顯示更多',
'SUBMIT': '送出',
'Search': '搜尋',
'Select': '選擇',
'Sign in': '登入',
'Sign out': '登出',
'Subtitle was added': '字幕已新增',
'Tags': '標籤',
'Terms': '使用條款',
'UPLOAD': '上傳',
'Up next': '即將播放',
'Upload': '上傳',
'Upload media': '上傳媒體',
'Uploads': '上傳內容',
'VIEW ALL': '查看全部',
'View all': '瀏覽全部',
'comment': '留言',
'is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media': '這是一個現代化且功能完整的開源影音內容管理系統,專為現代網路平台的觀賞與分享需求所打造。',
'media in category': '此分類下的媒體',
'media in tag': '此標籤下的媒體',
'view': '次觀看',
'views': '次觀看',
'yet': ' ', # no such usage in this language,
"+ Create Playlist": "",
"00 - 20 min": "00 - 20 分鐘",
"1 result for": "1 個結果",
"20 - 40 min": "20 - 40 分鐘",
"40 - 60 min": "40 - 60 分鐘",
"60 - 120 min+": "60 - 120 分鐘+",
"ABOUT": "關於",
"AUTOPLAY": "自動播放",
"About": "關於",
"Add / Remove Co-Editors": "",
"Add / Remove Co-Owners": "",
"Add / Remove Co-Viewers": "",
"Add / Remove Tags": "",
"Add / Remove from Categories": "",
"Add a ": "新增",
"Add to": "",
"Add to / Remove from Category": "",
"Add to / Remove from Playlist": "",
"All": "全部",
"All categories already added": "",
"All tags already added": "",
"Alphabetically - A-Z": "依字母順序 - A-Z",
"Alphabetically - Z-A": "依字母順序 - Z-A",
"Audio": "音訊",
"Browse your files": "瀏覽您的檔案",
"Bulk Actions": "",
"COMMENT": "留言",
"Cancel": "",
"Categories": "分類",
"Category": "分類",
"Change Language": "切換語言",
"Change Owner": "",
"Change password": "變更密碼",
"Click 'Start Recording' and select the screen or tab to record. Once recording is finished, click 'Stop Recording,' and the recording will be uploaded.": "點擊「開始錄製」並選擇要錄製的螢幕或分頁。錄製完成後,點擊「停止錄製」,錄製的內容將會上傳。",
"Co-Editors": "",
"Co-Owners": "",
"Co-Viewers": "",
"Comment": "留言",
"Comments": "留言",
"Comments are disabled": "留言功能已關閉",
"Confirm": "",
"Confirm Action": "",
"Contact": "聯絡資訊",
"Copy Media": "",
"Create": "",
"DELETE": "刪除",
"DELETE MEDIA": "刪除影片",
"DOWNLOAD": "下載",
"DURATION": "時長",
"Delete Media": "",
"Delete media": "刪除媒體",
"Disable Comments": "",
"Disable Download": "",
"Drag and drop files": "拖放檔案",
"EDIT MEDIA": "編輯影片",
"EDIT PROFILE": "編輯個人資料",
"EDIT SUBTITLE": "編輯字幕",
"Edit media": "編輯影片",
"Edit profile": "編輯個人資料",
"Edit subtitle": "編輯字幕",
"Enable Comments": "",
"Enable Download": "",
"Enter playlist name...": "",
"Failed to add categories": "",
"Failed to add media to playlists": "",
"Failed to add tags": "",
"Failed to add users": "",
"Failed to change owner": "",
"Failed to change owner. Please try again.": "",
"Failed to copy media.": "複製媒體失敗。",
"Failed to create playlist": "",
"Failed to delete media. Please try again.": "刪除媒體失敗。請再試一次。",
"Failed to disable comments.": "停用留言失敗。",
"Failed to disable download.": "停用下載失敗。",
"Failed to enable comments.": "啟用留言失敗。",
"Failed to enable download.": "啟用下載失敗。",
"Failed to fetch all categories": "",
"Failed to fetch all tags": "",
"Failed to fetch existing categories": "",
"Failed to fetch existing tags": "",
"Failed to fetch existing users": "",
"Failed to fetch playlist membership": "",
"Failed to fetch playlists": "",
"Failed to load categories": "",
"Failed to load existing permissions": "",
"Failed to load playlists": "",
"Failed to load tags": "",
"Failed to remove categories": "",
"Failed to remove media from playlists": "",
"Failed to remove tags": "",
"Failed to remove users": "",
"Failed to search users": "",
"Failed to set publish state": "",
"Failed to set publish state. Please try again.": "",
"Failed to update categories. Please try again.": "",
"Failed to update permissions. Please try again.": "",
"Failed to update playlists. Please try again.": "",
"Failed to update tags. Please try again.": "",
"Featured": "精選內容",
"Filter existing users...": "",
"Filter playlists...": "",
"Filters": "篩選器",
"Go": "執行",
"History": "觀看紀錄",
"Home": "首頁",
"Image": "圖片",
"Language": "語言",
"Latest": "最新內容",
"Like count": "按讚數",
"Liked media": "我喜歡的影片",
"Likes - Least": "按讚數 - 最少",
"Likes - Most": "按讚數 - 最多",
"Loading categories...": "",
"Loading existing users...": "",
"Loading playlists...": "",
"Loading tags...": "",
"MEDIA TYPE": "媒體類型",
"Manage": "",
"Manage Playlists": "",
"Manage comments": "留言管理",
"Manage media": "媒體管理",
"Manage users": "使用者管理",
"Media": "媒體",
"Media I own": "",
"Media was edited": "媒體已更新",
"Members": "會員",
"My media": "我的媒體",
"My playlists": "我的播放清單",
"No": "",
"No categories": "",
"No comment yet": "尚無留言",
"No comments yet": "尚未有留言",
"No existing": "",
"No playlists available": "",
"No playlists selected": "",
"No results for": "查無相關結果:",
"No tags": "",
"No users to add": "",
"PLAYLISTS": "播放清單",
"PUBLISH STATE": "發布狀態",
"Pdf": "PDF",
"Playlists": "播放清單",
"Plays - Least": "播放次數 - 最少",
"Plays - Most": "播放次數 - 最多",
"Please select a publish state": "",
"Please select a user": "",
"Powered by": "技術提供為",
"Private": "私人",
"Proceed": "",
"Processing...": "",
"Public": "",
"Publish": "發布",
"Publish State": "",
"Published": "已發布",
"Published on": "發布日期為",
"Recent uploads": "最近上傳",
"Recommended": "推薦內容",
"Record Screen": "螢幕錄製",
"Register": "註冊",
"Remove category": "",
"Remove from list": "",
"Remove tag": "",
"Remove user": "",
"SAVE": "儲存",
"SEARCH": "搜尋",
"SHARE": "分享",
"SHOW MORE": "顯示更多",
"SORT BY": "排序方式",
"SUBMIT": "送出",
"Search": "搜尋",
"Search for user...": "",
"Search users to add...": "",
"Select": "選擇",
"Select Owner": "",
"Select all": "",
"Select all media": "",
"Select publish state:": "",
"Selected": "",
"Shared by me": "我分享的",
"Shared with me": "與我分享",
"Sign in": "登入",
"Sign out": "登出",
"Sort By": "排序方式",
"Start Recording": "開始錄製",
"Start uploading media and sharing your work. Media that you upload will show up here.": "開始上傳媒體並分享您的作品。您上傳的媒體將顯示在此處。",
"Stop Recording": "停止錄製",
"Submit": "",
"Subtitle was added": "字幕已新增",
"Subtitles": "字幕",
"Successfully Copied": "成功複製",
"Successfully Disabled Download": "成功停用下載",
"Successfully Disabled comments": "成功停用留言",
"Successfully Enabled Download": "成功啟用下載",
"Successfully Enabled comments": "成功啟用留言",
"Successfully changed owner": "",
"Successfully deleted": "成功刪除",
"Successfully updated": "",
"Successfully updated categories": "",
"Successfully updated playlist membership": "",
"Successfully updated publish state": "",
"Successfully updated tags": "",
"TAGS": "標籤",
"Tag": "標籤",
"Tags": "標籤",
"Terms": "使用條款",
"The intersection of categories in the selected media is shown": "",
"The intersection of playlists in the selected media is shown": "",
"The intersection of tags in the selected media is shown": "",
"The intersection of users in the selected media is shown": "",
"The media was deleted successfully.": "媒體已成功刪除。",
"This month": "本月",
"This week": "本週",
"This works in Chrome, Safari and Edge browsers.": "此功能適用於 Chrome、Safari 和 Edge 瀏覽器。",
"This year": "今年",
"To add": "",
"Today": "今天",
"Trim": "修剪",
"UPLOAD": "上傳",
"UPLOAD DATE": "上傳日期",
"UPLOAD MEDIA": "上傳媒體",
"Undo removal": "",
"Unlisted": "未列出",
"Up Next": "下一個",
"Up next": "即將播放",
"Upload": "上傳",
"Upload date (newest)": "上傳日期(最新)",
"Upload date (oldest)": "上傳日期(最舊)",
"Upload date - Newest": "上傳日期 - 最新",
"Upload date - Oldest": "上傳日期 - 最舊",
"Upload media": "上傳媒體",
"Uploads": "上傳內容",
"Users": "",
"VIEW ALL": "查看全部",
"Video": "影片",
"View all": "瀏覽全部",
"View count": "觀看次數",
"View media": "查看媒體",
"Welcome": "歡迎",
"You are going to copy": "您即將複製",
"You are going to delete": "您即將刪除",
"You are going to disable comments to": "您即將停用留言",
"You are going to disable download for": "您即將停用下載",
"You are going to enable comments to": "您即將啟用留言",
"You are going to enable download for": "您即將啟用下載",
"comment": "留言",
"is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media": "這是一個現代化且功能完整的開源影音內容管理系統,專為現代網路平台的觀賞與分享需求所打造。",
"media in category": "此分類下的媒體",
"media in tag": "此標籤下的媒體",
"media, are you sure?": "媒體,您確定嗎?",
"media.": "媒體。",
"or": "或者",
"results for": "個結果",
"selected": "",
"view": "次觀看",
"views": "次觀看",
"yet": " ",
}
replacement_strings = {
'Apr': '四月',
'Aug': '八月',
'Dec': '十二月',
'Feb': '二月',
'Jan': '一月',
'Jul': '七月',
'Jun': '六月',
'Mar': '三月',
'May': '五月',
'Nov': '十一月',
'Oct': '十月',
'Sep': '九月',
'day ago': '天前',
'days ago': '天前',
'hour ago': '小時前',
'hours ago': '小時前',
'just now': '剛剛',
'minute ago': '分鐘前',
'minutes ago': '分鐘前',
'month ago': '個月前',
'months ago': '個月前',
'second ago': '秒前',
'seconds ago': '秒前',
'week ago': '週前',
'weeks ago': '週前',
'year ago': '年前',
'years ago': '年前',
"Apr": "四月",
"Aug": "八月",
"Dec": "十二月",
"Feb": "二月",
"Jan": "一月",
"Jul": "七月",
"Jun": "六月",
"Mar": "三月",
"May": "五月",
"Nov": "十一月",
"Oct": "十月",
"Sep": "九月",
"day ago": "天前",
"days ago": "天前",
"hour ago": "小時前",
"hours ago": "小時前",
"just now": "剛剛",
"minute ago": "分鐘前",
"minutes ago": "分鐘前",
"month ago": "個月前",
"months ago": "個月前",
"second ago": "秒前",
"seconds ago": "秒前",
"week ago": "週前",
"weeks ago": "週前",
"year ago": "年前",
"years ago": "年前",
}

View File

@ -34,12 +34,6 @@ BUF_SIZE_MULTIPLIER = 1.5
KEYFRAME_DISTANCE = 4
KEYFRAME_DISTANCE_MIN = 2
# speed presets
# see https://trac.ffmpeg.org/wiki/Encode/H.264
X26x_PRESET = "medium" # "medium"
X265_PRESET = "medium"
X26x_PRESET_BIG_HEIGHT = "faster"
# VP9_SPEED = 1 # between 0 and 4, lower is slower
VP9_SPEED = 2
@ -55,6 +49,7 @@ VIDEO_CRFS = {
VIDEO_BITRATES = {
"h264": {
25: {
144: 150,
240: 300,
360: 500,
480: 1000,
@ -67,6 +62,7 @@ VIDEO_BITRATES = {
},
"h265": {
25: {
144: 75,
240: 150,
360: 275,
480: 500,
@ -79,6 +75,7 @@ VIDEO_BITRATES = {
},
"vp9": {
25: {
144: 75,
240: 150,
360: 275,
480: 500,
@ -173,7 +170,7 @@ def rm_dir(directory):
def url_from_path(filename):
# TODO: find a way to preserver http - https ...
return "{0}{1}".format(settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, ""))
return f"{settings.MEDIA_URL}{filename.replace(settings.MEDIA_ROOT, '')}"
def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY):
@ -488,7 +485,7 @@ def show_file_size(size):
if size:
size = size / 1000000
size = round(size, 1)
size = "{0}MB".format(str(size))
size = f"{str(size)}MB"
return size
@ -596,17 +593,13 @@ def get_base_ffmpeg_command(
cmd = base_cmd[:]
# preset settings
preset = getattr(settings, "FFMPEG_DEFAULT_PRESET", "medium")
if encoder == "libvpx-vp9":
if pass_number == 1:
speed = 4
else:
speed = VP9_SPEED
elif encoder in ["libx264"]:
preset = X26x_PRESET
elif encoder in ["libx265"]:
preset = X265_PRESET
if target_height >= 720:
preset = X26x_PRESET_BIG_HEIGHT
if encoder == "libx264":
level = "4.2" if target_height <= 1080 else "5.2"
@ -730,7 +723,7 @@ def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_fi
return False
if media_info.get("video_height") < resolution:
if resolution not in [240, 360]: # always get these two
if resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
return False
# if codec == "h264_baseline":
@ -917,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):
@ -927,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]

View File

@ -1,3 +1,5 @@
from django.conf import settings
from django.db.models import Q
from drf_yasg import openapi as openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
@ -46,6 +48,7 @@ class MediaList(APIView):
featured = params.get("featured", "").strip()
is_reviewed = params.get("is_reviewed", "").strip()
category = params.get("category", "").strip()
sort_by_options = [
"title",
@ -98,6 +101,9 @@ class MediaList(APIView):
if is_reviewed != "all":
qs = qs.filter(is_reviewed=is_reviewed)
if category:
qs = qs.filter(category__title__contains=category)
media = qs.order_by(f"{ordering}{sort_by}")
paginator = pagination_class()
@ -215,6 +221,13 @@ class UserList(APIView):
elif role == "editor":
qs = qs.filter(is_editor=True)
if settings.USERS_NEEDS_TO_BE_APPROVED:
is_approved = request.GET.get("is_approved")
if is_approved == "true":
qs = qs.filter(is_approved=True)
elif is_approved == "false":
qs = qs.filter(Q(is_approved=False) | Q(is_approved__isnull=True))
users = qs.order_by(f"{ordering}{sort_by}")
paginator = pagination_class()

View File

@ -3,6 +3,7 @@
import itertools
import logging
import os
import random
import re
import subprocess
@ -166,14 +167,14 @@ Media becomes private if it gets reported %s times\n
)
if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - Media was reported"
d = {}
d["title"] = title
d["msg"] = msg
d["to"] = settings.ADMIN_EMAIL_LIST
notify_items.append(d)
if settings.USERS_NOTIFICATIONS.get("MEDIA_REPORTED", False):
title = "[{}] - Media was reported".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - Media was reported"
d = {}
d["title"] = title
d["msg"] = msg
@ -182,7 +183,7 @@ Media becomes private if it gets reported %s times\n
if action == "media_added" and media:
if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False):
title = "[{}] - Media was added".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - Media was added"
msg = """
Media %s was added by user %s.
""" % (
@ -195,7 +196,7 @@ Media %s was added by user %s.
d["to"] = settings.ADMIN_EMAIL_LIST
notify_items.append(d)
if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False):
title = "[{}] - Your media was added".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - Your media was added"
msg = """
Your media has been added! It will be encoded and will be available soon.
URL: %s
@ -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
@ -339,7 +344,7 @@ def notify_user_on_comment(friendly_token):
media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
if user.notification_on_comments:
title = "[{}] - A comment was added".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - A comment was added"
msg = """
A comment has been added to your media %s .
View it on %s
@ -363,7 +368,7 @@ def notify_user_on_mention(friendly_token, user_mentioned, cleaned_comment):
media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url()
if user.notification_on_comments:
title = "[{}] - You were mentioned in a comment".format(settings.PORTAL_NAME)
title = f"[{settings.PORTAL_NAME}] - You were mentioned in a comment"
msg = """
You were mentioned in a comment on %s .
View it on %s
@ -401,6 +406,44 @@ def clean_comment(raw_comment):
return cleaned_comment
def user_allowed_to_upload(request):
"""Any custom logic for whether a user is allowed
to upload content lives here
"""
if request.user.is_anonymous:
return False
if is_mediacms_editor(request.user):
return True
# Check if user has reached the maximum number of uploads
if hasattr(settings, 'NUMBER_OF_MEDIA_USER_CAN_UPLOAD'):
if models.Media.objects.filter(user=request.user).count() >= settings.NUMBER_OF_MEDIA_USER_CAN_UPLOAD:
return False
if settings.CAN_ADD_MEDIA == "all":
return True
elif settings.CAN_ADD_MEDIA == "email_verified":
if request.user.email_is_verified:
return True
elif settings.CAN_ADD_MEDIA == "advancedUser":
if request.user.advancedUser:
return True
return False
def can_transcribe_video(user):
"""Checks if a user can transcribe a video."""
if not getattr(settings, 'USE_WHISPER_TRANSCRIBE', False):
return False
if is_mediacms_editor(user):
return True
if getattr(settings, 'USER_CAN_TRANSCRIBE_VIDEO', False):
return True
return False
def kill_ffmpeg_process(filepath):
"""Kill ffmpeg process that is processing a specific file
@ -432,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])
@ -459,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
@ -481,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
@ -566,4 +643,90 @@ def handle_video_chapters(media, chapters):
else:
video_chapter = models.VideoChapterData.objects.create(media=media, data=chapters)
return media.chapter_data
return {'chapters': media.chapter_data}
def change_media_owner(media_id, new_user):
"""Change the owner of a media
Args:
media_id: ID of the media to change owner
new_user: New user object to set as owner
Returns:
Media object or None if media not found
"""
media = models.Media.objects.filter(id=media_id).first()
if not media:
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"])
# 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):
"""Create a copy of a media
Args:
media: Media object to copy
Returns:
None
"""
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):
if "all" in settings.ALLOWED_MEDIA_UPLOAD_TYPES:
return True
return media.media_type in settings.ALLOWED_MEDIA_UPLOAD_TYPES

View File

@ -0,0 +1,17 @@
# Generated by Django 5.1.6 on 2025-06-20 08:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0008_alter_media_state_videotrimrequest'),
]
operations = [
migrations.AlterField(
model_name='media',
name='friendly_token',
field=models.CharField(blank=True, db_index=True, help_text='Identifier for the Media', max_length=150, unique=True),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.1.6 on 2025-07-05 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0009_alter_media_friendly_token'),
]
operations = [
migrations.AlterField(
model_name='encodeprofile',
name='resolution',
field=models.IntegerField(blank=True, choices=[(2160, '2160'), (1440, '1440'), (1080, '1080'), (720, '720'), (480, '480'), (360, '360'), (240, '240'), (144, '144')], null=True),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 5.1.6 on 2025-07-08 19:15
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0010_alter_encodeprofile_resolution'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MediaPermission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('permission', models.CharField(choices=[('viewer', 'Viewer'), ('editor', 'Editor'), ('owner', 'Owner')], max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('media', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='permissions', to='files.media')),
('owner_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='granted_permissions', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'media')},
},
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 5.1.6 on 2025-08-31 08:28
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0011_mediapermission'),
]
operations = [
migrations.AlterField(
model_name='language',
name='code',
field=models.CharField(help_text='language code', max_length=30),
),
migrations.AddField(
model_name='media',
name='allow_whisper_transcribe',
field=models.BooleanField(default=False, verbose_name='Transcribe auto-detected language'),
),
migrations.AddField(
model_name='media',
name='allow_whisper_transcribe_and_translate',
field=models.BooleanField(default=False, verbose_name='Transcribe auto-detected language and translate to English'),
),
migrations.CreateModel(
name='TranscriptionRequest',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('add_date', models.DateTimeField(auto_now_add=True)),
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('fail', 'Fail'), ('success', 'Success')], db_index=True, default='pending', max_length=20)),
('translate_to_english', models.BooleanField(default=False)),
('logs', models.TextField(blank=True, null=True)),
('media', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transcriptionrequests', to='files.media')),
],
),
]

View File

@ -0,0 +1,42 @@
# Generated by Django 5.2.6 on 2025-09-21 11:49
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0012_media_allow_whisper_transcribe_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Page',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(max_length=200, unique=True)),
('title', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('add_date', models.DateTimeField(auto_now_add=True)),
('edit_date', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name='TinyMCEMedia',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(upload_to='tinymce_media/')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('file_type', models.CharField(choices=[('image', 'Image'), ('media', 'Media')], max_length=10)),
('original_filename', models.CharField(max_length=255)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'TinyMCE Media',
'verbose_name_plural': 'TinyMCE Media',
'ordering': ['-uploaded_at'],
},
),
]

26
files/models/__init__.py Normal file
View File

@ -0,0 +1,26 @@
# Import all models for backward compatibility
from .category import Category, Tag # noqa: F401
from .comment import Comment # noqa: F401
from .encoding import EncodeProfile, Encoding # noqa: F401
from .license import License # noqa: F401
from .media import Media, MediaPermission # noqa: F401
from .page import Page, TinyMCEMedia # noqa: F401
from .playlist import Playlist, PlaylistMedia # noqa: F401
from .rating import Rating, RatingCategory # noqa: F401
from .subtitle import Language, Subtitle, TranscriptionRequest # noqa: F401
from .utils import CODECS # noqa: F401
from .utils import ENCODE_EXTENSIONS # noqa: F401
from .utils import ENCODE_EXTENSIONS_KEYS # noqa: F401
from .utils import ENCODE_RESOLUTIONS # noqa: F401
from .utils import ENCODE_RESOLUTIONS_KEYS # noqa: F401
from .utils import MEDIA_ENCODING_STATUS # noqa: F401
from .utils import MEDIA_STATES # noqa: F401
from .utils import MEDIA_TYPES_SUPPORTED # noqa: F401
from .utils import category_thumb_path # noqa: F401
from .utils import encoding_media_file_path # noqa: F401
from .utils import generate_uid # noqa: F401
from .utils import original_media_file_path # noqa: F401
from .utils import original_thumbnail_file_path # noqa: F401
from .utils import subtitles_file_path # noqa: F401
from .utils import validate_rating # noqa: F401
from .video_data import VideoChapterData, VideoTrimRequest # noqa: F401

156
files/models/category.py Normal file
View File

@ -0,0 +1,156 @@
from django.db import models
from django.urls import reverse
from django.utils.html import strip_tags
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFit
from .. import helpers
from .utils import category_thumb_path, generate_uid
class Category(models.Model):
"""A Category base model"""
uid = models.CharField(unique=True, max_length=36, default=generate_uid)
add_date = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, db_index=True)
description = models.TextField(blank=True)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, blank=True, null=True)
is_global = models.BooleanField(default=False, help_text="global categories or user specific")
media_count = models.IntegerField(default=0, help_text="number of media")
thumbnail = ProcessedImageField(
upload_to=category_thumb_path,
processors=[ResizeToFit(width=344, height=None)],
format="JPEG",
options={"quality": 85},
blank=True,
)
listings_thumbnail = models.CharField(max_length=400, blank=True, null=True, help_text="Thumbnail to show on listings")
is_rbac_category = models.BooleanField(default=False, db_index=True, help_text='If access to Category is controlled by role based membership of Groups')
identity_provider = models.ForeignKey(
'socialaccount.SocialApp',
blank=True,
null=True,
on_delete=models.CASCADE,
related_name='categories',
help_text='If category is related with a specific Identity Provider',
verbose_name='IDP Config Name',
)
def __str__(self):
return self.title
class Meta:
ordering = ["title"]
verbose_name_plural = "Categories"
def get_absolute_url(self):
return f"{reverse('search')}?c={self.title}"
def update_category_media(self):
"""Set media_count"""
# Always set number of Category the total number of media
# Depending on how RBAC is set and Permissions etc it is
# possible that users won't see all media in a Category
# but it's worth to handle this on the UI level
# (eg through a message that says that you see only files you have permissions to see)
self.media_count = Media.objects.filter(category=self).count()
self.save(update_fields=["media_count"])
# OLD logic
# if getattr(settings, 'USE_RBAC', False) and self.is_rbac_category:
# self.media_count = Media.objects.filter(category=self).count()
# else:
# self.media_count = Media.objects.filter(listable=True, category=self).count()
self.save(update_fields=["media_count"])
return True
@property
def thumbnail_url(self):
"""Return thumbnail for category
prioritize processed value of listings_thumbnail
then thumbnail
"""
if self.thumbnail:
return helpers.url_from_path(self.thumbnail.path)
if self.listings_thumbnail:
return self.listings_thumbnail
# 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
def save(self, *args, **kwargs):
strip_text_items = ["title", "description"]
for item in strip_text_items:
setattr(self, item, strip_tags(getattr(self, item, None)))
super(Category, self).save(*args, **kwargs)
class Tag(models.Model):
"""A Tag model"""
title = models.CharField(max_length=100, unique=True, db_index=True)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, blank=True, null=True)
media_count = models.IntegerField(default=0, help_text="number of media")
listings_thumbnail = models.CharField(
max_length=400,
blank=True,
null=True,
help_text="Thumbnail to show on listings",
db_index=True,
)
def __str__(self):
return self.title
class Meta:
ordering = ["title"]
def get_absolute_url(self):
return f"{reverse('search')}?t={self.title}"
def update_tag_media(self):
self.media_count = Media.objects.filter(state="public", is_reviewed=True, tags=self).count()
self.save(update_fields=["media_count"])
return True
def save(self, *args, **kwargs):
self.title = helpers.get_alphanumeric_only(self.title)
self.title = self.title[:100]
super(Tag, self).save(*args, **kwargs)
@property
def thumbnail_url(self):
if self.listings_thumbnail:
return self.listings_thumbnail
media = Media.objects.filter(tags=self, state="public").order_by("-views").first()
if media:
return media.thumbnail_url
return None
# Import Media to avoid circular imports
from .media import Media # noqa

46
files/models/comment.py Normal file
View File

@ -0,0 +1,46 @@
import uuid
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.html import strip_tags
from mptt.models import MPTTModel, TreeForeignKey
class Comment(MPTTModel):
"""Comments model"""
add_date = models.DateTimeField(auto_now_add=True)
media = models.ForeignKey("Media", on_delete=models.CASCADE, db_index=True, related_name="comments")
parent = TreeForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name="children")
text = models.TextField(help_text="text")
uid = models.UUIDField(unique=True, default=uuid.uuid4)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, db_index=True)
class MPTTMeta:
order_insertion_by = ["add_date"]
def __str__(self):
return f"On {self.media.title} by {self.user.username}"
def save(self, *args, **kwargs):
strip_text_items = ["text"]
for item in strip_text_items:
setattr(self, item, strip_tags(getattr(self, item, None)))
if self.text:
self.text = self.text[: settings.MAX_CHARS_FOR_COMMENT]
super(Comment, self).save(*args, **kwargs)
def get_absolute_url(self):
return f"{reverse('get_media')}?m={self.media.friendly_token}"
@property
def media_url(self):
return self.get_absolute_url()

303
files/models/encoding.py Normal file
View File

@ -0,0 +1,303 @@
import json
import tempfile
from django.conf import settings
from django.core.files import File
from django.db import models
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
from django.urls import reverse
from .. import helpers
from .utils import (
CODECS,
ENCODE_EXTENSIONS,
ENCODE_RESOLUTIONS,
MEDIA_ENCODING_STATUS,
encoding_media_file_path,
)
class EncodeProfile(models.Model):
"""Encode Profile model
keeps information for each profile
"""
name = models.CharField(max_length=90)
extension = models.CharField(max_length=10, choices=ENCODE_EXTENSIONS)
resolution = models.IntegerField(choices=ENCODE_RESOLUTIONS, blank=True, null=True)
codec = models.CharField(max_length=10, choices=CODECS, blank=True, null=True)
description = models.TextField(blank=True, help_text="description")
active = models.BooleanField(default=True)
def __str__(self):
return self.name
class Meta:
ordering = ["resolution"]
class Encoding(models.Model):
"""Encoding Media Instances"""
add_date = models.DateTimeField(auto_now_add=True)
commands = models.TextField(blank=True, help_text="commands run")
chunk = models.BooleanField(default=False, db_index=True, help_text="is chunk?")
chunk_file_path = models.CharField(max_length=400, blank=True)
chunks_info = models.TextField(blank=True)
logs = models.TextField(blank=True)
md5sum = models.CharField(max_length=50, blank=True, null=True)
media = models.ForeignKey("Media", on_delete=models.CASCADE, related_name="encodings")
media_file = models.FileField("encoding file", upload_to=encoding_media_file_path, blank=True, max_length=500)
profile = models.ForeignKey(EncodeProfile, on_delete=models.CASCADE)
progress = models.PositiveSmallIntegerField(default=0)
update_date = models.DateTimeField(auto_now=True)
retries = models.IntegerField(default=0)
size = models.CharField(max_length=20, blank=True)
status = models.CharField(max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending")
temp_file = models.CharField(max_length=400, blank=True)
task_id = models.CharField(max_length=100, blank=True)
total_run_time = models.IntegerField(default=0)
worker = models.CharField(max_length=100, blank=True)
@property
def media_encoding_url(self):
if self.media_file:
return helpers.url_from_path(self.media_file.path)
return None
@property
def media_chunk_url(self):
if self.chunk_file_path:
return helpers.url_from_path(self.chunk_file_path)
return None
def save(self, *args, **kwargs):
if self.media_file:
cmd = ["stat", "-c", "%s", self.media_file.path]
stdout = helpers.run_command(cmd).get("out")
if stdout:
size = int(stdout.strip())
self.size = helpers.show_file_size(size)
if self.chunk_file_path and not self.md5sum:
cmd = ["md5sum", self.chunk_file_path]
stdout = helpers.run_command(cmd).get("out")
if stdout:
md5sum = stdout.strip().split()[0]
self.md5sum = md5sum
super(Encoding, self).save(*args, **kwargs)
def update_size_without_save(self):
"""Update the size of an encoding without saving to avoid calling signals"""
if self.media_file:
cmd = ["stat", "-c", "%s", self.media_file.path]
stdout = helpers.run_command(cmd).get("out")
if stdout:
size = int(stdout.strip())
size = helpers.show_file_size(size)
Encoding.objects.filter(pk=self.pk).update(size=size)
return True
return False
def set_progress(self, progress, commit=True):
if isinstance(progress, int):
if 0 <= progress <= 100:
self.progress = progress
# save object with filter update
# to avoid calling signals
Encoding.objects.filter(pk=self.pk).update(progress=progress)
return True
return False
def __str__(self):
return f"{self.profile.name}-{self.media.title}"
def get_absolute_url(self):
return reverse("api_get_encoding", kwargs={"encoding_id": self.id})
@receiver(post_save, sender=Encoding)
def encoding_file_save(sender, instance, created, **kwargs):
"""Performs actions on encoding file delete
For example, if encoding is a chunk file, with encoding_status success,
perform a check if this is the final chunk file of a media, then
concatenate chunks, create final encoding file and delete chunks
"""
if instance.chunk and instance.status == "success":
# a chunk got completed
# check if all chunks are OK
# then concatenate to new Encoding - and remove chunks
# this should run only once!
if instance.media_file:
try:
orig_chunks = json.loads(instance.chunks_info).keys()
except BaseException:
instance.delete()
return False
chunks = Encoding.objects.filter(
media=instance.media,
profile=instance.profile,
chunks_info=instance.chunks_info,
chunk=True,
).order_by("add_date")
complete = True
# perform validation, make sure everything is there
for chunk in orig_chunks:
if not chunks.filter(chunk_file_path=chunk):
complete = False
break
for chunk in chunks:
if not (chunk.media_file and chunk.media_file.path):
complete = False
break
if complete:
# concatenate chunks and create final encoding file
chunks_paths = [f.media_file.path for f in chunks]
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
seg_file = helpers.create_temp_file(suffix=".txt", dir=temp_dir)
tf = helpers.create_temp_file(suffix=f".{instance.profile.extension}", dir=temp_dir)
with open(seg_file, "w") as ff:
for f in chunks_paths:
ff.write(f"file {f}\n")
cmd = [
settings.FFMPEG_COMMAND,
"-y",
"-f",
"concat",
"-safe",
"0",
"-i",
seg_file,
"-c",
"copy",
"-pix_fmt",
"yuv420p",
"-movflags",
"faststart",
tf,
]
stdout = helpers.run_command(cmd)
encoding = Encoding(
media=instance.media,
profile=instance.profile,
status="success",
progress=100,
)
all_logs = "\n".join([st.logs for st in chunks])
encoding.logs = f"{chunks_paths}\n{stdout}\n{all_logs}"
workers = list(set([st.worker for st in chunks]))
encoding.worker = json.dumps({"workers": workers})
start_date = min([st.add_date for st in chunks])
end_date = max([st.update_date for st in chunks])
encoding.total_run_time = (end_date - start_date).seconds
encoding.save()
with open(tf, "rb") as f:
myfile = File(f)
output_name = f"{helpers.get_file_name(instance.media.media_file.path)}.{instance.profile.extension}"
encoding.media_file.save(content=myfile, name=output_name)
# encoding is saved, deleting chunks
# and any other encoding that might exist
# first perform one last validation
# to avoid that this is run twice
if (
len(orig_chunks)
== Encoding.objects.filter( # noqa
media=instance.media,
profile=instance.profile,
chunks_info=instance.chunks_info,
).count()
):
# if two chunks are finished at the same time, this
# will be changed
who = Encoding.objects.filter(media=encoding.media, profile=encoding.profile).exclude(id=encoding.id)
who.delete()
else:
encoding.delete()
if not Encoding.objects.filter(chunks_info=instance.chunks_info):
# TODO: in case of remote workers, files should be deleted
# example
# for worker in workers:
# for chunk in json.loads(instance.chunks_info).keys():
# remove_media_file.delay(media_file=chunk)
for chunk in json.loads(instance.chunks_info).keys():
helpers.rm_file(chunk)
instance.media.post_encode_actions(encoding=instance, action="add")
elif instance.chunk and instance.status == "fail":
encoding = Encoding(media=instance.media, profile=instance.profile, status="fail", progress=100)
chunks = Encoding.objects.filter(media=instance.media, chunks_info=instance.chunks_info, chunk=True).order_by("add_date")
chunks_paths = [f.media_file.path for f in chunks]
all_logs = "\n".join([st.logs for st in chunks])
encoding.logs = f"{chunks_paths}\n{all_logs}"
workers = list(set([st.worker for st in chunks]))
encoding.worker = json.dumps({"workers": workers})
start_date = min([st.add_date for st in chunks])
end_date = max([st.update_date for st in chunks])
encoding.total_run_time = (end_date - start_date).seconds
encoding.save()
who = Encoding.objects.filter(media=encoding.media, profile=encoding.profile).exclude(id=encoding.id)
who.delete()
# TODO: merge with above if, do not repeat code
else:
if instance.status in ["fail", "success"]:
instance.media.post_encode_actions(encoding=instance, action="add")
encodings = set([encoding.status for encoding in Encoding.objects.filter(media=instance.media)])
if ("running" in encodings) or ("pending" in encodings):
return
@receiver(post_delete, sender=Encoding)
def encoding_file_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `Encoding` object is deleted.
"""
if instance.media_file:
helpers.rm_file(instance.media_file.path)
if not instance.chunk:
instance.media.post_encode_actions(encoding=instance, action="delete")
# delete local chunks, and remote chunks + media file. Only when the
# last encoding of a media is complete

11
files/models/license.py Normal file
View File

@ -0,0 +1,11 @@
from django.db import models
class License(models.Model):
"""A Base license model to be used in Media"""
title = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.title

File diff suppressed because it is too large Load Diff

42
files/models/page.py Normal file
View File

@ -0,0 +1,42 @@
from django.db import models
from django.urls import reverse
class Page(models.Model):
slug = models.SlugField(max_length=200, unique=True)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
add_date = models.DateTimeField(auto_now_add=True)
edit_date = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("get_page", args=[str(self.slug)])
class TinyMCEMedia(models.Model):
file = models.FileField(upload_to='tinymce_media/')
uploaded_at = models.DateTimeField(auto_now_add=True)
file_type = models.CharField(
max_length=10,
choices=(
('image', 'Image'),
('media', 'Media'),
),
)
original_filename = models.CharField(max_length=255)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, null=True, blank=True)
class Meta:
verbose_name = 'TinyMCE Media'
verbose_name_plural = 'TinyMCE Media'
ordering = ['-uploaded_at']
def __str__(self):
return f"{self.original_filename} ({self.file_type})"
@property
def url(self):
return self.file.url

97
files/models/playlist.py Normal file
View File

@ -0,0 +1,97 @@
import uuid
from django.db import models
from django.urls import reverse
from django.utils.html import strip_tags
from .. import helpers
class Playlist(models.Model):
"""Playlists model"""
add_date = models.DateTimeField(auto_now_add=True, db_index=True)
description = models.TextField(blank=True, help_text="description")
friendly_token = models.CharField(blank=True, max_length=12, db_index=True)
media = models.ManyToManyField("Media", through="playlistmedia", blank=True)
title = models.CharField(max_length=100, db_index=True)
uid = models.UUIDField(unique=True, default=uuid.uuid4)
user = models.ForeignKey("users.User", on_delete=models.CASCADE, db_index=True, related_name="playlists")
def __str__(self):
return self.title
@property
def media_count(self):
return self.media.filter(listable=True).count()
def get_absolute_url(self, api=False):
if api:
return reverse("api_get_playlist", kwargs={"friendly_token": self.friendly_token})
else:
return reverse("get_playlist", kwargs={"friendly_token": self.friendly_token})
@property
def url(self):
return self.get_absolute_url()
@property
def api_url(self):
return self.get_absolute_url(api=True)
def user_thumbnail_url(self):
if self.user.logo:
return helpers.url_from_path(self.user.logo.path)
return None
def set_ordering(self, media, ordering):
if media not in self.media.all():
return False
pm = PlaylistMedia.objects.filter(playlist=self, media=media).first()
if pm and isinstance(ordering, int) and 0 < ordering:
pm.ordering = ordering
pm.save()
return True
return False
def save(self, *args, **kwargs):
strip_text_items = ["title", "description"]
for item in strip_text_items:
setattr(self, item, strip_tags(getattr(self, item, None)))
self.title = self.title[:100]
if not self.friendly_token:
while True:
friendly_token = helpers.produce_friendly_token()
if not Playlist.objects.filter(friendly_token=friendly_token):
self.friendly_token = friendly_token
break
super(Playlist, self).save(*args, **kwargs)
@property
def thumbnail_url(self):
pm = self.playlistmedia_set.filter(media__listable=True).first()
if pm and pm.media.thumbnail:
return helpers.url_from_path(pm.media.thumbnail.path)
return None
class PlaylistMedia(models.Model):
"""Helper model to store playlist specific media"""
action_date = models.DateTimeField(auto_now=True)
media = models.ForeignKey("Media", on_delete=models.CASCADE)
playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE)
ordering = models.IntegerField(default=1)
class Meta:
ordering = ["ordering", "-action_date"]

47
files/models/rating.py Normal file
View File

@ -0,0 +1,47 @@
from django.db import models
from .utils import validate_rating
class RatingCategory(models.Model):
"""Rating Category
Facilitate user ratings.
One or more rating categories per Category can exist
will be shown to the media if they are enabled
"""
description = models.TextField(blank=True)
enabled = models.BooleanField(default=True)
title = models.CharField(max_length=200, unique=True, db_index=True)
class Meta:
verbose_name_plural = "Rating Categories"
def __str__(self):
return f"{self.title}"
class Rating(models.Model):
"""User Rating"""
add_date = models.DateTimeField(auto_now_add=True)
media = models.ForeignKey("Media", on_delete=models.CASCADE, related_name="ratings")
rating_category = models.ForeignKey(RatingCategory, on_delete=models.CASCADE)
score = models.IntegerField(validators=[validate_rating])
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
class Meta:
verbose_name_plural = "Ratings"
indexes = [
models.Index(fields=["user", "media"]),
]
unique_together = ("user", "media", "rating_category")
def __str__(self):
return f"{self.user.username}, rate for {self.media.title} for category {self.rating_category.title}"

114
files/models/subtitle.py Normal file
View File

@ -0,0 +1,114 @@
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
from .utils import MEDIA_ENCODING_STATUS, subtitles_file_path
class Language(models.Model):
"""Language model
to be used with Subtitles
"""
code = models.CharField(max_length=30, help_text="language code")
title = models.CharField(max_length=100, help_text="language code")
class Meta:
ordering = ["id"]
def __str__(self):
return f"{self.code}-{self.title}"
class Subtitle(models.Model):
"""Subtitles model"""
language = models.ForeignKey(Language, on_delete=models.CASCADE)
media = models.ForeignKey("Media", on_delete=models.CASCADE, related_name="subtitles")
subtitle_file = models.FileField(
"Subtitle/CC file",
help_text="File has to be WebVTT format",
upload_to=subtitles_file_path,
max_length=500,
)
user = models.ForeignKey("users.User", on_delete=models.CASCADE)
class Meta:
verbose_name = "Caption"
verbose_name_plural = "Captions"
ordering = ["language__title"]
def __str__(self):
return f"{self.media.title}-{self.language.title}"
def get_absolute_url(self):
return f"{reverse('edit_subtitle')}?id={self.id}"
@property
def url(self):
return self.get_absolute_url()
def convert_to_srt(self):
input_path = self.subtitle_file.path
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
pysub = settings.PYSUBS_COMMAND
cmd = [pysub, input_path, "--to", "vtt", "-o", tmpdirname]
stdout = helpers.run_command(cmd)
list_of_files = os.listdir(tmpdirname)
if list_of_files:
subtitles_file = os.path.join(tmpdirname, list_of_files[0])
cmd = ["cp", subtitles_file, input_path]
stdout = helpers.run_command(cmd) # noqa
else:
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
media = models.ForeignKey("Media", on_delete=models.CASCADE, related_name="transcriptionrequests")
add_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending", db_index=True)
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,
)

99
files/models/utils.py Normal file
View File

@ -0,0 +1,99 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.crypto import get_random_string
from .. import helpers
# this is used by Media and Encoding models
# reflects media encoding status for objects
MEDIA_ENCODING_STATUS = (
("pending", "Pending"),
("running", "Running"),
("fail", "Fail"),
("success", "Success"),
)
# the media state of a Media object
# this is set by default according to the portal workflow
MEDIA_STATES = (
("private", "Private"),
("public", "Public"),
("unlisted", "Unlisted"),
)
# each uploaded Media gets a media_type hint
# by helpers.get_file_type
MEDIA_TYPES_SUPPORTED = (
("video", "Video"),
("image", "Image"),
("pdf", "Pdf"),
("audio", "Audio"),
)
ENCODE_EXTENSIONS = (
("mp4", "mp4"),
("webm", "webm"),
("gif", "gif"),
)
ENCODE_RESOLUTIONS = (
(2160, "2160"),
(1440, "1440"),
(1080, "1080"),
(720, "720"),
(480, "480"),
(360, "360"),
(240, "240"),
(144, "144"),
)
CODECS = (
("h265", "h265"),
("h264", "h264"),
("vp9", "vp9"),
)
ENCODE_EXTENSIONS_KEYS = [extension for extension, name in ENCODE_EXTENSIONS]
ENCODE_RESOLUTIONS_KEYS = [resolution for resolution, name in ENCODE_RESOLUTIONS]
def generate_uid():
return get_random_string(length=16)
def original_media_file_path(instance, filename):
"""Helper function to place original media file"""
file_name = f"{instance.uid.hex}.{helpers.get_file_name(filename)}"
return settings.MEDIA_UPLOAD_DIR + f"user/{instance.user.username}/{file_name}"
def encoding_media_file_path(instance, filename):
"""Helper function to place encoded media file"""
file_name = f"{instance.media.uid.hex}.{helpers.get_file_name(filename)}"
return settings.MEDIA_ENCODING_DIR + f"{instance.profile.id}/{instance.media.user.username}/{file_name}"
def original_thumbnail_file_path(instance, filename):
"""Helper function to place original media thumbnail file"""
return settings.THUMBNAIL_UPLOAD_DIR + f"user/{instance.user.username}/{filename}"
def subtitles_file_path(instance, filename):
"""Helper function to place subtitle file"""
return settings.SUBTITLES_UPLOAD_DIR + f"user/{instance.media.user.username}/{filename}"
def category_thumb_path(instance, filename):
"""Helper function to place category thumbnail file"""
file_name = f"{instance.uid}.{helpers.get_file_name(filename)}"
return settings.MEDIA_UPLOAD_DIR + f"categories/{file_name}"
def validate_rating(value):
if -1 >= value or value > 5:
raise ValidationError("score has to be between 0 and 5")

View File

@ -0,0 +1,69 @@
from django.db import models
from django.db.models.signals import post_delete
from django.dispatch import receiver
from .. import helpers
class VideoChapterData(models.Model):
data = models.JSONField(null=False, blank=False, help_text="Chapter data")
media = models.ForeignKey('Media', on_delete=models.CASCADE, related_name='chapters')
class Meta:
unique_together = ['media']
@property
def chapter_data(self):
# ensure response is consistent
data = []
if self.data and isinstance(self.data, list):
for item in self.data:
if item.get("startTime") and item.get("endTime") and item.get("chapterTitle"):
chapter_item = {
'startTime': item.get("startTime"),
'endTime': item.get("endTime"),
'chapterTitle': item.get("chapterTitle"),
}
data.append(chapter_item)
return data
class VideoTrimRequest(models.Model):
"""Model to handle video trimming requests"""
VIDEO_TRIM_STATUS = (
("initial", "Initial"),
("running", "Running"),
("success", "Success"),
("fail", "Fail"),
)
VIDEO_ACTION_CHOICES = (
("replace", "Replace Original"),
("save_new", "Save as New"),
("create_segments", "Create Segments"),
)
TRIM_STYLE_CHOICES = (
("no_encoding", "No Encoding"),
("precise", "Precise"),
)
media = models.ForeignKey('Media', on_delete=models.CASCADE, related_name='trim_requests')
status = models.CharField(max_length=20, choices=VIDEO_TRIM_STATUS, default="initial")
add_date = models.DateTimeField(auto_now_add=True)
video_action = models.CharField(max_length=20, choices=VIDEO_ACTION_CHOICES)
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})"
@receiver(post_delete, sender=VideoChapterData)
def videochapterdata_delete(sender, instance, **kwargs):
helpers.rm_dir(instance.media.video_chapters_folder)

View File

@ -212,6 +212,7 @@ class CategorySerializer(serializers.ModelSerializer):
model = Category
fields = (
"title",
"uid",
"description",
"is_global",
"media_count",
@ -232,7 +233,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

@ -46,10 +46,12 @@ from .models import (
Category,
EncodeProfile,
Encoding,
Language,
Media,
Rating,
Subtitle,
Tag,
VideoChapterData,
TranscriptionRequest,
VideoTrimRequest,
)
@ -136,8 +138,8 @@ def chunkize_media(self, friendly_token, profiles, force=True):
cwd = os.path.dirname(os.path.realpath(media.media_file.path))
file_name = media.media_file.path.split("/")[-1]
random_prefix = produce_friendly_token()
file_format = "{0}_{1}".format(random_prefix, file_name)
chunks_file_name = "%02d_{0}".format(file_format)
file_format = f"{random_prefix}_{file_name}"
chunks_file_name = f"%02d_{file_format}"
chunks_file_name += ".mkv"
cmd = [
settings.FFMPEG_COMMAND,
@ -162,7 +164,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
chunks.append(ch[0])
if not chunks:
# command completely failed to segment file.putting to normal encode
logger.info("Failed to break file {0} in chunks." " Putting to normal encode queue".format(friendly_token))
logger.info(f"Failed to break file {friendly_token} in chunks. Putting to normal encode queue")
for profile in profiles:
if media.video_height and media.video_height < profile.resolution:
if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
@ -211,7 +213,7 @@ def chunkize_media(self, friendly_token, profiles, force=True):
priority=priority,
)
logger.info("got {0} chunks and will encode to {1} profiles".format(len(chunks), to_profiles))
logger.info(f"got {len(chunks)} chunks and will encode to {to_profiles} profiles")
return True
@ -355,8 +357,8 @@ def encode_media(
# return False
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir:
tf = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir)
tfpass = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir)
tf = create_temp_file(suffix=f".{profile.extension}", dir=temp_dir)
tfpass = create_temp_file(suffix=f".{profile.extension}", dir=temp_dir)
ffmpeg_commands = produce_ffmpeg_commands(
original_media_path,
media.media_info,
@ -398,7 +400,7 @@ def encode_media(
if n_times % 60 == 0:
encoding.progress = percent
encoding.save(update_fields=["progress", "update_date"])
logger.info("Saved {0}".format(round(percent, 2)))
logger.info(f"Saved {round(percent, 2)}")
n_times += 1
except DatabaseError:
# primary reason for this is that the encoding has been deleted, because
@ -451,7 +453,7 @@ def encode_media(
with open(tf, "rb") as f:
myfile = File(f)
output_name = "{0}.{1}".format(get_file_name(original_media_path), profile.extension)
output_name = f"{get_file_name(original_media_path)}.{profile.extension}"
encoding.media_file.save(content=myfile, name=output_name)
encoding.total_run_time = (encoding.update_date - encoding.add_date).seconds
@ -465,6 +467,78 @@ def encode_media(
return success
@task(name="whisper_transcribe", queue="long_tasks", soft_time_limit=60 * 60 * 2)
def whisper_transcribe(friendly_token, translate_to_english=False):
try:
media = Media.objects.get(friendly_token=friendly_token)
except: # noqa
logger.info(f"failed to get media {friendly_token}")
return False
request = TranscriptionRequest.objects.filter(media=media, status="pending", translate_to_english=translate_to_english).first()
if not request:
logger.info(f"No pending transcription request for media {friendly_token}")
return False
if translate_to_english:
language = Language.objects.filter(code="whisper-translation").first()
if not language:
language = Language.objects.create(code="whisper-translation", title="English Translation")
else:
language = Language.objects.filter(code="whisper").first()
if not language:
language = Language.objects.create(code="whisper", title="Transcription")
cwd = os.path.dirname(os.path.realpath(media.media_file.path))
request.status = "running"
request.save(update_fields=["status"])
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
video_file_path = get_file_name(media.media_file.name)
video_file_path = '.'.join(video_file_path.split('.')[:-1]) # needed by whisper without the extension
subtitle_name = f"{video_file_path}.vtt"
output_name = f"{tmpdirname}/{subtitle_name}"
cmd = f"whisper /home/mediacms.io/mediacms/media_files/{media.media_file.name} --model {settings.WHISPER_MODEL} --output_dir {tmpdirname}"
if translate_to_english:
cmd += " --task translate"
logger.info(f"Whisper transcribe: ready to run command {cmd}")
start_time = datetime.now()
ret = run_command(cmd, cwd=cwd) # noqa
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
if os.path.exists(output_name):
subtitle = Subtitle.objects.create(media=media, user=media.user, language=language)
with open(output_name, 'rb') as f:
subtitle.subtitle_file.save(subtitle_name, File(f))
request.status = "success"
request.logs = f"Transcription took {duration:.2f} seconds." # noqa
request.save(update_fields=["status", "logs"])
return True
request.status = "fail"
request.logs = f"Transcription failed after {duration:.2f} seconds. Error: {ret.get('error')}" # noqa
request.save(update_fields=["status", "logs"])
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"""
@ -472,7 +546,7 @@ def produce_sprite_from_video(friendly_token):
try:
media = Media.objects.get(friendly_token=friendly_token)
except BaseException:
logger.info("failed to get media with friendly_token %s" % friendly_token)
logger.info(f"failed to get media with friendly_token {friendly_token}")
return False
with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname:
@ -516,7 +590,7 @@ def create_hls(friendly_token):
try:
media = Media.objects.get(friendly_token=friendly_token)
except BaseException:
logger.info("failed to get media with friendly_token %s" % friendly_token)
logger.info(f"failed to get media with friendly_token {friendly_token}")
return False
p = media.uid.hex
@ -558,7 +632,7 @@ def check_running_states():
encodings = Encoding.objects.filter(status="running")
logger.info("got {0} encodings that are in state running".format(encodings.count()))
logger.info(f"got {encodings.count()} encodings that are in state running")
changed = 0
for encoding in encodings:
now = datetime.now(encoding.update_date.tzinfo)
@ -575,7 +649,7 @@ def check_running_states():
# TODO: allign with new code + chunksize...
changed += 1
if changed:
logger.info("changed from running to pending on {0} items".format(changed))
logger.info(f"changed from running to pending on {changed} items")
return True
@ -585,7 +659,7 @@ def check_media_states():
# check encoding status of not success media
media = Media.objects.filter(Q(encoding_status="running") | Q(encoding_status="fail") | Q(encoding_status="pending"))
logger.info("got {0} media that are not in state success".format(media.count()))
logger.info(f"got {media.count()} media that are not in state success")
changed = 0
for m in media:
@ -593,7 +667,7 @@ def check_media_states():
m.save(update_fields=["encoding_status"])
changed += 1
if changed:
logger.info("changed encoding status to {0} media items".format(changed))
logger.info(f"changed encoding status to {changed} media items")
return True
@ -628,7 +702,7 @@ def check_pending_states():
media.encode(profiles=[profile], force=False)
changed += 1
if changed:
logger.info("set to the encode queue {0} encodings that were on pending state".format(changed))
logger.info(f"set to the encode queue {changed} encodings that were on pending state")
return True
@ -652,7 +726,7 @@ def check_missing_profiles():
# if they appear on the meanwhile (eg on a big queue)
changed += 1
if changed:
logger.info("set to the encode queue {0} profiles".format(changed))
logger.info(f"set to the encode queue {changed} profiles")
return True
@ -820,7 +894,7 @@ def update_listings_thumbnails():
# Categories
used_media = []
saved = 0
qs = Category.objects.filter().order_by("-media_count")
qs = Category.objects.filter()
for object in qs:
media = Media.objects.exclude(friendly_token__in=used_media).filter(category=object, state="public", is_reviewed=True).order_by("-views").first()
if media:
@ -828,12 +902,12 @@ def update_listings_thumbnails():
object.save(update_fields=["listings_thumbnail"])
used_media.append(media.friendly_token)
saved += 1
logger.info("updated {} categories".format(saved))
logger.info(f"updated {saved} categories")
# Tags
used_media = []
saved = 0
qs = Tag.objects.filter().order_by("-media_count")
qs = Tag.objects.filter()
for object in qs:
media = Media.objects.exclude(friendly_token__in=used_media).filter(tags=object, state="public", is_reviewed=True).order_by("-views").first()
if media:
@ -841,7 +915,7 @@ def update_listings_thumbnails():
object.save(update_fields=["listings_thumbnail"])
used_media.append(media.friendly_token)
saved += 1
logger.info("updated {} tags".format(saved))
logger.info(f"updated {saved} tags")
return True
@ -886,45 +960,6 @@ def update_encoding_size(encoding_id):
return False
@task(name="produce_video_chapters", queue="short_tasks")
def produce_video_chapters(chapter_id):
# this is not used
return False
chapter_object = VideoChapterData.objects.filter(id=chapter_id).first()
if not chapter_object:
return False
media = chapter_object.media
video_path = media.media_file.path
output_folder = media.video_chapters_folder
chapters = chapter_object.data
width = 336
height = 188
if not os.path.exists(output_folder):
os.makedirs(output_folder)
results = []
for i, chapter in enumerate(chapters):
timestamp = chapter["start"]
title = chapter["title"]
output_filename = f"thumbnail_{i:02d}.jpg" # noqa
output_path = os.path.join(output_folder, output_filename)
command = [settings.FFMPEG_COMMAND, "-y", "-ss", str(timestamp), "-i", video_path, "-vframes", "1", "-q:v", "2", "-s", f"{width}x{height}", output_path]
ret = run_command(command) # noqa
if os.path.exists(output_path) and get_file_type(output_path) == "image":
results.append({"start": timestamp, "title": title, "thumbnail": output_path})
chapter_object.data = results
chapter_object.save(update_fields=["data"])
return True
@task(name="post_trim_action", queue="short_tasks", soft_time_limit=600)
def post_trim_action(friendly_token):
"""Perform post-processing actions after video trimming
@ -956,10 +991,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
@ -979,7 +1014,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"])

20
files/tinymce_handlers.py Normal file
View File

@ -0,0 +1,20 @@
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .models import TinyMCEMedia
@csrf_exempt
def upload_image(request):
if not (request.user.is_authenticated and request.user.is_active and request.user.is_staff and request.user.is_superuser):
return JsonResponse({'error': 'Admin access required'}, status=403)
if request.method == "POST":
file_obj = request.FILES.get('file')
if file_obj:
# Create a new TinyMCEMedia instance for the image
media = TinyMCEMedia(file=file_obj, file_type='image', original_filename=file_obj.name, user=request.user if request.user.is_authenticated else None)
media.save()
return JsonResponse({'location': media.url})
return JsonResponse({'error': 'Invalid request'}, status=400)

View File

@ -4,9 +4,11 @@ from django.conf.urls import include
from django.conf.urls.static import static
from django.urls import path, re_path
from . import management_views, views
from . import management_views, tinymce_handlers, views
from .feeds import IndexRSSFeed, SearchRSSFeed
friendly_token = r"(?P<friendly_token>[\w\-_]*)"
urlpatterns = [
path("i18n/", include("django.conf.urls.i18n")),
re_path(r"^$", views.index),
@ -28,12 +30,12 @@ urlpatterns = [
re_path(r"^latest$", views.latest_media),
re_path(r"^members", views.members, name="members"),
re_path(
r"^playlist/(?P<friendly_token>[\w]*)$",
rf"^playlist/{friendly_token}$",
views.view_playlist,
name="get_playlist",
),
re_path(
r"^playlists/(?P<friendly_token>[\w]*)$",
rf"^playlists/{friendly_token}$",
views.view_playlist,
name="get_playlist",
),
@ -41,6 +43,7 @@ urlpatterns = [
re_path(r"^recommended$", views.recommended_media),
path("rss/", IndexRSSFeed()),
re_path("^rss/search", SearchRSSFeed()),
re_path(r"^record_screen", views.record_screen, name="record_screen"),
re_path(r"^search", views.search, name="search"),
re_path(r"^scpublisher", views.upload_media, name="upload_media"),
re_path(r"^tags", views.tags, name="tags"),
@ -48,10 +51,12 @@ urlpatterns = [
re_path(r"^view", views.view_media, name="get_media"),
re_path(r"^upload", views.upload_media, name="upload_media"),
# API VIEWS
re_path(r"^api/v1/media/user/bulk_actions$", views.MediaBulkUserActions.as_view()),
re_path(r"^api/v1/media/user/bulk_actions/$", views.MediaBulkUserActions.as_view()),
re_path(r"^api/v1/media$", views.MediaList.as_view()),
re_path(r"^api/v1/media/$", views.MediaList.as_view()),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)$",
rf"^api/v1/media/{friendly_token}$",
views.MediaDetail.as_view(),
name="api_get_media",
),
@ -62,32 +67,32 @@ urlpatterns = [
),
re_path(r"^api/v1/search$", views.MediaSearch.as_view()),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)/actions$",
rf"^api/v1/media/{friendly_token}/actions$",
views.MediaActions.as_view(),
),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)/chapters$",
rf"^api/v1/media/{friendly_token}/chapters$",
views.video_chapters,
),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)/trim_video$",
rf"^api/v1/media/{friendly_token}/trim_video$",
views.trim_video,
),
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
re_path(r"^api/v1/tags$", views.TagList.as_view()),
re_path(r"^api/v1/comments$", views.CommentList.as_view()),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)/comments$",
rf"^api/v1/media/{friendly_token}/comments$",
views.CommentDetail.as_view(),
),
re_path(
r"^api/v1/media/(?P<friendly_token>[\w]*)/comments/(?P<uid>[\w-]*)$",
rf"^api/v1/media/{friendly_token}/comments/(?P<uid>[\w-]*)$",
views.CommentDetail.as_view(),
),
re_path(r"^api/v1/playlists$", views.PlaylistList.as_view()),
re_path(r"^api/v1/playlists/$", views.PlaylistList.as_view()),
re_path(
r"^api/v1/playlists/(?P<friendly_token>[\w]*)$",
rf"^api/v1/playlists/{friendly_token}$",
views.PlaylistDetail.as_view(),
name="api_get_playlist",
),
@ -103,8 +108,15 @@ urlpatterns = [
re_path(r"^manage/comments$", views.manage_comments, name="manage_comments"),
re_path(r"^manage/media$", views.manage_media, name="manage_media"),
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
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.USERS_NEEDS_TO_BE_APPROVED:
urlpatterns.append(re_path(r"^approval_required/", views.approval_required, name="approval_required"))
if hasattr(settings, "USE_SAML") and settings.USE_SAML:
urlpatterns.append(re_path(r"^saml/metadata", views.saml_metadata, name="saml-metadata"))

File diff suppressed because it is too large Load Diff

47
files/views/__init__.py Normal file
View File

@ -0,0 +1,47 @@
# Import all views for backward compatibility
from .auth import custom_login_view, saml_metadata # noqa: F401
from .categories import CategoryList, TagList # noqa: F401
from .comments import CommentDetail, CommentList # noqa: F401
from .encoding import EncodeProfileList, EncodingDetail # noqa: F401
from .media import MediaActions # noqa: F401
from .media import MediaBulkUserActions # noqa: F401
from .media import MediaDetail # noqa: F401
from .media import MediaList # noqa: F401
from .media import MediaSearch # noqa: F401
from .pages import about # noqa: F401
from .pages import add_subtitle # noqa: F401
from .pages import approval_required # noqa: F401
from .pages import categories # noqa: F401
from .pages import contact # noqa: F401
from .pages import edit_chapters # noqa: F401
from .pages import edit_media # noqa: F401
from .pages import edit_subtitle # noqa: F401
from .pages import edit_video # noqa: F401
from .pages import embed_media # noqa: F401
from .pages import featured_media # noqa: F401
from .pages import get_page # noqa: F401
from .pages import history # noqa: F401
from .pages import index # noqa: F401
from .pages import latest_media # noqa: F401
from .pages import liked_media # noqa: F401
from .pages import manage_comments # noqa: F401
from .pages import manage_media # noqa: F401
from .pages import manage_users # noqa: F401
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 search # noqa: F401
from .pages import setlanguage # noqa: F401
from .pages import sitemap # noqa: F401
from .pages import tags # noqa: F401
from .pages import tos # noqa: F401
from .pages import trim_video # noqa: F401
from .pages import upload_media # noqa: F401
from .pages import video_chapters # noqa: F401
from .pages import view_media # noqa: F401
from .pages import view_playlist # noqa: F401
from .playlists import PlaylistDetail, PlaylistList # noqa: F401
from .tasks import TaskDetail, TasksList # noqa: F401
from .user import UserActions # noqa: F401

42
files/views/auth.py Normal file
View File

@ -0,0 +1,42 @@
from allauth.socialaccount.models import SocialApp
from django.conf import settings
from django.http import Http404, HttpResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from identity_providers.models import LoginOption
def saml_metadata(request):
if not (hasattr(settings, "USE_SAML") and settings.USE_SAML):
raise Http404
xml_parts = ['<?xml version="1.0"?>']
saml_social_apps = SocialApp.objects.filter(provider='saml')
entity_id = f"{settings.FRONTEND_HOST}/saml/metadata/"
xml_parts.append(f'<md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" Name="{entity_id}">') # noqa
xml_parts.append(f' <md:EntityDescriptor entityID="{entity_id}">') # noqa
xml_parts.append(' <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">') # noqa
# Add multiple AssertionConsumerService elements with different indices
for index, app in enumerate(saml_social_apps, start=1):
xml_parts.append(
f' <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" ' # noqa
f'Location="{settings.FRONTEND_HOST}/accounts/saml/{app.client_id}/acs/" index="{index}"/>' # noqa
)
xml_parts.append(' </md:SPSSODescriptor>') # noqa
xml_parts.append(' </md:EntityDescriptor>') # noqa
xml_parts.append('</md:EntitiesDescriptor>') # noqa
metadata_xml = '\n'.join(xml_parts)
return HttpResponse(metadata_xml, content_type='application/xml')
def custom_login_view(request):
if not (hasattr(settings, "USE_IDENTITY_PROVIDERS") and settings.USE_IDENTITY_PROVIDERS):
return redirect(reverse('login_system'))
login_options = []
for option in LoginOption.objects.filter(active=True):
login_options.append({'url': option.url, 'title': option.title})
return render(request, 'account/custom_login_selector.html', {'login_options': login_options})

66
files/views/categories.py Normal file
View File

@ -0,0 +1,66 @@
from django.conf import settings
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from ..methods import is_mediacms_editor
from ..models import Category, Tag
from ..serializers import CategorySerializer, TagSerializer
class CategoryList(APIView):
"""List categories"""
@swagger_auto_schema(
manual_parameters=[],
tags=['Categories'],
operation_summary='Lists Categories',
operation_description='Lists all categories',
responses={
200: openapi.Response('response description', CategorySerializer),
},
)
def get(self, request, format=None):
base_filters = {}
if not is_mediacms_editor(request.user):
base_filters = {"is_rbac_category": False}
base_queryset = Category.objects.prefetch_related("user")
categories = base_queryset.filter(**base_filters)
if not is_mediacms_editor(request.user):
if getattr(settings, 'USE_RBAC', False) and request.user.is_authenticated:
rbac_categories = request.user.get_rbac_categories_as_member()
categories = categories.union(rbac_categories)
categories = categories.order_by("title")
serializer = CategorySerializer(categories, many=True, context={"request": request})
ret = serializer.data
return Response(ret)
class TagList(APIView):
"""List tags"""
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
],
tags=['Tags'],
operation_summary='Lists Tags',
operation_description='Paginated listing of all tags',
responses={
200: openapi.Response('response description', TagSerializer),
},
)
def get(self, request, format=None):
tags = Tag.objects.filter().order_by("-media_count")
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
page = paginator.paginate_queryset(tags, request)
serializer = TagSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)

159
files/views/comments.py Normal file
View File

@ -0,0 +1,159 @@
from django.conf import settings
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import permissions, status
from rest_framework.exceptions import PermissionDenied
from rest_framework.parsers import (
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from cms.permissions import IsAuthorizedToAdd, IsAuthorizedToAddComment
from users.models import User
from ..methods import (
check_comment_for_mention,
is_mediacms_editor,
notify_user_on_comment,
)
from ..models import Comment, Media
from ..serializers import CommentSerializer
class CommentList(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
openapi.Parameter(name='author', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='username'),
],
tags=['Comments'],
operation_summary='Lists Comments',
operation_description='Paginated listing of all comments',
responses={
200: openapi.Response('response description', CommentSerializer(many=True)),
},
)
def get(self, request, format=None):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
comments = Comment.objects.filter(media__state="public").order_by("-add_date")
comments = comments.prefetch_related("user")
comments = comments.prefetch_related("media")
params = self.request.query_params
if "author" in params:
author_param = params["author"].strip()
user_queryset = User.objects.all()
user = get_object_or_404(user_queryset, username=author_param)
comments = comments.filter(user=user)
page = paginator.paginate_queryset(comments, request)
serializer = CommentSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
class CommentDetail(APIView):
"""Comments related views
Listings of comments for a media (GET)
Create comment (POST)
Delete comment (DELETE)
"""
permission_classes = (IsAuthorizedToAddComment,)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
def get_object(self, friendly_token):
try:
media = Media.objects.select_related("user").get(friendly_token=friendly_token)
self.check_object_permissions(self.request, media)
if media.state == "private" and self.request.user != media.user:
return Response({"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST)
return media
except PermissionDenied:
return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response(
{"detail": "media file does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
@swagger_auto_schema(
manual_parameters=[],
tags=['Media'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def get(self, request, friendly_token):
# list comments for a media
media = self.get_object(friendly_token)
if isinstance(media, Response):
return media
comments = media.comments.filter().prefetch_related("user")
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
page = paginator.paginate_queryset(comments, request)
serializer = CommentSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
@swagger_auto_schema(
manual_parameters=[],
tags=['Media'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def delete(self, request, friendly_token, uid=None):
"""Delete a comment
Administrators, MediaCMS editors and managers,
media owner, and comment owners, can delete a comment
"""
if uid:
try:
comment = Comment.objects.get(uid=uid)
except BaseException:
return Response(
{"detail": "comment does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
if (comment.user == self.request.user) or comment.media.user == self.request.user or is_mediacms_editor(self.request.user):
comment.delete()
else:
return Response({"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT)
@swagger_auto_schema(
manual_parameters=[],
tags=['Media'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def post(self, request, friendly_token):
"""Create a comment"""
media = self.get_object(friendly_token)
if isinstance(media, Response):
return media
if not media.enable_comments:
return Response(
{"detail": "comments not allowed here"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = CommentSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
serializer.save(user=request.user, media=media)
if request.user != media.user:
notify_user_on_comment(friendly_token=media.friendly_token)
# here forward the comment to check if a user was mentioned
if settings.ALLOW_MENTION_IN_COMMENTS:
check_comment_for_mention(friendly_token=media.friendly_token, comment_text=serializer.data['text'])
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

179
files/views/encoding.py Normal file
View File

@ -0,0 +1,179 @@
from django.conf import settings
from drf_yasg.utils import swagger_auto_schema
from rest_framework import permissions, status
from rest_framework.parsers import (
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from rest_framework.response import Response
from rest_framework.views import APIView
from ..helpers import produce_ffmpeg_commands
from ..models import EncodeProfile, Encoding
from ..serializers import EncodeProfileSerializer
class EncodingDetail(APIView):
"""Experimental. This View is used by remote workers
Needs heavy testing and documentation.
"""
permission_classes = (permissions.IsAdminUser,)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
@swagger_auto_schema(auto_schema=None)
def post(self, request, encoding_id):
ret = {}
force = request.data.get("force", False)
task_id = request.data.get("task_id", False)
action = request.data.get("action", "")
chunk = request.data.get("chunk", False)
chunk_file_path = request.data.get("chunk_file_path", "")
encoding_status = request.data.get("status", "")
progress = request.data.get("progress", "")
commands = request.data.get("commands", "")
logs = request.data.get("logs", "")
retries = request.data.get("retries", "")
worker = request.data.get("worker", "")
temp_file = request.data.get("temp_file", "")
total_run_time = request.data.get("total_run_time", "")
if action == "start":
try:
encoding = Encoding.objects.get(id=encoding_id)
media = encoding.media
profile = encoding.profile
except BaseException:
Encoding.objects.filter(id=encoding_id).delete()
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
# TODO: break chunk True/False logic here
if (
Encoding.objects.filter(
media=media,
profile=profile,
chunk=chunk,
chunk_file_path=chunk_file_path,
).count()
> 1 # noqa
and force is False # noqa
):
Encoding.objects.filter(id=encoding_id).delete()
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
else:
Encoding.objects.filter(
media=media,
profile=profile,
chunk=chunk,
chunk_file_path=chunk_file_path,
).exclude(id=encoding.id).delete()
encoding.status = "running"
if task_id:
encoding.task_id = task_id
encoding.save()
if chunk:
original_media_path = chunk_file_path
original_media_md5sum = encoding.md5sum
original_media_url = settings.SSL_FRONTEND_HOST + encoding.media_chunk_url
else:
original_media_path = media.media_file.path
original_media_md5sum = media.md5sum
original_media_url = settings.SSL_FRONTEND_HOST + media.original_media_url
ret["original_media_url"] = original_media_url
ret["original_media_path"] = original_media_path
ret["original_media_md5sum"] = original_media_md5sum
# generating the commands here, and will replace these with temporary
# files created on the remote server
tf = "TEMP_FILE_REPLACE"
tfpass = "TEMP_FPASS_FILE_REPLACE"
ffmpeg_commands = produce_ffmpeg_commands(
original_media_path,
media.media_info,
resolution=profile.resolution,
codec=profile.codec,
output_filename=tf,
pass_file=tfpass,
chunk=chunk,
)
if not ffmpeg_commands:
encoding.delete()
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
ret["duration"] = media.duration
ret["ffmpeg_commands"] = ffmpeg_commands
ret["profile_extension"] = profile.extension
return Response(ret, status=status.HTTP_201_CREATED)
elif action == "update_fields":
try:
encoding = Encoding.objects.get(id=encoding_id)
except BaseException:
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
to_update = ["size", "update_date"]
if encoding_status:
encoding.status = encoding_status
to_update.append("status")
if progress:
encoding.progress = progress
to_update.append("progress")
if logs:
encoding.logs = logs
to_update.append("logs")
if commands:
encoding.commands = commands
to_update.append("commands")
if task_id:
encoding.task_id = task_id
to_update.append("task_id")
if total_run_time:
encoding.total_run_time = total_run_time
to_update.append("total_run_time")
if worker:
encoding.worker = worker
to_update.append("worker")
if temp_file:
encoding.temp_file = temp_file
to_update.append("temp_file")
if retries:
encoding.retries = retries
to_update.append("retries")
try:
encoding.save(update_fields=to_update)
except BaseException:
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
return Response({"status": "success"}, status=status.HTTP_201_CREATED)
@swagger_auto_schema(auto_schema=None)
def put(self, request, encoding_id, format=None):
encoding_file = request.data["file"]
encoding = Encoding.objects.filter(id=encoding_id).first()
if not encoding:
return Response(
{"detail": "encoding does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
encoding.media_file = encoding_file
encoding.save()
return Response({"detail": "ok"}, status=status.HTTP_201_CREATED)
class EncodeProfileList(APIView):
"""List encode profiles"""
@swagger_auto_schema(
manual_parameters=[],
tags=['Encoding Profiles'],
operation_summary='List Encoding Profiles',
operation_description='Lists all encoding profiles for videos',
responses={200: EncodeProfileSerializer(many=True)},
)
def get(self, request, format=None):
profiles = EncodeProfile.objects.all()
serializer = EncodeProfileSerializer(profiles, many=True, context={"request": request})
return Response(serializer.data)

1069
files/views/media.py Normal file

File diff suppressed because it is too large Load Diff

667
files/views/pages.py Normal file
View File

@ -0,0 +1,667 @@
import json
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.mail import EmailMessage
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from cms.version import VERSION
from files.methods import user_allowed_to_upload
from users.models import User
from .. import helpers
from ..forms import (
ContactForm,
EditSubtitleForm,
MediaMetadataForm,
MediaPublishForm,
SubtitleForm,
WhisperSubtitlesForm,
)
from ..frontend_translations import translate_string
from ..helpers import get_alphanumeric_only
from ..methods import (
can_transcribe_video,
create_video_trim_request,
get_user_or_session,
handle_video_chapters,
is_media_allowed_type,
is_mediacms_editor,
)
from ..models import Category, Media, Page, Playlist, Subtitle, Tag, VideoTrimRequest
from ..tasks import save_user_action, video_trim_task
def get_page(request, slug):
context = {}
page = Page.objects.filter(slug=slug).first()
if page:
context["page"] = page
else:
return render(request, "404.html", context)
return render(request, "cms/page.html", context)
@login_required
def record_screen(request):
"""Record screen view"""
context = {}
context["can_add"] = user_allowed_to_upload(request)
can_upload_exp = settings.CANNOT_ADD_MEDIA_MESSAGE
context["can_upload_exp"] = can_upload_exp
return render(request, "cms/record_screen.html", context)
def about(request):
"""About view"""
page = Page.objects.filter(slug="about").first()
if page:
context = {}
context["page"] = page
return render(request, "cms/page.html", context)
context = {"VERSION": VERSION}
return render(request, "cms/about.html", context)
def approval_required(request):
"""User needs approval view"""
return render(request, "cms/user_needs_approval.html", {})
def setlanguage(request):
"""Set Language view"""
context = {}
return render(request, "cms/set_language.html", context)
@login_required
def add_subtitle(request):
"""Add subtitle view"""
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 (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
# Initialize variables
form = None
whisper_form = None
show_whisper_form = can_transcribe_video(request.user)
if request.method == "POST":
if 'submit' in request.POST:
form = SubtitleForm(media, request.POST, request.FILES, prefix="form")
if form.is_valid():
subtitle = form.save()
try:
subtitle.convert_to_srt()
messages.add_message(request, messages.INFO, "Caption was added!")
return HttpResponseRedirect(subtitle.media.get_absolute_url())
except Exception as e: # noqa
subtitle.delete()
error_msg = "Invalid subtitle format. Use SubRip (.srt) or WebVTT (.vtt) files."
form.add_error("subtitle_file", error_msg)
elif 'submit_whisper' in request.POST and show_whisper_form:
whisper_form = WhisperSubtitlesForm(request.user, request.POST, instance=media, prefix="whisper_form")
if whisper_form.is_valid():
whisper_form.save()
messages.add_message(request, messages.INFO, "Request for transcription was sent")
return HttpResponseRedirect(media.get_absolute_url())
# GET request or form invalid
if form is None:
form = SubtitleForm(media_item=media, prefix="form")
if show_whisper_form and whisper_form is None:
whisper_form = WhisperSubtitlesForm(request.user, instance=media, prefix="whisper_form")
subtitles = media.subtitles.all()
context = {"media_object": media, "form": form, "subtitles": subtitles, "whisper_form": whisper_form}
return render(request, "cms/add_subtitle.html", context)
@login_required
def edit_subtitle(request):
subtitle_id = request.GET.get("id", "").strip()
action = request.GET.get("action", "").strip()
if not subtitle_id:
return HttpResponseRedirect("/")
subtitle = Subtitle.objects.filter(id=subtitle_id).first()
if not subtitle:
return HttpResponseRedirect("/")
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(subtitle.media)):
return HttpResponseRedirect("/")
context = {"subtitle": subtitle, "action": action}
if action == "download":
response = HttpResponse(subtitle.subtitle_file.read(), content_type="text/vtt")
filename = subtitle.subtitle_file.name.split("/")[-1]
if not filename.endswith(".vtt"):
filename = f"{filename}.vtt"
response["Content-Disposition"] = f"attachment; filename={filename}" # noqa
return response
if request.method == "GET":
form = EditSubtitleForm(subtitle)
context["form"] = form
elif request.method == "POST":
confirm = request.GET.get("confirm", "").strip()
if confirm == "true":
messages.add_message(request, messages.INFO, "Caption was deleted")
redirect_url = subtitle.media.get_absolute_url()
subtitle.delete()
return HttpResponseRedirect(redirect_url)
form = EditSubtitleForm(subtitle, request.POST)
subtitle_text = form.data["subtitle"]
with open(subtitle.subtitle_file.path, "w") as ff:
ff.write(subtitle_text)
messages.add_message(request, messages.INFO, "Caption was edited")
return HttpResponseRedirect(subtitle.media.get_absolute_url())
return render(request, "cms/edit_subtitle.html", context)
def categories(request):
"""List categories view"""
context = {}
return render(request, "cms/categories.html", context)
def contact(request):
"""Contact view"""
context = {}
if request.method == "GET":
form = ContactForm(request.user)
context["form"] = form
else:
form = ContactForm(request.user, request.POST)
if form.is_valid():
if request.user.is_authenticated:
from_email = request.user.email
name = request.user.name
else:
from_email = request.POST.get("from_email")
name = request.POST.get("name")
message = request.POST.get("message")
title = f"[{settings.PORTAL_NAME}] - Contact form message received"
msg = """
You have received a message through the contact form\n
Sender name: %s
Sender email: %s\n
\n %s
""" % (
name,
from_email,
message,
)
email = EmailMessage(
title,
msg,
settings.DEFAULT_FROM_EMAIL,
settings.ADMIN_EMAIL_LIST,
reply_to=[from_email],
)
email.send(fail_silently=True)
success_msg = "Message was sent! Thanks for contacting"
context["success_msg"] = success_msg
return render(request, "cms/contact.html", context)
def history(request):
"""Show personal history view"""
context = {}
return render(request, "cms/history.html", context)
@csrf_exempt
@login_required
def video_chapters(request, friendly_token):
if not request.method == "POST":
return HttpResponseRedirect("/")
media = Media.objects.filter(friendly_token=friendly_token).first()
if not media:
return HttpResponseRedirect("/")
if not (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
try:
request_data = json.loads(request.body)
data = request_data.get("chapters")
if data is None:
return JsonResponse({'success': False, 'error': 'Request must contain "chapters" array'}, status=400)
chapters = []
for _, chapter_data in enumerate(data):
start_time = chapter_data.get('startTime')
end_time = chapter_data.get('endTime')
chapter_title = chapter_data.get('chapterTitle')
if start_time and end_time and chapter_title:
chapters.append(
{
'startTime': start_time,
'endTime': end_time,
'chapterTitle': chapter_title,
}
)
except Exception as e: # noqa
return JsonResponse({'success': False, 'error': 'Request data must be a list of video chapters with startTime, endTime, chapterTitle'}, status=400)
ret = handle_video_chapters(media, chapters)
return JsonResponse(ret, safe=False)
@login_required
def edit_media(request):
"""Edit a media view"""
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 = MediaMetadataForm(request.user, request.POST, request.FILES, instance=media)
if form.is_valid():
media = form.save()
for tag in media.tags.all():
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]
if tag:
try:
tag = Tag.objects.get(title=tag)
except Tag.DoesNotExist:
tag = Tag.objects.create(title=tag, user=request.user)
if tag not in media.tags.all():
media.tags.add(tag)
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media was edited"))
return HttpResponseRedirect(media.get_absolute_url())
else:
form = MediaMetadataForm(request.user, instance=media)
return render(
request,
"cms/edit_media.html",
{"form": form, "media_object": media, "add_subtitle_url": media.add_subtitle_url},
)
@login_required
def publish_media(request):
"""Publish media"""
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 (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():
media = form.save()
messages.add_message(request, messages.INFO, translate_string(request.LANGUAGE_CODE, "Media was edited"))
return HttpResponseRedirect(media.get_absolute_url())
else:
form = MediaPublishForm(request.user, instance=media)
return render(
request,
"cms/publish_media.html",
{"form": form, "media_object": media, "add_subtitle_url": media.add_subtitle_url},
)
@login_required
def edit_chapters(request):
"""Edit chapters"""
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 (is_mediacms_editor(request.user) or request.user.has_contributor_access_to_media(media)):
return HttpResponseRedirect("/")
chapters = media.chapter_data
return render(
request,
"cms/edit_chapters.html",
{"media_object": media, "add_subtitle_url": media.add_subtitle_url, "media_file_path": helpers.url_from_path(media.media_file.path), "media_id": media.friendly_token, "chapters": chapters},
)
@csrf_exempt
@login_required
def trim_video(request, friendly_token):
if not settings.ALLOW_VIDEO_TRIMMER:
return JsonResponse({"success": False, "error": "Video trimming is not allowed"}, status=400)
if not request.method == "POST":
return HttpResponseRedirect("/")
media = Media.objects.filter(friendly_token=friendly_token).first()
if not media:
return HttpResponseRedirect("/")
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()
if existing_requests:
return JsonResponse({"success": False, "error": "A trim request is already in progress for this video"}, status=400)
try:
data = json.loads(request.body)
video_trim_request = create_video_trim_request(media, data)
video_trim_task.delay(video_trim_request.id)
ret = {"success": True, "request_id": video_trim_request.id}
return JsonResponse(ret, safe=False, status=200)
except Exception as e: # noqa
ret = {"success": False, "error": "Incorrect request data"}
return JsonResponse(ret, safe=False, status=400)
@login_required
def edit_video(request):
"""Edit video"""
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 (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 or audio")
return HttpResponseRedirect(media.get_absolute_url())
if not settings.ALLOW_VIDEO_TRIMMER:
messages.add_message(request, messages.INFO, "Video Trimmer is not enabled")
return HttpResponseRedirect(media.get_absolute_url())
# Check if there's a running trim request
running_trim_request = VideoTrimRequest.objects.filter(media=media, status__in=["initial", "running"]).exists()
if running_trim_request:
messages.add_message(request, messages.INFO, "Video trim request is already running")
return HttpResponseRedirect(media.get_absolute_url())
media_file_path = media.trim_video_url
if not media_file_path:
messages.add_message(request, messages.INFO, "Media processing has not finished yet")
return HttpResponseRedirect(media.get_absolute_url())
if media.encoding_status in ["pending", "running"]:
video_msg = "Media encoding hasn't finished yet. Attempting to show the original video file"
messages.add_message(request, messages.INFO, video_msg)
return render(
request,
"cms/edit_video.html",
{"media_object": media, "add_subtitle_url": media.add_subtitle_url, "media_file_path": media_file_path},
)
def embed_media(request):
"""Embed media view"""
friendly_token = request.GET.get("m", "").strip()
if not friendly_token:
return HttpResponseRedirect("/")
media = Media.objects.values("title").filter(friendly_token=friendly_token).first()
if not media:
return HttpResponseRedirect("/")
context = {}
context["media"] = friendly_token
return render(request, "cms/embed.html", context)
def featured_media(request):
"""List featured media view"""
context = {}
return render(request, "cms/featured-media.html", context)
def index(request):
"""Index view"""
context = {}
return render(request, "cms/index.html", context)
def latest_media(request):
"""List latest media view"""
context = {}
return render(request, "cms/latest-media.html", context)
def liked_media(request):
"""List user's liked media view"""
context = {}
return render(request, "cms/liked_media.html", context)
@login_required
def manage_users(request):
"""List users management view"""
if not is_mediacms_editor(request.user):
return HttpResponseRedirect("/")
context = {}
return render(request, "cms/manage_users.html", context)
@login_required
def manage_media(request):
"""List media management view"""
if not is_mediacms_editor(request.user):
return HttpResponseRedirect("/")
categories = Category.objects.all().order_by('title').values_list('title', flat=True)
context = {'categories': list(categories)}
return render(request, "cms/manage_media.html", context)
@login_required
def manage_comments(request):
"""List comments management view"""
if not is_mediacms_editor(request.user):
return HttpResponseRedirect("/")
context = {}
return render(request, "cms/manage_comments.html", context)
def members(request):
"""List members view"""
if settings.CAN_SEE_MEMBERS_PAGE == "editors" and not is_mediacms_editor(request.user):
return HttpResponseRedirect("/")
if settings.CAN_SEE_MEMBERS_PAGE == "admins" and not request.user.is_superuser:
return HttpResponseRedirect("/")
context = {}
return render(request, "cms/members.html", context)
def recommended_media(request):
"""List recommended media view"""
context = {}
return render(request, "cms/recommended-media.html", context)
def search(request):
"""Search view"""
context = {}
RSS_URL = f"/rss{request.environ.get('REQUEST_URI')}"
context["RSS_URL"] = RSS_URL
return render(request, "cms/search.html", context)
def sitemap(request):
"""Sitemap"""
context = {}
context["media"] = list(Media.objects.filter(listable=True).order_by("-add_date"))
context["playlists"] = list(Playlist.objects.filter().order_by("-add_date"))
context["users"] = list(User.objects.filter())
return render(request, "sitemap.xml", context, content_type="application/xml")
def tags(request):
"""List tags view"""
context = {}
return render(request, "cms/tags.html", context)
def tos(request):
"""Terms of service view"""
context = {}
return render(request, "cms/tos.html", context)
@login_required
def upload_media(request):
"""Upload media view"""
from allauth.account.forms import LoginForm
form = LoginForm()
context = {}
context["form"] = form
context["can_add"] = user_allowed_to_upload(request)
can_upload_exp = settings.CANNOT_ADD_MEDIA_MESSAGE
context["can_upload_exp"] = can_upload_exp
return render(request, "cms/add-media.html", context)
def view_media(request):
"""View media view"""
friendly_token = request.GET.get("m", "").strip()
context = {}
media = Media.objects.filter(friendly_token=friendly_token).first()
if not media:
context["media"] = None
return render(request, "cms/media.html", context)
user_or_session = get_user_or_session(request)
save_user_action.delay(user_or_session, friendly_token=friendly_token, action="watch")
context = {}
context["media"] = friendly_token
context["media_object"] = media
context["CAN_DELETE_MEDIA"] = False
context["CAN_EDIT_MEDIA"] = False
context["CAN_DELETE_COMMENTS"] = False
if request.user.is_authenticated:
if request.user.has_contributor_access_to_media(media) or is_mediacms_editor(request.user):
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':
video_msg = None
if media.encoding_status == "pending":
video_msg = "Media encoding hasn't started yet. Attempting to show the original video file"
if media.encoding_status == "running":
video_msg = "Media encoding is under processing. Attempting to show the original video file"
if video_msg and media.user == request.user:
messages.add_message(request, messages.INFO, video_msg)
context["is_media_allowed_type"] = is_media_allowed_type(media)
return render(request, "cms/media.html", context)
def view_playlist(request, friendly_token):
"""View playlist view"""
try:
playlist = Playlist.objects.get(friendly_token=friendly_token)
except BaseException:
playlist = None
context = {}
context["playlist"] = playlist
return render(request, "cms/playlist.html", context)

200
files/views/playlists.py Normal file
View File

@ -0,0 +1,200 @@
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
from rest_framework.exceptions import PermissionDenied
from rest_framework.parsers import (
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor
from ..models import Media, Playlist, PlaylistMedia
from ..serializers import MediaSerializer, PlaylistDetailSerializer, PlaylistSerializer
class PlaylistList(APIView):
"""Playlists listings and creation views"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
responses={
200: openapi.Response('response description', PlaylistSerializer(many=True)),
},
)
def get(self, request, format=None):
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
playlists = Playlist.objects.filter().prefetch_related("user")
if "author" in self.request.query_params:
author = self.request.query_params["author"].strip()
playlists = playlists.filter(user__username=author)
page = paginator.paginate_queryset(playlists, request)
serializer = PlaylistSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def post(self, request, format=None):
serializer = PlaylistSerializer(data=request.data, context={"request": request})
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class PlaylistDetail(APIView):
"""Playlist related views"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
def get_playlist(self, friendly_token):
try:
playlist = Playlist.objects.get(friendly_token=friendly_token)
self.check_object_permissions(self.request, playlist)
return playlist
except PermissionDenied:
return Response({"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response(
{"detail": "Playlist does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def get(self, request, friendly_token, format=None):
playlist = self.get_playlist(friendly_token)
if isinstance(playlist, Response):
return playlist
serializer = PlaylistDetailSerializer(playlist, context={"request": request})
# 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]
playlist_media_serializer = MediaSerializer(playlist_media, many=True, context={"request": request})
ret = serializer.data
ret["playlist_media"] = playlist_media_serializer.data
return Response(ret)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def post(self, request, friendly_token, format=None):
playlist = self.get_playlist(friendly_token)
if isinstance(playlist, Response):
return playlist
serializer = PlaylistDetailSerializer(playlist, data=request.data, context={"request": request})
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def put(self, request, friendly_token, format=None):
playlist = self.get_playlist(friendly_token)
if isinstance(playlist, Response):
return playlist
action = request.data.get("type")
media_friendly_token = request.data.get("media_friendly_token")
ordering = 0
if request.data.get("ordering"):
try:
ordering = int(request.data.get("ordering"))
except ValueError:
pass
if action in ["add", "remove", "ordering"]:
media = Media.objects.filter(friendly_token=media_friendly_token).first()
if media:
if action == "add":
media_in_playlist = PlaylistMedia.objects.filter(playlist=playlist).count()
if media_in_playlist >= settings.MAX_MEDIA_PER_PLAYLIST:
return Response(
{"detail": "max number of media for a Playlist reached"},
status=status.HTTP_400_BAD_REQUEST,
)
else:
obj, created = PlaylistMedia.objects.get_or_create(
playlist=playlist,
media=media,
ordering=media_in_playlist + 1,
)
obj.save()
return Response(
{"detail": "media added to Playlist"},
status=status.HTTP_201_CREATED,
)
elif action == "remove":
PlaylistMedia.objects.filter(playlist=playlist, media=media).delete()
return Response(
{"detail": "media removed from Playlist"},
status=status.HTTP_201_CREATED,
)
elif action == "ordering":
if ordering:
playlist.set_ordering(media, ordering)
return Response(
{"detail": "new ordering set"},
status=status.HTTP_201_CREATED,
)
else:
return Response({"detail": "media is not valid"}, status=status.HTTP_400_BAD_REQUEST)
return Response(
{"detail": "invalid or not specified action"},
status=status.HTTP_400_BAD_REQUEST,
)
@swagger_auto_schema(
manual_parameters=[],
tags=['Playlists'],
operation_summary='to_be_written',
operation_description='to_be_written',
)
def delete(self, request, friendly_token, format=None):
playlist = self.get_playlist(friendly_token)
if isinstance(playlist, Response):
return playlist
playlist.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

30
files/views/tasks.py Normal file
View File

@ -0,0 +1,30 @@
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
from ..methods import list_tasks
class TasksList(APIView):
"""List tasks"""
swagger_schema = None
permission_classes = (permissions.IsAdminUser,)
def get(self, request, format=None):
ret = list_tasks()
return Response(ret)
class TaskDetail(APIView):
"""Cancel a task"""
swagger_schema = None
permission_classes = (permissions.IsAdminUser,)
def delete(self, request, uid, format=None):
# This is not imported!
# revoke(uid, terminate=True)
return Response(status=status.HTTP_204_NO_CONTENT)

45
files/views/user.py Normal file
View File

@ -0,0 +1,45 @@
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.parsers import JSONParser
from rest_framework.settings import api_settings
from rest_framework.views import APIView
from actions.models import USER_MEDIA_ACTIONS
from ..models import Media
from ..serializers import MediaSerializer
VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS]
class UserActions(APIView):
parser_classes = (JSONParser,)
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(name='action', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='action', required=True, enum=VALID_USER_ACTIONS),
],
tags=['Users'],
operation_summary='List user actions',
operation_description='Lists user actions',
)
def get(self, request, action):
media = []
if action in VALID_USER_ACTIONS:
if request.user.is_authenticated:
media = Media.objects.select_related("user").filter(mediaactions__user=request.user, mediaactions__action=action).order_by("-mediaactions__action_date")
elif request.session.session_key:
media = (
Media.objects.select_related("user")
.filter(
mediaactions__session_key=request.session.session_key,
mediaactions__action=action,
)
.order_by("-mediaactions__action_date")
)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
paginator = pagination_class()
page = paginator.paginate_queryset(media, request)
serializer = MediaSerializer(page, many=True, context={"request": request})
return paginator.get_paginated_response(serializer.data)

View File

@ -1 +1 @@
[{"model": "files.encodeprofile", "pk": 19, "fields": {"name": "h264-2160", "extension": "mp4", "resolution": 2160, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 22, "fields": {"name": "vp9-2160", "extension": "webm", "resolution": 2160, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 16, "fields": {"name": "h265-2160", "extension": "mp4", "resolution": 2160, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 4, "fields": {"name": "h264-1440", "extension": "mp4", "resolution": 1440, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 5, "fields": {"name": "vp9-1440", "extension": "webm", "resolution": 1440, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 6, "fields": {"name": "h265-1440", "extension": "mp4", "resolution": 1440, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 7, "fields": {"name": "h264-1080", "extension": "mp4", "resolution": 1080, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 8, "fields": {"name": "vp9-1080", "extension": "webm", "resolution": 1080, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 9, "fields": {"name": "h265-1080", "extension": "mp4", "resolution": 1080, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 10, "fields": {"name": "h264-720", "extension": "mp4", "resolution": 720, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 11, "fields": {"name": "vp9-720", "extension": "webm", "resolution": 720, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 12, "fields": {"name": "h265-720", "extension": "mp4", "resolution": 720, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 13, "fields": {"name": "h264-480", "extension": "mp4", "resolution": 480, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 14, "fields": {"name": "vp9-480", "extension": "webm", "resolution": 480, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 15, "fields": {"name": "h265-480", "extension": "mp4", "resolution": 480, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 3, "fields": {"name": "h264-360", "extension": "mp4", "resolution": 360, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 17, "fields": {"name": "vp9-360", "extension": "webm", "resolution": 360, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 18, "fields": {"name": "h265-360", "extension": "mp4", "resolution": 360, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "h264-240", "extension": "mp4", "resolution": 240, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 20, "fields": {"name": "vp9-240", "extension": "webm", "resolution": 240, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 21, "fields": {"name": "h265-240", "extension": "mp4", "resolution": 240, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 1, "fields": {"name": "preview", "extension": "gif", "resolution": null, "codec": null, "description": "", "active": true}}]
[{"model": "files.encodeprofile", "pk": 19, "fields": {"name": "h264-2160", "extension": "mp4", "resolution": 2160, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 22, "fields": {"name": "vp9-2160", "extension": "webm", "resolution": 2160, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 23, "fields": {"name": "h264-144", "extension": "mp4", "resolution": 144, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 16, "fields": {"name": "h265-2160", "extension": "mp4", "resolution": 2160, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 4, "fields": {"name": "h264-1440", "extension": "mp4", "resolution": 1440, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 5, "fields": {"name": "vp9-1440", "extension": "webm", "resolution": 1440, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 6, "fields": {"name": "h265-1440", "extension": "mp4", "resolution": 1440, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 7, "fields": {"name": "h264-1080", "extension": "mp4", "resolution": 1080, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 8, "fields": {"name": "vp9-1080", "extension": "webm", "resolution": 1080, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 9, "fields": {"name": "h265-1080", "extension": "mp4", "resolution": 1080, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 10, "fields": {"name": "h264-720", "extension": "mp4", "resolution": 720, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 11, "fields": {"name": "vp9-720", "extension": "webm", "resolution": 720, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 12, "fields": {"name": "h265-720", "extension": "mp4", "resolution": 720, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 13, "fields": {"name": "h264-480", "extension": "mp4", "resolution": 480, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 14, "fields": {"name": "vp9-480", "extension": "webm", "resolution": 480, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 15, "fields": {"name": "h265-480", "extension": "mp4", "resolution": 480, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 3, "fields": {"name": "h264-360", "extension": "mp4", "resolution": 360, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 17, "fields": {"name": "vp9-360", "extension": "webm", "resolution": 360, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 18, "fields": {"name": "h265-360", "extension": "mp4", "resolution": 360, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "h264-240", "extension": "mp4", "resolution": 240, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 20, "fields": {"name": "vp9-240", "extension": "webm", "resolution": 240, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 21, "fields": {"name": "h265-240", "extension": "mp4", "resolution": 240, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 1, "fields": {"name": "preview", "extension": "gif", "resolution": null, "codec": null, "description": "", "active": true}}]

BIN
fixtures/test_image2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -0,0 +1,15 @@
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
yt.readme.md
client/public/videos/sample-video.mp4
client/public/videos/sample-video-30s.mp4
client/public/videos/sample-video-37s.mp4
videos/sample-video-37s.mp4
client/public/videos/sample-video-30s.mp4
client/public/videos/sample-video-1.mp4
client/public/videos/sample-video-10m.mp4
client/public/videos/sample-video-10s.mp4

View File

@ -0,0 +1,5 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"prettier.configPath": ".prettierrc"
}

View File

@ -0,0 +1,255 @@
# MediaCMS Chapters Editor
A modern browser-based chapter editing tool built with React and TypeScript that integrates with MediaCMS. The Chapters Editor allows users to create, manage, and edit video chapters with precise timing controls and an intuitive timeline interface.
## Features
- 📑 Create and manage video chapters with custom titles
- ⏱️ Precise timestamp controls for chapter start and end points
- ✂️ Split chapters and reorganize content
- 👁️ Preview chapters with jump-to navigation
- 🔄 Undo/redo support for all editing operations
- 🏷️ Chapter metadata editing (titles, descriptions)
- 💾 Save chapter data directly to MediaCMS
- 🎯 Timeline-based chapter visualization
- 📱 Responsive design for desktop and mobile
## Use Cases
- **Educational Content**: Add chapters to lectures and tutorials for better navigation
- **Entertainment**: Create chapters for movies, shows, or long-form content
- **Documentation**: Organize training videos and documentation with logical sections
- **Accessibility**: Improve content accessibility with structured navigation
## Tech Stack
- React 18
- TypeScript
- Vite
## Installation
### Prerequisites
- Node.js (v20+) - Use `nvm use 20` if you have nvm installed
- Yarn or npm package manager
### Setup
```bash
# Navigate to the Chapters Editor directory
cd frontend-tools/chapters-editor
# Install dependencies with Yarn
yarn install
# Or with npm
npm install
```
## Development
The Chapters Editor can be run in two modes:
### Standalone Development Mode
This starts a local development server with hot reloading:
```bash
# Start the development server with Yarn
yarn dev
# Or with npm
npm run dev
```
### Frontend-only Development Mode
If you want to work only on the frontend with MediaCMS backend:
```bash
# Start frontend-only development with Yarn
yarn dev:frontend
# Or with npm
npm run dev:frontend
```
## Building
### For MediaCMS Integration
To build the Chapters Editor for integration with MediaCMS:
```bash
# Build for Django integration with Yarn
yarn build:django
# Or with npm
npm run build:django
```
This will compile the editor and place the output in the MediaCMS static directory.
### Standalone Build
To build the editor as a standalone application:
```bash
# Build for production with Yarn
yarn build
# Or with npm
npm run build
```
## Deployment
To deploy the Chapters Editor, you can use the build and deploy script (recommended):
```bash
# Run the deployment script
sh deploy/scripts/build_and_deploy.sh
```
The build script handles all necessary steps for compiling and deploying the editor to MediaCMS.
You can also deploy manually after building:
```bash
# With Yarn
yarn deploy
# Or with npm
npm run deploy
```
## Project Structure
- `/client` - Frontend React application
- `/src` - Source code
- `/components` - React components for chapter editing
- `/hooks` - Custom React hooks for chapter management
- `/lib` - Utility functions and helpers
- `/services` - API services for MediaCMS integration
- `/styles` - CSS and style definitions
- `/shared` - Shared TypeScript types and utilities
## API Integration
The Chapters Editor interfaces with MediaCMS through a set of API endpoints for:
- Retrieving video metadata and existing chapters
- Saving chapter data (timestamps, titles, descriptions)
- Validating chapter structure and timing
- Integration with MediaCMS user permissions
### Chapter Data Format
Chapters are stored in the following format:
```json
{
"chapters": [
{
"id": "chapter-1",
"title": "Introduction",
"startTime": 0,
"endTime": 120,
"description": "Opening remarks and overview"
},
{
"id": "chapter-2",
"title": "Main Content",
"startTime": 120,
"endTime": 600,
"description": "Core educational material"
}
]
}
```
## Code Formatting
To automatically format all source files using [Prettier](https://prettier.io):
```bash
# Format all code in the src directory
npx prettier --write client/src/
# Or format specific file types
npx prettier --write "client/src/**/*.{js,jsx,ts,tsx,json,css,scss,md}"
```
You can also add this as a script in `package.json`:
```json
"scripts": {
"format": "prettier --write client/src/"
}
```
Then run:
```bash
yarn format
# or
npm run format
```
## Testing
Run the test suite to ensure Chapters Editor functionality:
```bash
# Run tests with Yarn
yarn test
# Or with npm
npm test
# Run tests in watch mode
yarn test:watch
npm run test:watch
```
## Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/chapter-enhancement`
3. Make your changes and add tests
4. Run the formatter: `yarn format`
5. Run tests: `yarn test`
6. Commit your changes: `git commit -m "Add chapter enhancement"`
7. Push to the branch: `git push origin feature/chapter-enhancement`
8. Submit a pull request
## Troubleshooting
### Common Issues
**Chapter timestamps not saving**: Ensure the MediaCMS backend API is accessible and user has proper permissions.
**Timeline not displaying correctly**: Check browser console for JavaScript errors and ensure video file is properly loaded.
**Performance issues with long videos**: The editor is optimized for videos up to 2 hours. For longer content, consider splitting into multiple files.
### Debug Mode
Enable debug mode for detailed logging:
```bash
# Start with debug logging
DEBUG=true yarn dev
```
## Browser Support
- Chrome/Chromium 90+
- Firefox 88+
- Safari 14+
- Edge 90+
## License
This project is licensed under the same license as MediaCMS. See the main MediaCMS repository for license details.

View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<title>Chapters Editor</title>
<!-- Add meta tag to help iOS devices render as desktop -->
<script>
// Try to detect iOS
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (isIOS) {
// Replace viewport meta tag with one optimized for desktop view
const viewportMeta = document.querySelector('meta[name="viewport"]');
if (viewportMeta) {
viewportMeta.setAttribute(
'content',
'width=1024, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
);
}
// Add a class to the HTML element for iOS-specific styles
document.documentElement.classList.add('ios-device');
}
</script>
</head>
<body>
<div id="chapters-editor-root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

View File

@ -0,0 +1,186 @@
import { formatDetailedTime } from './lib/timeUtils';
import logger from './lib/logger';
import VideoPlayer from '@/components/VideoPlayer';
import TimelineControls from '@/components/TimelineControls';
import EditingTools from '@/components/EditingTools';
import ClipSegments from '@/components/ClipSegments';
import MobilePlayPrompt from '@/components/IOSPlayPrompt';
import useVideoChapters from '@/hooks/useVideoChapters';
import { useEffect } from 'react';
const App = () => {
const {
videoRef,
currentTime,
duration,
isPlaying,
setIsPlaying,
isMuted,
trimStart,
trimEnd,
splitPoints,
zoomLevel,
clipSegments,
selectedSegmentId,
hasUnsavedChanges,
historyPosition,
history,
handleTrimStartChange,
handleTrimEndChange,
handleZoomChange,
handleMobileSafeSeek,
handleSplit,
handleReset,
handleUndo,
handleRedo,
toggleMute,
handleSegmentUpdate,
handleChapterSave,
handleSelectedSegmentChange,
isMobile,
videoInitialized,
setVideoInitialized,
initializeSafariIfNeeded,
} = useVideoChapters();
const handlePlay = async () => {
if (!videoRef.current) return;
const video = videoRef.current;
// If already playing, just pause the video
if (isPlaying) {
video.pause();
setIsPlaying(false);
logger.debug('Video paused');
return;
}
// Safari: Try to initialize if needed before playing
if (duration === 0) {
const initialized = await initializeSafariIfNeeded();
if (initialized) {
// Wait a moment for initialization to complete
setTimeout(() => handlePlay(), 200);
return;
}
}
// Start playing - no boundary checking, play through entire timeline
video
.play()
.then(() => {
setIsPlaying(true);
setVideoInitialized(true);
logger.debug('Continuous playback started from:', formatDetailedTime(video.currentTime));
})
.catch((err) => {
console.error('Error playing video:', err);
});
};
// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
// Don't handle keyboard shortcuts if user is typing in an input field
const target = event.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return;
}
switch (event.code) {
case 'Space':
event.preventDefault(); // Prevent default spacebar behavior (scrolling, button activation)
handlePlay();
break;
case 'ArrowLeft':
event.preventDefault();
if (videoRef.current) {
// Use the video element's current time directly to avoid stale state
const newTime = Math.max(videoRef.current.currentTime - 10, 0);
handleMobileSafeSeek(newTime);
logger.debug('Jumped backward 10 seconds to:', formatDetailedTime(newTime));
}
break;
case 'ArrowRight':
event.preventDefault();
if (videoRef.current) {
// Use the video element's current time directly to avoid stale state
const newTime = Math.min(videoRef.current.currentTime + 10, duration);
handleMobileSafeSeek(newTime);
logger.debug('Jumped forward 10 seconds to:', formatDetailedTime(newTime));
}
break;
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [handlePlay, handleMobileSafeSeek, duration, videoRef]);
return (
<div className="bg-background min-h-screen">
<MobilePlayPrompt videoRef={videoRef} onPlay={handlePlay} />
<div className="container mx-auto px-4 py-6 max-w-6xl">
{/* Video Player */}
<VideoPlayer
videoRef={videoRef}
currentTime={currentTime}
duration={duration}
isPlaying={isPlaying}
isMuted={isMuted}
onPlayPause={handlePlay}
onSeek={handleMobileSafeSeek}
onToggleMute={toggleMute}
/>
{/* Editing Tools */}
<EditingTools
onSplit={handleSplit}
onReset={handleReset}
onUndo={handleUndo}
onRedo={handleRedo}
onPlay={handlePlay}
isPlaying={isPlaying}
canUndo={historyPosition > 0}
canRedo={historyPosition < history.length - 1}
/>
{/* Timeline Controls */}
<TimelineControls
currentTime={currentTime}
duration={duration}
thumbnails={[]}
trimStart={trimStart}
trimEnd={trimEnd}
splitPoints={splitPoints}
zoomLevel={zoomLevel}
clipSegments={clipSegments}
selectedSegmentId={selectedSegmentId}
onSelectedSegmentChange={handleSelectedSegmentChange}
onSegmentUpdate={handleSegmentUpdate}
onChapterSave={handleChapterSave}
onTrimStartChange={handleTrimStartChange}
onTrimEndChange={handleTrimEndChange}
onZoomChange={handleZoomChange}
onSeek={handleMobileSafeSeek}
videoRef={videoRef}
hasUnsavedChanges={hasUnsavedChanges}
isIOSUninitialized={isMobile && !videoInitialized}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
onPlayPause={handlePlay}
/>
{/* Clip Segments */}
<ClipSegments segments={clipSegments} selectedSegmentId={selectedSegmentId} />
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,6 @@
// Import the audio poster image as a module
// Vite will handle this and provide the correct URL
import audioPosterJpg from '../../public/audio-poster.jpg';
export const AUDIO_POSTER_URL = audioPosterJpg;

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><svg style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css">.st0{fill:#333333;}.st1{fill:none;stroke:#333333;stroke-width:32;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}</style><g id="Layer_1"/><g id="Layer_2"><g><g><path class="st0" d="M208.15,380.19h-91.19c-5.7,0-10.32-4.62-10.32-10.32V142.13c0-5.7,4.62-10.32,10.32-10.32h91.19 c5.7,0,10.32,4.62,10.32,10.32v227.74C218.47,375.57,213.85,380.19,208.15,380.19z"/></g><g><path class="st0" d="M395.04,380.19h-91.19c-5.7,0-10.32-4.62-10.32-10.32V142.13c0-5.7,4.62-10.32,10.32-10.32h91.19 c5.7,0,10.32,4.62,10.32,10.32v227.74C405.36,375.57,400.74,380.19,395.04,380.19z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 832 B

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