Compare commits
15 commits
nix-packag
...
dev
Author | SHA1 | Date | |
---|---|---|---|
505a632200 | |||
a10c33a25c | |||
99933083e7 | |||
16a5a94fa0 | |||
f04e1ad8a9 | |||
5e489a6408 | |||
7a2145353d | |||
00c3cf9322 | |||
|
9e2eefa6fc | ||
|
6e921f6c9b | ||
|
570077a3aa | ||
|
3f1e73930d | ||
|
f44d22aad3 | ||
|
2fdc9bf0e9 | ||
|
7d2757a67d |
14 changed files with 160 additions and 350 deletions
1
.envrc
1
.envrc
|
@ -1 +0,0 @@
|
|||
use flake
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -26,7 +26,4 @@ music-tmp/
|
|||
|
||||
presenter-build
|
||||
|
||||
node_modules/
|
||||
|
||||
.direnv/
|
||||
result
|
||||
node_modules/
|
|
@ -88,6 +88,10 @@
|
|||
{
|
||||
"optionDest": "collect-all",
|
||||
"value": "setproctitle"
|
||||
},
|
||||
{
|
||||
"optionDest": "collect-all",
|
||||
"value": "tracerite"
|
||||
}
|
||||
],
|
||||
"nonPyinstallerOptions": {
|
||||
|
|
61
flake.lock
61
flake.lock
|
@ -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
|
||||
}
|
91
flake.nix
91
flake.nix
|
@ -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
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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'
|
||||
+ ],
|
||||
+ },
|
||||
)
|
|
@ -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",
|
||||
)
|
|
@ -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
152
player.py
|
@ -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):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "bapsicle"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
description = ""
|
||||
authors = ["University Radio York"]
|
||||
readme = "README.md"
|
||||
|
|
|
@ -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; [];
|
||||
};
|
||||
}
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue