Compare commits
58 commits
feat-video
...
main
Author | SHA1 | Date | |
---|---|---|---|
c5047d8df8 | |||
dcbfaca91c | |||
918df010f5 | |||
e9739bab45 | |||
e7ce9ef5c0 | |||
4829adf110 | |||
fdff0811a1 | |||
92c0ff579a | |||
847cff2b5c | |||
e8d3ff25be | |||
15d217453b | |||
029665145e | |||
487e098b96 | |||
fe7427a1f2 | |||
4bf41fe80e | |||
1fd04ca947 | |||
a1962d4b32 | |||
6e9c9ed81f | |||
51186e3253 | |||
150967b342 | |||
bb6244d862 | |||
a002422b77 | |||
24167b9624 | |||
b9db1a5e2e | |||
296aeac567 | |||
10c386f886 | |||
367faaddd1 | |||
3d59b87f09 | |||
5dee41de39 | |||
08bba5fc05 | |||
102414b514 | |||
c866fdd6ba | |||
5b601698a4 | |||
f040f73f51 | |||
b7a70d92fa | |||
2f43cef8da | |||
ad633e6fdf | |||
cd8d0ea49a | |||
a3997bfb1c | |||
4b0718c43f | |||
91d8179fa0 | |||
6532b19849 | |||
6ea8fd12a3 | |||
d971bb955f | |||
b52b008f89 | |||
30cf5d7176 | |||
6fd9a7d37f | |||
9c6d13559b | |||
8ec97a8219 | |||
de8f9ca718 | |||
a4bedca4db | |||
da565b3bfc | |||
239ff6cb60 | |||
da840b156d | |||
b08d493823 | |||
25eaa35758 | |||
cba2ed75ed | |||
de5bd07a90 |
20
.github/workflows/ci.yml
vendored
Normal file
20
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: "CI"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**/README.md'
|
||||
jobs:
|
||||
pre-commit:
|
||||
uses: ./.github/workflows/pre-commit.yml
|
||||
test:
|
||||
uses: ./.github/workflows/python.yml
|
||||
needs: [pre-commit]
|
||||
release:
|
||||
uses: ./.github/workflows/docker-build-push.yml
|
||||
secrets: inherit # pass all secrets
|
||||
needs: [test]
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
52
.github/workflows/docker-build-push.yml
vendored
Normal file
52
.github/workflows/docker-build-push.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
name: Docker build and push
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Build & release to DockerHub
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
# List of Docker images to use as base name for tags
|
||||
images: |
|
||||
mediacms/mediacms
|
||||
# Generate Docker tags based on the following events/attributes
|
||||
# Set latest tag for default branch
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=MediaCMS
|
||||
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API.
|
||||
org.opencontainers.image.vendor=MediaCMS
|
||||
org.opencontainers.image.url=https://mediacms.io/
|
||||
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
|
||||
org.opencontainers.image.licenses=AGPL-3.0
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
15
.github/workflows/lint_test.yml
vendored
15
.github/workflows/lint_test.yml
vendored
|
@ -1,15 +0,0 @@
|
|||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: pre-commit/action@v2.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
15
.github/workflows/pre-commit.yml
vendored
Normal file
15
.github/workflows/pre-commit.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: pre-commit
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Pre-Commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
9
.github/workflows/python.yml
vendored
9
.github/workflows/python.yml
vendored
|
@ -1,14 +1,11 @@
|
|||
name: Python Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
name: Build & test via docker-compose
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -29,7 +26,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Run Django Tests
|
||||
run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
||||
run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
|
||||
|
||||
# Run with coverage, saves report on htmlcov dir
|
||||
# run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest --cov --cov-report=html --cov-config=.coveragerc
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
repos:
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.9
|
||||
- repo: https://github.com/pycqa/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.5.4
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
rev: 23.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
Yiannis Stergiou - ys.stergiou@gmail.com
|
||||
Markos Gogoulos - mgogoulos@gmail.com
|
||||
Swift Ugandan - swiftugandan@gmail.com
|
||||
|
||||
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.8-buster AS compile-image
|
||||
FROM python:3.11.4-bookworm AS compile-image
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
|
@ -11,6 +11,7 @@ RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python
|
|||
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
|
@ -24,7 +25,7 @@ RUN wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-u
|
|||
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||
|
||||
############ RUNTIME IMAGE ############
|
||||
FROM python:3.8-slim-buster as runtime-image
|
||||
FROM python:3.11.4-bookworm as runtime-image
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM mediacms/mediacms:latest
|
||||
FROM python:3.11.4-bookworm AS compile-image
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
|
@ -7,10 +7,67 @@ ENV VIRTUAL_ENV=/home/mediacms.io
|
|||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
RUN cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
COPY requirements.txt .
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
COPY requirements-dev.txt .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
RUN pip install -r requirements-dev.txt
|
||||
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
|
||||
RUN wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
|
||||
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d ../bento4 && \
|
||||
mv ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* ../bento4/ && \
|
||||
rm -rf ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
|
||||
rm -rf ../bento4/docs && \
|
||||
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||
|
||||
############ RUNTIME IMAGE ############
|
||||
FROM python:3.11.4-bookworm as runtime-image
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# See: https://github.com/celery/celery/issues/6285#issuecomment-715316219
|
||||
ENV CELERY_APP='cms'
|
||||
|
||||
# Use these to toggle which processes supervisord should run
|
||||
ENV ENABLE_UWSGI='yes'
|
||||
ENV ENABLE_NGINX='yes'
|
||||
ENV ENABLE_CELERY_BEAT='yes'
|
||||
ENV ENABLE_CELERY_SHORT='yes'
|
||||
ENV ENABLE_CELERY_LONG='yes'
|
||||
ENV ENABLE_MIGRATIONS='yes'
|
||||
|
||||
# Set up virtualenv
|
||||
ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
COPY --chown=www-data:www-data --from=compile-image /home/mediacms.io /home/mediacms.io
|
||||
|
||||
RUN apt-get update -y && apt-get -y upgrade && apt-get install --no-install-recommends \
|
||||
supervisor nginx imagemagick procps wget xz-utils -y && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
apt-get purge --auto-remove && \
|
||||
apt-get clean
|
||||
|
||||
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
|
||||
mkdir -p ffmpeg-tmp && \
|
||||
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
|
||||
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
|
||||
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
|
||||
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
|
||||
EXPOSE 9000 80
|
||||
|
||||
RUN chmod +x ./deploy/docker/entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
|
||||
|
||||
CMD ["./deploy/docker/start.sh"]
|
||||
|
|
23
HISTORY.md
Normal file
23
HISTORY.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# History
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Features
|
||||
- Updates Python/Django requirements and Dockerfile to use latest 3.11 Python - https://github.com/mediacms-io/mediacms/pull/826/files. This update requires some manual steps, for existing (not new) installations. Check the update section under the [Admin docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#2-server-installation), either for single server or for Docker Compose installations
|
||||
- Upgrade postgres on Docker Compose - https://github.com/mediacms-io/mediacms/pull/749
|
||||
|
||||
### Fixes
|
||||
- video player options for HLS - https://github.com/mediacms-io/mediacms/pull/832
|
||||
- AVI videos not correctly recognised as videos - https://github.com/mediacms-io/mediacms/pull/833
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Fixes
|
||||
- Increase uwsgi buffer-size parameter. This prevents an error by uwsgi with large headers - [#5b60](https://github.com/mediacms-io/mediacms/commit/5b601698a41ad97f08c1830e14b1c18f73ab8315)
|
||||
- Fix issues with comments. These were not reported on the tracker but it is certain that they would not show comments on media files (non videos but also videos). Unfortunately this reverts work done with Timestamps on comments + Mentions on comments, more on PR [#802](https://github.com/mediacms-io/mediacms/pull/802)
|
||||
|
||||
### Features
|
||||
- Allow tags to contains other characters too, not only English alphabet ones [#801](https://github.com/mediacms-io/mediacms/pull/801)
|
||||
- Add simple cookie consent code [#799](https://github.com/mediacms-io/mediacms/pull/799)
|
||||
- Allow password reset & email verify pages on global login required [#790](https://github.com/mediacms-io/mediacms/pull/790)
|
||||
- Add api_url field to search api [#692](https://github.com/mediacms-io/mediacms/pull/692)
|
35
README.md
35
README.md
|
@ -1,15 +1,12 @@
|
|||
# MediaCMS
|
||||
|
||||
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/python/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:python)
|
||||
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/javascript/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:javascript)
|
||||
<br/>
|
||||
[![GitHub license](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
|
||||
[![Releases](https://img.shields.io/github/v/release/mediacms-io/mediacms?color=green)](https://github.com/mediacms-io/mediacms/releases/)
|
||||
[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/repository/docker/mediacms/mediacms/)
|
||||
[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/r/mediacms/mediacms)
|
||||
|
||||
|
||||
|
||||
MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes.
|
||||
MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes.
|
||||
|
||||
It is built mostly using the modern stack Django + React and includes a REST API.
|
||||
|
||||
|
@ -59,15 +56,15 @@ A demo is available at https://demo.mediacms.io
|
|||
|
||||
## Philosophy
|
||||
|
||||
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
|
||||
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
|
||||
|
||||
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
|
||||
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
|
||||
Copyright Markos Gogoulos and Yiannis Stergiou
|
||||
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
|
||||
Copyright Markos Gogoulos.
|
||||
|
||||
|
||||
## Support and paid services
|
||||
|
@ -76,9 +73,9 @@ We provide custom installations, development of extra functionality, migration f
|
|||
|
||||
|
||||
|
||||
## Hardware dependencies
|
||||
## Hardware considerations
|
||||
|
||||
For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram.
|
||||
For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram.
|
||||
|
||||
In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365).
|
||||
|
||||
|
@ -95,18 +92,26 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
|
|||
* [Single Server](docs/admins_docs.md#2-server-installation) page
|
||||
* [Docker Compose](docs/admins_docs.md#3-docker-installation) page
|
||||
|
||||
A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
## Technology
|
||||
|
||||
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, uWSGI, React, Fine Uploader, video.js, FFMPEG, Bento4
|
||||
|
||||
|
||||
|
@ -114,7 +119,7 @@ This software uses the following list of awesome technologies: Python, Django, D
|
|||
|
||||
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
||||
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org
|
||||
- **Heritales** International Heritage Film Festival - https://stage.heritales.org
|
||||
- **American Association of Gynecologic Laparoscopists** - https://surgeryu.aagl.org/
|
||||
|
||||
|
||||
## How to contribute
|
||||
|
@ -124,10 +129,12 @@ If you like the project, here's a few things you can do
|
|||
- Suggest us to others that are interested to hire us
|
||||
- Write a blog post/article about MediaCMS
|
||||
- Share on social media about the project
|
||||
- Open issues, participate on discussions, report bugs, suggest ideas
|
||||
- Open issues, participate on [discussions](https://github.com/mediacms-io/mediacms/discussions), report bugs, suggest ideas
|
||||
- [Show and tell](https://github.com/mediacms-io/mediacms/discussions/categories/show-and-tell) how you are using the project
|
||||
- Star the project
|
||||
- Add functionality, work on a PR, fix an issue!
|
||||
- Add functionality, work on a PR, fix an issue!
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
info@mediacms.io
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
@ -6,7 +6,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
@ -59,7 +59,7 @@ def login():
|
|||
file.writelines(f'USERNAME={json.loads(response.text)["username"]}\n')
|
||||
print(f"Welcome to MediaCMS [bold blue]{username}[/bold blue]. Your auth creds have been suceesfully stored in the .env file", ":v:")
|
||||
else:
|
||||
print(f'Error: {"non_field_errors":["User not found."]}')
|
||||
print(f'Error: {"non_field_errors": ["User not found."]}')
|
||||
|
||||
|
||||
@apis.command()
|
||||
|
@ -73,7 +73,7 @@ def upload_media():
|
|||
if os.path.isdir(path):
|
||||
for filename in os.listdir(path):
|
||||
files = {}
|
||||
abs = os.path.abspath("{path}/{filename}")
|
||||
abs = os.path.abspath(f"{path}/{filename}")
|
||||
files['media_file'] = open(f'{abs}', 'rb')
|
||||
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
|
||||
if response.status_code == 201:
|
||||
|
|
|
@ -18,7 +18,6 @@ class FastPaginationWithoutCount(PageNumberPagination):
|
|||
django_paginator_class = FasterDjangoPaginator
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
|
||||
return Response(
|
||||
OrderedDict(
|
||||
[
|
||||
|
|
48
cms/dev_settings.py
Normal file
48
cms/dev_settings.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Development settings, used in docker-compose-dev.yaml
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'imagekit',
|
||||
'files.apps.FilesConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'actions.apps.ActionsConfig',
|
||||
'debug_toolbar',
|
||||
'mptt',
|
||||
'crispy_forms',
|
||||
'uploader.apps.UploaderConfig',
|
||||
'djcelery_email',
|
||||
'ckeditor',
|
||||
'drf_yasg',
|
||||
'corsheaders',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
]
|
||||
|
||||
DEBUG = True
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static/'),)
|
||||
STATIC_ROOT = None
|
|
@ -7,6 +7,7 @@ DEBUG = False
|
|||
# PORTAL NAME, this is the portal title and
|
||||
# is also shown on several places as emails
|
||||
PORTAL_NAME = "MediaCMS"
|
||||
PORTAL_DESCRIPTION = ""
|
||||
LANGUAGE_CODE = "en-us"
|
||||
TIME_ZONE = "Europe/London"
|
||||
|
||||
|
@ -86,11 +87,15 @@ MAX_MEDIA_PER_PLAYLIST = 70
|
|||
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
|
||||
|
||||
MAX_CHARS_FOR_COMMENT = 10000 # so that it doesn't end up huge
|
||||
TIMESTAMP_IN_TIMEBAR = False # shows timestamped comments in the timebar for videos
|
||||
ALLOW_MENTION_IN_COMMENTS = False # allowing to mention other users with @ in the comments
|
||||
|
||||
# valid options: content, author
|
||||
RELATED_MEDIA_STRATEGY = "content"
|
||||
|
||||
# Whether or not to generate a sitemap.xml listing the pages on the site (default: False)
|
||||
GENERATE_SITEMAP = False
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
@ -465,7 +470,7 @@ except ImportError:
|
|||
|
||||
if "http" not in FRONTEND_HOST:
|
||||
# FRONTEND_HOST needs a http:// preffix
|
||||
FRONTEND_HOST = f"http://{FRONTEND_HOST}"
|
||||
FRONTEND_HOST = f"http://{FRONTEND_HOST}" # noqa
|
||||
|
||||
if LOCAL_INSTALL:
|
||||
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
|
||||
|
@ -479,4 +484,22 @@ if GLOBAL_LOGIN_REQUIRED:
|
|||
r'/accounts/login/$',
|
||||
r'/accounts/logout/$',
|
||||
r'/accounts/signup/$',
|
||||
r'/accounts/password/.*/$',
|
||||
r'/accounts/confirm-email/.*/$',
|
||||
r'/api/v[0-9]+/',
|
||||
]
|
||||
|
||||
# if True, only show original, don't perform any action on videos
|
||||
DO_NOT_TRANSCODE_VIDEO = False
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
# the following is related to local development using docker
|
||||
# and docker-compose-dev.yaml
|
||||
try:
|
||||
DEVELOPMENT_MODE = os.environ.get("DEVELOPMENT_MODE")
|
||||
if DEVELOPMENT_MODE:
|
||||
# keep a dev_settings.py file for local overrides
|
||||
from .dev_settings import * # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import debug_toolbar
|
||||
from django.conf.urls import include, re_path
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.urls import path, re_path
|
||||
from django.views.generic.base import TemplateView
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
|
|
|
@ -16,6 +16,10 @@ server {
|
|||
|
||||
location /media {
|
||||
alias /home/mediacms.io/mediacms/media_files ;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
}
|
||||
|
||||
location / {
|
||||
|
|
|
@ -7,7 +7,7 @@ if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
|
|||
echo "Running migrations service"
|
||||
python manage.py migrate
|
||||
EXISTING_INSTALLATION=`echo "from users.models import User; print(User.objects.exists())" |python manage.py shell`
|
||||
if [ "$EXISTING_INSTALLATION" = "True" ]; then
|
||||
if [ "$EXISTING_INSTALLATION" = "True" ]; then
|
||||
echo "Loaddata has already run"
|
||||
else
|
||||
echo "Running loaddata and creating admin user"
|
||||
|
@ -67,4 +67,5 @@ fi
|
|||
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
|
||||
echo "Enabling celery-long task worker"
|
||||
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
|
||||
rm /var/run/mediacms/* -f # remove any stale id, so that on forced restarts of celery workers there are no stale processes that prevent new ones
|
||||
fi
|
||||
|
|
|
@ -21,3 +21,4 @@ vacuum = true
|
|||
hook-master-start = unix_signal:15 gracefully_kill_them_all
|
||||
need-app = true
|
||||
die-on-term = true
|
||||
buffer-size=32768
|
||||
|
|
|
@ -8,15 +8,13 @@ User=www-data
|
|||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
Environment=CELERY_APP="cms"
|
||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} beat -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms beat --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
|
||||
ExecStop=/bin/kill -s TERM $MAINPID
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -8,23 +8,21 @@ User=www-data
|
|||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERYD_NODES="long1"
|
||||
Environment=CELERY_QUEUE="long_tasks"
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
Environment=CELERY_APP="cms"
|
||||
Environment=CELERYD_MULTI="multi"
|
||||
Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1"
|
||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -8,14 +8,13 @@ User=www-data
|
|||
Group=www-data
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
WorkingDirectory=/home/mediacms.io/mediacms
|
||||
Environment=CELERYD_NODES="short1 short2"
|
||||
Environment=CELERY_QUEUE="short_tasks"
|
||||
# Absolute or relative path to the 'celery' command:
|
||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
||||
# App instance to use
|
||||
# comment out this line if you don't use an app
|
||||
Environment=CELERY_APP="cms"
|
||||
# or fully qualified:
|
||||
#CELERY_APP="proj.tasks:app"
|
||||
# How to call manage.py
|
||||
|
@ -28,13 +27,12 @@ Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
|
|||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
||||
Environment=APP_DIR="/home/mediacms.io/mediacms"
|
||||
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
||||
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
|
||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
34
deploy/local_install/selinux-mediacms.te
Normal file
34
deploy/local_install/selinux-mediacms.te
Normal file
|
@ -0,0 +1,34 @@
|
|||
module selinux-mediacms 1.0;
|
||||
|
||||
require {
|
||||
type init_t;
|
||||
type var_t;
|
||||
type redis_port_t;
|
||||
type postgresql_port_t;
|
||||
type httpd_t;
|
||||
type httpd_sys_content_t;
|
||||
type httpd_sys_rw_content_t;
|
||||
class file { append create execute execute_no_trans getattr ioctl lock open read rename setattr unlink write };
|
||||
class dir { add_name remove_name rmdir };
|
||||
class tcp_socket name_connect;
|
||||
class lnk_file read;
|
||||
}
|
||||
|
||||
#============= httpd_t ==============
|
||||
|
||||
allow httpd_t var_t:file { getattr open read };
|
||||
|
||||
#============= init_t ==============
|
||||
allow init_t postgresql_port_t:tcp_socket name_connect;
|
||||
|
||||
allow init_t redis_port_t:tcp_socket name_connect;
|
||||
|
||||
allow init_t httpd_sys_content_t:dir rmdir;
|
||||
|
||||
allow init_t httpd_sys_content_t:file { append create execute execute_no_trans ioctl lock open read rename setattr unlink write };
|
||||
|
||||
allow init_t httpd_sys_content_t:lnk_file read;
|
||||
|
||||
allow init_t httpd_sys_rw_content_t:dir { add_name remove_name rmdir };
|
||||
|
||||
allow init_t httpd_sys_rw_content_t:file { create ioctl lock open read setattr unlink write };
|
|
@ -24,4 +24,4 @@ vacuum = true
|
|||
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
|
||||
|
||||
disable-logging = true
|
||||
|
||||
buffer-size=32768
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
migrations:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-dev
|
||||
image: mediacms/mediacms-dev:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
command: "python manage.py migrate"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: "True"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
frontend:
|
||||
image: node:14
|
||||
volumes:
|
||||
|
@ -18,17 +34,20 @@ services:
|
|||
context: .
|
||||
dockerfile: ./Dockerfile-dev
|
||||
image: mediacms/mediacms-dev:latest
|
||||
command: "python manage.py runserver 0.0.0.0:80"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: "True"
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_PASSWORD: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -36,8 +55,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
@ -49,3 +69,16 @@ services:
|
|||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
celery_worker:
|
||||
image: mediacms/mediacms-dev:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- web
|
||||
|
|
|
@ -68,7 +68,7 @@ services:
|
|||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data/:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -76,8 +76,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
|
|
@ -70,7 +70,7 @@ services:
|
|||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data/:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -78,8 +78,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
|
|
@ -90,7 +90,7 @@ services:
|
|||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -98,8 +98,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
|
|
@ -66,7 +66,7 @@ services:
|
|||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -74,8 +74,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
|
|
@ -62,7 +62,7 @@ services:
|
|||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:13
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
|
@ -70,8 +70,9 @@ services:
|
|||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
- [1. Welcome](#1-welcome)
|
||||
- [2. Server Installaton](#2-server-installation)
|
||||
- [3. Docker Installation](#3-docker-installation)
|
||||
- [4. Docker Deployement options](#4-docker-deployment-options)
|
||||
- [4. Docker Deployment options](#4-docker-deployment-options)
|
||||
- [5. Configuration](#5-configuration)
|
||||
- [6. Manage pages](#6-manage-pages)
|
||||
- [7. Django admin dashboard](#7-django-admin-dashboard)
|
||||
|
@ -16,19 +16,21 @@
|
|||
- [13. How To Add A Static Page To The Sidebar](#13-how-to-add-a-static-page-to-the-sidebar)
|
||||
- [14. Add Google Analytics](#14-add-google-analytics)
|
||||
- [15. Debugging email issues](#15-debugging-email-issues)
|
||||
|
||||
- [16. Frequently Asked Questions](#16-frequently-asked-questions)
|
||||
- [17. Cookie consent code](#17-cookie-consent-code)
|
||||
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
|
||||
|
||||
## 1. Welcome
|
||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||
|
||||
## 2. Server Installation
|
||||
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu (tested on versions 20, 22).
|
||||
|
||||
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
Installation on an Ubuntu system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
|
||||
Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
|
||||
Automated script - tested on Ubuntu 20, Ubuntu 22 and Debian Buster
|
||||
|
||||
```bash
|
||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||
|
@ -36,7 +38,7 @@ git clone https://github.com/mediacms-io/mediacms
|
|||
cd /home/mediacms.io/mediacms/ && bash ./install.sh
|
||||
```
|
||||
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
|
||||
|
||||
### Update
|
||||
|
@ -47,10 +49,25 @@ If you've used the above way to install MediaCMS, update with the following:
|
|||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||
source /home/mediacms.io/bin/activate # use virtualenv
|
||||
git pull # update code
|
||||
pip install -r requirements.txt -U # run pip install to update
|
||||
python manage.py migrate # run Django migrations
|
||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
||||
```
|
||||
|
||||
### Update from version 2 to version 3
|
||||
Version 3 is using Django 4 and Celery 5, and needs a recent Python 3.x version. If you are updating from an older version, make sure Python is updated first. Version 2 could run on Python 3.6, but version 3 needs Python3.8 and higher.
|
||||
The syntax for starting Celery has also changed, so you have to copy the celery related systemctl files and restart
|
||||
|
||||
```
|
||||
# cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
|
||||
# cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
|
||||
# cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
|
||||
# systemctl daemon-reload
|
||||
# systemctl start celery_long celery_short celery_beat
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Configuration
|
||||
Checkout the configuration section here.
|
||||
|
||||
|
@ -64,7 +81,7 @@ 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 18/20 systems this is:
|
||||
For Ubuntu 20/22 systems this is:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
|
@ -110,6 +127,18 @@ docker-compose down
|
|||
docker-compose up
|
||||
```
|
||||
|
||||
### Update from version 2 to version 3
|
||||
Version 3 is using Python 3.11 and PostgreSQL 15. If you are updating from an older version, that was using PostgreSQL 13, the automatic update will not work, as you will receive the following message when the PostgreSQL container starts:
|
||||
|
||||
```
|
||||
db_1 | 2023-06-27 11:07:42.959 UTC [1] FATAL: database files are incompatible with server
|
||||
db_1 | 2023-06-27 11:07:42.959 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 13, which is not compatible with this version 15.2.
|
||||
```
|
||||
|
||||
At this point there are two options: either edit the Docker Compose file and make use of the existing postgres:13 image, or otherwise you have to perform the migration from postgresql 13 to version 15. More notes on https://github.com/mediacms-io/mediacms/pull/749
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
Checkout the configuration docs here.
|
||||
|
||||
|
@ -144,9 +173,9 @@ The main container runs migrations, mediacms_web, celery_beat, celery_workers (c
|
|||
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
|
||||
|
||||
### Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
|
||||
Before trying this out make sure the ip points to my_domain.com.
|
||||
Before trying this out make sure the ip points to my_domain.com.
|
||||
|
||||
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
|
||||
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
|
||||
|
||||
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
|
||||
|
||||
|
@ -176,15 +205,15 @@ The architecture below generalises all the deployment scenarios above, and provi
|
|||
## 5. Configuration
|
||||
Several options are available on `cms/settings.py`, most of the things that are allowed or should be disallowed are described there.
|
||||
|
||||
It is advisable to override any of them by adding it to `local_settings.py` .
|
||||
It is advisable to override any of them by adding it to `local_settings.py` .
|
||||
|
||||
In case of a the single server installation, add to `cms/local_settings.py` .
|
||||
|
||||
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
|
||||
|
||||
Any change needs restart of MediaCMS in order to take effect.
|
||||
Any change needs restart of MediaCMS in order to take effect.
|
||||
|
||||
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
|
||||
```bash
|
||||
#systemctl restart mediacms
|
||||
|
@ -212,7 +241,7 @@ PORTAL_NAME = 'my awesome portal'
|
|||
|
||||
By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are:
|
||||
|
||||
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
|
||||
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
|
||||
|
||||
- **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
|
||||
|
||||
|
@ -281,7 +310,7 @@ Make changes (True/False) to any of the following:
|
|||
|
||||
### 5.9 Show or hide the download option on a media
|
||||
|
||||
Edit `templates/config/installation/features.html` and set
|
||||
Edit `templates/config/installation/features.html` and set
|
||||
|
||||
```
|
||||
download: false
|
||||
|
@ -290,7 +319,7 @@ download: false
|
|||
### 5.10 Automatically hide media upon being reported
|
||||
|
||||
set a low number for variable `REPORTED_TIMES_THRESHOLD`
|
||||
eg
|
||||
eg
|
||||
|
||||
```
|
||||
REPORTED_TIMES_THRESHOLD = 2
|
||||
|
@ -338,7 +367,7 @@ set value
|
|||
MEDIA_IS_REVIEWED = False
|
||||
```
|
||||
|
||||
any uploaded media now needs to be reviewed before it can appear to the listings.
|
||||
any uploaded media now needs to be reviewed before it can appear to the listings.
|
||||
MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed
|
||||
|
||||
### 5.15 Specify maximum number of media for a playlist
|
||||
|
@ -353,7 +382,7 @@ MAX_MEDIA_PER_PLAYLIST = 14
|
|||
|
||||
### 5.16 Specify maximum size of a media that can be uploaded
|
||||
|
||||
change `UPLOAD_MAX_SIZE`.
|
||||
change `UPLOAD_MAX_SIZE`.
|
||||
|
||||
default is 4GB
|
||||
|
||||
|
@ -416,7 +445,7 @@ Global notifications that are implemented are controlled by the following option
|
|||
|
||||
```
|
||||
USERS_NOTIFICATIONS = {
|
||||
'MEDIA_ADDED': True,
|
||||
'MEDIA_ADDED': True,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -441,6 +470,14 @@ ADMINS_NOTIFICATIONS = {
|
|||
- Make the portal workflow public, but at the same time set `GLOBAL_LOGIN_REQUIRED = True` so that only logged in users can see content.
|
||||
- You can either set `REGISTER_ALLOWED = False` if you want to add members yourself or checkout options on "django-allauth settings" that affects registration in `cms/settings.py`. Eg set the portal invite only, or set email confirmation as mandatory, so that you control who registers.
|
||||
|
||||
### 5.24 Enable the sitemap
|
||||
|
||||
Whether or not to enable generation of a sitemap file at http://your_installation/sitemap.xml (default: False)
|
||||
|
||||
```
|
||||
GENERATE_SITEMAP = False
|
||||
```
|
||||
|
||||
## 6. Manage pages
|
||||
to be written
|
||||
|
||||
|
@ -459,17 +496,19 @@ to be written
|
|||
Through the admin section - http://your_installation/admin/
|
||||
|
||||
## 12. Video transcoding
|
||||
Add / remove resolutions and profiles through http://your_installation/admin/encodeprofile
|
||||
Add / remove resolutions and profiles by modifying the database table of `Encode profiles` through https://your_installation/admin/files/encodeprofile/
|
||||
|
||||
For example, the `Active` state of any profile can be toggled to enable or disable it.
|
||||
|
||||
## 13. How To Add A Static Page To The Sidebar
|
||||
|
||||
### 1. Create your html page in templates/cms/
|
||||
### 1. Create your html page in templates/cms/
|
||||
e.g. duplicate and rename about.html
|
||||
```
|
||||
sudo cp templates/cms/about.html templates/cms/volunteer.html
|
||||
```
|
||||
|
||||
### 2. Create your css file in static/css/
|
||||
### 2. Create your css file in static/css/
|
||||
```
|
||||
touch static/css/volunteer.css
|
||||
```
|
||||
|
@ -533,24 +572,24 @@ urlpatterns = [
|
|||
|
||||
### 8. Add your page to the left sidebar
|
||||
To add a link to your page as a menu item in the left sidebar,
|
||||
add the following code after the last line in _commons.js
|
||||
add the following code after the last line in _commons.js
|
||||
```
|
||||
/* Checks that a given selector has loaded. */
|
||||
const checkElement = async selector => {
|
||||
while ( document.querySelector(selector) === null) {
|
||||
await new Promise( resolve => requestAnimationFrame(resolve) )
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
return document.querySelector(selector);
|
||||
};
|
||||
|
||||
/* Checks that sidebar nav menu has loaded, then adds menu item. */
|
||||
checkElement('.nav-menu')
|
||||
.then((element) => {
|
||||
(function(){
|
||||
var a = document.createElement('a');
|
||||
(function(){
|
||||
var a = document.createElement('a');
|
||||
a.href = "/volunteer";
|
||||
a.title = "Volunteer";
|
||||
|
||||
|
||||
var s = document.createElement('span');
|
||||
s.className = "menu-item-icon";
|
||||
|
||||
|
@ -560,7 +599,7 @@ checkElement('.nav-menu')
|
|||
|
||||
s.appendChild(icon);
|
||||
a.appendChild(s);
|
||||
|
||||
|
||||
var linkText = document.createTextNode("Volunteer");
|
||||
var t = document.createElement('span');
|
||||
|
||||
|
@ -572,14 +611,14 @@ checkElement('.nav-menu')
|
|||
listItem.appendChild(a);
|
||||
|
||||
//if signed out use 3rd nav-menu
|
||||
var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
|
||||
var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
|
||||
var loc = elem.innerText;
|
||||
if (loc.includes("About")){
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
} else { //if signed in use 4th nav-menu
|
||||
elem = document.querySelector(".nav-menu:nth-child(4) nav ul");
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
```
|
||||
|
@ -605,7 +644,7 @@ Instructions contributed by @alberto98fx
|
|||
|
||||
2. Add the Gtag/Analytics script
|
||||
|
||||
3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
|
||||
3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
|
||||
|
||||
```
|
||||
<head>
|
||||
|
@ -616,7 +655,7 @@ Instructions contributed by @alberto98fx
|
|||
{% include "common/head-meta.html" %}
|
||||
|
||||
{% block headermeta %}
|
||||
|
||||
|
||||
<meta property="og:title" content="{{PORTAL_NAME}}">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
|
@ -629,17 +668,17 @@ Instructions contributed by @alberto98fx
|
|||
{% block topimports %}{%endblock topimports %}
|
||||
|
||||
{% include "config/index.html" %}
|
||||
|
||||
|
||||
{% endblock head %}
|
||||
|
||||
</head>
|
||||
```
|
||||
|
||||
4. Add ``` {% include "tracking.html" %} ``` at the end inside the section ```<head>```
|
||||
|
||||
5. If you are using Docker and didn't mount the entire dir you need to bind a new volume:
|
||||
|
||||
5. If you are using Docker and didn't mount the entire dir you need to bind a new volume:
|
||||
```
|
||||
|
||||
|
||||
web:
|
||||
image: mediacms/mediacms:latest
|
||||
restart: unless-stopped
|
||||
|
@ -650,7 +689,7 @@ Instructions contributed by @alberto98fx
|
|||
volumes:
|
||||
- ./templates/root.html:/home/mediacms.io/mediacms/templates/root.html
|
||||
- ./templates/tracking.html://home/mediacms.io/mediacms/templates/tracking.html
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 15. Debugging email issues
|
||||
|
@ -681,9 +720,62 @@ email = EmailMessage(
|
|||
email.send(fail_silently=False)
|
||||
```
|
||||
|
||||
You have the chance to either receive the email (in this case it will be sent to recipient@email.com) otherwise you will see the error.
|
||||
You have the chance to either receive the email (in this case it will be sent to recipient@email.com) otherwise you will see the error.
|
||||
For example, while specifying wrong password for my Gmail account I get
|
||||
|
||||
```
|
||||
SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted. Learn more at\n5.7.8 https://support.google.com/mail/?p=BadCredentials d4sm12687785wrc.34 - gsmtp')
|
||||
```
|
||||
|
||||
## 16. Frequently Asked Questions
|
||||
Video is playing but preview thumbnails are not showing for large video files
|
||||
|
||||
Chances are that the sprites file was not created correctly.
|
||||
The output of files.tasks.produce_sprite_from_video() function in this case is something like this
|
||||
|
||||
```
|
||||
convert-im6.q16: width or height exceeds limit `/tmp/img001.jpg' @ error/cache.c/OpenPixelCache/3912.
|
||||
```
|
||||
|
||||
Solution: edit file `/etc/ImageMagick-6/policy.xml` and set bigger values for the lines that contain width and height. For example
|
||||
|
||||
```
|
||||
<policy domain="resource" name="height" value="16000KP"/>
|
||||
<policy domain="resource" name="width" value="16000KP"/>
|
||||
```
|
||||
|
||||
Newly added video files now will be able to produce the sprites file needed for thumbnail previews. To re-run that task on existing videos, enter the Django shell
|
||||
|
||||
|
||||
```
|
||||
root@8433f923ccf5:/home/mediacms.io/mediacms# source /home/mediacms.io/bin/activate
|
||||
root@8433f923ccf5:/home/mediacms.io/mediacms# python manage.py shell
|
||||
Python 3.8.14 (default, Sep 13 2022, 02:23:58)
|
||||
```
|
||||
|
||||
and run
|
||||
|
||||
```
|
||||
In [1]: from files.models import Media
|
||||
In [2]: from files.tasks import produce_sprite_from_video
|
||||
|
||||
In [3]: for media in Media.objects.filter(media_type='video', sprites=''):
|
||||
...: produce_sprite_from_video(media.friendly_token)
|
||||
```
|
||||
|
||||
this will re-create the sprites for videos that the task failed.
|
||||
|
||||
|
||||
## 17. Cookie consent code
|
||||
On file `templates/components/header.html` you can find a simple cookie consent code. It is commented, so you have to remove the `{% comment %}` and `{% endcomment %}` lines in order to enable it. Or you can replace that part with your own code that handles cookie consent banners.
|
||||
|
||||
![Simple Cookie Consent](images/cookie_consent.png)
|
||||
|
||||
## 18. Disable encoding and show only original file
|
||||
When videos are uploaded, they are getting encoded to multiple resolutions, a procedure called transcoding. Sometimes this is not needed and you only need to show the original file, eg when MediaCMS is running on a low capabilities server. To achieve this, edit settings.py and set
|
||||
|
||||
```
|
||||
DO_NOT_TRANSCODE_VIDEO = True
|
||||
```
|
||||
|
||||
This will disable the transcoding process and only the original file will be shown. Note that this will also disable the sprites file creation, so you will not have the preview thumbnails on the video player.
|
60
docs/dev_exp.md
Normal file
60
docs/dev_exp.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Developer Experience
|
||||
There is ongoing effort to provide a better developer experience and document it.
|
||||
|
||||
## How to develop locally with Docker
|
||||
First install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
Then run `docker-compose -f docker-compose-dev.yaml up`
|
||||
|
||||
```
|
||||
user@user:~/mediacms$ docker-compose -f docker-compose-dev.yaml up
|
||||
```
|
||||
|
||||
In a few minutes the app will be available at http://localhost . Login via admin/admin
|
||||
|
||||
### What does docker-compose-dev.yaml do?
|
||||
It build the two images used for backend and frontend.
|
||||
|
||||
* Backend: `mediacms/mediacms-dev:latest`
|
||||
* Frontend: `frontend`
|
||||
|
||||
and will start all services required for MediaCMS, as Celery/Redis for asynchronous tasks, PostgreSQL database, Django and React
|
||||
|
||||
For Django, the changes from the image produced by docker-compose.yaml are these:
|
||||
|
||||
* Django runs in debug mode, with `python manage.py runserver`
|
||||
* uwsgi and nginx are not run
|
||||
* Django runs in Debug mode, with Debug Toolbar
|
||||
* Static files (js/css) are loaded from static/ folder
|
||||
* corsheaders is installed and configured to allow all origins
|
||||
|
||||
For React, it will run `npm start` in the frontend folder, which will start the development server.
|
||||
Check it on http://localhost:8088/
|
||||
|
||||
### How to develop in Django
|
||||
Django starts at http://localhost and is reloading automatically. Making any change to the python code should refresh Django.
|
||||
|
||||
### How to develop in React
|
||||
React is started on http://localhost:8088/ , code is located in frontend/ , so making changes there should have instant effect on the page. Keep in mind that React is loading data from Django, and that it has to be built so that Django can serve it.
|
||||
|
||||
### Making changes to the frontend
|
||||
|
||||
The way React is added is more complicated than the usual SPA project and this is because React is used as a library loaded by Django Templates, so it is not a standalone project and is not handling routes etc.
|
||||
|
||||
The two directories to consider are:
|
||||
* frontend/src , for the React files
|
||||
* templates/, for the Django templates.
|
||||
|
||||
Django is using a highly intuitive hierarchical templating system (https://docs.djangoproject.com/en/4.2/ref/templates/), where the base template is templates/root.html and all other templates are extending it.
|
||||
|
||||
React is called through the Django templates, eg templates/cms/media.html is loading js/media.js
|
||||
|
||||
In order to make changes to React code, edit code on frontend/src and check it's effect on http://localhost:8088/ . Once ready, build it and copy it to the Django static folder, so that it is served by Django.
|
||||
|
||||
### Development workflow with the frontend
|
||||
1. Edit frontend/src/ files
|
||||
2. Check changes on http://localhost:8088/
|
||||
3. Build frontend with `docker-compose -f docker-compose-dev.yaml exec frontend npm run dist`
|
||||
4. Copy static files to Django static folder with`cp -r frontend/dist/static/* static/`
|
||||
5. Restart Django - `docker-compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
|
||||
6. Commit the changes
|
|
@ -54,6 +54,13 @@ docker-compose -f docker-compose-dev.yaml build
|
|||
docker-compose -f docker-compose-dev.yaml up
|
||||
```
|
||||
|
||||
An `admin` user is created during the installation process. Its attributes are defined in `docker-compose-dev.yaml`:
|
||||
```
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_PASSWORD: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
```
|
||||
|
||||
### Frontend application changes
|
||||
Eg change `frontend/src/static/js/pages/HomePage.tsx` , dev application refreshes in a number of seconds (hot reloading) and I see the changes, once I'm happy I can run
|
||||
|
||||
|
|
BIN
docs/images/TimebarComments_Hit.png
Normal file
BIN
docs/images/TimebarComments_Hit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 750 KiB |
BIN
docs/images/TimebarComments_Hover.png
Normal file
BIN
docs/images/TimebarComments_Hover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 91 KiB |
BIN
docs/images/cookie_consent.png
Normal file
BIN
docs/images/cookie_consent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 580 KiB |
|
@ -7,6 +7,7 @@
|
|||
- [Search media](#search-media)
|
||||
- [Using Timestamps for sharing](#using-timestamps-for-sharing)
|
||||
- [Mentionning users in comments](#Mentionning-users-in-comments)
|
||||
- [Show comments in the Timebar](#Show-comments-in-the-Timebar)
|
||||
- [Share media](#share-media)
|
||||
- [Embed media](#embed-media)
|
||||
- [Customize my profile options](#customize-my-profile-options)
|
||||
|
@ -234,6 +235,17 @@ Comments send with mentions will contain a link to the user page, and can be set
|
|||
<img src="./images/Mention4.png"/>
|
||||
</p>
|
||||
|
||||
## Show comments in the Timebar
|
||||
|
||||
When enabled, comments including a timestamp will also be displayed in the current video Timebar as a little colorful dot. The comment can be previewed by hovering the dot (left image) and it will be displayed on top of the video when reaching the correct time (right image).
|
||||
|
||||
Only comments with correct timestamps formats (HH:MM:SS or MM:SS) will be picked up and appear in the Timebar.
|
||||
|
||||
<p align="left">
|
||||
<img src="./images/TimebarComments_Hover.png" height="180" alt="Comment preview on hover"/>
|
||||
<img src="./images/TimebarComments_Hit.png" height="180" alt="Comment shown when the timestamp is reached "/>
|
||||
</p>
|
||||
|
||||
## Search media
|
||||
How search can be used
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@ class MediaAdmin(admin.ModelAdmin):
|
|||
def get_comments_count(self, obj):
|
||||
return obj.comments.count()
|
||||
|
||||
@admin.action(description="Generate missing encoding(s)", permissions=["change"])
|
||||
def generate_missing_encodings(modeladmin, request, queryset):
|
||||
for m in queryset:
|
||||
m.encode(force=False)
|
||||
|
||||
actions = [generate_missing_encodings]
|
||||
get_comments_count.short_description = "Comments count"
|
||||
|
||||
|
||||
|
@ -74,7 +80,18 @@ class SubtitleAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class EncodingAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
list_display = ["get_title", "chunk", "profile", "progress", "status", "has_file"]
|
||||
list_filter = ["chunk", "profile", "status"]
|
||||
|
||||
def get_title(self, obj):
|
||||
return str(obj)
|
||||
|
||||
get_title.short_description = "Encoding"
|
||||
|
||||
def has_file(self, obj):
|
||||
return obj.media_encoding_url is not None
|
||||
|
||||
has_file.short_description = "Has file"
|
||||
|
||||
|
||||
admin.site.register(EncodeProfile, EncodeProfileAdmin)
|
||||
|
|
|
@ -6,13 +6,15 @@ from .methods import is_mediacms_editor, is_mediacms_manager
|
|||
def stuff(request):
|
||||
"""Pass settings to the frontend"""
|
||||
ret = {}
|
||||
ret["FRONTEND_HOST"] = request.build_absolute_uri('/')
|
||||
ret["FRONTEND_HOST"] = request.build_absolute_uri('/').rstrip('/')
|
||||
ret["DEFAULT_THEME"] = settings.DEFAULT_THEME
|
||||
ret["PORTAL_NAME"] = settings.PORTAL_NAME
|
||||
ret["PORTAL_DESCRIPTION"] = settings.PORTAL_DESCRIPTION
|
||||
ret["LOAD_FROM_CDN"] = settings.LOAD_FROM_CDN
|
||||
ret["CAN_LOGIN"] = settings.LOGIN_ALLOWED
|
||||
ret["CAN_REGISTER"] = settings.REGISTER_ALLOWED
|
||||
ret["CAN_UPLOAD_MEDIA"] = settings.UPLOAD_MEDIA_ALLOWED
|
||||
ret["TIMESTAMP_IN_TIMEBAR"] = settings.TIMESTAMP_IN_TIMEBAR
|
||||
ret["CAN_MENTION_IN_COMMENTS"] = settings.ALLOW_MENTION_IN_COMMENTS
|
||||
ret["CAN_LIKE_MEDIA"] = settings.CAN_LIKE_MEDIA
|
||||
ret["CAN_DISLIKE_MEDIA"] = settings.CAN_DISLIKE_MEDIA
|
||||
|
|
|
@ -102,7 +102,7 @@ class SearchRSSFeed(Feed):
|
|||
description = "Latest Media RSS feed"
|
||||
|
||||
def link(self, obj):
|
||||
return f"/rss/search"
|
||||
return "/rss/search"
|
||||
|
||||
def get_object(self, request):
|
||||
category = request.GET.get("c", "")
|
||||
|
|
|
@ -538,8 +538,8 @@ def get_base_ffmpeg_command(
|
|||
|
||||
target_width = round(target_height * 16 / 9)
|
||||
scale_filter_opts = [
|
||||
f"if(lt(iw\\,ih)\\,{target_height}\\,{target_width})",
|
||||
f"if(lt(iw\\,ih)\\,{target_width}\\,{target_height})",
|
||||
f"if(lt(iw\\,ih)\\,{target_height}\\,{target_width})", # noqa
|
||||
f"if(lt(iw\\,ih)\\,{target_width}\\,{target_height})", # noqa
|
||||
"force_original_aspect_ratio=decrease",
|
||||
"force_divisible_by=2",
|
||||
"flags=lanczos",
|
||||
|
@ -785,3 +785,11 @@ def clean_query(query):
|
|||
query = query.replace(char, "")
|
||||
|
||||
return query.lower()
|
||||
|
||||
|
||||
def get_alphanumeric_only(string):
|
||||
"""Returns a query that contains only alphanumeric characters
|
||||
This include characters other than the English alphabet too
|
||||
"""
|
||||
string = "".join([char for char in string if char.isalnum()])
|
||||
return string.lower()
|
||||
|
|
|
@ -305,7 +305,6 @@ def show_related_media_author(media, request, limit):
|
|||
|
||||
|
||||
def show_related_media_calculated(media, request, limit):
|
||||
|
||||
"""Return a list of related media based on ML recommendations
|
||||
A big todo!
|
||||
"""
|
||||
|
|
|
@ -10,7 +10,6 @@ import files.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
@ -8,7 +8,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('files', '0002_auto_20201201_0712'),
|
||||
]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import glob
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
@ -15,7 +16,6 @@ from django.core.files import File
|
|||
from django.db import connection, models
|
||||
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete
|
||||
from django.dispatch import receiver
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.html import strip_tags
|
||||
|
@ -313,7 +313,6 @@ class Media(models.Model):
|
|||
self.__original_uploaded_poster = self.uploaded_poster
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if not self.title:
|
||||
self.title = self.media_file.path.split("/")[-1]
|
||||
|
||||
|
@ -371,7 +370,6 @@ class Media(models.Model):
|
|||
# will run only when a poster is uploaded for the first time
|
||||
if self.uploaded_poster and self.uploaded_poster != self.__original_uploaded_poster:
|
||||
with open(self.uploaded_poster.path, "rb") as f:
|
||||
|
||||
# set this otherwise gets to infinite loop
|
||||
self.__original_uploaded_poster = self.uploaded_poster
|
||||
|
||||
|
@ -429,12 +427,16 @@ class Media(models.Model):
|
|||
Performs all related tasks, as check for media type,
|
||||
video duration, encode
|
||||
"""
|
||||
|
||||
self.set_media_type()
|
||||
if self.media_type == "video":
|
||||
self.set_thumbnail(force=True)
|
||||
self.produce_sprite_from_video()
|
||||
self.encode()
|
||||
if settings.DO_NOT_TRANSCODE_VIDEO:
|
||||
self.encoding_status = "success"
|
||||
self.save()
|
||||
self.produce_sprite_from_video()
|
||||
else:
|
||||
self.produce_sprite_from_video()
|
||||
self.encode()
|
||||
elif self.media_type == "image":
|
||||
self.set_thumbnail(force=True)
|
||||
return True
|
||||
|
@ -479,7 +481,10 @@ class Media(models.Model):
|
|||
self.duration = int(round(float(ret.get("video_duration", 0))))
|
||||
self.video_height = int(ret.get("video_height"))
|
||||
if ret.get("video_info", {}).get("codec_name", {}) in ["mjpeg"]:
|
||||
audio_file_with_thumb = True
|
||||
# best guess that this is an audio file with a thumbnail
|
||||
# in other cases, it is not (eg it can be an AVI file)
|
||||
if ret.get("video_info", {}).get("avg_frame_rate", "") == '0/0':
|
||||
audio_file_with_thumb = True
|
||||
|
||||
if ret.get("is_audio") or audio_file_with_thumb:
|
||||
self.media_type = "audio"
|
||||
|
@ -579,9 +584,7 @@ class Media(models.Model):
|
|||
|
||||
# attempt to break media file in chunks
|
||||
if self.duration > settings.CHUNKIZE_VIDEO_DURATION and chunkize:
|
||||
|
||||
for profile in profiles:
|
||||
|
||||
if profile.extension == "gif":
|
||||
profiles.remove(profile)
|
||||
encoding = Encoding(media=self, profile=profile)
|
||||
|
@ -669,6 +672,13 @@ class Media(models.Model):
|
|||
return ret
|
||||
for key in ENCODE_RESOLUTIONS_KEYS:
|
||||
ret[key] = {}
|
||||
|
||||
# if this is enabled, return original file on a way
|
||||
# that video.js can consume
|
||||
if settings.DO_NOT_TRANSCODE_VIDEO:
|
||||
ret['0-original'] = {"h264": {"url": helpers.url_from_path(self.media_file.path), "status": "success", "progress": 100}}
|
||||
return ret
|
||||
|
||||
for encoding in self.encodings.select_related("profile").filter(chunk=False):
|
||||
if encoding.profile.extension == "gif":
|
||||
continue
|
||||
|
@ -820,6 +830,7 @@ class Media(models.Model):
|
|||
"""
|
||||
|
||||
res = {}
|
||||
valid_resolutions = [240, 360, 480, 720, 1080, 1440, 2160]
|
||||
if self.hls_file:
|
||||
if os.path.exists(self.hls_file):
|
||||
hls_file = self.hls_file
|
||||
|
@ -831,11 +842,20 @@ class Media(models.Model):
|
|||
uri = os.path.join(p, iframe_playlist.uri)
|
||||
if os.path.exists(uri):
|
||||
resolution = iframe_playlist.iframe_stream_info.resolution[1]
|
||||
# most probably video is vertical, getting the first value to
|
||||
# be the resolution
|
||||
if resolution not in valid_resolutions:
|
||||
resolution = iframe_playlist.iframe_stream_info.resolution[0]
|
||||
|
||||
res["{}_iframe".format(resolution)] = helpers.url_from_path(uri)
|
||||
for playlist in m3u8_obj.playlists:
|
||||
uri = os.path.join(p, playlist.uri)
|
||||
if os.path.exists(uri):
|
||||
resolution = playlist.stream_info.resolution[1]
|
||||
# same as above
|
||||
if resolution not in valid_resolutions:
|
||||
resolution = playlist.stream_info.resolution[0]
|
||||
|
||||
res["{}_playlist".format(resolution)] = helpers.url_from_path(uri)
|
||||
return res
|
||||
|
||||
|
@ -1003,10 +1023,8 @@ class Tag(models.Model):
|
|||
return True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.title = slugify(self.title[:99])
|
||||
strip_text_items = ["title"]
|
||||
for item in strip_text_items:
|
||||
setattr(self, item, strip_tags(getattr(self, item, None)))
|
||||
self.title = helpers.get_alphanumeric_only(self.title)
|
||||
self.title = self.title[:99]
|
||||
super(Tag, self).save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
@ -1405,6 +1423,13 @@ def media_file_delete(sender, instance, **kwargs):
|
|||
helpers.rm_dir(p)
|
||||
instance.user.update_user_media()
|
||||
|
||||
# remove extra zombie thumbnails
|
||||
if instance.thumbnail:
|
||||
thumbnails_path = os.path.dirname(instance.thumbnail.path)
|
||||
thumbnails = glob.glob(f'{thumbnails_path}/{instance.uid.hex}.*')
|
||||
for thumbnail in thumbnails:
|
||||
helpers.rm_file(thumbnail)
|
||||
|
||||
|
||||
@receiver(m2m_changed, sender=Media.category.through)
|
||||
def media_m2m(sender, instance, **kwargs):
|
||||
|
|
|
@ -150,10 +150,14 @@ class SingleMediaSerializer(serializers.ModelSerializer):
|
|||
|
||||
class MediaSearchSerializer(serializers.ModelSerializer):
|
||||
url = serializers.SerializerMethodField()
|
||||
api_url = serializers.SerializerMethodField()
|
||||
|
||||
def get_url(self, obj):
|
||||
return self.context["request"].build_absolute_uri(obj.get_absolute_url())
|
||||
|
||||
def get_api_url(self, obj):
|
||||
return self.context["request"].build_absolute_uri(obj.get_absolute_url(api=True))
|
||||
|
||||
class Meta:
|
||||
model = Media
|
||||
fields = (
|
||||
|
@ -167,6 +171,7 @@ class MediaSearchSerializer(serializers.ModelSerializer):
|
|||
"friendly_token",
|
||||
"duration",
|
||||
"url",
|
||||
"api_url",
|
||||
"media_type",
|
||||
"preview_url",
|
||||
"categories_info",
|
||||
|
|
|
@ -7,10 +7,11 @@ import tempfile
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from celery import Task
|
||||
from celery.decorators import task
|
||||
from celery import shared_task as task
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
from celery.signals import task_revoked
|
||||
from celery.task.control import revoke
|
||||
|
||||
# from celery.task.control import revoke
|
||||
from celery.utils.log import get_task_logger
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
@ -268,7 +269,6 @@ 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)
|
||||
ffmpeg_commands = produce_ffmpeg_commands(
|
||||
|
@ -461,10 +461,11 @@ def check_running_states():
|
|||
if (now - encoding.update_date).seconds > settings.RUNNING_STATE_STALE:
|
||||
media = encoding.media
|
||||
profile = encoding.profile
|
||||
task_id = encoding.task_id
|
||||
# task_id = encoding.task_id
|
||||
# terminate task
|
||||
if task_id:
|
||||
revoke(task_id, terminate=True)
|
||||
# TODO: not imported
|
||||
# if task_id:
|
||||
# revoke(task_id, terminate=True)
|
||||
encoding.delete()
|
||||
media.encode(profiles=[profile])
|
||||
# TODO: allign with new code + chunksize...
|
||||
|
@ -643,7 +644,11 @@ def save_user_action(user_or_session, friendly_token=None, action="watch", extra
|
|||
|
||||
if action == "watch":
|
||||
media.views += 1
|
||||
media.save(update_fields=["views"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(views=media.views)
|
||||
|
||||
# update field without calling save, to avoid post_save signals being triggered
|
||||
# same in other actions
|
||||
|
||||
elif action == "report":
|
||||
media.reported_times += 1
|
||||
|
||||
|
@ -658,10 +663,10 @@ def save_user_action(user_or_session, friendly_token=None, action="watch", extra
|
|||
)
|
||||
elif action == "like":
|
||||
media.likes += 1
|
||||
media.save(update_fields=["likes"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(likes=media.likes)
|
||||
elif action == "dislike":
|
||||
media.dislikes += 1
|
||||
media.save(update_fields=["dislikes"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(dislikes=media.dislikes)
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.conf import settings
|
||||
from django.conf.urls import include, re_path
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path
|
||||
from django.urls import path, re_path
|
||||
|
||||
from . import management_views, views
|
||||
from .feeds import IndexRSSFeed, SearchRSSFeed
|
||||
|
@ -89,3 +89,6 @@ urlpatterns = [
|
|||
re_path(r"^manage/media$", views.manage_media, name="manage_media"),
|
||||
re_path(r"^manage/users$", views.manage_users, name="manage_users"),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if hasattr(settings, "GENERATE_SITEMAP") and settings.GENERATE_SITEMAP:
|
||||
urlpatterns.append(path("sitemap.xml", views.sitemap, name="sitemap"))
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from celery.task.control import revoke
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
@ -9,7 +8,6 @@ from django.core.mail import EmailMessage
|
|||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.template.defaultfilters import slugify
|
||||
from drf_yasg import openapi as openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework import permissions, status
|
||||
|
@ -30,7 +28,7 @@ from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor, user_allowed_to_u
|
|||
from users.models import User
|
||||
|
||||
from .forms import ContactForm, MediaForm, SubtitleForm
|
||||
from .helpers import clean_query, produce_ffmpeg_commands
|
||||
from .helpers import clean_query, get_alphanumeric_only, produce_ffmpeg_commands
|
||||
from .methods import (
|
||||
check_comment_for_mention,
|
||||
get_user_or_session,
|
||||
|
@ -182,7 +180,8 @@ def edit_media(request):
|
|||
media.tags.remove(tag)
|
||||
if form.cleaned_data.get("new_tags"):
|
||||
for tag in form.cleaned_data.get("new_tags").split(","):
|
||||
tag = slugify(tag)
|
||||
tag = get_alphanumeric_only(tag)
|
||||
tag = tag[:99]
|
||||
if tag:
|
||||
try:
|
||||
tag = Tag.objects.get(title=tag)
|
||||
|
@ -293,6 +292,16 @@ def search(request):
|
|||
return render(request, "cms/search.html", context)
|
||||
|
||||
|
||||
def sitemap(request):
|
||||
"""Sitemap"""
|
||||
|
||||
context = {}
|
||||
context["media"] = list(Media.objects.filter(Q(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"""
|
||||
|
||||
|
@ -1396,5 +1405,6 @@ class TaskDetail(APIView):
|
|||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def delete(self, request, uid, format=None):
|
||||
revoke(uid, terminate=True)
|
||||
# This is not imported!
|
||||
# revoke(uid, terminate=True)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
@ -20,11 +20,11 @@ const formatPage = (page) => {
|
|||
? templates.renderPageContent({ page: { id: pageContentId, component: page.component } })
|
||||
: undefined;
|
||||
const headLinks = [
|
||||
{ rel: 'preload', href: './static/lib/video-js/7.7.5/video.min.js', as: 'script' },
|
||||
{ rel: 'preload', href: './static/lib/video-js/7.20.2/video.min.js', as: 'script' },
|
||||
...(page.headLinks ? page.headLinks : []),
|
||||
];
|
||||
const bodyScripts = [
|
||||
{ src: './static/lib/video-js/7.7.5/video.min.js' },
|
||||
{ src: './static/lib/video-js/7.20.2/video.min.js' },
|
||||
...(page.bodyScripts ? page.bodyScripts : []),
|
||||
];
|
||||
|
||||
|
|
54
frontend/package-lock.json
generated
54
frontend/package-lock.json
generated
|
@ -1091,7 +1091,6 @@
|
|||
"version": "7.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.6.tgz",
|
||||
"integrity": "sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
|
@ -5154,6 +5153,14 @@
|
|||
"ipaddr.js": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
|
@ -5948,7 +5955,13 @@
|
|||
}
|
||||
},
|
||||
"mediacms-vjs-plugin": {
|
||||
"version": "file:packages/vjs-plugin"
|
||||
"version": "file:packages/vjs-plugin",
|
||||
"requires": {
|
||||
"mediacms-vjs-plugin-font-icons": "file:packages/vjs-plugin-font-icons"
|
||||
}
|
||||
},
|
||||
"mediacms-vjs-plugin-font-icons": {
|
||||
"version": "file:packages/vjs-plugin-font-icons"
|
||||
},
|
||||
"memory-fs": {
|
||||
"version": "0.4.1",
|
||||
|
@ -7664,7 +7677,6 @@
|
|||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
@ -7841,8 +7853,28 @@
|
|||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-mentions": {
|
||||
"version": "4.4.7",
|
||||
"resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.4.7.tgz",
|
||||
"integrity": "sha512-VNriu2h/uOB+RS0mwZgPG2Vf+UtdDvRh5zbXa2TNc1WqacKuNDgTdhlbo9LEOZRBxRzIeTUYQmYJ7p9M9rDHqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.4.5",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.5.8",
|
||||
"substyle": "^9.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz",
|
||||
"integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-cache": {
|
||||
"version": "1.0.0",
|
||||
|
@ -7956,8 +7988,7 @@
|
|||
"regenerator-runtime": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
|
||||
"dev": true
|
||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||
},
|
||||
"regenerator-transform": {
|
||||
"version": "0.14.5",
|
||||
|
@ -9233,6 +9264,15 @@
|
|||
"postcss-selector-parser": "^6.0.4"
|
||||
}
|
||||
},
|
||||
"substyle": {
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/substyle/-/substyle-9.4.1.tgz",
|
||||
"integrity": "sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.4",
|
||||
"invariant": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
|
|
@ -8,6 +8,10 @@ import { PageActions, MediaPageActions } from '../../utils/actions/';
|
|||
import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/';
|
||||
import { PopupMain, UserThumbnail } from '../_shared';
|
||||
|
||||
import './videojs-markers.js';
|
||||
import './videojs.markers.css';
|
||||
import {enableMarkers, addMarker} from './videojs-markers_config.js'
|
||||
|
||||
import './Comments.scss';
|
||||
|
||||
const commentsText = {
|
||||
|
@ -76,7 +80,7 @@ function CommentForm(props) {
|
|||
|
||||
function onChangeWithMention(event, newValue, newPlainTextValue, mentions) {
|
||||
textareaRef.current.style.height = '';
|
||||
|
||||
|
||||
setValue(newValue);
|
||||
setMadeChanges(true);
|
||||
|
||||
|
@ -140,8 +144,8 @@ function CommentForm(props) {
|
|||
<UserThumbnail />
|
||||
<div className="form">
|
||||
<div className={'form-textarea-wrap' + (textareaFocused ? ' focused' : '')}>
|
||||
{ MediaCMS.features.media.actions.comment_mention ?
|
||||
<MentionsInput
|
||||
{ MediaCMS.features.media.actions.comment_mention ?
|
||||
<MentionsInput
|
||||
inputRef={textareaRef}
|
||||
className="form-textarea"
|
||||
rows="1"
|
||||
|
@ -427,18 +431,34 @@ export default function CommentsList(props) {
|
|||
function onCommentsLoad() {
|
||||
const retrievedComments = [...MediaPageStore.get('media-comments')];
|
||||
|
||||
if (MediaCMS.features.media.actions.comment_mention === true)
|
||||
{
|
||||
retrievedComments.forEach(comment => {
|
||||
comment.text = setMentions(comment.text);
|
||||
});
|
||||
}
|
||||
retrievedComments.forEach(comment => {
|
||||
comment.text = setTimestampAnchors(comment.text);
|
||||
});
|
||||
|
||||
displayCommentsRelatedAlert();
|
||||
setComments(retrievedComments);
|
||||
setComments([...retrievedComments]);
|
||||
|
||||
// TODO: this code is breaking, beed ti debug, until then removing the extra
|
||||
// functionality related with video/timestamp/user mentions
|
||||
// const video = videojs('vjs_video_3');
|
||||
|
||||
// if (MediaCMS.features.media.actions.timestampTimebar)
|
||||
//{
|
||||
// enableMarkers(video);
|
||||
//}
|
||||
|
||||
//if (MediaCMS.features.media.actions.comment_mention === true)
|
||||
//{
|
||||
// retrievedComments.forEach(comment => {
|
||||
// comment.text = setMentions(comment.text);
|
||||
// });
|
||||
//}
|
||||
|
||||
// TODO: this code is breaking
|
||||
// video.one('loadedmetadata', () => {
|
||||
// retrievedComments.forEach(comment => {
|
||||
// comment.text = setTimestampAnchorsAndMarkers(comment.text, video);
|
||||
// });
|
||||
|
||||
// displayCommentsRelatedAlert();
|
||||
// setComments([...retrievedComments]);
|
||||
//});
|
||||
//setComments([...retrievedComments]);
|
||||
}
|
||||
|
||||
function setMentions(text)
|
||||
|
@ -448,9 +468,9 @@ export default function CommentsList(props) {
|
|||
return sanitizedComment.split('_]').join("</a>");
|
||||
}
|
||||
|
||||
function setTimestampAnchors(text)
|
||||
function setTimestampAnchorsAndMarkers(text, videoPlayer)
|
||||
{
|
||||
function wrapTimestampWithAnchor(match, string)
|
||||
function wrapTimestampWithAnchor(match, string)
|
||||
{
|
||||
let split = match.split(':'), s = 0, m = 1;
|
||||
let searchParameters = new URLSearchParams(window.location.search);
|
||||
|
@ -460,6 +480,10 @@ export default function CommentsList(props) {
|
|||
s += m * parseInt(split.pop(), 10);
|
||||
m *= 60;
|
||||
}
|
||||
if (MediaCMS.features.media.actions.timestampTimebar)
|
||||
{
|
||||
addMarker(videoPlayer, s, text);
|
||||
}
|
||||
|
||||
searchParameters.set('t', s)
|
||||
const wrapped = "<a href=\"" + MediaPageStore.get('media-url').split('?')[0] + "?" + searchParameters + "\">" + match + "</a>";
|
||||
|
|
525
frontend/src/static/js/components/comments/videojs-markers.js
Normal file
525
frontend/src/static/js/components/comments/videojs-markers.js
Normal file
|
@ -0,0 +1,525 @@
|
|||
// based on https://github.com/spchuang/videojs-markers
|
||||
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(['mediacms-player'], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(require('mediacms-player'));
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
global = window;
|
||||
factory(global.videojs);
|
||||
global.videojsMarkers = mod.exports;
|
||||
}
|
||||
})(this, function (_video) {
|
||||
/*! videojs-markers - v1.0.1 - 2018-02-03
|
||||
* Copyright (c) 2018 ; Licensed */
|
||||
'use strict';
|
||||
|
||||
var _video2 = _interopRequireDefault(_video);
|
||||
|
||||
function _interopRequireDefault(obj) {
|
||||
return obj && obj.__esModule ? obj : {
|
||||
default: obj
|
||||
};
|
||||
}
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
|
||||
return typeof obj;
|
||||
} : function (obj) {
|
||||
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
||||
};
|
||||
|
||||
// default setting
|
||||
var defaultSetting = {
|
||||
markerStyle: {
|
||||
'width': '7px',
|
||||
'border-radius': '30%',
|
||||
'background-color': 'red'
|
||||
},
|
||||
markerTip: {
|
||||
display: true,
|
||||
text: function text(marker) {
|
||||
return "Break: " + marker.text;
|
||||
},
|
||||
time: function time(marker) {
|
||||
return marker.time;
|
||||
}
|
||||
},
|
||||
breakOverlay: {
|
||||
display: true,
|
||||
displayTime: 3,
|
||||
text: function text(marker) {
|
||||
return "Break overlay: " + marker.overlayText;
|
||||
},
|
||||
style: {
|
||||
'width': '100%',
|
||||
'height': '20%',
|
||||
'background-color': 'rgba(0,0,0,0.7)',
|
||||
'color': 'white',
|
||||
'font-size': '17px'
|
||||
}
|
||||
},
|
||||
onMarkerClick: function onMarkerClick(marker) {},
|
||||
onMarkerReached: function onMarkerReached(marker, index) {},
|
||||
markers: []
|
||||
};
|
||||
|
||||
// create a non-colliding random number
|
||||
function generateUUID() {
|
||||
var d = new Date().getTime();
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (d + Math.random() * 16) % 16 | 0;
|
||||
d = Math.floor(d / 16);
|
||||
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the size of an element and its position
|
||||
* a default Object with 0 on each of its properties
|
||||
* its return in case there's an error
|
||||
* @param {Element} element el to get the size and position
|
||||
* @return {DOMRect|Object} size and position of an element
|
||||
*/
|
||||
function getElementBounding(element) {
|
||||
var elementBounding;
|
||||
var defaultBoundingRect = {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
right: 0
|
||||
};
|
||||
|
||||
try {
|
||||
elementBounding = element.getBoundingClientRect();
|
||||
} catch (e) {
|
||||
elementBounding = defaultBoundingRect;
|
||||
}
|
||||
|
||||
return elementBounding;
|
||||
}
|
||||
|
||||
var NULL_INDEX = -1;
|
||||
|
||||
function registerVideoJsMarkersPlugin(options) {
|
||||
// copied from video.js/src/js/utils/merge-options.js since
|
||||
// videojs 4 doens't support it by defualt.
|
||||
if (!_video2.default.mergeOptions) {
|
||||
var isPlain = function isPlain(value) {
|
||||
return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && toString.call(value) === '[object Object]' && value.constructor === Object;
|
||||
};
|
||||
|
||||
var mergeOptions = function mergeOptions(source1, source2) {
|
||||
|
||||
var result = {};
|
||||
var sources = [source1, source2];
|
||||
sources.forEach(function (source) {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
Object.keys(source).forEach(function (key) {
|
||||
var value = source[key];
|
||||
if (!isPlain(value)) {
|
||||
result[key] = value;
|
||||
return;
|
||||
}
|
||||
if (!isPlain(result[key])) {
|
||||
result[key] = {};
|
||||
}
|
||||
result[key] = mergeOptions(result[key], value);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
_video2.default.mergeOptions = mergeOptions;
|
||||
}
|
||||
|
||||
if (!_video2.default.createEl) {
|
||||
_video2.default.createEl = function (tagName, props, attrs) {
|
||||
var el = _video2.default.Player.prototype.createEl(tagName, props);
|
||||
if (!!attrs) {
|
||||
Object.keys(attrs).forEach(function (key) {
|
||||
el.setAttribute(key, attrs[key]);
|
||||
});
|
||||
}
|
||||
return el;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* register the markers plugin (dependent on jquery)
|
||||
*/
|
||||
var setting = _video2.default.mergeOptions(defaultSetting, options),
|
||||
markersMap = {},
|
||||
markersList = [],
|
||||
// list of markers sorted by time
|
||||
currentMarkerIndex = NULL_INDEX,
|
||||
player = this,
|
||||
markerTip = null,
|
||||
breakOverlay = null,
|
||||
overlayIndex = NULL_INDEX;
|
||||
|
||||
function sortMarkersList() {
|
||||
// sort the list by time in asc order
|
||||
markersList.sort(function (a, b) {
|
||||
return setting.markerTip.time(a) - setting.markerTip.time(b);
|
||||
});
|
||||
}
|
||||
|
||||
function addMarkers(newMarkers) {
|
||||
newMarkers.forEach(function (marker) {
|
||||
marker.key = generateUUID();
|
||||
|
||||
player.el().querySelector('.vjs-progress-holder').appendChild(createMarkerDiv(marker));
|
||||
|
||||
// store marker in an internal hash map
|
||||
markersMap[marker.key] = marker;
|
||||
markersList.push(marker);
|
||||
});
|
||||
|
||||
sortMarkersList();
|
||||
}
|
||||
|
||||
function getPosition(marker) {
|
||||
return setting.markerTip.time(marker) / player.duration() * 100;
|
||||
}
|
||||
|
||||
function setMarkderDivStyle(marker, markerDiv) {
|
||||
markerDiv.className = 'vjs-marker ' + (marker.class || "");
|
||||
|
||||
Object.keys(setting.markerStyle).forEach(function (key) {
|
||||
markerDiv.style[key] = setting.markerStyle[key];
|
||||
});
|
||||
|
||||
// hide out-of-bound markers
|
||||
var ratio = marker.time / player.duration();
|
||||
if (ratio < 0 || ratio > 1) {
|
||||
markerDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
// set position
|
||||
markerDiv.style.left = getPosition(marker) + '%';
|
||||
if (marker.duration) {
|
||||
markerDiv.style.width = marker.duration / player.duration() * 100 + '%';
|
||||
markerDiv.style.marginLeft = '0px';
|
||||
} else {
|
||||
var markerDivBounding = getElementBounding(markerDiv);
|
||||
markerDiv.style.marginLeft = markerDivBounding.width / 2 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function createMarkerDiv(marker) {
|
||||
|
||||
var markerDiv = _video2.default.createEl('div', {}, {
|
||||
'data-marker-key': marker.key,
|
||||
'data-marker-time': setting.markerTip.time(marker)
|
||||
});
|
||||
|
||||
setMarkderDivStyle(marker, markerDiv);
|
||||
|
||||
// bind click event to seek to marker time
|
||||
markerDiv.addEventListener('click', function (e) {
|
||||
var preventDefault = false;
|
||||
if (typeof setting.onMarkerClick === "function") {
|
||||
// if return false, prevent default behavior
|
||||
preventDefault = setting.onMarkerClick(marker) === false;
|
||||
}
|
||||
|
||||
if (!preventDefault) {
|
||||
var key = this.getAttribute('data-marker-key');
|
||||
player.currentTime(setting.markerTip.time(markersMap[key]));
|
||||
}
|
||||
});
|
||||
|
||||
if (setting.markerTip.display) {
|
||||
registerMarkerTipHandler(markerDiv);
|
||||
}
|
||||
|
||||
return markerDiv;
|
||||
}
|
||||
|
||||
function updateMarkers(force) {
|
||||
// update UI for markers whose time changed
|
||||
markersList.forEach(function (marker) {
|
||||
var markerDiv = player.el().querySelector(".vjs-marker[data-marker-key='" + marker.key + "']");
|
||||
var markerTime = setting.markerTip.time(marker);
|
||||
|
||||
if (force || markerDiv.getAttribute('data-marker-time') !== markerTime) {
|
||||
setMarkderDivStyle(marker, markerDiv);
|
||||
markerDiv.setAttribute('data-marker-time', markerTime);
|
||||
}
|
||||
});
|
||||
sortMarkersList();
|
||||
}
|
||||
|
||||
function removeMarkers(indexArray) {
|
||||
// reset overlay
|
||||
if (!!breakOverlay) {
|
||||
overlayIndex = NULL_INDEX;
|
||||
breakOverlay.style.visibility = "hidden";
|
||||
}
|
||||
currentMarkerIndex = NULL_INDEX;
|
||||
|
||||
var deleteIndexList = [];
|
||||
indexArray.forEach(function (index) {
|
||||
var marker = markersList[index];
|
||||
if (marker) {
|
||||
// delete from memory
|
||||
delete markersMap[marker.key];
|
||||
deleteIndexList.push(index);
|
||||
|
||||
// delete from dom
|
||||
var el = player.el().querySelector(".vjs-marker[data-marker-key='" + marker.key + "']");
|
||||
el && el.parentNode.removeChild(el);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// clean up markers array
|
||||
deleteIndexList.reverse();
|
||||
deleteIndexList.forEach(function (deleteIndex) {
|
||||
markersList.splice(deleteIndex, 1);
|
||||
});
|
||||
} catch (error) {
|
||||
//Splice is the most likely culprit
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
// sort again
|
||||
sortMarkersList();
|
||||
}
|
||||
|
||||
// attach hover event handler
|
||||
function registerMarkerTipHandler(markerDiv) {
|
||||
markerDiv.addEventListener('mouseover', function () {
|
||||
var marker = markersMap[markerDiv.getAttribute('data-marker-key')];
|
||||
if (!!markerTip) {
|
||||
markerTip.querySelector('.vjs-tip-inner').innerText = setting.markerTip.text(marker);
|
||||
// margin-left needs to minus the padding length to align correctly with the marker
|
||||
markerTip.style.left = getPosition(marker) + '%';
|
||||
var markerTipBounding = getElementBounding(markerTip);
|
||||
var markerDivBounding = getElementBounding(markerDiv);
|
||||
markerTip.style.marginLeft = -parseFloat(markerTipBounding.width / 2) + parseFloat(markerDivBounding.width / 4) + 'px';
|
||||
markerTip.style.visibility = 'visible';
|
||||
}
|
||||
});
|
||||
|
||||
markerDiv.addEventListener('mouseout', function () {
|
||||
if (!!markerTip) {
|
||||
markerTip.style.visibility = "hidden";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeMarkerTip() {
|
||||
markerTip = _video2.default.createEl('div', {
|
||||
className: 'vjs-tip',
|
||||
innerHTML: "<div class='vjs-tip-arrow'></div><div class='vjs-tip-inner'></div>"
|
||||
});
|
||||
player.el().querySelector('.vjs-progress-holder').appendChild(markerTip);
|
||||
}
|
||||
|
||||
// show or hide break overlays
|
||||
function updateBreakOverlay() {
|
||||
if (!setting.breakOverlay.display || currentMarkerIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTime = player.currentTime();
|
||||
var marker = markersList[currentMarkerIndex];
|
||||
var markerTime = setting.markerTip.time(marker);
|
||||
|
||||
if (currentTime >= markerTime && currentTime <= markerTime + setting.breakOverlay.displayTime) {
|
||||
if (overlayIndex !== currentMarkerIndex) {
|
||||
overlayIndex = currentMarkerIndex;
|
||||
if (breakOverlay) {
|
||||
breakOverlay.querySelector('.vjs-break-overlay-text').innerHTML = setting.breakOverlay.text(marker);
|
||||
}
|
||||
}
|
||||
|
||||
if (breakOverlay) {
|
||||
breakOverlay.style.visibility = "visible";
|
||||
}
|
||||
} else {
|
||||
overlayIndex = NULL_INDEX;
|
||||
if (breakOverlay) {
|
||||
breakOverlay.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// problem when the next marker is within the overlay display time from the previous marker
|
||||
function initializeOverlay() {
|
||||
breakOverlay = _video2.default.createEl('div', {
|
||||
className: 'vjs-break-overlay',
|
||||
innerHTML: "<div class='vjs-break-overlay-text'></div>"
|
||||
});
|
||||
Object.keys(setting.breakOverlay.style).forEach(function (key) {
|
||||
if (breakOverlay) {
|
||||
breakOverlay.style[key] = setting.breakOverlay.style[key];
|
||||
}
|
||||
});
|
||||
player.el().appendChild(breakOverlay);
|
||||
overlayIndex = NULL_INDEX;
|
||||
}
|
||||
|
||||
function onTimeUpdate() {
|
||||
onUpdateMarker();
|
||||
updateBreakOverlay();
|
||||
options.onTimeUpdateAfterMarkerUpdate && options.onTimeUpdateAfterMarkerUpdate();
|
||||
}
|
||||
|
||||
function onUpdateMarker() {
|
||||
/*
|
||||
check marker reached in between markers
|
||||
the logic here is that it triggers a new marker reached event only if the player
|
||||
enters a new marker range (e.g. from marker 1 to marker 2). Thus, if player is on marker 1 and user clicked on marker 1 again, no new reached event is triggered)
|
||||
*/
|
||||
if (!markersList.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var getNextMarkerTime = function getNextMarkerTime(index) {
|
||||
if (index < markersList.length - 1) {
|
||||
return setting.markerTip.time(markersList[index + 1]);
|
||||
}
|
||||
// next marker time of last marker would be end of video time
|
||||
return player.duration();
|
||||
};
|
||||
var currentTime = player.currentTime();
|
||||
var newMarkerIndex = NULL_INDEX;
|
||||
|
||||
if (currentMarkerIndex !== NULL_INDEX) {
|
||||
// check if staying at same marker
|
||||
var nextMarkerTime = getNextMarkerTime(currentMarkerIndex);
|
||||
if (currentTime >= setting.markerTip.time(markersList[currentMarkerIndex]) && currentTime < nextMarkerTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for ending (at the end current time equals player duration)
|
||||
if (currentMarkerIndex === markersList.length - 1 && currentTime === player.duration()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check first marker, no marker is selected
|
||||
if (currentTime < setting.markerTip.time(markersList[0])) {
|
||||
newMarkerIndex = NULL_INDEX;
|
||||
} else {
|
||||
// look for new index
|
||||
for (var i = 0; i < markersList.length; i++) {
|
||||
nextMarkerTime = getNextMarkerTime(i);
|
||||
if (currentTime >= setting.markerTip.time(markersList[i]) && currentTime < nextMarkerTime) {
|
||||
newMarkerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set new marker index
|
||||
if (newMarkerIndex !== currentMarkerIndex) {
|
||||
// trigger event if index is not null
|
||||
if (newMarkerIndex !== NULL_INDEX && options.onMarkerReached) {
|
||||
options.onMarkerReached(markersList[newMarkerIndex], newMarkerIndex);
|
||||
}
|
||||
currentMarkerIndex = newMarkerIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// setup the whole thing
|
||||
function initialize() {
|
||||
if (setting.markerTip.display) {
|
||||
initializeMarkerTip();
|
||||
}
|
||||
|
||||
// remove existing markers if already initialized
|
||||
player.markers.removeAll();
|
||||
addMarkers(setting.markers);
|
||||
|
||||
if (setting.breakOverlay.display) {
|
||||
initializeOverlay();
|
||||
}
|
||||
onTimeUpdate();
|
||||
player.on("timeupdate", onTimeUpdate);
|
||||
player.off("loadedmetadata");
|
||||
}
|
||||
|
||||
// setup the plugin after we loaded video's meta data
|
||||
player.on("loadedmetadata", function () {
|
||||
initialize();
|
||||
});
|
||||
|
||||
// exposed plugin API
|
||||
player.markers = {
|
||||
getMarkers: function getMarkers() {
|
||||
return markersList;
|
||||
},
|
||||
next: function next() {
|
||||
// go to the next marker from current timestamp
|
||||
var currentTime = player.currentTime();
|
||||
for (var i = 0; i < markersList.length; i++) {
|
||||
var markerTime = setting.markerTip.time(markersList[i]);
|
||||
if (markerTime > currentTime) {
|
||||
player.currentTime(markerTime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
prev: function prev() {
|
||||
// go to previous marker
|
||||
var currentTime = player.currentTime();
|
||||
for (var i = markersList.length - 1; i >= 0; i--) {
|
||||
var markerTime = setting.markerTip.time(markersList[i]);
|
||||
// add a threshold
|
||||
if (markerTime + 0.5 < currentTime) {
|
||||
player.currentTime(markerTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
add: function add(newMarkers) {
|
||||
// add new markers given an array of index
|
||||
addMarkers(newMarkers);
|
||||
},
|
||||
remove: function remove(indexArray) {
|
||||
// remove markers given an array of index
|
||||
removeMarkers(indexArray);
|
||||
},
|
||||
removeAll: function removeAll() {
|
||||
var indexArray = [];
|
||||
for (var i = 0; i < markersList.length; i++) {
|
||||
indexArray.push(i);
|
||||
}
|
||||
removeMarkers(indexArray);
|
||||
},
|
||||
// force - force all markers to be updated, regardless of if they have changed or not.
|
||||
updateTime: function updateTime(force) {
|
||||
// notify the plugin to update the UI for changes in marker times
|
||||
updateMarkers(force);
|
||||
},
|
||||
reset: function reset(newMarkers) {
|
||||
// remove all the existing markers and add new ones
|
||||
player.markers.removeAll();
|
||||
addMarkers(newMarkers);
|
||||
},
|
||||
destroy: function destroy() {
|
||||
// unregister the plugins and clean up even handlers
|
||||
player.markers.removeAll();
|
||||
breakOverlay && breakOverlay.remove();
|
||||
markerTip && markerTip.remove();
|
||||
player.off("timeupdate", updateBreakOverlay);
|
||||
delete player.markers;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_video2.default.plugin('markers', registerVideoJsMarkersPlugin);
|
||||
});
|
||||
//# sourceMappingURL=videojs-markers.js.map
|
|
@ -0,0 +1,44 @@
|
|||
//markers on the timebar
|
||||
const markerStyle = {
|
||||
width: "15px",
|
||||
"background-color": "#DD7373"
|
||||
};
|
||||
|
||||
//the comment overlay
|
||||
const overlayStyle = {
|
||||
width: "100%",
|
||||
height: "auto",
|
||||
};
|
||||
|
||||
function enableMarkers(video) {
|
||||
if (!(typeof video.markers === 'object')) {
|
||||
video.markers({
|
||||
markerStyle: markerStyle,
|
||||
markerTip: {
|
||||
display: true,
|
||||
text: function (marker) {
|
||||
return marker.text;
|
||||
}
|
||||
},
|
||||
breakOverlay: {
|
||||
display: true,
|
||||
displayTime: 20,
|
||||
text: function (marker) {
|
||||
return marker.text;
|
||||
},
|
||||
style : overlayStyle
|
||||
},
|
||||
markers: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addMarker(videoPlayer, time, text)
|
||||
{
|
||||
videoPlayer.markers.add([{
|
||||
time: time,
|
||||
text: text
|
||||
}]);
|
||||
}
|
||||
|
||||
export {enableMarkers, addMarker};
|
|
@ -0,0 +1,59 @@
|
|||
.vjs-marker {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0em;
|
||||
opacity: 1;
|
||||
height: 100%;
|
||||
transition: opacity .2s ease;
|
||||
-webkit-transition: opacity .2s ease;
|
||||
-moz-transition: opacity .2s ease;
|
||||
z-index: 100;
|
||||
}
|
||||
.vjs-marker:hover {
|
||||
cursor: pointer;
|
||||
-webkit-transform: scale(1.3, 1.3);
|
||||
-moz-transform: scale(1.3, 1.3);
|
||||
-o-transform: scale(1.3, 1.3);
|
||||
-ms-transform: scale(1.3, 1.3);
|
||||
transform: scale(1.3, 1.3);
|
||||
}
|
||||
.vjs-tip {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
padding: 5px;
|
||||
font-size: 10px;
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
z-index: 100000;
|
||||
}
|
||||
.vjs-tip .vjs-tip-arrow {
|
||||
background: url(data:image/gif;base64,R0lGODlhCQAJAIABAAAAAAAAACH5BAEAAAEALAAAAAAJAAkAAAIRjAOnwIrcDJxvwkplPtchVQAAOw==) no-repeat top left;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
background-position: bottom left;
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 5px;
|
||||
}
|
||||
.vjs-tip .vjs-tip-inner {
|
||||
border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
padding: 5px 8px 4px 8px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
max-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
.vjs-break-overlay {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
z-index: 100000;
|
||||
top: 0;
|
||||
}
|
||||
.vjs-break-overlay .vjs-break-overlay-text {
|
||||
padding: 9px;
|
||||
text-align: center;
|
||||
}
|
|
@ -22,7 +22,7 @@ function downloadOptions(mediaData, allowDownload) {
|
|||
if (Object.keys(encodingsInfo[k]).length) {
|
||||
for (g in encodingsInfo[k]) {
|
||||
if (encodingsInfo[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodingsInfo[k][g].status && 100 === encodingsInfo[k][g].progress) {
|
||||
if ('success' === encodingsInfo[k][g].status && 100 === encodingsInfo[k][g].progress && null !== encodingsInfo[k][g].url) {
|
||||
options[encodingsInfo[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodingsInfo[k][g].size + ')',
|
||||
link: formatInnerLink(encodingsInfo[k][g].url, site.url),
|
||||
|
|
|
@ -19,7 +19,7 @@ function downloadOptionsList() {
|
|||
if (Object.keys(encodings_info[k]).length) {
|
||||
for (g in encodings_info[k]) {
|
||||
if (encodings_info[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodings_info[k][g].status && 100 === encodings_info[k][g].progress) {
|
||||
if ('success' === encodings_info[k][g].status && 100 === encodings_info[k][g].progress && null !== encodings_info[k][g].url) {
|
||||
optionsList[encodings_info[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodings_info[k][g].size + ')',
|
||||
link: formatInnerLink(encodings_info[k][g].url, SiteContext._currentValue.url),
|
||||
|
|
|
@ -192,7 +192,7 @@ export function VideoPlayer(props) {
|
|||
document.addEventListener('visibilitychange', initPlayer);
|
||||
}
|
||||
|
||||
player.player.one('loadedmetadata', () => {
|
||||
player && player.player.one('loadedmetadata', () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paramT = Number(urlParams.get('t'));
|
||||
const timestamp = !isNaN(paramT) ? paramT : 0;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@use "sass:math";
|
||||
@import '../../../lib/video-js/7.7.5/video-js.min.css';
|
||||
@import '../../../lib/video-js/7.20.2/video-js.min.css';
|
||||
@import '../../../css/includes/_variables.scss';
|
||||
|
||||
@keyframes up-next-circle-countdown {
|
||||
|
|
|
@ -194,7 +194,9 @@ class MediaPageStore extends EventEmitter {
|
|||
}
|
||||
|
||||
this.loadPlaylists();
|
||||
this.loadUsers();
|
||||
if (MediaCMS.features.media.actions.comment_mention === true) {
|
||||
this.loadUsers();
|
||||
}
|
||||
|
||||
if (this.mediacms_config.member.can.readComment) {
|
||||
this.loadComments();
|
||||
|
|
|
@ -379,21 +379,38 @@
|
|||
|
||||
.video-js.vjs-fluid,
|
||||
.video-js.vjs-16-9,
|
||||
.video-js.vjs-4-3 {
|
||||
.video-js.vjs-4-3,
|
||||
.video-js.vjs-9-16,
|
||||
.video-js.vjs-1-1 {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fluid:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-16-9:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-4-3:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-9-16:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.video-js.vjs-16-9 {
|
||||
.video-js.vjs-16-9:not(.vjs-audio-only-mode) {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
|
||||
.video-js.vjs-4-3 {
|
||||
.video-js.vjs-4-3:not(.vjs-audio-only-mode) {
|
||||
padding-top: 75%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fill {
|
||||
.video-js.vjs-9-16:not(.vjs-audio-only-mode) {
|
||||
padding-top: 177.7777777778%;
|
||||
}
|
||||
|
||||
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fill:not(.vjs-audio-only-mode) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -406,6 +423,10 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-tech {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.vjs-full-window {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -422,7 +443,7 @@ body.vjs-full-window {
|
|||
right: 0;
|
||||
}
|
||||
|
||||
.video-js.vjs-fullscreen {
|
||||
.video-js.vjs-fullscreen:not(.vjs-ios-native-fs) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding-top: 0 !important;
|
||||
|
@ -451,8 +472,8 @@ body.vjs-full-window {
|
|||
|
||||
.vjs-lock-showing {
|
||||
display: block !important;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.vjs-no-js {
|
||||
|
@ -614,6 +635,11 @@ body.vjs-full-window {
|
|||
color: #2B333F;
|
||||
}
|
||||
|
||||
.video-js .vjs-menu *:not(.vjs-selected):focus:not(:focus-visible),
|
||||
.js-focus-visible .vjs-menu *:not(.vjs-selected):focus:not(.focus-visible) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.vjs-menu li.vjs-menu-title {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
@ -736,7 +762,8 @@ body.vjs-full-window {
|
|||
background-color: rgba(43, 51, 63, 0.7);
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-control-bar {
|
||||
.vjs-has-started .vjs-control-bar,
|
||||
.vjs-audio-only-mode .vjs-control-bar {
|
||||
display: flex;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
@ -746,6 +773,7 @@ body.vjs-full-window {
|
|||
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
visibility: visible;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: visibility 1s, opacity 1s;
|
||||
}
|
||||
|
||||
|
@ -755,9 +783,11 @@ body.vjs-full-window {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar,
|
||||
.vjs-audio-only-mode.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.vjs-has-started.vjs-no-flex .vjs-control-bar {
|
||||
|
@ -774,18 +804,28 @@ body.vjs-full-window {
|
|||
flex: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-control.vjs-visible-text {
|
||||
width: auto;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.vjs-button > .vjs-icon-placeholder:before {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.67;
|
||||
}
|
||||
|
||||
.vjs-button > .vjs-icon-placeholder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js .vjs-control:focus:before,
|
||||
.video-js .vjs-control:hover:before,
|
||||
.video-js .vjs-control:focus {
|
||||
text-shadow: 0em 0em 1em white;
|
||||
}
|
||||
|
||||
.video-js .vjs-control-text {
|
||||
.video-js *:not(.vjs-visible-text) > .vjs-control-text {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
|
@ -1074,6 +1114,7 @@ body.vjs-full-window {
|
|||
.video-js .vjs-volume-level:before {
|
||||
position: absolute;
|
||||
font-size: 0.9em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vjs-slider-vertical .vjs-volume-level {
|
||||
|
@ -1082,6 +1123,7 @@ body.vjs-full-window {
|
|||
.vjs-slider-vertical .vjs-volume-level:before {
|
||||
top: -0.5em;
|
||||
left: -0.3em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vjs-slider-horizontal .vjs-volume-level {
|
||||
|
@ -1116,6 +1158,77 @@ body.vjs-full-window {
|
|||
left: -2em;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-tooltip {
|
||||
background-color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 0.3em;
|
||||
color: #000;
|
||||
float: right;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
padding: 6px 8px 8px 8px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -3.4em;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control:hover .vjs-volume-tooltip,
|
||||
.video-js .vjs-volume-control:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-vertical:hover .vjs-volume-tooltip,
|
||||
.video-js .vjs-volume-vertical:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
|
||||
left: 1em;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control.disabled:hover .vjs-volume-tooltip {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control .vjs-mouse-display {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-horizontal .vjs-mouse-display {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-no-flex .vjs-volume-control .vjs-mouse-display {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control:hover .vjs-mouse-display {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js.vjs-user-inactive .vjs-volume-control .vjs-mouse-display {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 1s, opacity 1s;
|
||||
}
|
||||
|
||||
.video-js.vjs-user-inactive.vjs-no-flex .vjs-volume-control .vjs-mouse-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-mouse-display .vjs-volume-tooltip {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.vjs-poster {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
@ -1134,18 +1247,16 @@ body.vjs-full-window {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started .vjs-poster {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-poster,
|
||||
.vjs-using-native-controls .vjs-poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started .vjs-poster,
|
||||
.vjs-has-started.vjs-audio-poster-mode .vjs-poster {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js .vjs-live-control {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
@ -1166,6 +1277,7 @@ body.vjs-full-window {
|
|||
}
|
||||
|
||||
.video-js .vjs-seek-to-live-control {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
|
@ -1252,6 +1364,7 @@ body.vjs-full-window {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.video-js.vjs-controls-disabled .vjs-text-track-display,
|
||||
.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display {
|
||||
bottom: 1em;
|
||||
}
|
||||
|
@ -1278,6 +1391,7 @@ video::-webkit-media-text-track-display {
|
|||
transform: translateY(-3em);
|
||||
}
|
||||
|
||||
.video-js.vjs-controls-disabled video::-webkit-media-text-track-display,
|
||||
.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display {
|
||||
transform: translateY(-1.5em);
|
||||
}
|
||||
|
@ -1286,10 +1400,18 @@ video::-webkit-media-text-track-display {
|
|||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-picture-in-picture-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-fullscreen-control {
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-fullscreen-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-playback-rate > .vjs-menu-button,
|
||||
.vjs-playback-rate .vjs-playback-rate-value {
|
||||
position: absolute;
|
||||
|
@ -1445,10 +1567,18 @@ video::-webkit-media-text-track-display {
|
|||
border-top-color: #73859f;
|
||||
}
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-captions-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-chapters-button .vjs-menu ul {
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-descriptions-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -1462,6 +1592,10 @@ video::-webkit-media-text-track-display {
|
|||
line-height: inherit;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-subs-caps-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -1475,62 +1609,38 @@ video::-webkit-media-text-track-display {
|
|||
line-height: inherit;
|
||||
}
|
||||
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control {
|
||||
.video-js.vjs-layout-small .vjs-current-time,
|
||||
.video-js.vjs-layout-small .vjs-time-divider,
|
||||
.video-js.vjs-layout-small .vjs-duration,
|
||||
.video-js.vjs-layout-small .vjs-remaining-time,
|
||||
.video-js.vjs-layout-small .vjs-playback-rate,
|
||||
.video-js.vjs-layout-small .vjs-volume-control, .video-js.vjs-layout-x-small .vjs-current-time,
|
||||
.video-js.vjs-layout-x-small .vjs-time-divider,
|
||||
.video-js.vjs-layout-x-small .vjs-duration,
|
||||
.video-js.vjs-layout-x-small .vjs-remaining-time,
|
||||
.video-js.vjs-layout-x-small .vjs-playback-rate,
|
||||
.video-js.vjs-layout-x-small .vjs-volume-control, .video-js.vjs-layout-tiny .vjs-current-time,
|
||||
.video-js.vjs-layout-tiny .vjs-time-divider,
|
||||
.video-js.vjs-layout-tiny .vjs-duration,
|
||||
.video-js.vjs-layout-tiny .vjs-remaining-time,
|
||||
.video-js.vjs-layout-tiny .vjs-playback-rate,
|
||||
.video-js.vjs-layout-tiny .vjs-volume-control {
|
||||
display: none;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active {
|
||||
.video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover {
|
||||
width: auto;
|
||||
width: initial;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button {
|
||||
.video-js.vjs-layout-x-small .vjs-progress-control, .video-js.vjs-layout-tiny .vjs-progress-control {
|
||||
display: none;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer {
|
||||
.video-js.vjs-layout-x-small .vjs-custom-control-spacer {
|
||||
flex: auto;
|
||||
display: block;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer {
|
||||
.video-js.vjs-layout-x-small.vjs-no-flex .vjs-custom-control-spacer {
|
||||
width: auto;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-modal-dialog.vjs-text-track-settings {
|
||||
background-color: #2B333F;
|
||||
|
@ -1653,11 +1763,8 @@ video::-webkit-media-text-track-display {
|
|||
|
||||
.js-focus-visible .video-js *:focus:not(.focus-visible) {
|
||||
outline: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.video-js *:focus:not(:focus-visible),
|
||||
.video-js .vjs-menu *:focus:not(:focus-visible) {
|
||||
.video-js *:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
background: none;
|
||||
}
|
1
frontend/src/static/lib/video-js/7.20.2/video-js.min.css
vendored
Normal file
1
frontend/src/static/lib/video-js/7.20.2/video-js.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
55216
frontend/src/static/lib/video-js/7.20.2/video.cjs.js
Normal file
55216
frontend/src/static/lib/video-js/7.20.2/video.cjs.js
Normal file
File diff suppressed because it is too large
Load diff
55198
frontend/src/static/lib/video-js/7.20.2/video.es.js
Normal file
55198
frontend/src/static/lib/video-js/7.20.2/video.es.js
Normal file
File diff suppressed because it is too large
Load diff
53148
frontend/src/static/lib/video-js/7.7.5/video.js → frontend/src/static/lib/video-js/7.20.2/video.js
Executable file → Normal file
53148
frontend/src/static/lib/video-js/7.7.5/video.js → frontend/src/static/lib/video-js/7.20.2/video.js
Executable file → Normal file
File diff suppressed because it is too large
Load diff
25
frontend/src/static/lib/video-js/7.20.2/video.min.js
vendored
Normal file
25
frontend/src/static/lib/video-js/7.20.2/video.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,13 +0,0 @@
|
|||
Copyright Brightcove, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
302
install-rhel.sh
Normal file
302
install-rhel.sh
Normal file
|
@ -0,0 +1,302 @@
|
|||
#!/bin/bash
|
||||
# should be run as root on a rhel8-like system
|
||||
|
||||
function update_permissions
|
||||
{
|
||||
# fix permissions of /srv/mediacms directory
|
||||
chown -R nginx:root $1
|
||||
}
|
||||
|
||||
echo "Welcome to the MediacMS installation!";
|
||||
|
||||
if [ `id -u` -ne 0 ]; then
|
||||
echo "Please run as root user"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
while true; do
|
||||
read -p "
|
||||
This script will attempt to perform a system update, install required dependencies, and configure PostgreSQL, NGINX, Redis and a few other utilities.
|
||||
It is expected to run on a new system **with no running instances of any these services**. Make sure you check the script before you continue. Then enter y or n
|
||||
" yn
|
||||
case $yn in
|
||||
[Yy]* ) echo "OK!"; break;;
|
||||
[Nn]* ) echo "Have a great day"; exit;;
|
||||
* ) echo "Please answer y or n.";;
|
||||
esac
|
||||
done
|
||||
|
||||
# update configuration files
|
||||
|
||||
sed -i 's/\/home\/mediacms\.io\/mediacms\/Bento4-SDK-1-6-0-637\.x86_64-unknown-linux\/bin\/mp4hls/\/srv\/mediacms\/bento4\/bin\/mp4hls/g' cms/settings.py
|
||||
sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g' deploy/local_install/celery_*.service
|
||||
sed -i 's/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.io
|
||||
sed -i 's/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.service
|
||||
sed -i 's/\/home\/mediacms\.io\/mediacms/\/var\/log\/mediacms/g' deploy/local_install/mediacms_logrorate
|
||||
sed -i 's/www-data/nginx/g' deploy/local_install/nginx.conf
|
||||
sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io/\/srv\/mediacms\/virtualenv/g' deploy/local_install/uwsgi.ini
|
||||
|
||||
osVersion=
|
||||
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
osVersion=$(grep ^ID /etc/os-release)
|
||||
fi
|
||||
|
||||
if [[ $osVersion == *"fedora"* ]] || [[ $osVersion == *"rhel"* ]] || [[ $osVersion == *"centos"* ]] || [[ *"rocky"* ]]; then
|
||||
dnf install -y epel-release https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm yum-utils
|
||||
yum-config-manager --enable powertools
|
||||
dnf install -y python3-virtualenv python39-devel redis postgresql postgresql-server nginx git gcc vim unzip ImageMagick python3-certbot-nginx certbot wget xz ffmpeg policycoreutils-devel cmake gcc gcc-c++ wget git bsdtar
|
||||
else
|
||||
echo "unsupported or unknown os"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# fix permissions of /srv/mediacms directory
|
||||
update_permissions /srv/mediacms/
|
||||
|
||||
read -p "Enter portal URL, or press enter for localhost : " FRONTEND_HOST
|
||||
read -p "Enter portal name, or press enter for 'MediaCMS : " PORTAL_NAME
|
||||
|
||||
[ -z "$PORTAL_NAME" ] && PORTAL_NAME='MediaCMS'
|
||||
[ -z "$FRONTEND_HOST" ] && FRONTEND_HOST='localhost'
|
||||
|
||||
echo "Configuring postgres"
|
||||
if [ ! command -v postgresql-setup > /dev/null 2>&1 ]; then
|
||||
echo "Something went wrong, the command 'postgresql-setup' was not found in the system path."
|
||||
exit -1
|
||||
fi
|
||||
|
||||
postgresql-setup --initdb
|
||||
|
||||
# set authentication method for mediacms user to scram-sha-256
|
||||
sed -i 's/.*password_encryption.*/password_encryption = scram-sha-256/' /var/lib/pgsql/data/postgresql.conf
|
||||
sed -i '/# IPv4 local connections:/a host\tmediacms\tmediacms\t127.0.0.1/32\tscram-sha-256' /var/lib/pgsql/data/pg_hba.conf
|
||||
|
||||
systemctl enable postgresql.service --now
|
||||
|
||||
su -c "psql -c \"CREATE DATABASE mediacms\"" postgres
|
||||
su -c "psql -c \"CREATE USER mediacms WITH ENCRYPTED PASSWORD 'mediacms'\"" postgres
|
||||
su -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE mediacms TO mediacms\"" postgres
|
||||
|
||||
echo 'Creating python virtualenv on /srv/mediacms/virtualenv/'
|
||||
|
||||
mkdir /srv/mediacms/virtualenv/
|
||||
cd /srv/mediacms/virtualenv/
|
||||
virtualenv . --python=python3
|
||||
source /srv/mediacms/virtualenv/bin/activate
|
||||
cd /srv/mediacms/
|
||||
pip install -r requirements.txt
|
||||
|
||||
systemctl enable redis.service --now
|
||||
|
||||
SECRET_KEY=`python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`
|
||||
|
||||
# remove http or https prefix
|
||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/http:\/\///g'`
|
||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/https:\/\///g'`
|
||||
|
||||
FRONTEND_HOST_HTTP_PREFIX='http://'$FRONTEND_HOST
|
||||
|
||||
echo 'FRONTEND_HOST='\'"$FRONTEND_HOST_HTTP_PREFIX"\' >> cms/local_settings.py
|
||||
echo 'PORTAL_NAME='\'"$PORTAL_NAME"\' >> cms/local_settings.py
|
||||
echo "SSL_FRONTEND_HOST = FRONTEND_HOST.replace('http', 'https')" >> cms/local_settings.py
|
||||
|
||||
echo 'SECRET_KEY='\'"$SECRET_KEY"\' >> cms/local_settings.py
|
||||
echo "LOCAL_INSTALL = True" >> cms/local_settings.py
|
||||
|
||||
mkdir /var/log/mediacms/
|
||||
mkdir pids
|
||||
|
||||
update_permissions /var/log/mediacms/
|
||||
|
||||
python manage.py migrate
|
||||
python manage.py loaddata fixtures/encoding_profiles.json
|
||||
python manage.py loaddata fixtures/categories.json
|
||||
python manage.py collectstatic --noinput
|
||||
|
||||
ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"`
|
||||
echo "from users.models import User; User.objects.create_superuser('admin', 'admin@example.com', '$ADMIN_PASS')" | python manage.py shell
|
||||
|
||||
echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
|
||||
|
||||
update_permissions /srv/mediacms/
|
||||
|
||||
cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
|
||||
cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
|
||||
cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
|
||||
cp deploy/local_install/mediacms.service /etc/systemd/system/mediacms.service
|
||||
|
||||
mkdir -p /etc/letsencrypt/live/$FRONTEND_HOST
|
||||
mkdir -p /etc/nginx/sites-enabled
|
||||
mkdir -p /etc/nginx/sites-available
|
||||
mkdir -p /etc/nginx/dhparams/
|
||||
rm -rf /etc/nginx/conf.d/default.conf
|
||||
rm -rf /etc/nginx/sites-enabled/default
|
||||
cp deploy/local_install/mediacms.io_fullchain.pem /etc/letsencrypt/live/$FRONTEND_HOST/fullchain.pem
|
||||
cp deploy/local_install/mediacms.io_privkey.pem /etc/letsencrypt/live/$FRONTEND_HOST/privkey.pem
|
||||
cp deploy/local_install/mediacms.io /etc/nginx/sites-available/mediacms.io
|
||||
ln -s /etc/nginx/sites-available/mediacms.io /etc/nginx/sites-enabled/mediacms.io
|
||||
cp deploy/local_install/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
|
||||
cp deploy/local_install/nginx.conf /etc/nginx/
|
||||
|
||||
# attempt to get a valid certificate for specified domain
|
||||
while true ; do
|
||||
echo "Would you like to run [c]ertbot, or [s]kip?"
|
||||
read -p " : " certbotConfig
|
||||
|
||||
case $certbotConfig in
|
||||
[cC*] )
|
||||
if [ "$FRONTEND_HOST" != "localhost" ]; then
|
||||
systemctl start
|
||||
echo 'attempt to get a valid certificate for specified url $FRONTEND_HOST'
|
||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
||||
# unfortunately for some reason it needs to be run two times in order to create the entries
|
||||
# and directory structure!!!
|
||||
systemctl stop nginx
|
||||
|
||||
# Generate individual DH params
|
||||
openssl dhparam -out /etc/nginx/dhparams/dhparams.pem 4096
|
||||
fi
|
||||
|
||||
break
|
||||
;;
|
||||
[sS*] )
|
||||
echo "will not call certbot utility to update ssl certificate for url 'localhost', using default ssl certificate"
|
||||
cp deploy/local_install/dhparams.pem /etc/nginx/dhparams/dhparams.pem
|
||||
|
||||
break
|
||||
;;
|
||||
* )
|
||||
echo "Unknown option: $certbotConfig"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# configure bento4 utility installation, for HLS
|
||||
while true ; do
|
||||
echo "Configuring Bento4"
|
||||
echo "Would you like to [d]ownload a pre-compiled bento4 binary, or [b]uild it now?"
|
||||
read -p "b/d : " bentoConfig
|
||||
|
||||
case $bentoConfig in
|
||||
[bB*] )
|
||||
echo "Building bento4 from source"
|
||||
git clone -b v1.6.0-640 https://github.com/axiomatic-systems/Bento4 /srv/mediacms/bento4
|
||||
cd /srv/mediacms/bento4/
|
||||
mkdir bin
|
||||
cd /srv/mediacms/bento4/bin/
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
make -j$(nproc)
|
||||
|
||||
chmod +x ../Source/Python/utils/mp4-hls.py
|
||||
|
||||
echo -e '#!/bin/bash' >> mp4hls
|
||||
echo -e 'BASEDIR=$(pwd)' >> mp4hls
|
||||
echo -e 'exec python3 "$BASEDIR/../Source/Python/utils/mp4-hls.py"' >> mp4hls
|
||||
|
||||
chmod +x mp4hls
|
||||
|
||||
break
|
||||
;;
|
||||
[dD*] )
|
||||
cd /srv/mediacms/
|
||||
wget http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
||||
bsdtar -xf Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -s '/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bento4/'
|
||||
|
||||
break
|
||||
;;
|
||||
* )
|
||||
echo "Unknown option: $bentoConfig"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir /srv/mediacms/media_files/hls
|
||||
|
||||
# update permissions
|
||||
|
||||
update_permissions /srv/mediacms/
|
||||
|
||||
# configure selinux
|
||||
|
||||
while true ; do
|
||||
echo "Configuring SELinux"
|
||||
echo "Would you like to [d]isable SELinux until next reboot, [c]onfigure our SELinux module, or [s]kip and not do any SELinux confgiguration?"
|
||||
read -p "d/c/s : " seConfig
|
||||
|
||||
case $seConfig in
|
||||
[Dd]* )
|
||||
echo "Disabling SELinux until next reboot"
|
||||
break
|
||||
;;
|
||||
[Cc]* )
|
||||
echo "Configuring custom mediacms selinux module"
|
||||
|
||||
semanage fcontext -a -t bin_t /srv/mediacms/virtualenv/bin/
|
||||
semanage fcontext -a -t httpd_sys_content_t "/srv/mediacms(/.*)?"
|
||||
restorecon -FRv /srv/mediacms/
|
||||
|
||||
sebools=(httpd_can_network_connect httpd_graceful_shutdown httpd_can_network_relay nis_enabled httpd_setrlimit domain_can_mmap_files)
|
||||
|
||||
for bool in "${sebools[@]}"
|
||||
do
|
||||
setsebool -P $bool 1
|
||||
done
|
||||
|
||||
cd /srv/mediacms/deploy/local_install/
|
||||
make -f /usr/share/selinux/devel/Makefile selinux-mediacms.pp
|
||||
semodule -i selinux-mediacms.pp
|
||||
|
||||
break
|
||||
;;
|
||||
[Ss]* )
|
||||
echo "Skipping SELinux configuration"
|
||||
break
|
||||
;;
|
||||
* )
|
||||
echo "Unknown option: $seConfig"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# configure firewall
|
||||
if command -v firewall-cmd > /dev/null 2>&1 ; then
|
||||
while true ; do
|
||||
echo "Configuring firewall"
|
||||
echo "Would you like to configure http, https, or skip and not do any firewall configuration?"
|
||||
read -p "http/https/skip : " fwConfig
|
||||
|
||||
case $fwConfig in
|
||||
http )
|
||||
echo "Opening port 80 until next reboot"
|
||||
firewall-cmd --add-port=80/tcp
|
||||
break
|
||||
;;
|
||||
https )
|
||||
echo "Opening port 443 permanently"
|
||||
firewall-cmd --add-port=443/tcp --permanent
|
||||
firewall-cmd --reload
|
||||
break
|
||||
;;
|
||||
skip )
|
||||
echo "Skipping firewall configuration"
|
||||
break
|
||||
;;
|
||||
* )
|
||||
echo "Unknown option: $fwConfig"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl start celery_long.service
|
||||
systemctl start celery_short.service
|
||||
systemctl start celery_beat.service
|
||||
systemctl start mediacms.service
|
||||
systemctl start nginx.service
|
||||
|
||||
echo 'MediaCMS installation completed, open browser on http://'"$FRONTEND_HOST"' and login with user admin and password '"$ADMIN_PASS"''
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
# should be run as root and only on Ubuntu 18/20, Debian 10/11 (Buster/Bullseye) versions!
|
||||
# should be run as root and only on Ubuntu 20/22, Debian 10/11 (Buster/Bullseye) versions!
|
||||
echo "Welcome to the MediacMS installation!";
|
||||
|
||||
if [ `id -u` -ne 0 ]
|
||||
|
@ -22,11 +22,11 @@ done
|
|||
|
||||
|
||||
osVersion=$(lsb_release -d)
|
||||
if [[ $osVersion == *"Ubuntu 20"* ]] || [[ $osVersion == *"Ubuntu 18"* ]] || [[ $osVersion == *"buster"* ]] || [[ $osVersion == *"bullseye"* ]]; then
|
||||
if [[ $osVersion == *"Ubuntu 20"* ]] || [[ $osVersion == *"Ubuntu 22"* ]] || [[ $osVersion == *"buster"* ]] || [[ $osVersion == *"bullseye"* ]]; then
|
||||
echo 'Performing system update and dependency installation, this will take a few minutes'
|
||||
apt-get update && apt-get -y upgrade && apt-get install python3-venv python3-dev virtualenv redis-server postgresql nginx git gcc vim unzip imagemagick python3-certbot-nginx certbot wget xz-utils -y
|
||||
else
|
||||
echo "This script is tested for Ubuntu 18 and 20 versions only, if you want to try MediaCMS on another system you have to perform the manual installation"
|
||||
echo "This script is tested for Ubuntu 20/22 versions only, if you want to try MediaCMS on another system you have to perform the manual installation"
|
||||
exit
|
||||
fi
|
||||
|
||||
|
|
|
@ -12,3 +12,5 @@ pytest-cov
|
|||
pytest-django
|
||||
pytest-factoryboy
|
||||
Faker
|
||||
django-cors-headers
|
||||
|
||||
|
|
|
@ -1,33 +1,21 @@
|
|||
Django==3.1.12
|
||||
djangorestframework==3.12.2
|
||||
django-allauth==0.44.0
|
||||
|
||||
psycopg2-binary==2.8.6
|
||||
|
||||
uwsgi==2.0.19.1
|
||||
|
||||
django-redis==4.12.1
|
||||
celery==4.4.7
|
||||
|
||||
drf-yasg==1.20.0
|
||||
|
||||
Pillow==8.2.0
|
||||
django-imagekit
|
||||
|
||||
markdown
|
||||
django-filter
|
||||
|
||||
filetype
|
||||
django-mptt
|
||||
|
||||
django-crispy-forms
|
||||
|
||||
requests==2.25.0
|
||||
|
||||
django-celery-email
|
||||
m3u8
|
||||
|
||||
django-ckeditor
|
||||
django-debug-toolbar==3.2.4
|
||||
|
||||
django-login-required-middleware==0.6.1
|
||||
Django==4.2.2
|
||||
djangorestframework==3.14.0
|
||||
django-allauth==0.54.0
|
||||
psycopg==3.1.9
|
||||
uwsgi==2.0.21
|
||||
django-redis==5.3.0
|
||||
celery==5.3.1
|
||||
drf-yasg==1.21.6
|
||||
Pillow==9.5.0
|
||||
django-imagekit==4.1.0
|
||||
markdown==3.4.3
|
||||
django-filter==23.2
|
||||
filetype==1.2.0
|
||||
django-mptt==0.14.0
|
||||
django-crispy-forms==1.13.0
|
||||
requests==2.31.0
|
||||
django-celery-email==3.0.0
|
||||
m3u8==3.5.0
|
||||
django-ckeditor==6.6.1
|
||||
django-debug-toolbar==4.1.0
|
||||
django-login-required-middleware==0.9.0
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
239
frontend/src/static/lib/video-js/7.7.5/video-js.css → static/lib/video-js/7.20.2/video-js.css
Executable file → Normal file
239
frontend/src/static/lib/video-js/7.7.5/video-js.css → static/lib/video-js/7.20.2/video-js.css
Executable file → Normal file
|
@ -379,21 +379,38 @@
|
|||
|
||||
.video-js.vjs-fluid,
|
||||
.video-js.vjs-16-9,
|
||||
.video-js.vjs-4-3 {
|
||||
.video-js.vjs-4-3,
|
||||
.video-js.vjs-9-16,
|
||||
.video-js.vjs-1-1 {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fluid:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-16-9:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-4-3:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-9-16:not(.vjs-audio-only-mode),
|
||||
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.video-js.vjs-16-9 {
|
||||
.video-js.vjs-16-9:not(.vjs-audio-only-mode) {
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
|
||||
.video-js.vjs-4-3 {
|
||||
.video-js.vjs-4-3:not(.vjs-audio-only-mode) {
|
||||
padding-top: 75%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fill {
|
||||
.video-js.vjs-9-16:not(.vjs-audio-only-mode) {
|
||||
padding-top: 177.7777777778%;
|
||||
}
|
||||
|
||||
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||
padding-top: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-fill:not(.vjs-audio-only-mode) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
@ -406,6 +423,10 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-tech {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.vjs-full-window {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -422,7 +443,7 @@ body.vjs-full-window {
|
|||
right: 0;
|
||||
}
|
||||
|
||||
.video-js.vjs-fullscreen {
|
||||
.video-js.vjs-fullscreen:not(.vjs-ios-native-fs) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
padding-top: 0 !important;
|
||||
|
@ -451,8 +472,8 @@ body.vjs-full-window {
|
|||
|
||||
.vjs-lock-showing {
|
||||
display: block !important;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.vjs-no-js {
|
||||
|
@ -614,6 +635,11 @@ body.vjs-full-window {
|
|||
color: #2B333F;
|
||||
}
|
||||
|
||||
.video-js .vjs-menu *:not(.vjs-selected):focus:not(:focus-visible),
|
||||
.js-focus-visible .vjs-menu *:not(.vjs-selected):focus:not(.focus-visible) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.vjs-menu li.vjs-menu-title {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
|
@ -736,7 +762,8 @@ body.vjs-full-window {
|
|||
background-color: rgba(43, 51, 63, 0.7);
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-control-bar {
|
||||
.vjs-has-started .vjs-control-bar,
|
||||
.vjs-audio-only-mode .vjs-control-bar {
|
||||
display: flex;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
@ -746,6 +773,7 @@ body.vjs-full-window {
|
|||
.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
visibility: visible;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: visibility 1s, opacity 1s;
|
||||
}
|
||||
|
||||
|
@ -755,9 +783,11 @@ body.vjs-full-window {
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar,
|
||||
.vjs-audio-only-mode.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.vjs-has-started.vjs-no-flex .vjs-control-bar {
|
||||
|
@ -774,18 +804,28 @@ body.vjs-full-window {
|
|||
flex: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-control.vjs-visible-text {
|
||||
width: auto;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.vjs-button > .vjs-icon-placeholder:before {
|
||||
font-size: 1.8em;
|
||||
line-height: 1.67;
|
||||
}
|
||||
|
||||
.vjs-button > .vjs-icon-placeholder {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js .vjs-control:focus:before,
|
||||
.video-js .vjs-control:hover:before,
|
||||
.video-js .vjs-control:focus {
|
||||
text-shadow: 0em 0em 1em white;
|
||||
}
|
||||
|
||||
.video-js .vjs-control-text {
|
||||
.video-js *:not(.vjs-visible-text) > .vjs-control-text {
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
|
@ -1074,6 +1114,7 @@ body.vjs-full-window {
|
|||
.video-js .vjs-volume-level:before {
|
||||
position: absolute;
|
||||
font-size: 0.9em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vjs-slider-vertical .vjs-volume-level {
|
||||
|
@ -1082,6 +1123,7 @@ body.vjs-full-window {
|
|||
.vjs-slider-vertical .vjs-volume-level:before {
|
||||
top: -0.5em;
|
||||
left: -0.3em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vjs-slider-horizontal .vjs-volume-level {
|
||||
|
@ -1116,6 +1158,77 @@ body.vjs-full-window {
|
|||
left: -2em;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-tooltip {
|
||||
background-color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 0.3em;
|
||||
color: #000;
|
||||
float: right;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 1em;
|
||||
padding: 6px 8px 8px 8px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: -3.4em;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control:hover .vjs-volume-tooltip,
|
||||
.video-js .vjs-volume-control:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
|
||||
display: block;
|
||||
font-size: 1em;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-vertical:hover .vjs-volume-tooltip,
|
||||
.video-js .vjs-volume-vertical:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
|
||||
left: 1em;
|
||||
top: -12px;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control.disabled:hover .vjs-volume-tooltip {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control .vjs-mouse-display {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #000;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-horizontal .vjs-mouse-display {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-no-flex .vjs-volume-control .vjs-mouse-display {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.video-js .vjs-volume-control:hover .vjs-mouse-display {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js.vjs-user-inactive .vjs-volume-control .vjs-mouse-display {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 1s, opacity 1s;
|
||||
}
|
||||
|
||||
.video-js.vjs-user-inactive.vjs-no-flex .vjs-volume-control .vjs-mouse-display {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-mouse-display .vjs-volume-tooltip {
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.vjs-poster {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
@ -1134,18 +1247,16 @@ body.vjs-full-window {
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started .vjs-poster {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vjs-has-started .vjs-poster,
|
||||
.vjs-using-native-controls .vjs-poster {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-audio.vjs-has-started .vjs-poster,
|
||||
.vjs-has-started.vjs-audio-poster-mode .vjs-poster {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-js .vjs-live-control {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
@ -1166,6 +1277,7 @@ body.vjs-full-window {
|
|||
}
|
||||
|
||||
.video-js .vjs-seek-to-live-control {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
|
@ -1252,6 +1364,7 @@ body.vjs-full-window {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.video-js.vjs-controls-disabled .vjs-text-track-display,
|
||||
.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display {
|
||||
bottom: 1em;
|
||||
}
|
||||
|
@ -1278,6 +1391,7 @@ video::-webkit-media-text-track-display {
|
|||
transform: translateY(-3em);
|
||||
}
|
||||
|
||||
.video-js.vjs-controls-disabled video::-webkit-media-text-track-display,
|
||||
.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display {
|
||||
transform: translateY(-1.5em);
|
||||
}
|
||||
|
@ -1286,10 +1400,18 @@ video::-webkit-media-text-track-display {
|
|||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-picture-in-picture-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-fullscreen-control {
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-fullscreen-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-playback-rate > .vjs-menu-button,
|
||||
.vjs-playback-rate .vjs-playback-rate-value {
|
||||
position: absolute;
|
||||
|
@ -1445,10 +1567,18 @@ video::-webkit-media-text-track-display {
|
|||
border-top-color: #73859f;
|
||||
}
|
||||
}
|
||||
.video-js.vjs-audio-only-mode .vjs-captions-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-chapters-button .vjs-menu ul {
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-descriptions-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-subs-caps-button + .vjs-menu .vjs-captions-menu-item .vjs-menu-item-text .vjs-icon-placeholder {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -1462,6 +1592,10 @@ video::-webkit-media-text-track-display {
|
|||
line-height: inherit;
|
||||
}
|
||||
|
||||
.video-js.vjs-audio-only-mode .vjs-subs-caps-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-js .vjs-audio-button + .vjs-menu .vjs-main-desc-menu-item .vjs-menu-item-text .vjs-icon-placeholder {
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
|
@ -1475,62 +1609,38 @@ video::-webkit-media-text-track-display {
|
|||
line-height: inherit;
|
||||
}
|
||||
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-current-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-time-divider,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-duration,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-remaining-time,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-playback-rate,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-chapters-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-descriptions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-captions-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subtitles-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-audio-button,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-control {
|
||||
.video-js.vjs-layout-small .vjs-current-time,
|
||||
.video-js.vjs-layout-small .vjs-time-divider,
|
||||
.video-js.vjs-layout-small .vjs-duration,
|
||||
.video-js.vjs-layout-small .vjs-remaining-time,
|
||||
.video-js.vjs-layout-small .vjs-playback-rate,
|
||||
.video-js.vjs-layout-small .vjs-volume-control, .video-js.vjs-layout-x-small .vjs-current-time,
|
||||
.video-js.vjs-layout-x-small .vjs-time-divider,
|
||||
.video-js.vjs-layout-x-small .vjs-duration,
|
||||
.video-js.vjs-layout-x-small .vjs-remaining-time,
|
||||
.video-js.vjs-layout-x-small .vjs-playback-rate,
|
||||
.video-js.vjs-layout-x-small .vjs-volume-control, .video-js.vjs-layout-tiny .vjs-current-time,
|
||||
.video-js.vjs-layout-tiny .vjs-time-divider,
|
||||
.video-js.vjs-layout-tiny .vjs-duration,
|
||||
.video-js.vjs-layout-tiny .vjs-remaining-time,
|
||||
.video-js.vjs-layout-tiny .vjs-playback-rate,
|
||||
.video-js.vjs-layout-tiny .vjs-volume-control {
|
||||
display: none;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active,
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active {
|
||||
.video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-x-small .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active, .video-js.vjs-layout-tiny .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover {
|
||||
width: auto;
|
||||
width: initial;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-liveui) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-x-small:not(.vjs-live) .vjs-subs-caps-button, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-subs-caps-button {
|
||||
.video-js.vjs-layout-x-small .vjs-progress-control, .video-js.vjs-layout-tiny .vjs-progress-control {
|
||||
display: none;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-custom-control-spacer {
|
||||
.video-js.vjs-layout-x-small .vjs-custom-control-spacer {
|
||||
flex: auto;
|
||||
display: block;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui.vjs-no-flex .vjs-custom-control-spacer, .video-js:not(.vjs-fullscreen).vjs-layout-tiny.vjs-no-flex .vjs-custom-control-spacer {
|
||||
.video-js.vjs-layout-x-small.vjs-no-flex .vjs-custom-control-spacer {
|
||||
width: auto;
|
||||
}
|
||||
.video-js:not(.vjs-fullscreen).vjs-layout-x-small.vjs-liveui .vjs-progress-control, .video-js:not(.vjs-fullscreen).vjs-layout-tiny .vjs-progress-control {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vjs-modal-dialog.vjs-text-track-settings {
|
||||
background-color: #2B333F;
|
||||
|
@ -1653,11 +1763,8 @@ video::-webkit-media-text-track-display {
|
|||
|
||||
.js-focus-visible .video-js *:focus:not(.focus-visible) {
|
||||
outline: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.video-js *:focus:not(:focus-visible),
|
||||
.video-js .vjs-menu *:focus:not(:focus-visible) {
|
||||
.video-js *:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
background: none;
|
||||
}
|
1
static/lib/video-js/7.20.2/video-js.min.css
vendored
Normal file
1
static/lib/video-js/7.20.2/video-js.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
static/lib/video-js/7.20.2/video.cjs.js
Normal file
2
static/lib/video-js/7.20.2/video.cjs.js
Normal file
File diff suppressed because one or more lines are too long
17
static/lib/video-js/7.20.2/video.cjs.js.LICENSE.txt
Normal file
17
static/lib/video-js/7.20.2/video.cjs.js.LICENSE.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*! @name @videojs/http-streaming @version 2.14.2 @license Apache-2.0 */
|
||||
|
||||
/*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
|
||||
|
||||
/*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Video.js 7.20.2 <http://videojs.com/>
|
||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
||||
*
|
||||
* Includes vtt.js <https://github.com/mozilla/vtt.js>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
||||
*/
|
2
static/lib/video-js/7.20.2/video.es.js
Normal file
2
static/lib/video-js/7.20.2/video.es.js
Normal file
File diff suppressed because one or more lines are too long
17
static/lib/video-js/7.20.2/video.es.js.LICENSE.txt
Normal file
17
static/lib/video-js/7.20.2/video.es.js.LICENSE.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*! @name @videojs/http-streaming @version 2.14.2 @license Apache-2.0 */
|
||||
|
||||
/*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
|
||||
|
||||
/*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Video.js 7.20.2 <http://videojs.com/>
|
||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
||||
*
|
||||
* Includes vtt.js <https://github.com/mozilla/vtt.js>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
||||
*/
|
2
static/lib/video-js/7.20.2/video.js
Normal file
2
static/lib/video-js/7.20.2/video.js
Normal file
File diff suppressed because one or more lines are too long
29
static/lib/video-js/7.20.2/video.js.LICENSE.txt
Normal file
29
static/lib/video-js/7.20.2/video.js.LICENSE.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*!
|
||||
* Programatically add the following
|
||||
*/
|
||||
|
||||
/*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
|
||||
|
||||
/*! @name m3u8-parser @version 4.7.1 @license Apache-2.0 */
|
||||
|
||||
/*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* slighly modified parse-headers 2.0.2 <https://github.com/kesla/parse-headers/>
|
||||
* Copyright (c) 2014 David Björklund
|
||||
* Available under the MIT license
|
||||
* <https://github.com/kesla/parse-headers/blob/master/LICENCE>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Video.js 7.20.2 <http://videojs.com/>
|
||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
||||
*
|
||||
* Includes vtt.js <https://github.com/mozilla/vtt.js>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
||||
*/
|
2
static/lib/video-js/7.20.2/video.min.js
vendored
Normal file
2
static/lib/video-js/7.20.2/video.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,8 @@
|
|||
/*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */
|
||||
/*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
|
||||
|
||||
/*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */
|
||||
/*! @name m3u8-parser @version 4.7.1 @license Apache-2.0 */
|
||||
|
||||
/*! @name pkcs7 @version 1.0.4 @license Apache-2.0 */
|
||||
|
||||
/**
|
||||
* @license
|
||||
|
@ -10,21 +12,14 @@
|
|||
* <https://github.com/kesla/parse-headers/blob/master/LICENCE>
|
||||
*/
|
||||
|
||||
/**
|
||||
* @videojs/http-streaming
|
||||
* @version 1.11.2
|
||||
* @copyright 2019 Brightcove, Inc
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Video.js 7.7.5 <http://videojs.com/>
|
||||
* Video.js 7.20.2 <http://videojs.com/>
|
||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/videojs/video.js/blob/master/LICENSE>
|
||||
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
||||
*
|
||||
* Includes vtt.js <https://github.com/mozilla/vtt.js>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
|
||||
* <https://github.com/mozilla/vtt.js/blob/main/LICENSE>
|
||||
*/
|
|
@ -1,13 +0,0 @@
|
|||
Copyright Brightcove, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
1
static/lib/video-js/7.7.5/video-js.min.css
vendored
1
static/lib/video-js/7.7.5/video-js.min.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,15 +0,0 @@
|
|||
/*! @name m3u8-parser @version 4.4.0 @license Apache-2.0 */
|
||||
|
||||
/*! @name mpd-parser @version 0.8.1 @license Apache-2.0 */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Video.js 7.7.5 <http://videojs.com/>
|
||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/videojs/video.js/blob/master/LICENSE>
|
||||
*
|
||||
* Includes vtt.js <https://github.com/mozilla/vtt.js>
|
||||
* Available under Apache License Version 2.0
|
||||
* <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
|
||||
*/
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue