# Radio Bern Now Playing
This repo contains the tool we use to grab, aggregate and publish show, artist and track metadata from various sources.
The nowplaying project grabs info from RaBes playout solution and publishes them to broadcast vectors like DAB+ and Webstreams.
It also takes care of generating our live ticker at songticker.rabe.ch.
## Overview
The nowplaying daemon takes various sources into account:
- The RaBe playout solution (via the [virtual-saemubox](https://github.com/radiorabe/virtual-saemubox) project)
- Input from [Klangbecken](https://github.com/radiorabe/klangbecken)
It then make an informed decision as to what should be our leading PAD data and pushes this to it's track handlers for the following sinks:
- DAB+ (via the [ODR-EncoderManager](https://github.com/Opendigitalradio/ODR-EncoderManager) API)
- Webstream by pushing to our [Icecast](https://icecast.org/) instances
- Statically hosted XML output for browsers on songticker.rabe.ch
The sources are currently individually implemented and are being replaced with generic [RaBe CloudEvents](https://github.com/radiorabe/event-spec) based sources. In many places the legacy system is underdocumented and this documentation documents the new system.
## Usage
TBD
### RaBe CloudEvents
The nowplaying projects receives httpd [RaBe CloudEvents](https://github.com/radiorabe/event-spec) on a dedicated web service. It reacts to them depending on the event type and source
It supports the following event types:
- `ch.rabe.api.events.track.v1.trackStarted`
- `ch.rabe.api.events.track.v1.trackFinished`
An example `trackStarted` event looks like this:
```json
{
"specversion": "1.0",
"type": "ch.rabe.api.events.track.v1.trackStarted",
"source": "<source>",
"subject": null,
"id": "<id>",
"time": "2021-12-28T19:31:00Z",
"datacontenttype": "application/json",
"data": {
"item.artist": "hairmare fusion sounds collective",
"item.title": "C L O U D E V E N T W A V E",
"item.length": 36000
}
}
```
It can be sent to the nowplaying service using cURL as follows:
```bash
curl -vvv -u rabe:rabe -H 'Content-Type: application/cloudevents+json' -X POST -d '@event.json' localhost:8080/webhook
```
In most cases the use of a cloudevents-sdk is recommended. The following example is based on the same [python-sdk](https://github.com/cloudevents/sdk-python) nowplaying uses.
```python
import requests
from cloudevents.http import CloudEvent, to_structured
def send_event(url, username, password):
# This data defines a cloudevent
attributes = {
"specversion": "1.0",
# as defined by the events-spec repo
"type": "ch.rabe.api.events.track.v1.trackStarted",
# for klangbecken the github link is always used as source (as per events-spec)
"source": "https://github.com/radiorabe/klangbecken",
# this should be generated and could/should point to a real
# URL on either https://klangbecken.service.int.example.org
# using a `crid://` URL based on the upcoming crid-spec.
"id": "uri:demo:12345",
}
data = {
"item.title": "Track Title",
"item.artist": "Artist",
# length in seconds, optional if you also implement sending the
# not "completely specced yet" trackFinished event
"item.length": 60,
}
event = CloudEvent(attributes, data)
headers, body = to_structured(event)
# send and print event
requests.post(url, headers=headers, data=body, auth=(username, password))
print(f"Sent {event['id']} from {event['source']} with {event.data}")
if __name__ == "__main__":
# local config
url = "https://nowplaying.service.int.example.org/webhook"
username = "rabe"
password = "rabe"
# do work
send_event(url, username, password)
```
## Contributing
### pre-commit hook
```bash
pip install pre-commit
pip install -r requirements-dev.txt -U
pre-commit install
```
### testing
```bash
pytest
```
## Release Management
The CI/CD setup uses semantic commit messages following the [conventional commits standard](https://www.conventionalcommits.org/en/v1.0.0/).
There is a GitHub Action in [.github/workflows/semantic-release.yaml](./.github/workflows/semantic-release.yaml)
that uses [go-semantic-commit](https://go-semantic-release.xyz/) to create new
releases.
The commit message should be structured as follows:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
The commit contains the following structural elements, to communicate intent to the consumers of your library:
1. **fix:** a commit of the type `fix` patches gets released with a PATCH version bump
1. **feat:** a commit of the type `feat` gets released as a MINOR version bump
1. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:` gets released as a MAJOR version bump
1. types other than `fix:` and `feat:` are allowed and don't trigger a release
If a commit does not contain a conventional commit style message you can fix
it during the squash and merge operation on the PR.
Once a commit has landed on the `main` branch a release will be created and automatically published to [pypi](https://pypi.org/)
using the GitHub Action in [.github/workflows/pypi.yaml](./.github/workflows/pypi.yaml) which uses [twine](https://twine.readthedocs.io/)
to publish the package to pypi. Additionaly a container image based on the [RaBe Python Minimal Base Image](https://github.com/radiorabe/container-image-python-minimal) is built and published using [Docker build-push Action](https://github.com/docker/build-push-action).
This is managed in [.github/workflows/release.yaml](./.github/workflows/release.yaml).
## License
This application is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, version 3 of the License.
## Copyright
Copyright (c) 2022 [Radio Bern RaBe](http://www.rabe.ch)
Raw data
{
"_id": null,
"home_page": "https://github.com/radiorabe/nowplaying",
"name": "rabe-nowplaying",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.11,<4.0",
"maintainer_email": "",
"keywords": "",
"author": "RaBe IT-Reaktion",
"author_email": "it@rabe.ch",
"download_url": "https://files.pythonhosted.org/packages/f1/22/d65db2037530270aac3da04fe31e9e42f96c229fa0ccbf6fec411c8fa9ca/rabe_nowplaying-2.17.0.tar.gz",
"platform": null,
"description": "# Radio Bern Now Playing\n\nThis repo contains the tool we use to grab, aggregate and publish show, artist and track metadata from various sources.\n\nThe nowplaying project grabs info from RaBes playout solution and publishes them to broadcast vectors like DAB+ and Webstreams.\n\nIt also takes care of generating our live ticker at songticker.rabe.ch.\n\n## Overview\n\nThe nowplaying daemon takes various sources into account:\n\n- The RaBe playout solution (via the [virtual-saemubox](https://github.com/radiorabe/virtual-saemubox) project)\n- Input from [Klangbecken](https://github.com/radiorabe/klangbecken)\n\nIt then make an informed decision as to what should be our leading PAD data and pushes this to it's track handlers for the following sinks:\n\n- DAB+ (via the [ODR-EncoderManager](https://github.com/Opendigitalradio/ODR-EncoderManager) API)\n- Webstream by pushing to our [Icecast](https://icecast.org/) instances\n- Statically hosted XML output for browsers on songticker.rabe.ch\n\nThe sources are currently individually implemented and are being replaced with generic [RaBe CloudEvents](https://github.com/radiorabe/event-spec) based sources. In many places the legacy system is underdocumented and this documentation documents the new system.\n\n## Usage\n\nTBD\n\n### RaBe CloudEvents\n\nThe nowplaying projects receives httpd [RaBe CloudEvents](https://github.com/radiorabe/event-spec) on a dedicated web service. It reacts to them depending on the event type and source\n\nIt supports the following event types:\n\n- `ch.rabe.api.events.track.v1.trackStarted`\n- `ch.rabe.api.events.track.v1.trackFinished`\n\nAn example `trackStarted` event looks like this:\n\n```json\n{\n \"specversion\": \"1.0\",\n \"type\": \"ch.rabe.api.events.track.v1.trackStarted\",\n \"source\": \"<source>\",\n \"subject\": null,\n \"id\": \"<id>\",\n \"time\": \"2021-12-28T19:31:00Z\",\n \"datacontenttype\": \"application/json\",\n \"data\": {\n \"item.artist\": \"hairmare fusion sounds collective\",\n \"item.title\": \"C L O U D E V E N T W A V E\",\n \"item.length\": 36000\n }\n}\n```\n\nIt can be sent to the nowplaying service using cURL as follows:\n\n```bash\ncurl -vvv -u rabe:rabe -H 'Content-Type: application/cloudevents+json' -X POST -d '@event.json' localhost:8080/webhook\n```\n\nIn most cases the use of a cloudevents-sdk is recommended. The following example is based on the same [python-sdk](https://github.com/cloudevents/sdk-python) nowplaying uses.\n\n```python\nimport requests\n\nfrom cloudevents.http import CloudEvent, to_structured\n\ndef send_event(url, username, password):\n # This data defines a cloudevent\n attributes = {\n \"specversion\": \"1.0\",\n # as defined by the events-spec repo\n \"type\": \"ch.rabe.api.events.track.v1.trackStarted\",\n # for klangbecken the github link is always used as source (as per events-spec)\n \"source\": \"https://github.com/radiorabe/klangbecken\",\n # this should be generated and could/should point to a real\n # URL on either https://klangbecken.service.int.example.org\n # using a `crid://` URL based on the upcoming crid-spec.\n \"id\": \"uri:demo:12345\",\n }\n data = {\n \"item.title\": \"Track Title\",\n \"item.artist\": \"Artist\",\n # length in seconds, optional if you also implement sending the\n # not \"completely specced yet\" trackFinished event\n \"item.length\": 60,\n }\n\n event = CloudEvent(attributes, data)\n headers, body = to_structured(event)\n\n # send and print event\n requests.post(url, headers=headers, data=body, auth=(username, password))\n print(f\"Sent {event['id']} from {event['source']} with {event.data}\")\n\nif __name__ == \"__main__\":\n # local config\n url = \"https://nowplaying.service.int.example.org/webhook\"\n username = \"rabe\"\n password = \"rabe\"\n\n # do work\n send_event(url, username, password)\n```\n\n## Contributing\n\n### pre-commit hook\n\n```bash\npip install pre-commit\npip install -r requirements-dev.txt -U\npre-commit install\n```\n\n### testing\n\n```bash\npytest\n```\n\n## Release Management\n\nThe CI/CD setup uses semantic commit messages following the [conventional commits standard](https://www.conventionalcommits.org/en/v1.0.0/).\nThere is a GitHub Action in [.github/workflows/semantic-release.yaml](./.github/workflows/semantic-release.yaml)\nthat uses [go-semantic-commit](https://go-semantic-release.xyz/) to create new\nreleases.\n\nThe commit message should be structured as follows:\n\n```\n<type>[optional scope]: <description>\n\n[optional body]\n\n[optional footer(s)]\n```\n\nThe commit contains the following structural elements, to communicate intent to the consumers of your library:\n\n1. **fix:** a commit of the type `fix` patches gets released with a PATCH version bump\n1. **feat:** a commit of the type `feat` gets released as a MINOR version bump\n1. **BREAKING CHANGE:** a commit that has a footer `BREAKING CHANGE:` gets released as a MAJOR version bump\n1. types other than `fix:` and `feat:` are allowed and don't trigger a release\n\nIf a commit does not contain a conventional commit style message you can fix\nit during the squash and merge operation on the PR.\n\nOnce a commit has landed on the `main` branch a release will be created and automatically published to [pypi](https://pypi.org/)\nusing the GitHub Action in [.github/workflows/pypi.yaml](./.github/workflows/pypi.yaml) which uses [twine](https://twine.readthedocs.io/)\nto publish the package to pypi. Additionaly a container image based on the [RaBe Python Minimal Base Image](https://github.com/radiorabe/container-image-python-minimal) is built and published using [Docker build-push Action](https://github.com/docker/build-push-action).\nThis is managed in [.github/workflows/release.yaml](./.github/workflows/release.yaml).\n\n## License\n\nThis application is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Affero General Public License as published by the Free\nSoftware Foundation, version 3 of the License.\n\n## Copyright\n\nCopyright (c) 2022 [Radio Bern RaBe](http://www.rabe.ch)\n",
"bugtrack_url": null,
"license": "AGPL-3",
"summary": "Now Playing RaBe Songticker",
"version": "2.17.0",
"project_urls": {
"Homepage": "https://github.com/radiorabe/nowplaying",
"Repository": "https://github.com/radiorabe/nowplaying"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "580a2562ec1a972942e981e7d7b854855f2f750a7ecd1ac65004d7bf41273365",
"md5": "5d7816f196fcca9248a556ce91079458",
"sha256": "ea294e0c768984ada7faaaf68b96d61b94b3b7961e82f679944ec3e33ee2ba8e"
},
"downloads": -1,
"filename": "rabe_nowplaying-2.17.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5d7816f196fcca9248a556ce91079458",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11,<4.0",
"size": 40705,
"upload_time": "2023-10-31T19:52:10",
"upload_time_iso_8601": "2023-10-31T19:52:10.507472Z",
"url": "https://files.pythonhosted.org/packages/58/0a/2562ec1a972942e981e7d7b854855f2f750a7ecd1ac65004d7bf41273365/rabe_nowplaying-2.17.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f122d65db2037530270aac3da04fe31e9e42f96c229fa0ccbf6fec411c8fa9ca",
"md5": "d297b395e2f0327e3a9ed03cbb582d48",
"sha256": "08d05c81671013c69ca86c2bb33dd7bd14a9639d8721bb9be8b7a02a0e7d6bab"
},
"downloads": -1,
"filename": "rabe_nowplaying-2.17.0.tar.gz",
"has_sig": false,
"md5_digest": "d297b395e2f0327e3a9ed03cbb582d48",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11,<4.0",
"size": 35094,
"upload_time": "2023-10-31T19:52:12",
"upload_time_iso_8601": "2023-10-31T19:52:12.238662Z",
"url": "https://files.pythonhosted.org/packages/f1/22/d65db2037530270aac3da04fe31e9e42f96c229fa0ccbf6fec411c8fa9ca/rabe_nowplaying-2.17.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-10-31 19:52:12",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "radiorabe",
"github_project": "nowplaying",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "rabe-nowplaying"
}