Skip to content
Merged

6.0.9 #115

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
e353602
chore: version bump to 6.0.9
martastain Dec 8, 2024
87621d0
Update README.md
martastain Dec 8, 2024
b5ac1d4
fix: reload scheduler after applying template
martastain Dec 9, 2024
f521c7a
Merge branch 'develop' of https://github.com/nebulabroadcast/nebula i…
martastain Dec 9, 2024
3ffd938
feat: copy events
martastain Dec 11, 2024
f5b1585
fix: inheritance in scheduler and rundown
martastain Dec 12, 2024
c854706
fix: update poster frame mark when it changes
martastain Dec 14, 2024
6823e67
Update README.md
martastain Jan 6, 2025
486e06b
chore: disable poetry package mode
martastain Jan 6, 2025
6e6b465
Update README.md
martastain Jan 7, 2025
1d6c150
Merge pull request #107 from nebulabroadcast/fix-metadata-inheritance
martastain Feb 11, 2025
15894d4
chore: switched to uv
martastain Feb 11, 2025
37c7a71
chore: updated dockerfile to use uv
martastain Feb 11, 2025
e9156b3
chore: update workflow to pick version from project section
martastain Feb 11, 2025
5e4e86c
fix: type fixes, be more strict
martastain Feb 11, 2025
3ba90aa
Merge pull request #108 from nebulabroadcast/migrate-to-uv
martastain Feb 12, 2025
e02250e
chore: migrate to typescript (base - w.i.p.)
martastain Feb 18, 2025
542714f
chore: add types
martastain Feb 18, 2025
1cd6b8e
fix: video player sizing
martastain Feb 18, 2025
8423e71
chore: new permission model
martastain Feb 18, 2025
c0e05ec
chore: configure eslint
martastain Feb 18, 2025
71558ce
fix: rogue log statement
martastain Feb 19, 2025
2c4dbbd
fix: simplified video seeking
martastain Feb 19, 2025
4c04ead
feat: new user model
martastain Feb 19, 2025
220fa74
fix: anyval checking
martastain Feb 20, 2025
b9d1cde
fix: missing packages in dockerfile
martastain Feb 21, 2025
a5de9db
fix: building frontend
martastain Feb 25, 2025
53f9ee4
fix: do not crash on missing channel
martastain Feb 25, 2025
697dc5d
fix: folder links memoization
martastain Feb 25, 2025
1e8eaca
chore: remove check before build
martastain Feb 26, 2025
89d26c6
fix: auto-populate ctime and mtime when creating users
martastain Feb 26, 2025
553f1e2
fix: video player padding
martastain Feb 26, 2025
92201a4
fix: video seeking improvements
martastain Feb 26, 2025
36e756e
Merge pull request #109 from nebulabroadcast/ts-migration
martastain Feb 26, 2025
6905799
feat: use file response for proxies
martastain Feb 26, 2025
e5a51e9
chore: remove ganian, tweak gunicorn
martastain Feb 26, 2025
84286df
refactor: rewrite video player to use frames as base
martastain Feb 26, 2025
23f42c0
Merge pull request #112 from nebulabroadcast/remove-granian-support
martastain Feb 27, 2025
22c13a3
chore: new lining rules, fix frontend errors
martastain Feb 27, 2025
09a00dc
Merge pull request #111 from nebulabroadcast/use-frames-in-video-player
martastain Mar 10, 2025
b3695bd
fix: dependency loop
martastain Mar 11, 2025
fe87d9e
fix: loop between marks
martastain Mar 14, 2025
ad740c8
fix: dependency arrays & marking
martastain Mar 14, 2025
48ca216
feat: backend support for sso (w.i.p.)
martastain Mar 21, 2025
2fa08f1
feat: sso P.O.C.
martastain Mar 22, 2025
d18e48b
chore: configurable clients
martastain Mar 22, 2025
9ed0ab3
error handling
martastain Mar 22, 2025
fdb758d
feat: load sso options from settings
martastain Mar 23, 2025
8ebda0a
fix: types
martastain Mar 23, 2025
9141f34
feat: custom session middleware
martastain Apr 15, 2025
7cf3bb9
Update backend/api/auth/token_exchange.py
martastain Apr 15, 2025
ade7406
fix: focus login field
martastain Apr 15, 2025
ce1d80f
Merge branch 'sso-support' of https://github.com/nebulabroadcast/nebu…
martastain Apr 15, 2025
a08872b
Merge pull request #113 from nebulabroadcast/sso-support
martastain Apr 15, 2025
420586e
chore(deps): bump axios from 1.7.9 to 1.8.2 in /frontend
dependabot[bot] Apr 16, 2025
fc75cb4
Merge pull request #114 from nebulabroadcast/dependabot/npm_and_yarn/…
martastain Apr 16, 2025
4f08d5a
feat: sso profiles
martastain Apr 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish a new version
name: Publish a dev version

on:
push:
Expand All @@ -22,7 +22,7 @@ jobs:
uses: SebRollen/[email protected]
with:
file: 'backend/pyproject.toml'
field: 'tool.poetry.version'
field: 'project.version'

- name: Build docker image
uses: docker/build-push-action@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
uses: SebRollen/[email protected]
with:
file: 'backend/pyproject.toml'
field: 'tool.poetry.version'
field: 'project.version'

- name: Build docker image
uses: docker/build-push-action@v4
Expand Down
38 changes: 19 additions & 19 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
FROM node:latest AS build

RUN mkdir /frontend
WORKDIR /frontend

COPY ./frontend/index.html /frontend/index.html
COPY ./frontend/package.json /frontend/package.json
COPY ./frontend/vite.config.js /frontend/vite.config.js
COPY ./frontend/src /frontend/src
COPY ./frontend/index.html .
COPY ./frontend/package.json .
COPY ./frontend/vite.config.ts .
COPY ./frontend/tsconfig.json .
COPY ./frontend/tsconfig.node.json .
COPY ./frontend/public /frontend/public

WORKDIR /frontend
RUN yarn install && yarn build
RUN yarn install
COPY ./frontend/src /frontend/src
RUN yarn build

FROM python:3.12-bullseye
FROM python:3.12-slim
ENV PYTHONBUFFERED=1

RUN \
apt-get update \
&& apt-get -yqq upgrade \
&& apt-get -yqq install \
cifs-utils
curl \
cifs-utils \
procps \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN mkdir /backend
WORKDIR /backend
COPY ./backend/pyproject.toml /backend/pyproject.toml

RUN \
pip install -U pip && \
pip install poetry && \
poetry config virtualenvs.create false && \
poetry install --no-interaction --no-ansi --only main
COPY ./backend/pyproject.toml /backend/uv.lock .
RUN --mount=from=ghcr.io/astral-sh/uv,source=/uv,target=/bin/uv \
uv pip install -r pyproject.toml --system

COPY ./backend /backend
COPY ./backend .
COPY --from=build /frontend/dist/ /frontend

CMD ["/bin/bash", "manage", "start"]
12 changes: 6 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
IMAGE_NAME=nebulabroadcast/nebula-server:dev
VERSION=$(shell cd backend && poetry run python -c 'import nebula' --version)
VERSION=$(shell cd backend && uv run python -c 'import nebula' --version)

check:
cd frontend && \
yarn format

cd backend && \
poetry version $(VERSION) && \
poetry run ruff format . && \
poetry run ruff check --fix . && \
poetry run mypy .
sed -i "s/^version = \".*\"/version = \"$(VERSION)\"/" pyproject.toml && \
uv run ruff format . && \
uv run ruff check --fix . && \
uv run mypy .

build: check
build:
docker build -t $(IMAGE_NAME) .

dist: build
Expand Down
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ NEBULA
======

![GitHub release (latest by date)](https://img.shields.io/github/v/release/nebulabroadcast/nebula?style=for-the-badge)
![Maintenance](https://img.shields.io/maintenance/yes/2024?style=for-the-badge)
![Maintenance](https://img.shields.io/maintenance/yes/2025?style=for-the-badge)
![Last commit](https://img.shields.io/github/last-commit/nebulabroadcast/nebula?style=for-the-badge)
![Python version](https://img.shields.io/badge/python-3.11-blue?style=for-the-badge)

Expand All @@ -26,13 +26,16 @@ Key features

### Media Asset Management

![Metadata editor](https://nebulabroadcast.com/screenshots/nb_screenshot_browser.jpg?)

Simple and fast media catalog based on [EBU Core](https://tech.ebu.ch/MetadataEbuCore) includes a description of asset
genre, editorial format, atmosphere, rights, relations, and technical metadata,
while its very fast search engine makes navigation among media files very easy.


The low-resolution preview allows for editorial review, trimming, and the creation of sub-clips.

![Metadata editor](https://nebulabroadcast.com/static/img/nebula-metadata-editor.webp)
![Video preview](https://nebulabroadcast.com/screenshots/nb_screenshot_preview.jpg?)

### Video and audio cross-conversion and normalization

Expand All @@ -43,11 +46,13 @@ smart frame rate and size normalization and [EBU R128](https://tech.ebu.ch/docs/
Automatic cross-conversion servers transcode files for playout, web, low-res proxies, customer previews, etc.
For **h.264** and **HEVC**, Nebula can take advantage of NVIDIA nvenc and leverage the speed of transcoding using GPUs.

![Jonbs view](https://nebulabroadcast.com/screenshots/nb_screenshot_jobs.jpg?)

It is possible to start conversions automatically (rule-based) or trigger them from the user interface.

### Linear scheduling

Firefly client provides a simple and user-friendly way to schedule linear broadcasting.
Nebula provides a simple and user-friendly way to schedule linear broadcasting.
Macro- and micro-scheduling patterns are finished intuitively using drag&drop, including live events.

Nebula has also the ability to schedule for playback assets, which aren't finished yet.
Expand All @@ -59,8 +64,7 @@ depending on the particular broadcast scheme, Dramatica selects and automaticall
It is the way to create a playlist for a music station where an algorithm automatically creates a playlist based on a predefined scheme.
Each clip in the rundown is picked by its editorial format, genre, tempo, atmosphere, etc.

![Detail of a scheduler panel in the Firefly application](https://nebulabroadcast.com/static/img/nebula-scheduler.webp)

![Detail of a scheduler panel in the web interface](https://nebulabroadcast.com/screenshots/nb_screenshot_scheduling.jpg?)

### Playout control

Expand All @@ -69,12 +73,12 @@ For linear broadcasting, Nebula can control
Broadcasting can run autonomously with and option of starting blocks at a specified time.

Users - master control room operators - can interfere with the rundown using [Firefly client](https://github.com/nebulabroadcast/firefly),
executing graphics or change run order until the last moment.
or the web interface, executing graphics or change run order until the last moment.

Playout control module offers a plug-in interface for secondary events execution such as CG, router or studio control,
recorders control and so on. Right at the operator's fingertips.

![Detail of a rundown panel with playout control interface](https://nebulabroadcast.com/static/img/nebula-playout-control.webp)
![Detail of a rundown panel with playout control interface](https://nebulabroadcast.com/screenshots/nb_screenshot_rundown.jpg?)

### Publishing

Expand Down Expand Up @@ -123,7 +127,7 @@ See the GNU General Public License for more details.
Need help?
----------

- Join [Open Source Broadcasting](https://discord.gg/WXaaHYGQ) group on Discord
- Join [Open Source Broadcasting](https://discord.gg/3UxJ4WKfy9) group on Discord
- Professional support for Nebula is provided by [Nebula Broadcast](https://nebulabroadcast.com)
- User documentation is available on [our website](https://nebulabroadcast.com/doc/nebula)
- Found a bug? Please [create an issue](https://github.com/nebulabroadcast/nebula/issues)
11 changes: 10 additions & 1 deletion backend/api/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
__all__ = ["LoginRequest", "LogoutRequest", "SetPasswordRequest"]
__all__ = [
"LoginRequest",
"LogoutRequest",
"SetPasswordRequest",
"SSOLoginRequest",
"SSOLoginCallback",
"TokenExchangeRequest",
]

from .login_request import LoginRequest
from .logout_request import LogoutRequest
from .set_password_request import SetPasswordRequest
from .sso import SSOLoginCallback, SSOLoginRequest
from .token_exchange import TokenExchangeRequest
27 changes: 1 addition & 26 deletions backend/api/auth/login_request.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,14 @@
import time

from fastapi import Request
from pydantic import Field

import nebula
from server.clientinfo import get_real_ip
from server.models import RequestModel, ResponseModel
from server.models.login import LoginRequestModel, LoginResponseModel
from server.request import APIRequest
from server.session import Session


class LoginRequestModel(RequestModel):
username: str = Field(
...,
title="Username",
examples=["admin"],
pattern=r"^[a-zA-Z0-9_\-\.]{2,}$",
)
password: str = Field(
...,
title="Password",
description="Password in plain text",
examples=["Password.123"],
)


class LoginResponseModel(ResponseModel):
access_token: str = Field(
...,
title="Access token",
description="Access token to be used in Authorization header"
"for the subsequent requests",
)


async def check_failed_login(ip_address: str) -> None:
banned_until = await nebula.redis.get("banned-ip-until", ip_address)
if banned_until is None:
Expand Down
78 changes: 78 additions & 0 deletions backend/api/auth/sso.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from urllib.parse import urlparse

from fastapi import Request
from fastapi.responses import RedirectResponse

import nebula
from server.request import APIRequest
from server.session import Session
from server.sso import NebulaSSO


class SSOLoginRequest(APIRequest):
name = "sso_login"
path = "/api/sso/login/{provider}"
methods = ["GET"]

async def handle(self, request: Request, provider: str) -> RedirectResponse:
client = NebulaSSO.client(provider)

referer = request.headers.get("referer")
if referer:
parsed_url = urlparse(referer)
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
else:
base_url = "http://localhost:4455"

# We cannot use request.url_for here because it screws the frontend
# dev server proxy

redirect_uri = f"{base_url}/api/sso/callback/{provider}"
nebula.log.debug(f"Redirect URI: {redirect_uri}")
return await client.authorize_redirect(request, redirect_uri)


class SSOLoginCallback(APIRequest):
name = "sso_callback"
path = "/api/sso/callback/{provider}"
methods = ["GET"]

async def handle(self, request: Request, provider: str) -> RedirectResponse:
remote = NebulaSSO.client(provider)
if not remote:
return RedirectResponse("/?error=Invalid provider")

code = request.query_params.get("code")
id_token = request.query_params.get("id_token")
oauth_verifier = request.query_params.get("oauth_verifier")

user_info = {}

if code:
token = await remote.authorize_access_token(request)
user_info = token.get("userinfo", {})

if id_token and not user_info:
token = {"id_token": id_token}
user_info = await remote.parse_id_token(request, token)

if oauth_verifier and not user_info:
token = await remote.authorize_access_token(request)

if token and not user_info:
user_info = await remote.userinfo(token=token)

if not user_info:
return RedirectResponse("/?error=Invalid response from provider")

email = user_info.get("email")

if not email:
return RedirectResponse("/?error=User email not found")

try:
user = await nebula.User.by_email(email)
except nebula.NotFoundException:
return RedirectResponse("/?error=User not found")
session = await Session.create(user, request, transient=True)
return RedirectResponse(f"/?authorize={session.token}")
32 changes: 32 additions & 0 deletions backend/api/auth/token_exchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from fastapi import Request

import nebula
from server.models.login import LoginResponseModel, TokenExchangeRequestModel
from server.request import APIRequest
from server.session import Session


class TokenExchangeRequest(APIRequest):
"""Exachange a transient access token for a normal one

This request will exchange an access token for a new one.
The original access token will be invalidated.
"""

name: str = "token-exchange"
response_model = LoginResponseModel

async def handle(
self,
request: Request,
payload: TokenExchangeRequestModel,
) -> LoginResponseModel:
session = await Session.check(payload.access_token, request, transient=True)
if not session:
raise nebula.UnauthorizedException("Invalid token")
user_id = session.user["id"]
user = await nebula.User.load(user_id)
session = await Session.create(user, request)
nebula.log.debug(f"{user} token exchanged")
await Session.delete(payload.access_token)
return LoginResponseModel(access_token=session.token)
6 changes: 3 additions & 3 deletions backend/api/browse.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ def sanitize_value(value: SerializableValue) -> str:
def build_conditions(conditions: list[ConditionModel]) -> list[str]:
cond_list: list[str] = []
for condition in conditions:
assert (
condition.key in nebula.settings.metatypes
), f"Invalid meta key {condition.key}"
assert condition.key in nebula.settings.metatypes, (
f"Invalid meta key {condition.key}"
)
condition.value = normalize_meta(condition.key, condition.value)
if condition.operator in ["IN", "NOT IN"]:
assert isinstance(condition.value, list), "Value must be a list"
Expand Down
Loading