VisibilityHandler #3

Merged
erth9960 merged 4 commits from visibility into master 2024-03-27 14:54:33 +01:00
22 changed files with 101 additions and 69 deletions

View File

@ -183,7 +183,6 @@ Valid top-level keys and their expected values are:
video. Relative to `upload_dir`.
Details under the heading [Slides](#slides).
### Subtitles
There are two top-level keys that deal with subtitles: `subtitles` and
@ -265,13 +264,18 @@ These are the valid keys for a source object:
only have a `true` value for one stream per presentation. If omitted on
stream creation, this will defauilt to `false`.
* `enabled`: `bool`
Whether this stream will be displayed in the player. At least one stream
must be enabled. If omitted on stream creation, this will deafult to `true`.
A `sources` object would look like this:
```json
{
"asourcename": {
"video": "some/path",
"poster": "some/other/path",
"playAudio": someBool
"playAudio": someBool,
"enabled": somebool,
},
"anothersource": {...},
...
@ -377,12 +381,14 @@ This is a job specification that has all keys and values:
"main": {
"video": "videos/myvideo.mp4",
"poster": "aposter.jpg",
"playAudio": true
"playAudio": true,
"enabled": true
},
"second": {
"video": "myothervideo.mp4",
"poster": "anotherposter.jpg",
"playAudio": false
"playAudio": false,
"enabled": false
}
},
"slides": {
@ -580,3 +586,7 @@ A source definition is a JSON object with the following keys:
* `playAudio`: `bool`
A boolean value denoting whether to this stream's audio track. This will
only be set to `true` for one source in a given package.
* `enabled`: `bool`
Whether this stream will be displayed in the player. At least one stream
will be enabled.

View File

@ -1,6 +1,5 @@
import logging
import logging.handlers
import multiprocessing as mp
import os
import shutil

View File

@ -1,7 +1,6 @@
import multiprocessing as mp
from .audio import AudioHandler
from .handler import Handler
from .metadata import MetadataHandler
from .poster import PosterHandler
from .slides import SlidesHandler
@ -9,6 +8,7 @@ from .subtitles_whisper_hack import SubtitlesWhisperHandler
from .subtitles_import import SubtitlesImportHandler
from .thumbnail import ThumbnailHandler
from .transcode import TranscodeHandler
from .visibility import VisibilityHandler
from ..ldap import Ldap
from ..utils import get_section
@ -20,6 +20,7 @@ allHandlers = [AudioHandler,
SubtitlesWhisperHandler,
ThumbnailHandler,
TranscodeHandler,
VisibilityHandler,
]
def init_handlers(collector, worker, config):

View File

@ -1,32 +0,0 @@
from .handler import Handler
from ..exceptions import ValidationException
@Handler.register
class VisibilityHandler(Handler):
"""
This class handles visibility settings for streams.
"""
@classmethod
def wants(cls, jobspec, existing_package):
"""
Return True if this handler wants to process this jobspec.
Raises an exception if the job is wanted but doesn't pass validation.
In order for a job to be wanted, the field 'sources' must exist and
at least one of the source items must contain a 'enabled' field.
"""
pass
@classmethod
def _validate(cls, jobspec, existing_package):
"""
Return True if the job is valid for this handler.
A job is valid as long as at least one of the package's source items
"""
pass
def _handle(self, jobspec, existing_package, tempdir):
pass

View File

@ -1,4 +1,3 @@
import logging
from abc import ABCMeta, abstractmethod
from pathlib import Path

View File

@ -1,5 +1,4 @@
from .handler import Handler
from ..exceptions import ValidationException
@Handler.register

View File

@ -79,7 +79,7 @@ class PosterHandler(Handler):
super()._validate(jobspec, existing_package)
if uploaded:
if 'upload_dir' not in jobspec:
raise ValidationException(f"upload_dir missing")
raise ValidationException("upload_dir missing")
for name, poster in uploaded.items():
if not path.isfile(path.join(jobspec['upload_dir'],
poster)):

View File

@ -1,5 +1,4 @@
from os import path, rename
from shutil import rmtree
import ffmpeg

View File

@ -1,4 +1,3 @@
import logging
from pathlib import Path

View File

@ -6,12 +6,10 @@ from multiprocessing import Process, Queue
from pathlib import Path
from queue import Empty
from time import sleep
from threading import Event
from .handler import Handler
from ..exceptions import ConfigurationException, ValidationException
from ..exceptions import ValidationException
import torch
import whisper
import whisper.utils
@ -58,7 +56,7 @@ def _whisper_processor(inqueue,
if language is None:
out_language = result['language']
logger.info(
f"Detected language '%s' in %s.", out_language, inpath)
"Detected language '%s' in %s.", out_language, inpath)
else:
out_language = language

View File

@ -5,7 +5,6 @@ from multiprocessing import Process, Queue
from pathlib import Path
from queue import Empty
from time import sleep
from threading import Event
from .handler import Handler
from ..exceptions import ConfigurationException, ValidationException

View File

@ -1,7 +1,6 @@
import logging
from os import path, remove, rename
from shutil import rmtree
import ffmpeg
@ -169,7 +168,7 @@ class TranscodeHandler(Handler):
"""
super()._validate(jobspec, existing_package)
if 'upload_dir' not in jobspec:
raise ValidationException(f"upload_dir missing")
raise ValidationException("upload_dir missing")
for name, source in jobspec['sources'].items():
if not path.isfile(path.join(jobspec['upload_dir'],
source['video'])):
@ -228,8 +227,10 @@ class TranscodeHandler(Handler):
else:
# Initialize a new source
# set playAudio to False by default so we don't conflict
# with the audio handler
sources[name] = {'playAudio': False}
# with the audio handler, set enabled to True so we don't
# conflict with the visibility handler
sources[name] = {'playAudio': False,
'enabled': True}
# Save new source videos
sources[name]['video'] = variants

View File

@ -0,0 +1,57 @@
import copy
from pathlib import Path
from .handler import Handler
from ..exceptions import ValidationException
from ..package import Package
@Handler.register
class VisibilityHandler(Handler):
"""
This class handles visibility settings for streams.
"""
@classmethod
def wants(cls, jobspec: dict, existing_package: Package) -> bool | ValidationException:
"""
Return True if this handler wants to process this jobspec.
Raises an exception if the job is wanted but doesn't pass validation.
In order for a job to be wanted, the field 'sources' must exist and
at least one of the source items must contain a 'enabled' field.
"""
if 'sources' in jobspec and any('enabled' in source for source in jobspec['sources'].values()):
return cls._validate(jobspec, existing_package)
return False
@classmethod
def _validate(cls, jobspec: dict, existing_package: Package) -> bool | ValidationException:
"""
Return True if the job is valid for this handler.
A job is valid as long as at least one of the package's source items
has its 'enabled' field set to True.
"""
sources = copy.deepcopy(existing_package.get('sources', {}))
for name, source in jobspec['sources'].items():
if name not in sources:
sources[name] = {}
if 'enabled' in source:
sources[name]['enabled'] = source['enabled']
for source in sources.values():
if 'enabled' in source:
return True
raise ValidationException("No enabled sources")
def _handle(self, jobspec: dict, existing_package: Package, tempdir: Path) -> callable:
"""
"""
def apply_func(package: Package) -> None:
sources = package.get('sources', {})
for name, source in jobspec['sources'].items():
if name not in sources:
sources[name] = {}
if 'enabled' in source:
sources[name]['enabled'] = source['enabled']
return apply_func

View File

@ -1,5 +1,4 @@
from ldap3 import Connection, ObjectDef, Reader, Server
from ldap3.core.exceptions import LDAPSocketSendError
class Ldap:
def __init__(self, conf):

View File

@ -1,5 +1,4 @@
import json
import logging
from dataclasses import asdict, dataclass
from pprint import pformat

View File

@ -2,7 +2,7 @@ import json
from copy import deepcopy
from os import mkdir, path, rename, remove
from shutil import copy, copytree
from shutil import copytree
import ffmpeg

View File

@ -110,4 +110,4 @@ class CatturaProcessor(Preprocessor):
for key in data.keys():
if key.startswith('mediapackage:'):
return data[key]
raise KeyError(f"no 'mediapackage' key in job specification")
raise KeyError("no 'mediapackage' key in job specification")

View File

@ -1,5 +1,4 @@
from abc import ABCMeta, abstractmethod
from os import mkdir, path
from ..queuethread import QueueThread

View File

@ -1,4 +1,3 @@
from os import chdir, path
from types import FunctionType
@ -23,6 +22,7 @@ canonical_jobspec = {
'source': str}},
'sources': {str: {'poster': str,
'playAudio': bool,
'enabled': bool,
'video': str}},
'slides': str,
'notification_url': str,
@ -49,6 +49,7 @@ canonical_manifest = {
'subtitles': {str: str},
'sources': {str: {'poster': str,
'playAudio': bool,
'enabled': bool,
'video': {'720': str,
'1080': str}}}
}

View File

@ -1,4 +1,3 @@
import logging
import multiprocessing as mp
from collections import deque
@ -6,7 +5,7 @@ from collections.abc import Iterable
from dataclasses import dataclass
from pprint import pformat
from threading import Event
from time import sleep, strftime
from time import sleep
from typing import Callable
from .queuethread import QueueThread

View File

@ -5,7 +5,6 @@ import sys
from configparser import ConfigParser
from os import path
from time import sleep
from pipeline import Pipeline

23
test.py
View File

@ -272,7 +272,7 @@ class PipelineTest(DaemonTest):
print("¤ Contents of invalid notification file ¤")
print(f.read())
print("¤ End invalid notification file contents ¤")
self.fail(f"Invalid JSON in result file.")
self.fail("Invalid JSON in result file.")
# Validate that this is the correct resultfile
self.assertEqual(jobid, result['jobid'])
@ -300,7 +300,7 @@ class PipelineTest(DaemonTest):
return final_result
def init_job(self, pkgid=False, url=False, subs=False, thumb=False,
source_count=0, poster_count=0):
source_count=0, disabled_source_count=0, poster_count=0):
jobspec = {}
if url == True:
@ -333,7 +333,7 @@ class PipelineTest(DaemonTest):
if source_count:
# poster_count determines the number of posters to include.
# Any remaining posters will be generated in pipeline
self.add_sources(jobspec, source_count, poster_count)
self.add_sources(jobspec, source_count, poster_count, disabled_source_count)
return jobspec
@ -343,7 +343,8 @@ class PipelineTest(DaemonTest):
str(uuid.uuid4()))
jobspec['upload_dir'] = uldir
makedirs(uldir)
return uldir
return uldir
return jobspec['upload_dir']
def add_subtitles(self, jobspec):
uldir = self.ensure_uldir(jobspec)
@ -356,16 +357,22 @@ class PipelineTest(DaemonTest):
copyfile(subspath, path.join(uldir, subsfile))
jobspec['subtitles'] = subspec
def add_sources(self, jobspec, count, poster_count=0):
def add_sources(self, jobspec, count, poster_count=0, disabled_count=0):
uldir = self.ensure_uldir(jobspec)
jobspec['sources'] = {}
if 'sources' not in jobspec:
jobspec['sources'] = {}
posters = 0
disabled = 0
for i in range(count):
videopath = next(self.videopaths)
videofile = path.basename(videopath)
copyfile(videopath, path.join(uldir, videofile))
sourcedef = {'video': videofile,
'playAudio': False}
'playAudio': False,
'enabled': True}
if disabled < disabled_count:
sourcedef['enabled'] = False
disabled += 1
if i == 0:
sourcedef['playAudio'] = True
if posters < poster_count:
@ -457,7 +464,7 @@ class PipelineTest(DaemonTest):
#@unittest.skip("This test is very slow")
def test_transcoding(self):
jobspec = self.init_job(source_count=4, poster_count=2)
jobspec = self.init_job(source_count=3, poster_count=2, disabled_source_count=1)
jobid = self.submit_default_job(jobspec)
result = self.wait_for_result(jobid, ['AudioHandler',