Compare commits

..

15 commits

Author SHA1 Message Date
505a632200
Merge branch 'release' into dev 2024-04-03 19:45:39 +01:00
a10c33a25c
chore: bump to 3.1.1 2024-04-03 19:28:40 +01:00
99933083e7
fix: include resources from tracerite package in final build 2024-04-03 19:16:45 +01:00
Ash
16a5a94fa0
Merge pull request #58 from UniversityRadioYork/ash-python-updates
Update python libraries
2024-04-03 16:38:43 +01:00
f04e1ad8a9
docs: tested up to python 3.11 2024-04-03 15:14:30 +01:00
5e489a6408
chore: bump minimum versions of other packages 2024-04-03 15:14:30 +01:00
7a2145353d
feat: update sanic to latest release 2024-04-03 15:14:27 +01:00
Ash
00c3cf9322
Merge pull request #57 from UniversityRadioYork/ash-linux-updates
chore: updates to build on latest python/linux
2024-04-03 15:14:08 +01:00
Matthew Stratford
9e2eefa6fc
Merge pull request #32 from UniversityRadioYork/mstratford/soundcard-fixes
3.0.1 Minor Patch for Audio Reliability
2021-10-01 00:41:48 +01:00
Matthew Stratford
6e921f6c9b Force sanic / cors to fix init error. 2021-10-01 00:30:30 +01:00
Matthew Stratford
570077a3aa Fix websocket serve change. 2021-10-01 00:08:24 +01:00
Matthew Stratford
3f1e73930d Bump to 3.0.1 2021-09-30 23:51:13 +01:00
Matthew Stratford
f44d22aad3 Fix presenter subdirectory. 2021-09-30 20:46:51 +01:00
Matthew Stratford
2fdc9bf0e9 Reinitialise audio output on every load, to fix silent channels. 2021-09-30 19:19:52 +01:00
Matthew Stratford
7d2757a67d
Merge pull request #1 from UniversityRadioYork/dev
This is BAPS3 (3.0.0 Release)
2021-05-27 21:50:14 +01:00
14 changed files with 160 additions and 350 deletions

1
.envrc
View file

@ -1 +0,0 @@
use flake

5
.gitignore vendored
View file

@ -26,7 +26,4 @@ music-tmp/
presenter-build
node_modules/
.direnv/
result
node_modules/

View file

@ -88,6 +88,10 @@
{
"optionDest": "collect-all",
"value": "setproctitle"
},
{
"optionDest": "collect-all",
"value": "tracerite"
}
],
"nonPyinstallerOptions": {

View file

@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1711715736,
"narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "807c549feabce7eddbf259dbdcec9e0600a0660d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,91 +0,0 @@
{
inputs = {
nixpkgs.url = github:NixOS/nixpkgs/nixpkgs-unstable;
flake-utils.url = github:numtide/flake-utils;
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs {
inherit system;
};
sanic-cors = import ./sanic-cors.nix {
inherit (pkgs) lib;
inherit (pkgs.python311Packages) setuptools packaging sanic buildPythonPackage fetchPypi;
};
webstudio = import ./webstudio.nix {
inherit pkgs;
};
ui-templates = pkgs.stdenv.mkDerivation {
name = "baps-ui-templates";
src = ./ui-templates;
phases = "installPhase";
installPhase = ''
mkdir -p $out
cp -R $src/. $out
'';
};
ui-static = pkgs.stdenv.mkDerivation {
name = "baps-ui-static";
src = ./ui-static;
phases = "installPhase";
installPhase = ''
mkdir -p $out
cp -R $src/. $out
'';
};
dependencies = ps: with ps; [
setuptools
wheel
sanic
sanic-cors
pygame
syncer
aiohttp
mutagen
sounddevice
setproctitle
pyttsx3
websockets
pyserial
requests
jinja2
pydub
psutil
];
version = self.shortRev or self.dirtyShortRev or "dirty-inputs";
in
{
packages = rec {
default = pkgs.python311Packages.buildPythonApplication {
pname = "bapsicle";
inherit version;
doCheck = false;
propagatedBuildInputs = dependencies pkgs.python311Packages;
src = ./.;
patches = [
./patches/0-setup.py-fixes.patch
(pkgs.substituteAll {
src = ./patches/1-presenter-build-path.patch;
baps_presenter = "${webstudio}";
ui_static = "${ui-static}";
ui_templates = "${ui-templates}";
})
./patches/2-not-beta.patch
];
};
inherit webstudio;
};
devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [
(python311.withPackages dependencies)
nodejs_20
yarn
ffmpeg_6-full
];
};
});
}

View file

@ -1,7 +1,7 @@
{
"name": "bapsicle",
"nice_name": "BAPSicle",
"version": "3.1.0",
"version": "3.1.1",
"description": "BAPS3, the third generation of University Radio York's Broadcast and Presenting Suite. This package includes the Server (BAPSicle) and Presenter (WebStudio)",
"main": "index.js",
"directories": {

View file

@ -1,53 +0,0 @@
diff --git a/launch.py b/launch.py
index 712bb6a..6210095 100755
--- a/launch.py
+++ b/launch.py
@@ -54,14 +54,7 @@ def notif(msg: str):
print("NOTIFICATION:{}".format(msg))
-if __name__ == "__main__":
- # On Windows, calling this function is necessary.
- # Causes all kinds of loops if not present.
- # IT HAS TO BE RIGHT HERE, AT THE TOP OF __MAIN__
- # NOT INSIDE AN IF STATEMENT. RIGHT. HERE.
- # If it's not here, multiprocessing just doesn't run in the package.
- # Freeze support refers to being packaged with Pyinstaller.
- multiprocessing.freeze_support()
+def main():
setproctitle("BAPSicle - Launcher")
if len(sys.argv) > 1:
# We got an argument! It's probably Platypus's UI.
@@ -86,3 +79,13 @@ if __name__ == "__main__":
else:
startServer()
sys.exit(0)
+
+if __name__ == "__main__":
+ # On Windows, calling this function is necessary.
+ # Causes all kinds of loops if not present.
+ # IT HAS TO BE RIGHT HERE, AT THE TOP OF __MAIN__
+ # NOT INSIDE AN IF STATEMENT. RIGHT. HERE.
+ # If it's not here, multiprocessing just doesn't run in the package.
+ # Freeze support refers to being packaged with Pyinstaller.
+ multiprocessing.freeze_support()
+ main()
diff --git a/setup.py b/setup.py
index 9af4e1d..3a6db65 100644
--- a/setup.py
+++ b/setup.py
@@ -7,5 +7,13 @@ setup(
description=package.DESCRIPTION,
author=package.AUTHOR,
license=package.LICENSE,
- packages=find_packages(),
+ packages=find_packages() + [
+ '.',
+ 'alerts',
+ ],
+ entry_points={
+ 'console_scripts': [
+ 'bapsicle = launch:main'
+ ],
+ },
)

View file

@ -1,34 +0,0 @@
diff --git a/web_server.py b/web_server.py
index ccc3979..2909ff8 100644
--- a/web_server.py
+++ b/web_server.py
@@ -32,7 +32,7 @@ import package
from baps_types.happytime import happytime
env = Environment(
- loader=FileSystemLoader("%s/ui-templates/" % os.path.dirname(__file__)),
+ loader=FileSystemLoader("@ui_templates@"),
autoescape=select_autoescape(),
)
@@ -444,16 +444,16 @@ async def audio_file(request, type: str, id: int):
# Static Files
app.static(
- "/favicon.ico", resolve_local_file_path("ui-static/favicon.ico"), name="ui-favicon"
+ "/favicon.ico", "@ui_static@/favicon.ico", name="ui-favicon"
)
-app.static("/static", resolve_local_file_path("ui-static"), name="ui-static")
+app.static("/static", "@ui_static@", name="ui-static")
-dist_directory = resolve_local_file_path("presenter-build")
+dist_directory = "@baps_presenter@";
app.static("/presenter", dist_directory)
app.static(
"/presenter/",
- resolve_local_file_path("presenter-build/index.html"),
+ "@baps_presenter@/index.html",
strict_slashes=True,
name="presenter-index",
)

View file

@ -1,21 +0,0 @@
diff --git a/package.py b/package.py
index 1689140..bacf68f 100644
--- a/package.py
+++ b/package.py
@@ -12,15 +12,7 @@ with open(resolve_local_file_path("package.json")) as file:
build_commit = "Dev"
build_branch = "Local"
- build_beta = True
- try:
- import build
-
- build_commit = build.BUILD
- build_branch = build.BRANCH
- build_beta = build_branch != "release"
- except (ModuleNotFoundError, AttributeError):
- pass
+ build_beta = False
BUILD: str = build_commit
BRANCH: str = build_branch
BETA: bool = build_beta

152
player.py
View file

@ -685,6 +685,158 @@ class Player:
self.state.update("show_plan", [])
return True
def load(self, weight: int):
if not self.isPlaying:
loaded_state = self.state.get()
self.unload()
self.logger.log.info("Resetting output (in case of sound output gone silent somehow) to " + str(loaded_state["output"]))
self.output(loaded_state["output"])
showplan = loaded_state["show_plan"]
loaded_item: Optional[PlanItem] = None
for i in range(len(showplan)):
if showplan[i].weight == weight:
loaded_item = showplan[i]
break
if loaded_item is None:
self.logger.log.error(
"Failed to find weight: {}".format(weight))
return False
reload = False
if loaded_item.filename == "" or loaded_item.filename is None:
self.logger.log.info(
"Filename is not specified, loading from API.")
reload = True
elif not os.path.exists(loaded_item.filename):
self.logger.log.warn(
"Filename given doesn't exist. Re-loading from API."
)
reload = True
if reload:
loaded_item.filename = sync(self.api.get_filename(item=loaded_item))
if not loaded_item.filename:
return False
self.state.update("loaded_item", loaded_item)
for i in range(len(showplan)):
if showplan[i].weight == weight:
self.state.update("show_plan", index=i, value=loaded_item)
break
# TODO: Update the show plan filenames???
load_attempt = 0
while load_attempt < 5:
load_attempt += 1
try:
self.logger.log.info("Loading file: " +
str(loaded_item.filename))
mixer.music.load(loaded_item.filename)
except Exception:
# We couldn't load that file.
self.logger.log.exception(
"Couldn't load file: " + str(loaded_item.filename)
)
time.sleep(1)
continue # Try loading again.
if not self.isLoaded:
self.logger.log.error("Pygame loaded file without error, but never actually loaded.")
time.sleep(1)
continue # Try loading again.
try:
if ".mp3" in loaded_item.filename:
song = MP3(loaded_item.filename)
self.state.update("length", song.info.length)
else:
self.state.update(
"length", mixer.Sound(
loaded_item.filename).get_length() / 1000
)
except Exception:
self.logger.log.exception(
"Failed to update the length of item.")
time.sleep(1)
continue # Try loading again.
# Everything worked, we made it!
if loaded_item.cue > 0:
self.seek(loaded_item.cue)
else:
self.seek(0)
if self.state.get()["play_on_load"]:
self.unpause()
return True
self.logger.log.error("Failed to load track after numerous retries.")
return False
return False
def unload(self):
if not self.isPlaying:
try:
mixer.music.unload()
self.state.update("paused", False)
self.state.update("loaded_item", None)
except Exception:
self.logger.log.exception("Failed to unload channel.")
return False
self._potentially_end_tracklist()
# If we unloaded successfully, reset the tracklist_id, ready for the next item.
if not self.isLoaded:
self.state.update("tracklist_id", None)
return not self.isLoaded
def quit(self):
try:
mixer.quit()
self.state.update("paused", False)
self.logger.log.info("Quit mixer.")
except Exception:
self.logger.log.exception("Failed to quit mixer.")
def output(self, name: Optional[str] = None):
wasPlaying = self.state.get()["playing"]
oldPos = self.state.get()["pos_true"]
name = None if (not name or name.lower() == "none") else name
self.quit()
self.state.update("output", name)
try:
if name:
mixer.init(44100, -16, 2, 1024, devicename=name)
else:
mixer.init(44100, -16, 2, 1024)
except Exception:
self.logger.log.exception(
"Failed to init mixer with device name: " + str(name)
)
return False
loadedItem = self.state.get()["loaded_item"]
if loadedItem:
self.logger.log.info("Reloading after output change.")
self.load(loadedItem.weight)
if wasPlaying:
self.logger.log.info("Resuming playback after output change.")
self.play(oldPos)
return True
# PlanItems can have markers. These are essentially bookmarked positions in the audio.
# Timeslotitemid can be a ghost (un-submitted item), so may be "IXXX", hence str.
def set_marker(self, timeslotitemid: str, marker_str: str):

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "bapsicle"
version = "3.1.0"
version = "3.1.1"
description = ""
authors = ["University Radio York"]
readme = "README.md"

View file

@ -1,36 +0,0 @@
{ lib
, buildPythonPackage
, fetchPypi
, setuptools
, packaging
, sanic
}:
buildPythonPackage rec {
pname = "Sanic-Cors";
version = "2.2.0";
pyproject= true;
src = fetchPypi {
inherit pname version;
hash = "sha256-+NdRXaTIuDeHHUIsZjFMS1cEOWp4iUtZxQ4mqnKpWHM=";
};
nativeBuildInputs = [
setuptools
];
propagatedBuildInputs = [
sanic
packaging
];
meta = with lib; {
changelog = "https://github.com/ashleysommer/sanic-cors/releases/tag/${version}";
description = "A Sanic extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.";
homepage = "https://github.com/ashleysommer/sanic-cors";
license = licenses.mit;
maintainers = with maintainers; [];
};
}

View file

@ -26,7 +26,7 @@ class WebsocketServer:
logger: LoggingManager
to_webstudio: Task
from_webstudio: Task
websocket_server: Serve
websocket_server: serve
def __init__(self, in_q, out_q, state):

View file

@ -1,46 +0,0 @@
{ pkgs }:
let
src = pkgs.fetchFromGitHub {
owner = "UniversityRadioYork";
repo = "WebStudio";
rev= "8b7f59cdc6ed80b525b2dff665308d808a526d97";
hash = "sha256-I+N/mskX8/gN065SqPxmOn3nrHKPWPcIZygSGbB6GEE=";
};
yarnOfflineCache = pkgs.fetchYarnDeps {
yarnLock = "${src}/yarn.lock";
hash = "sha256-AmKui+Sqyipy4/9lcg8vGWfp9lM2+/fHHDzEWoG8fqw=";
};
in
pkgs.stdenv.mkDerivation {
name = "baps-presenter";
inherit src;
nativeBuildInputs = with pkgs; [
nodejs
yarn
yarn2nix-moretea.fixup_yarn_lock
];
configurePhase = ''
export HOME=$(mktemp -d)
'';
buildPhase = ''
yarn config --offline set yarn-offline-mirror ${yarnOfflineCache}
fixup_yarn_lock yarn.lock
yarn install --offline \
--frozen-lockfile \
--ignore-engines \
--ignore-scripts
patchShebangs .
yarn run build-baps
'';
installPhase = ''
mkdir -p $out
cp -R build/. $out
'';
doDist = false;
}