Compare commits

...

3 Commits

Author SHA1 Message Date
Jan Koppe acdcad4256
add a bunch of shit that i changed a while back 2024-11-07 16:19:22 +01:00
Jan Koppe 6005ce6170
fix API paths 2024-05-04 14:06:46 +02:00
Jan Koppe 0a857a194c
wip 2024-04-22 14:25:15 +02:00
28 changed files with 1635 additions and 629 deletions

View File

@ -1,4 +1,4 @@
version: '2.4' version: "2.4"
services: services:
app: app:

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, transcodingprofile

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())
@ -171,9 +185,9 @@ 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
@ -214,3 +228,44 @@ def delete_pull(request, id: int):
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('/', response=List[Pull])
def list_pulls(request):
return get_objects_for_user(request.user, 'view_pull', models.Pull.objects.all())
@router.post('/', 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('/{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('/{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('/{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('/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('/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('/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('/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('/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('/', response=List[Restream])
def list_restreams(request):
return get_objects_for_user(request.user, 'view_restream', models.Restream.objects.all())
@router.post('/', 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('/{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('/{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('/{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('/', response=List[Stream])
def list_streams(request):
return get_objects_for_user(request.user, 'view_stream', models.Stream.objects.all())
@router.post('/', 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('/{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('/{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('/{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,63 @@
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 TranscodingProfile(ModelSchema):
class Meta:
model = models.TranscodingProfile
fields = "__all__"
class TranscodingProfilePatch(ModelSchema):
class Meta:
model = models.TranscodingProfile
exclude = ["id"]
fields_optional = "__all__"
extra = "forbid"
@router.get('/', response=List[TranscodingProfile])
def list_transcodingprofile(request):
return get_objects_for_user(request.user, 'view_transcodingprofile', models.TranscodingProfile.objects.all())
@router.get('/{id}', response=TranscodingProfile)
def get_transcodingprofile(request, id: int):
obj = get_object_or_404(models.TranscodingProfile, id=id)
if not request.user.has_perm('view_transcodingprofile', obj):
raise HttpError(401, "unauthorized")
return obj
@router.post('/', response=TranscodingProfile)
def create_transcodingprofile(request, payload: TranscodingProfilePatch):
obj = models.TranscodingProfile.objects.create(**payload.dict())
assign_perm( 'view_transcodingprofile', request.user, obj)
assign_perm('change_transcodingprofile', request.user, obj)
assign_perm('delete_transcodingprofile', request.user, obj)
return obj
@router.patch('/{id}', response=TranscodingProfile)
def patch_transcodingprofile(request, id: int, payload: TranscodingProfilePatch):
obj = get_object_or_404(models.TranscodingProfile, id=id)
if not request.user.has_perm('change_transcodingprofile', 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('/{id}', response=None)
def delete_transcodingprofile(request, id: int):
obj = get_object_or_404(models.TranscodingProfile, id=id)
if not request.user.has_perm('delete_transcodingprofile', obj):
raise HttpError(401, "unauthorized")
obj.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

@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% load fontawesome_5 %}
{% block 'sidenav' %}
{% with 'transcodingprofile' as section %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block 'content' %}
<h6>{% trans "confirm_delete_header" %}</h6>
<hr class="my-4" />
<div class="row">
<div class="col-sm border-right">
<form method="post">
{% csrf_token %}
<p>
{% blocktrans with transcodingprofile_config_name=object.name %}
are_you_sure_you_want_to_delete_"{{ transcodingprofile_config_name }}"?
{% endblocktrans %}
</p>
{% buttons %}
<button type="submit" class="btn btn-danger" value="login">
{% fa5_icon 'trash' %} {% trans "delete" %}
</button>
{% endbuttons %}
<input type="hidden" name="next" value="{{ next }}" />
</form>
</div>
<div class="col-sm"></div>
</div>
{% endblock %}

View File

@ -0,0 +1,121 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% load fontawesome_5 %}
{% load guardian_tags %}
{% block 'sidenav' %}
{% with 'transcodingprofile' as section %} {{ block.super }} {% endwith %}
{% endblock %}
{% block 'content' %}
<div class="row justify-content-between">
<div class="col">
<h6>{% trans "transcodingprofile_configuration_details_header" %}</h6>
</div>
{% get_obj_perms user for object as "obj_perms" %}
<div class="col-auto">
{% if "change_transcodingprofile" in obj_perms %}
<a
href="{% url 'config:transcodingprofile_change' pk=object.pk %}"
type="button"
class="btn btn-sm btn-outline-primary"
>{% fa5_icon 'edit' %} {% trans 'change' %}</a
>
{% endif %} {% if "delete_transcodingprofile" in obj_perms %}
<a
href="{% url 'config:transcodingprofile_delete' pk=object.pk %}"
type="button"
class="btn btn-sm btn-outline-danger"
>{% fa5_icon 'trash' %} {% trans 'delete' %}</a
>
{% endif %}
</div>
</div>
<hr class="my-4" />
<div class="row">
<div class="col-sm border-right">
<h5>Video</h5>
<dl class="row">
<dt class="col-sm-3">
{% trans "transcodingprofile_video_map_stream" %}
</dt>
<dd class="col-sm-9">{{ object.video_map_stream }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_codec" %}
</dt>
<dd class="col-sm-9">{{ object.get_video_codec_display }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_bitrate" %}
</dt>
<dd class="col-sm-9">{{ object.video_bitrate }} kbps</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_bitrate_mode" %}
</dt>
<dd class="col-sm-9">{{ object.get_video_bitrate_mode_display }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_gop_size" %}
</dt>
<dd class="col-sm-9">{{ object.video_gop_size }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_pixel_format" %}
</dt>
<dd class="col-sm-9">{{ object.get_video_pixel_format_display }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_resolution" %}
</dt>
<dd class="col-sm-9">{{ object.video_resolution }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_video_frame_rate" %}
</dt>
<dd class="col-sm-9">{{ object.video_frame_rate }}</dd>
</dl>
</div>
<div class="col-sm">
<h5>Audio</h5>
<dl class="row">
<dt class="col-sm-3">
{% trans "transcodingprofile_audio_map_stream" %}
</dt>
<dd class="col-sm-9">{{ object.audio_map_stream }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_audio_codec" %}
</dt>
<dd class="col-sm-9">{{ object.get_audio_codec_display }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_audio_bitrate" %}
</dt>
<dd class="col-sm-9">{{ object.audio_bitrate }} kbps</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_audio_channels" %}
</dt>
<dd class="col-sm-9">{{ object.audio_channels }}</dd>
<dt class="col-sm-3">
{% trans "transcodingprofile_audio_sample_rate" %}
</dt>
<dd class="col-sm-9">{{ object.audio_sample_rate }}</dd>
</dl>
</div>
</div>
<script>
$(function () {
$('[data-toggle="popover"]').popover();
});
$(function () {
$("#show_hide_source_url a").on("click", function (event) {
event.preventDefault();
if ($("#show_hide_source_url input").attr("type") == "text") {
$("#show_hide_source_url input").attr("type", "password");
$("#show_hide_source_url i").addClass("fa-eye-slash");
$("#show_hide_source_url i").removeClass("fa-eye");
} else if (
$("#show_hide_source_url input").attr("type") == "password"
) {
$("#show_hide_source_url input").attr("type", "text");
$("#show_hide_source_url i").removeClass("fa-eye-slash");
$("#show_hide_source_url i").addClass("fa-eye");
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% load fontawesome_5 %}
{% block 'sidenav' %}
{% with 'transcodingprofile' as section %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block 'content' %}
<div class="row justify-content-between">
<div class="col">
<h6>{% trans "transcodingprofile_configuration_header" %}</h6>
</div>
<div class="col-auto">
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<a
href="{% url 'config:transcodingprofile_create' %}"
type="button"
class="btn btn-sm btn-outline-primary"
>{% fa5_icon 'plus' %} {% trans "create" %}</a
>
</div>
</div>
</div>
</div>
<hr class="my-4" />
<div id="app">
<div v-if="isLoading">
<div class="my-4 text-center">
<div class="spinner-border" role="status">
<span class="sr-only">{% trans "loading..." %}</span>
</div>
</div>
</div>
<table v-else class="table">
<thead class="thead-light">
<tr>
<th scope="col">{% trans "name" %}</th>
<th scope="col" class="text-right">{% trans "actions" %}</th>
</tr>
</thead>
<tbody>
<tr v-for="cfg in cfgs">
<th scope="row">{% verbatim %}{{cfg.name}}{% endverbatim %}</th>
<td align="right">
<a
v-bind:href="detailLink(cfg.id)"
type="button"
class="btn btn-sm btn-outline-primary"
>{% trans "details" %}</a
>
<a
v-bind:href="deleteLink(cfg.id)"
type="button"
class="btn btn-sm btn-outline-danger"
>{% fa5_icon 'trash' %}</a
>
</td>
</tr>
</tbody>
</table>
</div>
<script src="{% static 'js/vue.min.js' %}"></script>
<script src="{% static 'js/axios.min.js' %}"></script>
<script src="{% static 'js/transcodingprofile-list.js' %}"></script>
{% endblock %}

View File

@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% load i18n %}
{% load bootstrap4 %}
{% block 'sidenav' %}
{% with 'transcodingprofile' as section %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block 'content' %}
<h6>{% trans "update_transcodingprofile_configuration_header" %}</h6>
<hr class="my-4">
<div class="row">
<div class="col">
{% trans "transcodingprofile_configuration_text_html" %}
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary" value="login">
{% trans "submit" %}
</button>
{% endbuttons %}
<input type="hidden" name="next" value="{{ next }}">
</form>
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,5 @@
from django.urls import path from django.urls import path
from config.views import restream, stream, pull from config.views import restream, stream, pull, transcodingprofile
from config.views.srs import callback_srs from config.views.srs import callback_srs
app_name = 'config' app_name = 'config'
@ -21,4 +21,9 @@ urlpatterns = [
path('pull/<int:pk>/change', pull.PullUpdate.as_view(), name='pull_change'), path('pull/<int:pk>/change', pull.PullUpdate.as_view(), name='pull_change'),
path('pull/<int:pk>/delete', pull.PullDelete.as_view(), name='pull_delete'), path('pull/<int:pk>/delete', pull.PullDelete.as_view(), name='pull_delete'),
path('pull/create', pull.PullCreate.as_view(), name='pull_create'), path('pull/create', pull.PullCreate.as_view(), name='pull_create'),
path('transcodingprofile/', transcodingprofile.TranscodingProfileList.as_view(), name='transcodingprofile_list'),
path('transcodingprofile/<int:pk>/', transcodingprofile.TranscodingProfileDetail.as_view(), name='transcodingprofile_detail'),
path('transcodingprofile/<int:pk>/change', transcodingprofile.TranscodingProfileUpdate.as_view(), name='transcodingprofile_change'),
path('transcodingprofile/<int:pk>/delete', transcodingprofile.TranscodingProfileDelete.as_view(), name='transcodingprofile_delete'),
path('transcodingprofile/create', transcodingprofile.TranscodingProfileCreate.as_view(), name='transcodingprofile_create'),
] ]

View File

@ -0,0 +1,86 @@
import logging
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
from guardian.decorators import permission_required_or_403
from guardian.shortcuts import assign_perm
from config import models, forms
logger = logging.getLogger(__name__)
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.add_restream'),
name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class TranscodingProfileList(ListView):
model = models.TranscodingProfile
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.view_transcodingprofile',
(models.TranscodingProfile, 'pk', 'pk')),
name='dispatch')
class TranscodingProfileDetail(DetailView):
model = models.TranscodingProfile
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.change_transcodingprofile',
(models.TranscodingProfile, 'pk', 'pk')),
name='dispatch')
class TranscodingProfileUpdate(UpdateView):
model = models.TranscodingProfile
template_name_suffix = '_update_form'
fields = [
'name',
'video_map_stream',
'video_codec',
'video_bitrate',
'video_frame_rate',
'video_resolution',
'video_gop_size',
'video_pixel_format',
'audio_map_stream',
'audio_codec',
'audio_bitrate',
'audio_sample_rate',
'audio_channels',
]
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.add_transcodingprofile'),
name='dispatch')
class TranscodingProfileCreate(CreateView):
model = models.TranscodingProfile
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
valid = super().form_valid(form)
if valid:
user = self.request.user
assign_perm('view_transcodingprofile', user, self.object)
assign_perm('change_transcodingprofile', user, self.object)
assign_perm('delete_transcodingprofile', user, self.object)
return valid
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.delete_transcodingprofile',
(models.TranscodingProfile, 'pk', 'pk')),
name='dispatch')
class TranscodingProfileDelete(DeleteView):
model = models.TranscodingProfile
success_url = reverse_lazy('config:transcodingprofile_list')

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

@ -1,339 +0,0 @@
# Copyright (C) Chaoswest TV
# This file is distributed under the same license as the PACKAGE package.
# Jan Koppe <post@jankoppe.de>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: portier 0.6.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-25 20:42+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Jan Koppe <post@jankoppe.de>\n"
"Language-Team: German <post@chaoswest.tv>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: portier/settings.py:141
msgid "German"
msgstr "Deutsch"
#: portier/settings.py:142
msgid "English"
msgstr "Englisch"
#: restream/models.py:16
msgid "restreamconfig_stream_help"
msgstr "Stream der als Quelle für die Weiterleitung verwendet wird"
#: restream/models.py:17
msgid "restreamconfig_target_help"
msgstr ""
"Ziel URL an die der Stream weitergeleitet wird, inklusive Schema (z.B. "
"rtmp://)"
#: restream/models.py:18
msgid "restreamconfig_name_help"
msgstr "Name für diese Restream Konfiguration"
#: restream/models.py:19
msgid "restreamconfig_activate_help"
msgstr "Nur aktive Konfigurationen werden bei eingehenden Streams ausgeführt"
#: restream/models.py:20
#, fuzzy
#| msgid "restreamconfig_target_help"
msgid "restreamconfig_format_help"
msgstr ""
"Ziel URL an die der Stream weitergeleitet wird, inklusive Schema (z.B. "
"rtmp://)"
#: restream/models.py:23
msgid "restreamconfig_verbose_name"
msgstr "Restream Konfiguration"
#: restream/models.py:24
msgid "restreamconfig_verbose_name_plural"
msgstr "Restream Konfigurationen"
#: restream/models.py:27
msgid "restreamconfig_class_name"
msgstr "Restream Konfiguration"
#: restream/templates/restream/restreamconfig_confirm_delete.html:6
#: rtmp/templates/rtmp/stream_confirm_delete.html:6
msgid "confirm_delete_header"
msgstr "Löschen bestätigen"
#: restream/templates/restream/restreamconfig_confirm_delete.html:12
#, python-format
msgid "are_you_sure_you_want_to_delete_\"%(restreamconfig_config_name)s\"?"
msgstr ""
"Bist du sicher, dass du \"%(restreamconfig_config_name)s\" löschen willst?"
#: restream/templates/restream/restreamconfig_confirm_delete.html:15
#: restream/templates/restream/restreamconfig_detail.html:20
#: rtmp/templates/rtmp/stream_confirm_delete.html:18
#: rtmp/templates/rtmp/stream_detail.html:20
msgid "delete"
msgstr "Löschen"
#: restream/templates/restream/restreamconfig_detail.html:12
msgid "restreamconfig_configuration_details_header"
msgstr "Restream Konfiguration Details"
#: restream/templates/restream/restreamconfig_detail.html:17
#: rtmp/templates/rtmp/stream_detail.html:17
msgid "change"
msgstr "Ändern"
#: restream/templates/restream/restreamconfig_detail.html:28
#: restream/templates/restream/restreamconfig_list.html:33
#: rtmp/templates/rtmp/stream_detail.html:28
#: rtmp/templates/rtmp/stream_list.html:33
msgid "name"
msgstr "Name"
#: restream/templates/restream/restreamconfig_detail.html:30
msgid "stream"
msgstr "Stream"
#: restream/templates/restream/restreamconfig_detail.html:32
#: restream/templates/restream/restreamconfig_list.html:34
msgid "active"
msgstr "Aktiv"
#: restream/templates/restream/restreamconfig_detail.html:43
msgid "configured_target_url"
msgstr "Konfigurierte Ziel-URL"
#: restream/templates/restream/restreamconfig_form.html:5
msgid "create_new_restreamconfig_configuration_header"
msgstr "Neue Restream Konfiguration erstellen"
#: restream/templates/restream/restreamconfig_form.html:14
#: restream/templates/restream/restreamconfig_update_form.html:14
#: rtmp/templates/rtmp/stream_form.html:14
#: rtmp/templates/rtmp/stream_update_form.html:14
#: templates/registration/password_change_form.html:14
#: templates/registration/password_reset_form.html:14
msgid "submit"
msgstr "Abschicken"
#: restream/templates/restream/restreamconfig_form.html:21
#: restream/templates/restream/restreamconfig_update_form.html:21
msgid "restreamconfig_configuration_text_html"
msgstr ""
"<p>Eine Restream Konfiguration erlaubt es dir eingehende Streams in das "
"System unverändert an externe System weiterzuleiten.</p><p>Die Restream "
"Konfiguration benötigt einen vorher konfigurierten Quell-Stream und ein "
"Ziel, dass du über eine RTMP URL frei definieren kannst. Die RTMP URL sollte "
"zum Beispiel so ausschauen: <code>rtmp://servername/app/streamkey</code></"
"p><p>Du kannst natürlich mehrere Restream Konfigurationen auf einen "
"einzelnen Quell-Stream einrichten. Das erlaubt es dir von deinem Encoder nur "
"einmal einen Stream zu senden, diesen aber automatisch an mehrere Platformen "
"weiterzuleiten.</p><p>Nur Restream Konfigurationen die auf Aktiv geschaltet "
"sind werden bei einem neu eingehenden Stream ausgeführt!</p>"
#: restream/templates/restream/restreamconfig_list.html:11
msgid "restreamconfig_configuration_header"
msgstr "Restream Konfiguration"
#: restream/templates/restream/restreamconfig_list.html:16
#: rtmp/templates/rtmp/stream_list.html:16
msgid "create"
msgstr "Erstellen"
#: restream/templates/restream/restreamconfig_list.html:26
#: rtmp/templates/rtmp/stream_list.html:26
msgid "loading..."
msgstr ""
#: restream/templates/restream/restreamconfig_list.html:35
#: rtmp/templates/rtmp/stream_list.html:35
msgid "actions"
msgstr "Aktionen"
#: restream/templates/restream/restreamconfig_list.html:52
#: rtmp/templates/rtmp/stream_list.html:46
msgid "details"
msgstr "Details"
#: restream/templates/restream/restreamconfig_update_form.html:5
msgid "update_restreamconfig_configuration_header"
msgstr "Restream Konfiguration anpassen"
#: rtmp/models.py:13
msgid "rtmp_application_name"
msgstr "RTMP Application Name"
#: rtmp/models.py:16
msgid "application_verbose_name"
msgstr "Application"
#: rtmp/models.py:17
msgid "application_verbose_name_plural"
msgstr "Applications"
#: rtmp/models.py:20
msgid "aplication_class_name"
msgstr "Application"
#: rtmp/models.py:27
msgid "stream_application_help"
msgstr "Unter welcher RTMP Application gilt diese Stream ID"
#: rtmp/models.py:28
msgid "stream_stream_help"
msgstr "RTMP Stream ID"
#: rtmp/models.py:29
msgid "stream_name_help"
msgstr "Name für diesen Stream"
#: rtmp/models.py:68
msgid "stream_class_name"
msgstr "Stream"
#: rtmp/templates/rtmp/stream_confirm_delete.html:13
msgid "deleting_stream_configuration_will_also_delete_all_depending_confgurations_warning"
msgstr ""
"Achtung! Beim Löschen dieser Stream Konfiguration werden auch alle "
"abhängigen Konfigurationen gelöscht."
#: rtmp/templates/rtmp/stream_confirm_delete.html:15
#, python-format
msgid "are_you_sure_you_want_to_delete_\"%(stream_config_name)s\"?"
msgstr "Willst du wirklich \"%(stream_config_name)s\" löschen?"
#: rtmp/templates/rtmp/stream_confirm_delete.html:25
msgid "deleting_configurations_list_header"
msgstr "Diese Konfigurationen werden gelöscht"
#: rtmp/templates/rtmp/stream_detail.html:12
msgid "stream_configuration_details_header"
msgstr "Stream Konfiguration Details"
#: rtmp/templates/rtmp/stream_detail.html:30
msgid "application"
msgstr "Application"
#: rtmp/templates/rtmp/stream_detail.html:33
msgid "how_to_configure_your_encoder_header"
msgstr "Wie du deinen Encoder konfigurierst"
#: rtmp/templates/rtmp/stream_detail.html:35
msgid "set_this_stream_server_in_encoder"
msgstr "Stelle diesen Stream Server in deinem Encoder ein"
#: rtmp/templates/rtmp/stream_detail.html:37
msgid "set_this_stream_id_in_encoder"
msgstr "Stelle diese Stream ID in deinem Encoder ein"
#: rtmp/templates/rtmp/stream_detail.html:46
#: rtmp/templates/rtmp/stream_form.html:21
#: rtmp/templates/rtmp/stream_update_form.html:21
msgid "stream_configuration_text_html"
msgstr ""
"<p>Eine Stream Konfiguration erlaubt es dir einen Stream in das System zu "
"schicken</p><p>Nur Streams die zu einer vorher erstellten Konfiguration "
"zugeordnet werden können werden vom System angenommen.</p><p>Der Name dient "
"dazu den Stream beim Erstellen von weiteren Konfigurationen leicht zu "
"identifizieren. Die tatsächliche Stream ID wird dir nach dem erstellen "
"angezeigt. <strong>Halte die Stream ID auf jeden Fall geheim.</strong></p>"
#: rtmp/templates/rtmp/stream_form.html:5
msgid "create_new_stream_configuration_header"
msgstr "Neue Stream Konfiguration erstellen"
#: rtmp/templates/rtmp/stream_list.html:11
msgid "stream_configuration_header"
msgstr "Stream Konfiguration"
#: rtmp/templates/rtmp/stream_list.html:34
msgid "receiving"
msgstr "Empfange"
#: rtmp/templates/rtmp/stream_update_form.html:5
msgid "update_stream_configuration_header"
msgstr "Stream Konfiguration anpassen"
#: templates/base.html:39
#, python-format
msgid "hello_%(username)s"
msgstr "Hallo, %(username)s!"
#: templates/base.html:42
msgid "navbar_account_password_change"
msgstr "Passwort ändern"
#: templates/base.html:43
msgid "navbar_account_logout"
msgstr "Abmelden"
#: templates/base.html:47
msgid "navbar_login"
msgstr "Anmelden"
#: templates/base.html:80
msgid "navbar_configuration_pull"
msgstr "Pull"
#: templates/base.html:83
msgid "navbar_configuration_stream"
msgstr "Stream"
#: templates/base.html:86
msgid "navbar_configuration_restream"
msgstr "Restream"
#: templates/base.html:89
msgid "navbar_configuration_publish"
msgstr "Publish"
#: templates/base.html:92
msgid "navbar_configuration_record"
msgstr "Record"
#: templates/base.html:95
msgid "navbar_configuration_switch"
msgstr "Switch"
#: templates/registration/login.html:5 templates/registration/login.html:14
msgid "login"
msgstr "Anmelden"
#: templates/registration/login.html:19
msgid "forgot_password_q"
msgstr "Passwort vergessen?"
#: templates/registration/password_change_done.html:5
msgid "password_change_successful"
msgstr "Passwort erfolgreich geändert"
#: templates/registration/password_change_done.html:7
msgid "password_change_successful_text"
msgstr ""
"Das Passwort wurde erfolgreich geändert. Du kannst dich ab jetzt mit dem "
"neuen Passwort anmelden."
#: templates/registration/password_change_form.html:5
msgid "change_password"
msgstr "Passwort ändern"
#: templates/registration/password_reset_form.html:5
msgid "reset_password"
msgstr "Passwort vergessen"
#: templates/registration/password_reset_form.html:21
msgid "reset_password_text_html"
msgstr ""
"Du hast dein Passwort vergessen? Kein Problem. Gib hier die E-mail Adresse "
"deines Nutzerkonto an, und wir schicken dir einen Link zu mit dem du ein "
"neues Passwort setzen kannst."
#~ msgid "navbar_streaming"
#~ msgstr "Streaming"

View File

@ -1,272 +1,581 @@
# Copyright (C) Chaoswest TV # SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# Jan Koppe <post@jankoppe.de>, 2020. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: portier 0.6.0\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-25 20:42+0000\n" "POT-Creation-Date: 2024-05-10 14:40+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Jan Koppe <post@jankoppe.de>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: english <post@chaoswest.tv>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: portier/settings.py:141 #: config/models.py:35
msgid "German" msgid "transcodingprofile_name_help"
msgstr "German" msgstr "Name for the transcoding profile"
#: portier/settings.py:142 #: config/models.py:37
msgid "English" msgid "transcodingprofile_video_map_stream_help"
msgstr "English"
#: restream/models.py:16
msgid "restreamconfig_stream_help"
msgstr "Stream that's being used as source for the restreaming"
#: restream/models.py:17
msgid "restreamconfig_target_help"
msgstr "" msgstr ""
"Target url that the stream is being restreamed to, including schema (e.g. " "Index of the Video stream to map into the output. Can be used to select a "
"rtmp://)" "specific video stream when the source contains multiple video streams."
#: restream/models.py:18 #: config/models.py:38
msgid "restreamconfig_name_help" msgid "transcodingprofile_video_codec_help"
msgstr "Name for this restream configuration"
#: restream/models.py:19
msgid "restreamconfig_activate_help"
msgstr "Only active configurations will be executed for incoming streams"
#: restream/models.py:20
#, fuzzy
#| msgid "restreamconfig_target_help"
msgid "restreamconfig_format_help"
msgstr "" msgstr ""
"Target url that the stream is being restreamed to, including schema (e.g. " "Video Codec to use for transcoding. COPY will copy the video stream without "
"rtmp://)" "transcoding."
#: restream/models.py:23 #: config/models.py:39
msgid "restreamconfig_verbose_name" msgid "transcodingprofile_video_bitrate_help"
msgstr "Restream configuration" msgstr ""
"Bitrate for the video stream in kbit/s. If the video codec is set to COPY, "
"this value is ignored."
#: restream/models.py:24 #: config/models.py:40
msgid "restreamconfig_verbose_name_plural" msgid "transcodingprofile_video_frame_rate_help"
msgstr "Restream configurations" msgstr ""
"Frame rate for the video stream. If the video codec is set to COPY, this "
"value is ignored."
#: restream/models.py:27 #: config/models.py:41
msgid "restreamconfig_class_name" msgid "transcodingprofile_video_resolution_help"
msgstr "Restream configuration" msgstr ""
"Scaling for the video stream. If the video codec is set to COPY, this value "
"is ignored. Use 1920:-2 to scale the video to 1080p while keeping the aspect "
"ratio."
#: restream/templates/restream/restreamconfig_confirm_delete.html:6 #: config/models.py:42
#: rtmp/templates/rtmp/stream_confirm_delete.html:6 msgid "transcodingprofile_video_gop_size_help"
msgstr ""
"Group of Pictures size for the video stream. If the video codec is set to "
"COPY, this value is ignored. A good value is 2 times the frame rate."
#: config/models.py:43
msgid "transcodingprofile_video_pixel_format_help"
msgstr ""
"Pixel format for the video stream. If the video codec is set to COPY, this "
"value is ignored. Use yuv420p for compatibility with most devices."
#: config/models.py:44
msgid "transcodingprofile_video_bitrate_mode_help"
msgstr ""
"Video bitrate mode. CBR will use a constant bitrate, VBR will use a variable "
"bitrate. For streaming, usually CBR is preferred as it keeps a consistent "
"network usage, but might not be as efficient as VBR."
#: config/models.py:46
msgid "transcodingprofile_audio_map_stream_help"
msgstr ""
"Index of the Audio stream to map into the output. Can be used to select a "
"specific audio stream when the source contains multiple audio streams."
#: config/models.py:47
msgid "transcodingprofile_audio_codec_help"
msgstr ""
"Audio Codec to use for transcoding. COPY will copy the audio stream without "
"transcoding."
#: config/models.py:48
msgid "transcodingprofile_audio_bitrate_help"
msgstr ""
"Bitrate for the audio stream in kbit/s. If the audio codec is set to COPY, "
"this value is ignored."
#: config/models.py:49
msgid "transcodingprofile_audio_channels_help"
msgstr ""
"Audio channels for the audio stream. If the audio codec is set to COPY, this "
"value is ignored. Use 2 for stereo audio."
#: config/models.py:50
msgid "transcodingprofile_audio_sample_rate_help"
msgstr ""
"Audio sample rate for the audio stream. If the audio codec is set to COPY, "
"this value is ignored. Use 48000 for compatibility with most devices."
#: config/models.py:53
msgid "transcodingprofile_verbose_name"
msgstr "Transcoding Profile"
#: config/models.py:54
msgid "transcodingprofile_verbose_name_plural"
msgstr "Transcoding Profiles"
#: config/models.py:57
msgid "transcodingprofile_class_name"
msgstr "TranscodingProfile"
#: config/models.py:82
msgid "stream_stream_help"
msgstr ""
#: config/models.py:83
msgid "stream_name_help"
msgstr ""
#: config/models.py:109
msgid "stream_class_name"
msgstr ""
#: config/models.py:116
msgid "srsnode_name_help"
msgstr ""
#: config/models.py:117
msgid "srsnode_api_base_help"
msgstr ""
#: config/models.py:118
msgid "srsnode_rtmp_base_help"
msgstr ""
#: config/models.py:119
msgid "srsnode_active_help"
msgstr ""
#: config/models.py:122
msgid "srsnode_verbose_name"
msgstr ""
#: config/models.py:123
msgid "srsnode_verbose_name_plural"
msgstr ""
#: config/models.py:130
msgid "srsstreaminstance_node_help"
msgstr ""
#: config/models.py:131
msgid "srsstreaminstance_stream_help"
msgstr ""
#: config/models.py:132
msgid "srsstreaminstance_last_update_help"
msgstr ""
#: config/models.py:133
msgid "srsstreaminstance_statusdata_help"
msgstr ""
#: config/models.py:136
msgid "srsstreaminstance_verbose_name"
msgstr ""
#: config/models.py:137
msgid "srsstreaminstance_verbose_name_plural"
msgstr ""
#: config/models.py:144
msgid "pull_stream_help"
msgstr ""
#: config/models.py:145
msgid "pull_source_help"
msgstr ""
#: config/models.py:146
msgid "pull_activate_help"
msgstr ""
#: config/models.py:147
msgid "pull_name_help"
msgstr ""
#: config/models.py:148 config/models.py:204
msgid "restream_transcodingprofile_help"
msgstr ""
#: config/models.py:151
msgid "pull_verbose_name"
msgstr ""
#: config/models.py:152
msgid "pull_verbose_name_plural"
msgstr ""
#: config/models.py:155
msgid "pull_class_name"
msgstr ""
#: config/models.py:199
msgid "restream_stream_help"
msgstr ""
#: config/models.py:200
msgid "restream_target_help"
msgstr ""
#: config/models.py:201
msgid "restream_name_help"
msgstr ""
#: config/models.py:202
msgid "restream_activate_help"
msgstr ""
#: config/models.py:203
msgid "restream_format_help"
msgstr ""
#: config/models.py:207
msgid "restream_verbose_name"
msgstr ""
#: config/models.py:208
msgid "restream_verbose_name_plural"
msgstr ""
#: config/models.py:211
msgid "restream_class_name"
msgstr ""
#: config/models.py:255
msgid "recordingstorage_name_help"
msgstr ""
#: config/models.py:262
msgid "localrecordingstorage_path_help"
msgstr ""
#: config/models.py:265
msgid "localrecordingstorage_verbose_name"
msgstr ""
#: config/models.py:266
msgid "localrecordingstorage_verbose_name_plural"
msgstr ""
#: config/models.py:272
msgid "s3recordingstorage_bucket_help"
msgstr ""
#: config/models.py:273
msgid "s3recordingstorage_access_key_help"
msgstr ""
#: config/models.py:274
msgid "s3recordingstorage_secret_key_help"
msgstr ""
#: config/models.py:275
msgid "s3recordingstorage_region_help"
msgstr ""
#: config/models.py:278
msgid "s3recordingstorage_verbose_name"
msgstr ""
#: config/models.py:279
msgid "s3recordingstorage_verbose_name_plural"
msgstr ""
#: config/models.py:288
msgid "recorder_stream_help"
msgstr ""
#: config/models.py:289
msgid "recorder_name_help"
msgstr ""
#: config/models.py:290
msgid "recorder_activate_help"
msgstr ""
#: config/templates/config/pull_confirm_delete.html:13
#: config/templates/config/restream_confirm_delete.html:13
#: config/templates/config/stream_confirm_delete.html:13
#: config/templates/config/transcodingprofile_confirm_delete.html:14
msgid "confirm_delete_header" msgid "confirm_delete_header"
msgstr "Confirm deletion" msgstr ""
#: restream/templates/restream/restreamconfig_confirm_delete.html:12 #: config/templates/config/pull_confirm_delete.html:19
#, python-format #, python-format
msgid "are_you_sure_you_want_to_delete_\"%(restreamconfig_config_name)s\"?" msgid "are_you_sure_you_want_to_delete_\"%(pull_config_name)s\"?"
msgstr "Are you sure you want to delete \"%(restreamconfig_config_name)s\"?" msgstr ""
#: restream/templates/restream/restreamconfig_confirm_delete.html:15 #: config/templates/config/pull_confirm_delete.html:22
#: restream/templates/restream/restreamconfig_detail.html:20 #: config/templates/config/pull_detail.html:27
#: rtmp/templates/rtmp/stream_confirm_delete.html:18 #: config/templates/config/restream_confirm_delete.html:22
#: rtmp/templates/rtmp/stream_detail.html:20 #: config/templates/config/restream_detail.html:27
#: config/templates/config/stream_confirm_delete.html:25
#: config/templates/config/stream_detail.html:27
#: config/templates/config/transcodingprofile_confirm_delete.html:27
#: config/templates/config/transcodingprofile_detail.html:34
msgid "delete" msgid "delete"
msgstr "Delete" msgstr ""
#: restream/templates/restream/restreamconfig_detail.html:12 #: config/templates/config/pull_detail.html:19
msgid "restreamconfig_configuration_details_header" msgid "pull_configuration_details_header"
msgstr "Restream configuration details" msgstr ""
#: restream/templates/restream/restreamconfig_detail.html:17 #: config/templates/config/pull_detail.html:24
#: rtmp/templates/rtmp/stream_detail.html:17 #: config/templates/config/restream_detail.html:24
#: config/templates/config/stream_detail.html:24
#: config/templates/config/transcodingprofile_detail.html:27
msgid "change" msgid "change"
msgstr "Change" msgstr "Change"
#: restream/templates/restream/restreamconfig_detail.html:28 #: config/templates/config/pull_detail.html:35
#: restream/templates/restream/restreamconfig_list.html:33 #: config/templates/config/pull_list.html:40
#: rtmp/templates/rtmp/stream_detail.html:28 #: config/templates/config/restream_detail.html:35
#: rtmp/templates/rtmp/stream_list.html:33 #: config/templates/config/restream_list.html:40
#: config/templates/config/stream_detail.html:35
#: config/templates/config/stream_list.html:39
#: config/templates/config/transcodingprofile_list.html:43
msgid "name" msgid "name"
msgstr "Name" msgstr "Name"
#: restream/templates/restream/restreamconfig_detail.html:30 #: config/templates/config/pull_detail.html:37
#: config/templates/config/restream_detail.html:37
msgid "stream" msgid "stream"
msgstr "Stream" msgstr "Stream"
#: restream/templates/restream/restreamconfig_detail.html:32 #: config/templates/config/pull_detail.html:39
#: restream/templates/restream/restreamconfig_list.html:34 #: config/templates/config/pull_list.html:41
#: config/templates/config/restream_detail.html:39
#: config/templates/config/restream_list.html:41
msgid "active" msgid "active"
msgstr "Active" msgstr "Active"
#: restream/templates/restream/restreamconfig_detail.html:43 #: config/templates/config/pull_detail.html:50
msgid "configured_target_url" msgid "configured_source_url"
msgstr "Configured target URL" msgstr ""
#: restream/templates/restream/restreamconfig_form.html:5 #: config/templates/config/pull_form.html:12
msgid "create_new_restreamconfig_configuration_header" msgid "create_new_pull_configuration_header"
msgstr "Create new restream configuration" msgstr ""
#: restream/templates/restream/restreamconfig_form.html:14 #: config/templates/config/pull_form.html:21
#: restream/templates/restream/restreamconfig_update_form.html:14 #: config/templates/config/pull_update_form.html:21
#: rtmp/templates/rtmp/stream_form.html:14 #: config/templates/config/restream_form.html:21
#: rtmp/templates/rtmp/stream_update_form.html:14 #: config/templates/config/restream_update_form.html:21
#: config/templates/config/stream_form.html:21
#: config/templates/config/stream_update_form.html:21
#: templates/registration/password_change_form.html:14 #: templates/registration/password_change_form.html:14
#: templates/registration/password_reset_form.html:14 #: templates/registration/password_reset_form.html:14
msgid "submit" msgid "submit"
msgstr "Submit"
#: restream/templates/restream/restreamconfig_form.html:21
#: restream/templates/restream/restreamconfig_update_form.html:21
msgid "restreamconfig_configuration_text_html"
msgstr "" msgstr ""
"<p>A restream configuration allows you to forward incoming streamswithout "
"further processing to external systems.<p><p>The restream configuration "
"needs a previously configured source stream and a target RTMP URL which you "
"can freely chose. The target RTMP URL should look e.g. like this: "
"<code>rtmp://servername/app/streamkey</code></p>You can of course create "
"multiple restream configurations with the same source stream. This allows "
"you to only send one stream from your encoder, but distribute this to "
"multiple platforms.</p><p>Only restream configurations that are marked "
"active will be executed for an incoming stream!</p>"
#: restream/templates/restream/restreamconfig_list.html:11 #: config/templates/config/pull_form.html:28
msgid "restreamconfig_configuration_header" #: config/templates/config/pull_update_form.html:28
msgstr "Restream configuration" msgid "pull_configuration_text_html"
msgstr ""
#: restream/templates/restream/restreamconfig_list.html:16 #: config/templates/config/pull_list.html:18
#: rtmp/templates/rtmp/stream_list.html:16 msgid "pull_configuration_header"
msgstr ""
#: config/templates/config/pull_list.html:23
#: config/templates/config/restream_list.html:23
#: config/templates/config/stream_list.html:22
#: config/templates/config/transcodingprofile_list.html:25
msgid "create" msgid "create"
msgstr "Create" msgstr ""
#: restream/templates/restream/restreamconfig_list.html:26 #: config/templates/config/pull_list.html:33
#: rtmp/templates/rtmp/stream_list.html:26 #: config/templates/config/restream_list.html:33
#: config/templates/config/stream_list.html:32
#: config/templates/config/transcodingprofile_list.html:36
msgid "loading..." msgid "loading..."
msgstr "" msgstr ""
#: restream/templates/restream/restreamconfig_list.html:35 #: config/templates/config/pull_list.html:42
#: rtmp/templates/rtmp/stream_list.html:35 #: config/templates/config/restream_list.html:42
#: config/templates/config/stream_list.html:41
#: config/templates/config/transcodingprofile_list.html:44
msgid "actions" msgid "actions"
msgstr "Actions" msgstr ""
#: restream/templates/restream/restreamconfig_list.html:52 #: config/templates/config/pull_list.html:59
#: rtmp/templates/rtmp/stream_list.html:46 #: config/templates/config/restream_list.html:59
#: config/templates/config/stream_list.html:52
#: config/templates/config/transcodingprofile_list.html:55
msgid "details" msgid "details"
msgstr "Details" msgstr ""
#: restream/templates/restream/restreamconfig_update_form.html:5 #: config/templates/config/pull_update_form.html:12
msgid "update_restreamconfig_configuration_header" msgid "update_pull_configuration_header"
msgstr "Update restream configuration" msgstr ""
#: rtmp/models.py:13 #: config/templates/config/restream_confirm_delete.html:19
msgid "rtmp_application_name" #, python-format
msgstr "RTMP application name" msgid "are_you_sure_you_want_to_delete_\"%(restream_config_name)s\"?"
msgstr ""
"Are you sure you want to delete \"%(restream_config_name)s\"? This action "
"cannot be undone."
#: rtmp/models.py:16 #: config/templates/config/restream_detail.html:19
msgid "application_verbose_name" msgid "restream_configuration_details_header"
msgstr "RTMP application" msgstr ""
#: rtmp/models.py:17 #: config/templates/config/restream_detail.html:50
msgid "application_verbose_name_plural" msgid "configured_target_url"
msgstr "RTMP applications" msgstr ""
"Configured target URL. This is the URL where the stream will be restreamed "
"to."
#: rtmp/models.py:20 #: config/templates/config/restream_form.html:12
msgid "aplication_class_name" msgid "create_new_restream_configuration_header"
msgstr "Application" msgstr ""
#: rtmp/models.py:27 #: config/templates/config/restream_form.html:28
msgid "stream_application_help" #: config/templates/config/restream_update_form.html:28
msgstr "Application which the stream is assigned to" msgid "restream_configuration_text_html"
msgstr ""
#: rtmp/models.py:28 #: config/templates/config/restream_list.html:18
msgid "stream_stream_help" msgid "restream_configuration_header"
msgstr "Stream ID for this stream" msgstr "Restream Configuration"
#: rtmp/models.py:29 #: config/templates/config/restream_update_form.html:12
msgid "stream_name_help" msgid "update_restream_configuration_header"
msgstr "Name for this stream" msgstr "Change Restream Configuration"
#: rtmp/models.py:68 #: config/templates/config/stream_confirm_delete.html:20
msgid "stream_class_name"
msgstr "Stream"
#: rtmp/templates/rtmp/stream_confirm_delete.html:13
msgid "deleting_stream_configuration_will_also_delete_all_depending_confgurations_warning" msgid "deleting_stream_configuration_will_also_delete_all_depending_confgurations_warning"
msgstr "" msgstr ""
"Attention! Deleting this stream configuration will also delete all depending " "Deleting this stream configuration will also delete all depending "
"configurations." "configurations. This action cannot be undone."
#: rtmp/templates/rtmp/stream_confirm_delete.html:15 #: config/templates/config/stream_confirm_delete.html:22
#, python-format #, python-format
msgid "are_you_sure_you_want_to_delete_\"%(stream_config_name)s\"?" msgid "are_you_sure_you_want_to_delete_\"%(stream_config_name)s\"?"
msgstr "Are you sure you want to delete \"%(stream_config_name)s\"?" msgstr ""
"Are you sure you want to delete \"%(stream_config_name)s\"? This will also "
"delete all depending configurations. This action cannot be undone."
#: rtmp/templates/rtmp/stream_confirm_delete.html:25 #: config/templates/config/stream_confirm_delete.html:32
msgid "deleting_configurations_list_header" msgid "deleting_configurations_list_header"
msgstr "List of deleted configurations" msgstr "Stream Configurations"
#: rtmp/templates/rtmp/stream_detail.html:12 #: config/templates/config/stream_detail.html:19
msgid "stream_configuration_details_header" msgid "stream_configuration_details_header"
msgstr "Stream configuration details" msgstr "Stream Configuration Details"
#: rtmp/templates/rtmp/stream_detail.html:30 #: config/templates/config/stream_detail.html:38
msgid "application"
msgstr "Application"
#: rtmp/templates/rtmp/stream_detail.html:33
msgid "how_to_configure_your_encoder_header" msgid "how_to_configure_your_encoder_header"
msgstr "How to configure your encoder" msgstr ""
#: rtmp/templates/rtmp/stream_detail.html:35 #: config/templates/config/stream_detail.html:40
msgid "set_this_stream_server_in_encoder" msgid "set_this_stream_server_in_encoder"
msgstr "Set this stream server in your encoder" msgstr "Set one of these stream servers in your encoder."
#: rtmp/templates/rtmp/stream_detail.html:37 #: config/templates/config/stream_detail.html:49
msgid "set_this_stream_id_in_encoder" msgid "set_this_stream_id_in_encoder"
msgstr "Set this stream ID in your encoder" msgstr "Set this stream ID as the key in your encoder."
#: rtmp/templates/rtmp/stream_detail.html:46 #: config/templates/config/stream_detail.html:58
#: rtmp/templates/rtmp/stream_form.html:21 #: config/templates/config/stream_form.html:28
#: rtmp/templates/rtmp/stream_update_form.html:21 #: config/templates/config/stream_update_form.html:28
msgid "stream_configuration_text_html" msgid "stream_configuration_text_html"
msgstr "" msgstr ""
"<p>A stream configuration allows you to send a stream into the system.</"
"p><p>Only streams that can be matched to a previously configured stream will "
"be accepted by the system.</p><p>The only purpose of the name is to make "
"this stream easily identifiable when creating further configurations.The "
"actual stream ID will be shown to you after creation. <strong>Always keep "
"your stream ID secret.</strong></p>"
#: rtmp/templates/rtmp/stream_form.html:5 #: config/templates/config/stream_form.html:12
msgid "create_new_stream_configuration_header" msgid "create_new_stream_configuration_header"
msgstr "Create new stream configuration" msgstr "Create new Stream Configuration"
#: rtmp/templates/rtmp/stream_list.html:11 #: config/templates/config/stream_list.html:17
msgid "stream_configuration_header" msgid "stream_configuration_header"
msgstr "Stream configuration" msgstr "Stream Configuration"
#: rtmp/templates/rtmp/stream_list.html:34 #: config/templates/config/stream_list.html:40
msgid "receiving" msgid "receiving"
msgstr "Receiving" msgstr "Reseiving"
#: rtmp/templates/rtmp/stream_update_form.html:5 #: config/templates/config/stream_update_form.html:12
msgid "update_stream_configuration_header" msgid "update_stream_configuration_header"
msgstr "Change stream configuration" msgstr "Edit Stream Configuration"
#: config/templates/config/transcodingprofile_detail.html:18
#, fuzzy
#| msgid "stream_configuration_details_header"
msgid "transcodingprofile_configuration_details_header"
msgstr "Stream Configuration Details"
#: config/templates/config/transcodingprofile_detail.html:44
msgid "transcodingprofile_video_map_stream"
msgstr "Stream"
#: config/templates/config/transcodingprofile_detail.html:46
msgid "transcodingprofile_video_codec"
msgstr "Codec"
#: config/templates/config/transcodingprofile_detail.html:48
msgid "transcodingprofile_video_bitrate"
msgstr "Bitrate"
#: config/templates/config/transcodingprofile_detail.html:50
msgid "transcodingprofile_video_bitrate_mode"
msgstr "Mode"
#: config/templates/config/transcodingprofile_detail.html:52
msgid "transcodingprofile_video_gop_size"
msgstr "GOP Size"
#: config/templates/config/transcodingprofile_detail.html:54
msgid "transcodingprofile_video_pixel_format"
msgstr "Pixelfmt"
#: config/templates/config/transcodingprofile_detail.html:56
msgid "transcodingprofile_video_resolution"
msgstr "Resolution"
#: config/templates/config/transcodingprofile_detail.html:58
msgid "transcodingprofile_video_frame_rate"
msgstr "Frame Rate"
#: config/templates/config/transcodingprofile_detail.html:65
msgid "transcodingprofile_audio_map_stream"
msgstr "Stream"
#: config/templates/config/transcodingprofile_detail.html:67
msgid "transcodingprofile_audio_codec"
msgstr "Codec"
#: config/templates/config/transcodingprofile_detail.html:69
msgid "transcodingprofile_audio_bitrate"
msgstr "Bitrate"
#: config/templates/config/transcodingprofile_detail.html:71
msgid "transcodingprofile_audio_channels"
msgstr "Channels"
#: config/templates/config/transcodingprofile_detail.html:73
msgid "transcodingprofile_audio_sample_rate"
msgstr "Samplerate"
#: config/templates/config/transcodingprofile_list.html:16
msgid "transcodingprofile_configuration_header"
msgstr "Transcoding Profile Configuration"
#: config/util.py:8
#, python-format
msgid ""
"Invalid URL: %(value)s. Must start with one of the following: %(protocols)s"
msgstr ""
"Invalid URL: %(value)s. Must start with one of the following: %(protocols)s"
#: portier/settings.py:143
msgid "English"
msgstr "English"
#: templates/base.html:39 #: templates/base.html:39
#, python-format #, python-format
msgid "hello_%(username)s" msgid "hello_%(username)s"
msgstr "Hello, %(username)s!" msgstr "Hello, %(username)s"
#: templates/base.html:42 #: templates/base.html:42
msgid "navbar_account_password_change" msgid "navbar_account_password_change"
msgstr "Change password" msgstr "Change Password"
#: templates/base.html:43 #: templates/base.html:43
msgid "navbar_account_logout" msgid "navbar_account_logout"
@ -276,27 +585,31 @@ msgstr "Logout"
msgid "navbar_login" msgid "navbar_login"
msgstr "Login" msgstr "Login"
#: templates/base.html:80 #: templates/base.html:81
msgid "navbar_configuration_pull"
msgstr "Pull"
#: templates/base.html:83
msgid "navbar_configuration_stream" msgid "navbar_configuration_stream"
msgstr "Stream" msgstr "Stream"
#: templates/base.html:84
msgid "navbar_configuration_transcodingprofiles"
msgstr "Profiles"
#: templates/base.html:86 #: templates/base.html:86
msgid "navbar_configuration_restream" msgid "navbar_configuration_restream"
msgstr "Restream" msgstr "Restream"
#: templates/base.html:89 #: templates/base.html:89
msgid "navbar_configuration_pull"
msgstr "Pull"
#: templates/base.html:92
msgid "navbar_configuration_publish" msgid "navbar_configuration_publish"
msgstr "Publish" msgstr "Publish"
#: templates/base.html:92 #: templates/base.html:95
msgid "navbar_configuration_record" msgid "navbar_configuration_record"
msgstr "Record" msgstr "Record"
#: templates/base.html:95 #: templates/base.html:98
msgid "navbar_configuration_switch" msgid "navbar_configuration_switch"
msgstr "Switch" msgstr "Switch"
@ -306,32 +619,34 @@ msgstr "Login"
#: templates/registration/login.html:19 #: templates/registration/login.html:19
msgid "forgot_password_q" msgid "forgot_password_q"
msgstr "Did you forget your password?" msgstr "Forgot Password?"
#: templates/registration/password_change_done.html:5 #: templates/registration/password_change_done.html:5
msgid "password_change_successful" msgid "password_change_successful"
msgstr "Password change was successful" msgstr "Password change was successful."
#: templates/registration/password_change_done.html:7 #: templates/registration/password_change_done.html:7
msgid "password_change_successful_text" msgid "password_change_successful_text"
msgstr "" msgstr "The password has been changed. Please login with your new password."
"The password has succesfully been changed. From now on you can login with "
"your new password."
#: templates/registration/password_change_form.html:5 #: templates/registration/password_change_form.html:5
msgid "change_password" msgid "change_password"
msgstr "Change password" msgstr "Change Password"
#: templates/registration/password_reset_form.html:5 #: templates/registration/password_reset_form.html:5
msgid "reset_password" msgid "reset_password"
msgstr "Password forgotten?" msgstr "Reset Password"
#: templates/registration/password_reset_form.html:21 #: templates/registration/password_reset_form.html:21
msgid "reset_password_text_html" msgid "reset_password_text_html"
msgstr "" msgstr "Reset your password by entering your email address."
"Did you forget your password? No worries. Enter the e-mail address of your "
"user account here. We will send an e-mail with a link to you, which you can "
"use to reset the password."
#~ msgid "navbar_streaming" #, fuzzy, python-format
#~ msgstr "Streaming" #~| msgid "are_you_sure_you_want_to_delete_\"%(restream_config_name)s\"?"
#~ msgid "are_you_sure_you_want_to_delete_\"%(transcodingprofile_config_name)s\"?"
#~ msgstr ""
#~ "Are you sure you want to delete \"%(restream_config_name)s\"? This action "
#~ "cannot be undone."
#~ msgid "German"
#~ msgstr "German"

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,10 @@ 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("/config/transcodingprofile/", config_api.transcodingprofile.router, auth=django_auth, tags=["Transcoding Profile Configuration API"])
api.add_router("/concierge/", concierge_router, auth=None, tags=["Concierge API"])

View File

@ -139,7 +139,7 @@ USE_I18N = True
USE_TZ = True USE_TZ = True
LANGUAGES = [ LANGUAGES = [
('de', _('German')), #('de', _('German')),
('en', _('English')), ('en', _('English')),
] ]
@ -196,7 +196,7 @@ DJANGO_CELERY_BEAT_TZ_AWARE = False
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
if DEBUG == "LOLOLOL": if DEBUG:
LOGGING = { LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,

View File

@ -1,37 +1,34 @@
var app = new Vue({ var app = new Vue({
el: '#app', el: "#app",
data: { data: {
cfgs: [], cfgs: [],
isLoading: true isLoading: true,
}, },
methods: { methods: {
detailLink(id) { detailLink(id) {
return `${id}/` return `${id}/`;
}, },
deleteLink(id) { deleteLink(id) {
return `${id}/delete` return `${id}/delete`;
}, },
toggleActive(cfg) { toggleActive(cfg) {
axios axios
.patch('/api/v2/config/pulls/' + cfg.id, { active: !cfg.active }) .patch("/api/v2/config/pull/" + cfg.id, { active: !cfg.active })
.then(response => { .then((response) => {
i = this.cfgs.findIndex((obj => obj.id == cfg.id)) i = this.cfgs.findIndex((obj) => obj.id == cfg.id);
Vue.set(this.cfgs, i, response.data) Vue.set(this.cfgs, i, response.data);
} });
)
}, },
fetchData() { fetchData() {
axios axios.get("/api/v2/config/pull").then((response) => {
.get('/api/v2/config/pulls') this.cfgs = response.data;
.then(response => { this.isLoading = false;
this.cfgs = response.data });
this.isLoading = false
})
}
}, },
mounted () { },
axios.defaults.xsrfCookieName = 'csrftoken' mounted() {
axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' axios.defaults.xsrfCookieName = "csrftoken";
this.fetchData() axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
} this.fetchData();
}) },
});

View File

@ -1,37 +1,34 @@
var app = new Vue({ var app = new Vue({
el: '#app', el: "#app",
data: { data: {
cfgs: [], cfgs: [],
isLoading: true isLoading: true,
}, },
methods: { methods: {
detailLink(id) { detailLink(id) {
return `${id}/` return `${id}/`;
}, },
deleteLink(id) { deleteLink(id) {
return `${id}/delete` return `${id}/delete`;
}, },
toggleActive(cfg) { toggleActive(cfg) {
axios axios
.patch('/api/v2/config/restreams/' + cfg.id, { active: !cfg.active }) .patch("/api/v2/config/restream/" + cfg.id, { active: !cfg.active })
.then(response => { .then((response) => {
i = this.cfgs.findIndex((obj => obj.id == cfg.id)) i = this.cfgs.findIndex((obj) => obj.id == cfg.id);
Vue.set(this.cfgs, i, response.data) Vue.set(this.cfgs, i, response.data);
} });
)
}, },
fetchData() { fetchData() {
axios axios.get("/api/v2/config/restream").then((response) => {
.get('/api/v2/config/restreams') this.cfgs = response.data;
.then(response => { this.isLoading = false;
this.cfgs = response.data });
this.isLoading = false
})
}
}, },
mounted () { },
axios.defaults.xsrfCookieName = 'csrftoken' mounted() {
axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' axios.defaults.xsrfCookieName = "csrftoken";
this.fetchData() axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
} this.fetchData();
}) },
});

View File

@ -1,34 +1,35 @@
var app = new Vue({ var app = new Vue({
el: '#app', el: "#app",
data: { data: {
streams: [], streams: [],
isLoading: true isLoading: true,
}, },
methods: { methods: {
isPublishing(stream) { isPublishing(stream) {
return stream.publish_counter > 0 return stream.publish_counter > 0;
}, },
detailLink(id) { detailLink(id) {
return `${id}/` return `${id}/`;
}, },
deleteLink(id) { deleteLink(id) {
return `${id}/delete` return `${id}/delete`;
}, },
fetchData() { fetchData() {
axios axios.get("/api/v2/config/stream").then((response) => {
.get('/api/v2/config/streams') this.streams = response.data;
.then(response => { this.isLoading = false;
this.streams = response.data });
this.isLoading = false
})
}
}, },
mounted () { },
axios.defaults.xsrfCookieName = 'csrftoken' mounted() {
axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' axios.defaults.xsrfCookieName = "csrftoken";
this.fetchData() axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
setInterval(function () {
this.fetchData(); this.fetchData();
}.bind(this), 1000); setInterval(
} function () {
}) this.fetchData();
}.bind(this),
1000,
);
},
});

View File

@ -0,0 +1,26 @@
var app = new Vue({
el: "#app",
data: {
cfgs: [],
isLoading: true,
},
methods: {
detailLink(id) {
return `${id}/`;
},
deleteLink(id) {
return `${id}/delete`;
},
fetchData() {
axios.get("/api/v2/config/transcodingprofile").then((response) => {
this.cfgs = response.data;
this.isLoading = false;
});
},
},
mounted() {
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
this.fetchData();
},
});

View File

@ -80,6 +80,8 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if not perms.config.add_stream %} disabled{% endif %}{% if section == "stream" %} active {% endif %}" href="{% url 'config:stream_list' %}">{% fa5_icon 'dot-circle' %} {% trans "navbar_configuration_stream" %}</a> <a class="nav-link{% if not perms.config.add_stream %} disabled{% endif %}{% if section == "stream" %} active {% endif %}" href="{% url 'config:stream_list' %}">{% fa5_icon 'dot-circle' %} {% trans "navbar_configuration_stream" %}</a>
</li> </li>
<li class="nav-item">
<a class="nav-link{% if not perms.config.add_transcodingprofile %} disabled{% endif %}{% if section == "transcodingprofile" %} active {% endif %}" href="{% url 'config:transcodingprofile_list' %}">{% fa5_icon 'sliders-h' %} {% trans "navbar_configuration_transcodingprofiles" %}</a>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link{% if not perms.config.add_restream %} disabled{% endif %}{% if section == "restream" %} active {% endif %}" href="{% url 'config:restream_list' %}">{% fa5_icon 'expand-arrows-alt' %} {% trans "navbar_configuration_restream" %}</a> <a class="nav-link{% if not perms.config.add_restream %} disabled{% endif %}{% if section == "restream" %} active {% endif %}" href="{% url 'config:restream_list' %}">{% fa5_icon 'expand-arrows-alt' %} {% trans "navbar_configuration_restream" %}</a>
</li> </li>