feat: adds minimum resolution of 144p

This commit is contained in:
Markos Gogoulos
2025-07-07 11:34:02 +03:00
committed by GitHub
parent 79f2e2bb11
commit a607996bfa
10 changed files with 92 additions and 33 deletions

View File

@ -38,7 +38,7 @@ A demo is available at https://demo.mediacms.io
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
- **Configuration options**: change logos, fonts, styling, add more pages
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (144p, 240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Adaptive video streaming**: possible through HLS protocol
- **Subtitles/CC**: support for multilingual subtitle files
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
@ -93,20 +93,14 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
## Configuration
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
## Information for developers
Check out the new section on the [Developer Experience](docs/dev_exp.md) page
## Documentation
* [Users documentation](docs/user_docs.md) page
* [Administrators documentation](docs/admins_docs.md) page
* [Developers documentation](docs/developers_docs.md) page
* [Configuration](docs/admins_docs.md#5-configuration) page
* [Transcoding](docs/transcoding.md) page
* [Developer Experience](docs/dev_exp.md) page
## Technology

View File

@ -186,7 +186,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
VIDEO_CHUNKS_DURATION = 60 * 4
# always get these two, even if upscaling
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360]
MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
# default settings for notifications
# not all of them are implemented
@ -497,6 +497,10 @@ USE_ROUNDED_CORNERS = True
ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
try:
# keep a local_settings.py file for local overrides
from .local_settings import * # noqa

View File

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

50
docs/transcoding.md Normal file
View File

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

View File

@ -34,12 +34,6 @@ BUF_SIZE_MULTIPLIER = 1.5
KEYFRAME_DISTANCE = 4
KEYFRAME_DISTANCE_MIN = 2
# speed presets
# see https://trac.ffmpeg.org/wiki/Encode/H.264
X26x_PRESET = "medium" # "medium"
X265_PRESET = "medium"
X26x_PRESET_BIG_HEIGHT = "faster"
# VP9_SPEED = 1 # between 0 and 4, lower is slower
VP9_SPEED = 2
@ -55,6 +49,7 @@ VIDEO_CRFS = {
VIDEO_BITRATES = {
"h264": {
25: {
144: 150,
240: 300,
360: 500,
480: 1000,
@ -67,6 +62,7 @@ VIDEO_BITRATES = {
},
"h265": {
25: {
144: 75,
240: 150,
360: 275,
480: 500,
@ -79,6 +75,7 @@ VIDEO_BITRATES = {
},
"vp9": {
25: {
144: 75,
240: 150,
360: 275,
480: 500,
@ -596,17 +593,13 @@ def get_base_ffmpeg_command(
cmd = base_cmd[:]
# preset settings
preset = getattr(settings, "FFMPEG_DEFAULT_PRESET", "medium")
if encoder == "libvpx-vp9":
if pass_number == 1:
speed = 4
else:
speed = VP9_SPEED
elif encoder in ["libx264"]:
preset = X26x_PRESET
elif encoder in ["libx265"]:
preset = X265_PRESET
if target_height >= 720:
preset = X26x_PRESET_BIG_HEIGHT
if encoder == "libx264":
level = "4.2" if target_height <= 1080 else "5.2"
@ -730,7 +723,7 @@ def produce_ffmpeg_commands(media_file, media_info, resolution, codec, output_fi
return False
if media_info.get("video_height") < resolution:
if resolution not in [240, 360]: # always get these two
if resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE:
return False
# if codec == "h264_baseline":

View File

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

View File

@ -73,6 +73,7 @@ ENCODE_RESOLUTIONS = (
(480, "480"),
(360, "360"),
(240, "240"),
(144, "144"),
)
CODECS = (
@ -896,7 +897,7 @@ class Media(models.Model):
"""
res = {}
valid_resolutions = [240, 360, 480, 720, 1080, 1440, 2160]
valid_resolutions = [144, 240, 360, 480, 720, 1080, 1440, 2160]
if self.hls_file:
if os.path.exists(self.hls_file):
hls_file = self.hls_file

View File

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

View File

@ -43,8 +43,8 @@ class TestX(TestCase):
self.assertEqual(Media.objects.filter(media_type='image').count(), 1, "Media identification failed")
self.assertEqual(Media.objects.filter(user=self.user).count(), 3, "User assignment failed")
medium_video = Media.objects.get(title="medium_video.mp4")
self.assertEqual(len(medium_video.hls_info), 11, "Problem with HLS info")
self.assertEqual(len(medium_video.hls_info), 13, "Problem with HLS info")
# using the provided EncodeProfiles, these two files should produce 9 Encoding objects.
# if new EncodeProfiles are added and enabled, this will break!
self.assertEqual(Encoding.objects.filter(status='success').count(), 9, "Not all video transcodings finished well")
self.assertEqual(Encoding.objects.filter(status='success').count(), 10, "Not all video transcodings finished well")

View File

@ -24,12 +24,12 @@ class TestFixtures(TestCase):
profiles = EncodeProfile.objects.all()
self.assertEqual(
profiles.count(),
22,
23,
"Problem with Encode Profile fixtures",
)
profiles = EncodeProfile.objects.filter(active=True)
self.assertEqual(
profiles.count(),
6,
7,
"Problem with Encode Profile fixtures, not as active as expected",
)