Mopidy-Radiopit 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: Mopidy-Radiopit
3
+ Version: 1.0.0
4
+ Summary: Mopidy extension for Radiopit (radiopit.drulle.lv)
5
+ License: Apache-2.0
6
+ Keywords: mopidy,radiopit,radio,streaming
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: Mopidy>=3.0
10
+ Requires-Dist: Pykka>=2.0
11
+ Requires-Dist: requests>=2.0
12
+
13
+ # Mopidy-Radiopit
14
+
15
+ [![PyPI version](https://badge.fury.io/py/Mopidy-Radiopit.svg)](https://pypi.org/project/Mopidy-Radiopit/)
16
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
17
+
18
+ Mopidy extension that exposes your [Radiopit](https://radiopit.drulle.lv) playlists and stations for browsing and playback. Works with Iris and other Mopidy web clients.
19
+
20
+ ## Installation
21
+
22
+ Install from PyPI:
23
+
24
+ ```bash
25
+ pip install Mopidy-Radiopit
26
+ ```
27
+
28
+ ## Requirements
29
+
30
+ - Python >= 3.9
31
+ - Mopidy >= 3.0
32
+ - Pykka >= 2.0
33
+ - requests >= 2.0
34
+
35
+ ## Configuration
36
+
37
+ Add to your `mopidy.conf`:
38
+
39
+ ```ini
40
+ [radiopit]
41
+ enabled = true
42
+ api_key = YOUR_API_KEY_HERE
43
+ api_url = https://radiopit-api.drulle.lv
44
+ ```
45
+
46
+ Get your API key from the Radiopit web or Android app (Settings → API Key).
47
+
48
+ ## Usage
49
+
50
+ In Iris (or any Mopidy browse client):
51
+
52
+ 1. Open **Browse**
53
+ 2. Click **Radiopit**
54
+ 3. Your playlists appear as folders
55
+ 4. Click a folder to see stations — click a station to play
56
+
57
+ ## Project structure
58
+
59
+ ```
60
+ mopidy_radiopit/
61
+ ├── __init__.py # Extension entry point
62
+ ├── actor.py # Backend + playback provider
63
+ ├── client.py # HTTP client for Radiopit API
64
+ ├── library.py # Browse/lookup library provider
65
+ └── ext.conf # Default config values
66
+ ```
67
+
68
+ ## License
69
+
70
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
71
+ ```
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ Mopidy_Radiopit.egg-info/PKG-INFO
4
+ Mopidy_Radiopit.egg-info/SOURCES.txt
5
+ Mopidy_Radiopit.egg-info/dependency_links.txt
6
+ Mopidy_Radiopit.egg-info/entry_points.txt
7
+ Mopidy_Radiopit.egg-info/requires.txt
8
+ Mopidy_Radiopit.egg-info/top_level.txt
9
+ mopidy_radiopit/__init__.py
10
+ mopidy_radiopit/actor.py
11
+ mopidy_radiopit/client.py
12
+ mopidy_radiopit/ext.conf
13
+ mopidy_radiopit/library.py
@@ -0,0 +1,2 @@
1
+ [mopidy.ext]
2
+ radiopit = mopidy_radiopit:Extension
@@ -0,0 +1,3 @@
1
+ Mopidy>=3.0
2
+ Pykka>=2.0
3
+ requests>=2.0
@@ -0,0 +1 @@
1
+ mopidy_radiopit
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: Mopidy-Radiopit
3
+ Version: 1.0.0
4
+ Summary: Mopidy extension for Radiopit (radiopit.drulle.lv)
5
+ License: Apache-2.0
6
+ Keywords: mopidy,radiopit,radio,streaming
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: Mopidy>=3.0
10
+ Requires-Dist: Pykka>=2.0
11
+ Requires-Dist: requests>=2.0
12
+
13
+ # Mopidy-Radiopit
14
+
15
+ [![PyPI version](https://badge.fury.io/py/Mopidy-Radiopit.svg)](https://pypi.org/project/Mopidy-Radiopit/)
16
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
17
+
18
+ Mopidy extension that exposes your [Radiopit](https://radiopit.drulle.lv) playlists and stations for browsing and playback. Works with Iris and other Mopidy web clients.
19
+
20
+ ## Installation
21
+
22
+ Install from PyPI:
23
+
24
+ ```bash
25
+ pip install Mopidy-Radiopit
26
+ ```
27
+
28
+ ## Requirements
29
+
30
+ - Python >= 3.9
31
+ - Mopidy >= 3.0
32
+ - Pykka >= 2.0
33
+ - requests >= 2.0
34
+
35
+ ## Configuration
36
+
37
+ Add to your `mopidy.conf`:
38
+
39
+ ```ini
40
+ [radiopit]
41
+ enabled = true
42
+ api_key = YOUR_API_KEY_HERE
43
+ api_url = https://radiopit-api.drulle.lv
44
+ ```
45
+
46
+ Get your API key from the Radiopit web or Android app (Settings → API Key).
47
+
48
+ ## Usage
49
+
50
+ In Iris (or any Mopidy browse client):
51
+
52
+ 1. Open **Browse**
53
+ 2. Click **Radiopit**
54
+ 3. Your playlists appear as folders
55
+ 4. Click a folder to see stations — click a station to play
56
+
57
+ ## Project structure
58
+
59
+ ```
60
+ mopidy_radiopit/
61
+ ├── __init__.py # Extension entry point
62
+ ├── actor.py # Backend + playback provider
63
+ ├── client.py # HTTP client for Radiopit API
64
+ ├── library.py # Browse/lookup library provider
65
+ └── ext.conf # Default config values
66
+ ```
67
+
68
+ ## License
69
+
70
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
71
+ ```
@@ -0,0 +1,59 @@
1
+ # Mopidy-Radiopit
2
+
3
+ [![PyPI version](https://badge.fury.io/py/Mopidy-Radiopit.svg)](https://pypi.org/project/Mopidy-Radiopit/)
4
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
+
6
+ Mopidy extension that exposes your [Radiopit](https://radiopit.drulle.lv) playlists and stations for browsing and playback. Works with Iris and other Mopidy web clients.
7
+
8
+ ## Installation
9
+
10
+ Install from PyPI:
11
+
12
+ ```bash
13
+ pip install Mopidy-Radiopit
14
+ ```
15
+
16
+ ## Requirements
17
+
18
+ - Python >= 3.9
19
+ - Mopidy >= 3.0
20
+ - Pykka >= 2.0
21
+ - requests >= 2.0
22
+
23
+ ## Configuration
24
+
25
+ Add to your `mopidy.conf`:
26
+
27
+ ```ini
28
+ [radiopit]
29
+ enabled = true
30
+ api_key = YOUR_API_KEY_HERE
31
+ api_url = https://radiopit-api.drulle.lv
32
+ ```
33
+
34
+ Get your API key from the Radiopit web or Android app (Settings → API Key).
35
+
36
+ ## Usage
37
+
38
+ In Iris (or any Mopidy browse client):
39
+
40
+ 1. Open **Browse**
41
+ 2. Click **Radiopit**
42
+ 3. Your playlists appear as folders
43
+ 4. Click a folder to see stations — click a station to play
44
+
45
+ ## Project structure
46
+
47
+ ```
48
+ mopidy_radiopit/
49
+ ├── __init__.py # Extension entry point
50
+ ├── actor.py # Backend + playback provider
51
+ ├── client.py # HTTP client for Radiopit API
52
+ ├── library.py # Browse/lookup library provider
53
+ └── ext.conf # Default config values
54
+ ```
55
+
56
+ ## License
57
+
58
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
59
+ ```
@@ -0,0 +1,25 @@
1
+ import pathlib
2
+
3
+ from mopidy import config, ext
4
+
5
+ __version__ = "1.0.0"
6
+
7
+
8
+ class Extension(ext.Extension):
9
+ dist_name = "Mopidy-Radiopit"
10
+ ext_name = "radiopit"
11
+ version = __version__
12
+
13
+ def get_default_config(self):
14
+ return config.read(pathlib.Path(__file__).parent / "ext.conf")
15
+
16
+ def get_config_schema(self):
17
+ schema = super().get_config_schema()
18
+ schema["api_key"] = config.String()
19
+ schema["api_url"] = config.String()
20
+ return schema
21
+
22
+ def setup(self, registry):
23
+ from .actor import RadiopitBackend
24
+
25
+ registry.add("backend", RadiopitBackend)
@@ -0,0 +1,42 @@
1
+ import logging
2
+
3
+ import pykka
4
+ from mopidy import backend
5
+
6
+ from .client import RadiopitClient
7
+ from .library import RadiopitLibraryProvider, STATION_URI_PREFIX
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class RadiopitBackend(pykka.ThreadingActor, backend.Backend):
13
+ uri_schemes = ["radiopit"]
14
+
15
+ def __init__(self, config, audio):
16
+ super().__init__()
17
+ cfg = config["radiopit"]
18
+ self._client = RadiopitClient(
19
+ api_url=cfg["api_url"],
20
+ api_key=cfg["api_key"],
21
+ )
22
+ self.library = RadiopitLibraryProvider(backend=self)
23
+ self.playback = RadiopitPlaybackProvider(audio=audio, backend=self)
24
+
25
+ def on_start(self):
26
+ logger.info("Radiopit backend started")
27
+
28
+ def on_stop(self):
29
+ logger.info("Radiopit backend stopped")
30
+
31
+
32
+ class RadiopitPlaybackProvider(backend.PlaybackProvider):
33
+ def translate_uri(self, uri):
34
+ if not uri.startswith(STATION_URI_PREFIX):
35
+ return None
36
+ station_id = uri[len(STATION_URI_PREFIX):]
37
+ station = self.backend._client.get_station(station_id)
38
+ if station:
39
+ logger.debug("Resolving %s -> %s", uri, station["url"])
40
+ return station["url"]
41
+ logger.warning("Station not found for URI: %s", uri)
42
+ return None
@@ -0,0 +1,37 @@
1
+ import logging
2
+
3
+ import requests
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class RadiopitClient:
9
+ def __init__(self, api_url, api_key):
10
+ self._api_url = api_url.rstrip("/")
11
+ self._session = requests.Session()
12
+ self._session.headers.update({"x-api-key": api_key})
13
+
14
+ def get_playlists(self):
15
+ try:
16
+ r = self._session.get(f"{self._api_url}/api/playlists", timeout=10)
17
+ r.raise_for_status()
18
+ return r.json()
19
+ except Exception as e:
20
+ logger.error("Failed to fetch playlists: %s", e)
21
+ return []
22
+
23
+ def get_stations(self):
24
+ try:
25
+ r = self._session.get(f"{self._api_url}/api/stations", timeout=10)
26
+ r.raise_for_status()
27
+ return r.json()
28
+ except Exception as e:
29
+ logger.error("Failed to fetch stations: %s", e)
30
+ return []
31
+
32
+ def get_station(self, station_id):
33
+ stations = self.get_stations()
34
+ for s in stations:
35
+ if s["_id"] == station_id:
36
+ return s
37
+ return None
@@ -0,0 +1,4 @@
1
+ [radiopit]
2
+ enabled = true
3
+ api_key =
4
+ api_url = https://radiopit-api.drulle.lv
@@ -0,0 +1,113 @@
1
+ import logging
2
+
3
+ from mopidy import backend
4
+ from mopidy.models import Album, Artist, Image, Ref, Track
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ ROOT_URI = "radiopit:root"
9
+ PLAYLIST_URI_PREFIX = "radiopit:playlist:"
10
+ STATION_URI_PREFIX = "radiopit:station:"
11
+
12
+ RADIOPIT_ICON = "https://radiopit.drulle.lv/icons/icon-192.png"
13
+
14
+
15
+ def playlist_uri(playlist_id):
16
+ return f"{PLAYLIST_URI_PREFIX}{playlist_id}"
17
+
18
+
19
+ def station_uri(station_id):
20
+ return f"{STATION_URI_PREFIX}{station_id}"
21
+
22
+
23
+ class RadiopitLibraryProvider(backend.LibraryProvider):
24
+ root_directory = Ref.directory(uri=ROOT_URI, name="Radiopit")
25
+
26
+ def __init__(self, backend):
27
+ super().__init__(backend)
28
+ self._client = backend._client
29
+
30
+ def browse(self, uri):
31
+ if uri == ROOT_URI:
32
+ return self._browse_root()
33
+ if uri.startswith(PLAYLIST_URI_PREFIX):
34
+ playlist_id = uri[len(PLAYLIST_URI_PREFIX):]
35
+ return self._browse_playlist(playlist_id)
36
+ return []
37
+
38
+ def _browse_root(self):
39
+ playlists = self._client.get_playlists()
40
+ stations = self._client.get_stations()
41
+
42
+ refs = []
43
+ for pl in sorted(playlists, key=lambda x: x.get("order", 0)):
44
+ refs.append(
45
+ Ref.directory(uri=playlist_uri(pl["_id"]), name=pl["name"])
46
+ )
47
+
48
+ unassigned = [s for s in stations if not s.get("playlistId")]
49
+ if unassigned:
50
+ refs.append(
51
+ Ref.directory(uri=playlist_uri("unassigned"), name="Unassigned")
52
+ )
53
+
54
+ return refs
55
+
56
+ def _browse_playlist(self, playlist_id):
57
+ stations = self._client.get_stations()
58
+
59
+ if playlist_id == "unassigned":
60
+ filtered = [s for s in stations if not s.get("playlistId")]
61
+ else:
62
+ filtered = [s for s in stations if s.get("playlistId") == playlist_id]
63
+
64
+ filtered = sorted(filtered, key=lambda x: x.get("order", 0))
65
+ return [Ref.track(uri=station_uri(s["_id"]), name=s["name"]) for s in filtered]
66
+
67
+ def lookup(self, uri):
68
+ if not uri.startswith(STATION_URI_PREFIX):
69
+ return []
70
+ station_id = uri[len(STATION_URI_PREFIX):]
71
+ station = self._client.get_station(station_id)
72
+ if station:
73
+ return [_station_to_track(station)]
74
+ return []
75
+
76
+ def get_images(self, uris):
77
+ logger.debug("get_images called with %d URIs: %s", len(uris), uris)
78
+ result = {}
79
+ stations = None
80
+
81
+ for uri in uris:
82
+ if uri == ROOT_URI:
83
+ result[uri] = [Image(uri=RADIOPIT_ICON)]
84
+
85
+ elif uri.startswith(PLAYLIST_URI_PREFIX):
86
+ result[uri] = [Image(uri=RADIOPIT_ICON)]
87
+
88
+ elif uri.startswith(STATION_URI_PREFIX):
89
+ if stations is None:
90
+ stations = self._client.get_stations()
91
+ station_id = uri[len(STATION_URI_PREFIX):]
92
+ for s in stations:
93
+ if s["_id"] == station_id:
94
+ icon_url = s.get("iconUrl") or RADIOPIT_ICON
95
+ result[uri] = [Image(uri=icon_url)]
96
+ logger.debug("Image for %s: %s", uri, icon_url)
97
+ break
98
+
99
+ return result
100
+
101
+ def search(self, query=None, uris=None, exact=False):
102
+ return None
103
+
104
+
105
+ def _station_to_track(station):
106
+ name = station["name"]
107
+ return Track(
108
+ uri=station_uri(station["_id"]),
109
+ name=name,
110
+ artists=[Artist(name=name)],
111
+ album=Album(name="Radiopit"),
112
+ length=None,
113
+ )
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "Mopidy-Radiopit"
7
+ version = "1.0.0"
8
+ description = "Mopidy extension for Radiopit (radiopit.drulle.lv)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Apache-2.0" }
12
+ keywords = ["mopidy", "radiopit", "radio", "streaming"]
13
+ dependencies = [
14
+ "Mopidy>=3.0",
15
+ "Pykka>=2.0",
16
+ "requests>=2.0",
17
+ ]
18
+
19
+ [project.entry-points."mopidy.ext"]
20
+ radiopit = "mopidy_radiopit:Extension"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["."]
24
+ include = ["mopidy_radiopit*"]
25
+
26
+ [tool.setuptools.package-data]
27
+ mopidy_radiopit = ["ext.conf"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+