This commit is contained in:
Jan Koppe 2024-04-22 14:25:15 +02:00
parent d2f980c318
commit 0a857a194c
Signed by: thunfisch
GPG Key ID: BE935B0735A2129B
12 changed files with 621 additions and 27 deletions

View File

@ -1,23 +1,38 @@
from django.contrib import admin from django.contrib import admin
from guardian.admin import GuardedModelAdmin from guardian.admin import GuardedModelAdmin
from config.models import Stream, Restream, Pull, SRSNode, SRSStreamInstance from config.models import TranscodingProfile, Stream, Restream, Pull, Recorder, LocalRecordingStorage, S3RecordingStorage, SRSNode, SRSStreamInstance
@admin.register(LocalRecordingStorage)
class LocalRecordingStorageAdmin(GuardedModelAdmin):
pass
@admin.register(S3RecordingStorage)
class S3RecordingStorageAdmin(GuardedModelAdmin):
pass
@admin.register(Recorder)
class RecorderAdmin(GuardedModelAdmin):
pass
@admin.register(TranscodingProfile)
class TranscodingProfileAdmin(GuardedModelAdmin):
pass
@admin.register(Stream) @admin.register(Stream)
class StreamAdmin(GuardedModelAdmin): class StreamAdmin(GuardedModelAdmin):
fields = ['stream', 'name', 'publish_counter'] pass
@admin.register(Restream) @admin.register(Restream)
class RestreamAdmin(GuardedModelAdmin): class RestreamAdmin(GuardedModelAdmin):
fields = ['name', 'active', 'stream', 'format', 'target'] pass
@admin.register(Pull) @admin.register(Pull)
class PullAdmin(GuardedModelAdmin): class PullAdmin(GuardedModelAdmin):
fields = ['name', 'active', 'stream', 'source'] pass
@admin.register(SRSNode) @admin.register(SRSNode)
class SRSNodeAdmin(GuardedModelAdmin): class SRSNodeAdmin(GuardedModelAdmin):
fields = ['name', 'api_base', 'rtmp_base', 'active'] pass
@admin.register(SRSStreamInstance) @admin.register(SRSStreamInstance)
class SRSStreamInstanceAdmin(GuardedModelAdmin): class SRSStreamInstanceAdmin(GuardedModelAdmin):

View File

@ -0,0 +1 @@
from . import pull, recorder, restream, stream

View File

@ -57,6 +57,20 @@ class StreamCreate(ModelSchema):
extra = "forbid" extra = "forbid"
class LocalRecordingStorage(ModelSchema):
class Meta:
model = models.LocalRecordingStorage
fields = "__all__"
class LocalRecordingStoragePatch(ModelSchema):
class Meta:
model = models.LocalRecordingStorage
exclude = ["id"]
fields_optional = "__all__"
extra = "forbid"
@router.get('/streams', response=List[Stream]) @router.get('/streams', response=List[Stream])
def list_streams(request): def list_streams(request):
return get_objects_for_user(request.user, 'view_stream', models.Stream.objects.all()) return get_objects_for_user(request.user, 'view_stream', models.Stream.objects.all())
@ -123,7 +137,7 @@ def create_restream(request, payload: Restream):
@router.get('/restreams/{id}', response=Restream) @router.get('/restreams/{id}', response=Restream)
def get_restream(request, id: int): def get_restream(request, id: int):
restream = get_object_or_404(models.Restream, id=id) restream = get_object_or_404(models.Restream, id=id)
if not request.user.has_perm('view_restream', restream): if not request.user.has_perm('view_restream', restream):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
@ -139,7 +153,7 @@ def update_restream(request, id: int, payload: RestreamPatch):
if payload.stream: if payload.stream:
payload.stream = get_object_or_404(models.Stream, id=payload.stream) payload.stream = get_object_or_404(models.Stream, id=payload.stream)
if not request.user.has_perm('view_stream', payload.stream): if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
@ -171,20 +185,20 @@ def create_pull(request, payload: Pull):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
pull = models.Pull.objects.create(**payload.dict()) pull = models.Pull.objects.create(**payload.dict())
assign_perm( 'view_pull', request.user, Pull) assign_perm( 'view_pull', request.user, pull)
assign_perm('change_pull', request.user, Pull) assign_perm('change_pull', request.user, pull)
assign_perm('delete_pull', request.user, Pull) assign_perm('delete_pull', request.user, pull)
return pull return pull
@router.get('/pulls/{id}', response=Pull) @router.get('/pulls/{id}', response=Pull)
def get_pull(request, id: int): def get_pull(request, id: int):
pull = get_object_or_404(models.Pull, id=id) pull = get_object_or_404(models.Pull, id=id)
if not request.user.has_perm('view_pull', pull): if not request.user.has_perm('view_pull', pull):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
return pull return pull
@router.patch('/pulls/{id}', response=Pull) @router.patch('/pulls/{id}', response=Pull)
@ -196,7 +210,7 @@ def patch_pull(request, id: int, payload: PullPatch):
if payload.stream: if payload.stream:
payload.stream = get_object_or_404(models.Stream, id=payload.stream) payload.stream = get_object_or_404(models.Stream, id=payload.stream)
if not request.user.has_perm('view_stream', payload.stream): if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
@ -213,4 +227,45 @@ def delete_pull(request, id: int):
if not request.user.has_perm('delete_pull', pull): if not request.user.has_perm('delete_pull', pull):
raise HttpError(401, "unauthorized") raise HttpError(401, "unauthorized")
pull.delete() pull.delete()
@router.get('/recording/storage/local', response=List[LocalRecordingStorage])
def list_local_recording_storage(request):
return get_objects_for_user(request.user, 'view_localrecordingstorage', models.LocalRecordingStorage.objects.all())
@router.get('/recording/storage/local/{id}', response=LocalRecordingStorage)
def get_local_recording_storage(request, id: int):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('view_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
return obj
@router.post('/recording/storage/local', response=LocalRecordingStorage)
def create_local_recording_storage(request, payload: LocalRecordingStorage):
obj = models.LocalRecordingStorage.objects.create(**payload.dict())
assign_perm( 'view_localrecordingstorage', request.user, obj)
assign_perm('change_localrecordingstorage', request.user, obj)
assign_perm('delete_localrecordingstorage', request.user, obj)
return obj
@router.patch('/recording/storage/local/{id}', response=LocalRecordingStorage)
def patch_local_recording_storage(request, id: int, payload: LocalRecordingStoragePatch):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('change_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
for key, value in payload.dict(exclude_unset=True).items():
setattr(obj, key, value)
obj.save()
return obj
@router.delete('/recording/storage/local/{id}', response=None)
def delete_local_recording_storage(request, id: int):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('delete_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
obj.delete()

77
source/config/api/pull.py Normal file
View File

@ -0,0 +1,77 @@
from ninja import Router, ModelSchema, Schema
from ninja.errors import HttpError
from config import models
from typing import List
from guardian.shortcuts import get_objects_for_user, assign_perm
from django.shortcuts import get_object_or_404
router = Router()
class Pull(ModelSchema):
class Meta:
model = models.Pull
fields = "__all__"
class PullPatch(ModelSchema):
class Meta:
model = models.Pull
exclude = ["id"]
fields_optional = "__all__"
extra = "forbid"
@router.get('/pulls', response=List[Pull])
def list_pulls(request):
return get_objects_for_user(request.user, 'view_pull', models.Pull.objects.all())
@router.post('/pulls', response=Pull)
def create_pull(request, payload: Pull):
if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized")
pull = models.Pull.objects.create(**payload.dict())
assign_perm( 'view_pull', request.user, pull)
assign_perm('change_pull', request.user, pull)
assign_perm('delete_pull', request.user, pull)
return pull
@router.get('/pulls/{id}', response=Pull)
def get_pull(request, id: int):
pull = get_object_or_404(models.Pull, id=id)
if not request.user.has_perm('view_pull', pull):
raise HttpError(401, "unauthorized")
return pull
@router.patch('/pulls/{id}', response=Pull)
def patch_pull(request, id: int, payload: PullPatch):
pull = get_object_or_404(models.Pull, id=id)
if not request.user.has_perm('change_pull', pull):
raise HttpError(401, "unauthorized")
if payload.stream:
payload.stream = get_object_or_404(models.Stream, id=payload.stream)
if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized")
for key, value in payload.dict(exclude_unset=True).items():
setattr(pull, key, value)
pull.save()
return pull
@router.delete('/pulls/{id}', response=None)
def delete_pull(request, id: int):
pull = get_object_or_404(models.Pull, id=id)
if not request.user.has_perm('delete_pull', pull):
raise HttpError(401, "unauthorized")
pull.delete()

View File

@ -0,0 +1,65 @@
from ninja import Router, ModelSchema, Schema
from ninja.errors import HttpError
from config import models
from typing import List
from guardian.shortcuts import get_objects_for_user, assign_perm
from django.shortcuts import get_object_or_404
router = Router()
class LocalRecordingStorage(ModelSchema):
class Meta:
model = models.LocalRecordingStorage
fields = "__all__"
class LocalRecordingStoragePatch(ModelSchema):
class Meta:
model = models.LocalRecordingStorage
exclude = ["id"]
fields_optional = "__all__"
extra = "forbid"
@router.get('/storage/local', response=List[LocalRecordingStorage])
def list_local_recording_storage(request):
return get_objects_for_user(request.user, 'view_localrecordingstorage', models.LocalRecordingStorage.objects.all())
@router.get('/storage/local/{id}', response=LocalRecordingStorage)
def get_local_recording_storage(request, id: int):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('view_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
return obj
@router.post('/storage/local', response=LocalRecordingStorage)
def create_local_recording_storage(request, payload: LocalRecordingStorage):
obj = models.LocalRecordingStorage.objects.create(**payload.dict())
assign_perm( 'view_localrecordingstorage', request.user, obj)
assign_perm('change_localrecordingstorage', request.user, obj)
assign_perm('delete_localrecordingstorage', request.user, obj)
return obj
@router.patch('/storage/local/{id}', response=LocalRecordingStorage)
def patch_local_recording_storage(request, id: int, payload: LocalRecordingStoragePatch):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('change_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
for key, value in payload.dict(exclude_unset=True).items():
setattr(obj, key, value)
obj.save()
return obj
@router.delete('/storage/local/{id}', response=None)
def delete_local_recording_storage(request, id: int):
obj = get_object_or_404(models.LocalRecordingStorage, id=id)
if not request.user.has_perm('delete_localrecordingstorage', obj):
raise HttpError(401, "unauthorized")
obj.delete()

View File

@ -0,0 +1,78 @@
from ninja import Router, ModelSchema, Schema
from ninja.errors import HttpError
from config import models
from typing import List
from guardian.shortcuts import get_objects_for_user, assign_perm
from django.shortcuts import get_object_or_404
router = Router()
class Restream(ModelSchema):
class Meta:
model = models.Restream
fields = "__all__"
class RestreamPatch(ModelSchema):
class Meta:
model = models.Restream
exclude = ["id"]
fields_optional = "__all__"
extra = "forbid"
@router.get('/restreams', response=List[Restream])
def list_restreams(request):
return get_objects_for_user(request.user, 'view_restream', models.Restream.objects.all())
@router.post('/restreams', response=Restream)
def create_restream(request, payload: Restream):
if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized")
restream = models.Restream.objects.create(**payload.dict())
assign_perm('view_restream', request.user, restream)
assign_perm('change_restream', request.user, restream)
assign_perm('delete_restream', request.user, restream)
return restream
@router.get('/restreams/{id}', response=Restream)
def get_restream(request, id: int):
restream = get_object_or_404(models.Restream, id=id)
if not request.user.has_perm('view_restream', restream):
raise HttpError(401, "unauthorized")
return restream
@router.patch('/restreams/{id}', response=Restream)
def update_restream(request, id: int, payload: RestreamPatch):
restream = get_object_or_404(models.Restream, id=id)
if not request.user.has_perm('change_restream', restream):
raise HttpError(401, "unauthorized")
if payload.stream:
payload.stream = get_object_or_404(models.Stream, id=payload.stream)
if not request.user.has_perm('view_stream', payload.stream):
raise HttpError(401, "unauthorized")
for key, value in payload.dict(exclude_unset=True).items():
setattr(restream, key, value)
restream.save()
return restream
@router.delete('/restreams/{id}', response=None)
def delete_restream(request, id: int):
restream = get_object_or_404(models.Restream, id=id)
if not request.user.has_perm('delete_restream', restream):
raise HttpError(401, "unauthorized")
restream.delete()

View File

@ -0,0 +1,73 @@
from ninja import Router, ModelSchema, Schema
from ninja.errors import HttpError
from config import models
from typing import List
from guardian.shortcuts import get_objects_for_user, assign_perm
from django.shortcuts import get_object_or_404
router = Router()
class Stream(ModelSchema):
class Meta:
model = models.Stream
fields = "__all__"
class StreamPatch(ModelSchema):
class Meta:
model = models.Stream
fields = ["name"]
fields_optional = "__all__"
extra = "forbid"
class StreamCreate(ModelSchema):
class Meta:
model = models.Stream
fields = ["name"]
extra = "forbid"
@router.get('/streams', response=List[Stream])
def list_streams(request):
return get_objects_for_user(request.user, 'view_stream', models.Stream.objects.all())
@router.post('/streams', response=Stream)
def create_stream(request, payload: StreamCreate):
stream = models.Stream.objects.create(**payload.dict())
assign_perm('view_stream', request.user, stream)
assign_perm('change_stream', request.user, stream)
assign_perm('delete_stream', request.user, stream)
return stream
@router.get('/streams/{id}', response=Stream)
def get_stream(request, id: int):
stream = get_object_or_404(models.Stream, id=id)
if not request.user.has_perm('view_stream', stream):
raise HttpError(401, "unauthorized")
return stream
@router.patch('/streams/{id}', response=Stream)
def update_stream(request, id: int, payload: StreamPatch):
stream = get_object_or_404(models.Stream, id=id)
if not request.user.has_perm('change_stream', stream):
raise HttpError(401, "unauthorized")
stream.name = payload.name
stream.save()
return stream
@router.delete('/streams/{id}', response=None)
def delete_stream(request, id: int):
stream = get_object_or_404(models.Stream, id=id)
if not request.user.has_perm('delete_stream', stream):
raise HttpError(401, "unauthorized")
stream.delete()

View File

@ -0,0 +1,59 @@
# Generated by Django 5.0.2 on 2024-04-01 18:42
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0004_alter_pull_source_alter_restream_target'),
]
operations = [
migrations.CreateModel(
name='TranscodingProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='transcodingprofile_name_help', max_length=100)),
('video_map_stream', models.PositiveIntegerField(default=0, help_text='transcodingprofile_video_map_stream_help')),
('video_codec', models.CharField(choices=[('copy', 'Copy'), ('h264', 'H.264'), ('h265', 'H.265')], help_text='transcodingprofile_video_codec_help', max_length=4)),
('video_bitrate', models.PositiveIntegerField(default=6000, help_text='transcodingprofile_video_bitrate_help')),
('video_frame_rate', models.PositiveIntegerField(default=30, help_text='transcodingprofile_video_frame_rate_help')),
('video_resolution', models.CharField(help_text='transcodingprofile_video_resolution_help', max_length=9)),
('video_gop_size', models.PositiveIntegerField(default=60, help_text='transcodingprofile_video_gop_size_help')),
('video_pixel_format', models.CharField(choices=[('yuv420', 'YUV420'), ('yuv422', 'YUV422'), ('yuv444', 'YUV444')], help_text='transcodingprofile_video_pixel_format_help', max_length=10)),
('video_bitrate_mode', models.CharField(choices=[('cbr', 'CBR'), ('vbr', 'VBR')], help_text='transcodingprofile_video_bitrate_mode_help', max_length=10)),
('audio_map_stream', models.PositiveIntegerField(default=0, help_text='transcodingprofile_audio_map_stream_help')),
('audio_codec', models.CharField(choices=[('copy', 'Copy'), ('aac', 'AAC')], help_text='transcodingprofile_audio_codec_help', max_length=10)),
('audio_bitrate', models.PositiveIntegerField(default=160, help_text='transcodingprofile_audio_bitrate_help')),
('audio_channels', models.PositiveIntegerField(default=2, help_text='transcodingprofile_audio_channels_help')),
('audio_sample_rate', models.PositiveIntegerField(default=48000, help_text='transcodingprofile_audio_sample_rate_help')),
],
options={
'verbose_name': 'transcodingprofile_verbose_name',
'verbose_name_plural': 'transcodingprofile_verbose_name_plural',
},
),
migrations.AlterField(
model_name='stream',
name='name',
field=models.CharField(help_text='stream_name_help', max_length=100),
),
migrations.AlterField(
model_name='stream',
name='stream',
field=models.UUIDField(default=uuid.uuid4, help_text='stream_stream_help', unique=True),
),
migrations.AddField(
model_name='pull',
name='transcodingprofile',
field=models.ForeignKey(blank=True, help_text='restream_transcodingprofile_help', null=True, on_delete=django.db.models.deletion.PROTECT, to='config.transcodingprofile'),
),
migrations.AddField(
model_name='restream',
name='transcodingprofile',
field=models.ForeignKey(blank=True, help_text='restream_transcodingprofile_help', null=True, on_delete=django.db.models.deletion.PROTECT, to='config.transcodingprofile'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 5.0.2 on 2024-04-13 08:52
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config', '0005_transcodingprofile_alter_stream_name_and_more'),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='LocalRecordingStorage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='recordingstorage_name_help', max_length=100)),
('path', models.CharField(help_text='localrecordingstorage_path_help', max_length=500)),
],
options={
'verbose_name': 'localrecordingstorage_verbose_name',
'verbose_name_plural': 'localrecordingstorage_verbose_name_plural',
},
),
migrations.CreateModel(
name='S3RecordingStorage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='recordingstorage_name_help', max_length=100)),
('bucket', models.CharField(help_text='s3recordingstorage_bucket_help', max_length=100)),
('access_key', models.CharField(help_text='s3recordingstorage_access_key_help', max_length=100)),
('secret_key', models.CharField(help_text='s3recordingstorage_secret_key_help', max_length=100)),
('region', models.CharField(help_text='s3recordingstorage_region_help', max_length=100)),
],
options={
'verbose_name': 's3recordingstorage_verbose_name',
'verbose_name_plural': 's3recordingstorage_verbose_name_plural',
},
),
migrations.CreateModel(
name='Recorder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='recorder_name_help', max_length=100)),
('active', models.BooleanField(help_text='recorder_activate_help')),
('storage_config_id', models.PositiveIntegerField()),
('storage_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(('app_label', 'config'), ('model', 'LocalRecordingStorage')), models.Q(('app_label', 'app'), ('model', 'S3RecordingStorage')), _connector='OR'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
('stream', models.ForeignKey(help_text='recorder_stream_help', on_delete=django.db.models.deletion.CASCADE, to='config.stream')),
],
),
]

View File

@ -1,6 +1,9 @@
import json import json
import uuid import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
@ -10,6 +13,70 @@ from portier.common import handlers
from config import signals_shared from config import signals_shared
from config.util import validate_stream_url from config.util import validate_stream_url
class TranscodingProfile(models.Model):
VIDEO_CODECS = (
('copy', 'Copy'),
('h264', 'H.264'),
('h265', 'H.265'),
)
AUDIO_CODECS = (
('copy', 'Copy'),
('aac', 'AAC'),
)
VIDEO_PIXEL_FORMATS = (
('yuv420', 'YUV420'),
('yuv422', 'YUV422'),
('yuv444', 'YUV444'),
)
VIDEO_BITRATE_MODES = (
('cbr', 'CBR'),
('vbr', 'VBR'),
)
name = models.CharField(max_length=100, help_text=_('transcodingprofile_name_help'))
video_map_stream = models.PositiveIntegerField(default=0, help_text=_('transcodingprofile_video_map_stream_help'))
video_codec = models.CharField(max_length=4, choices=VIDEO_CODECS, help_text=_('transcodingprofile_video_codec_help'))
video_bitrate = models.PositiveIntegerField(default=6000, help_text=_('transcodingprofile_video_bitrate_help'))
video_frame_rate = models.PositiveIntegerField(default=30, help_text=_('transcodingprofile_video_frame_rate_help'))
video_resolution = models.CharField(max_length=9, help_text=_('transcodingprofile_video_resolution_help'))
video_gop_size = models.PositiveIntegerField(default=60, help_text=_('transcodingprofile_video_gop_size_help'))
video_pixel_format = models.CharField(max_length=10, choices=VIDEO_PIXEL_FORMATS, help_text=_('transcodingprofile_video_pixel_format_help'))
video_bitrate_mode = models.CharField(max_length=10, choices=VIDEO_BITRATE_MODES, help_text=_('transcodingprofile_video_bitrate_mode_help'))
audio_map_stream = models.PositiveIntegerField(default=0, help_text=_('transcodingprofile_audio_map_stream_help'))
audio_codec = models.CharField(max_length=10, choices=AUDIO_CODECS, help_text=_('transcodingprofile_audio_codec_help'))
audio_bitrate = models.PositiveIntegerField(default=160, help_text=_('transcodingprofile_audio_bitrate_help'))
audio_channels = models.PositiveIntegerField(default=2, help_text=_('transcodingprofile_audio_channels_help'))
audio_sample_rate = models.PositiveIntegerField(default=48000, help_text=_('transcodingprofile_audio_sample_rate_help'))
class Meta:
verbose_name = _('transcodingprofile_verbose_name')
verbose_name_plural = _('transcodingprofile_verbose_name_plural')
def class_name(self):
return _('transcodingprofile_class_name')
def get_absolute_url(self):
return reverse('config:transcodingprofile_detail', kwargs={'pk': self.pk})
def __str__(self):
return str(self.name)
def get_concierge_configuration(self):
return {
'config_version': 1,
'video_map_stream': self.video_map_stream,
'video_codec': self.video_codec,
'video_bitrate': self.video_bitrate,
'video_frame_rate': self.video_frame_rate,
'video_resolution': self.video_resolution,
'audio_map_stream': self.audio_map_stream,
'audio_codec': self.audio_codec,
'audio_bitrate': self.audio_bitrate,
'audio_channels': self.audio_channels,
'audio_sample_rate': self.audio_sample_rate,
}
class Stream(models.Model): class Stream(models.Model):
stream = models.UUIDField(unique=True, default=uuid.uuid4, help_text=_('stream_stream_help')) stream = models.UUIDField(unique=True, default=uuid.uuid4, help_text=_('stream_stream_help'))
@ -45,9 +112,6 @@ class Stream(models.Model):
return str(self.name) return str(self.name)
pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Stream)
class SRSNode(models.Model): class SRSNode(models.Model):
name = models.CharField(max_length=100, help_text=_('srsnode_name_help')) name = models.CharField(max_length=100, help_text=_('srsnode_name_help'))
api_base = models.CharField(max_length=256, help_text=_('srsnode_api_base_help')) api_base = models.CharField(max_length=256, help_text=_('srsnode_api_base_help'))
@ -81,6 +145,7 @@ class Pull(models.Model):
source = models.CharField(max_length=500, validators=[validate_stream_url], help_text=_('pull_source_help')) source = models.CharField(max_length=500, validators=[validate_stream_url], help_text=_('pull_source_help'))
active = models.BooleanField(help_text=_('pull_activate_help')) active = models.BooleanField(help_text=_('pull_activate_help'))
name = models.CharField(max_length=100, help_text=_('pull_name_help')) name = models.CharField(max_length=100, help_text=_('pull_name_help'))
transcodingprofile = models.ForeignKey(TranscodingProfile, null=True, blank=True, on_delete=models.PROTECT, help_text=_('restream_transcodingprofile_help'))
class Meta: class Meta:
verbose_name = _('pull_verbose_name') verbose_name = _('pull_verbose_name')
@ -116,17 +181,16 @@ class Pull(models.Model):
node = SRSNode.objects.filter(active=True).order_by('?').first() node = SRSNode.objects.filter(active=True).order_by('?').first()
return { return {
'config_version': 1,
"type": "pull", "type": "pull",
"active": self.active, "active": self.active,
"name": self.name, "name": self.name,
"source": self.source, "source": self.source,
"target": f"{node.rtmp_base}/{settings.GLOBAL_STREAM_NAMESPACE}/{self.stream.stream}", "target": f"{node.rtmp_base}/{settings.GLOBAL_STREAM_NAMESPACE}/{self.stream.stream}",
'transcoding_profile': self.transcodingprofile.get_concierge_configuration(),
} }
pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Pull)
class Restream(models.Model): class Restream(models.Model):
FORMATS = ( FORMATS = (
('flv', 'flv (RTMP)'), ('flv', 'flv (RTMP)'),
@ -137,6 +201,7 @@ class Restream(models.Model):
name = models.CharField(max_length=100, help_text=_('restream_name_help')) name = models.CharField(max_length=100, help_text=_('restream_name_help'))
active = models.BooleanField(help_text=_('restream_activate_help')) active = models.BooleanField(help_text=_('restream_activate_help'))
format = models.CharField(max_length=6, choices=FORMATS, default='flv', help_text=_('restream_format_help')) format = models.CharField(max_length=6, choices=FORMATS, default='flv', help_text=_('restream_format_help'))
transcodingprofile = models.ForeignKey(TranscodingProfile, null=True, blank=True, on_delete=models.PROTECT, help_text=_('restream_transcodingprofile_help'))
class Meta: class Meta:
verbose_name = _('restream_verbose_name') verbose_name = _('restream_verbose_name')
@ -176,7 +241,53 @@ class Restream(models.Model):
'stream_source_url': f"{rtmp_base}/{settings.GLOBAL_STREAM_NAMESPACE}/{self.stream.stream}", 'stream_source_url': f"{rtmp_base}/{settings.GLOBAL_STREAM_NAMESPACE}/{self.stream.stream}",
'stream_target_url': self.target, 'stream_target_url': self.target,
'stream_target_transport': self.format, 'stream_target_transport': self.format,
'transcoding_profile': self.transcodingprofile.get_concierge_configuration(),
} }
pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Stream)
pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Restream) pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Restream)
pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Pull)
### Recording Storage configurations
class RecordingStorage(models.Model):
name = models.CharField(max_length=100, help_text=_('recordingstorage_name_help'))
class Meta:
abstract = True
class LocalRecordingStorage(RecordingStorage):
path = models.CharField(max_length=500, help_text=_('localrecordingstorage_path_help'))
class Meta:
verbose_name = _('localrecordingstorage_verbose_name')
verbose_name_plural = _('localrecordingstorage_verbose_name_plural')
def __str__(self):
return self.name
class S3RecordingStorage(RecordingStorage):
bucket = models.CharField(max_length=100, help_text=_('s3recordingstorage_bucket_help'))
access_key = models.CharField(max_length=100, help_text=_('s3recordingstorage_access_key_help'))
secret_key = models.CharField(max_length=100, help_text=_('s3recordingstorage_secret_key_help'))
region = models.CharField(max_length=100, help_text=_('s3recordingstorage_region_help'))
class Meta:
verbose_name = _('s3recordingstorage_verbose_name')
verbose_name_plural = _('s3recordingstorage_verbose_name_plural')
def __str__(self):
return self.name
class Recorder(models.Model):
storage_type_models = models.Q(app_label = 'config', model = 'LocalRecordingStorage') | models.Q(app_label = 'app', model = 'S3RecordingStorage')
stream = models.ForeignKey(Stream, on_delete=models.CASCADE, help_text=_('recorder_stream_help'))
name = models.CharField(max_length=100, help_text=_('recorder_name_help'))
active = models.BooleanField(help_text=_('recorder_activate_help'))
storage_type = models.ForeignKey(ContentType, limit_choices_to=storage_type_models, on_delete=models.CASCADE)
storage_config_id = models.PositiveIntegerField()
storage_config = GenericForeignKey('storage_type', 'storage_config_id')

View File

@ -5,7 +5,8 @@ from django.conf import settings
PERMISSIONS = [ PERMISSIONS = [
'add_stream', 'add_stream',
'add_restream' 'add_restream',
'add_pull',
] ]

View File

@ -2,7 +2,10 @@ from ninja import NinjaAPI, ModelSchema, Router
from ninja.security import django_auth from ninja.security import django_auth
from django.contrib.auth.models import User from django.contrib.auth.models import User
from config.api import router as config_router import config.api as config_api
#from config.api import router as config_router
#from config.api.recorder import router as config_recorder_router
#from config.api.recorder import router as config_recorder_router
from concierge.api import router as concierge_router from concierge.api import router as concierge_router
core_router = Router() core_router = Router()
@ -25,6 +28,9 @@ api = NinjaAPI(
csrf=False, # Disable CSRF for now csrf=False, # Disable CSRF for now
) )
api.add_router("/", core_router, auth=django_auth, tags=["Core"]) api.add_router("/", core_router, auth=django_auth, tags=["Core API"])
api.add_router("/config/", config_router, auth=django_auth, tags=["Configuration"]) api.add_router("/config/recorder/", config_api.recorder.router, auth=django_auth, tags=["Recorder Configuration API"])
api.add_router("/concierge/", concierge_router, auth=None, tags=["Concierge"]) api.add_router("/config/pull/", config_api.pull.router, auth=django_auth, tags=["Pull Configuration API"])
api.add_router("/config/stream/", config_api.stream.router, auth=django_auth, tags=["Stream Configuration API"])
api.add_router("/config/restream/", config_api.restream.router, auth=django_auth, tags=["Resteam Configuration API"])
api.add_router("/concierge/", concierge_router, auth=None, tags=["Concierge API"])