quite a bit of stuff
This commit is contained in:
1 parent
41f6690937
commit
d3b5a2120f
6 files changed
+283
-99
No files matched your search
+9
-99
@@ -1,20 +1,17 @@
|
|||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
# import time
|
|
||||||
|
|
||||||
import xml.etree
|
import xml.etree
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
# import libsonic
|
|
||||||
# import simpleaudio
|
|
||||||
# import pyaudio
|
|
||||||
|
|
||||||
|
|
||||||
# u = 'pseudo'
|
logger = logging.getLogger(__name__)
|
||||||
# p = 'telegram9dock-MICHEL'
|
|
||||||
|
|
||||||
class Album:
|
class Album:
|
||||||
id: str
|
id: str
|
||||||
@@ -65,7 +62,7 @@ class Song:
|
|||||||
self.duration = int(song_info['duration'])
|
self.duration = int(song_info['duration'])
|
||||||
self.bitRate = int(song_info['bitRate'])
|
self.bitRate = int(song_info['bitRate'])
|
||||||
self.path = song_info['path']
|
self.path = song_info['path']
|
||||||
self.discNumber = int(song_info['discNumber'])
|
self.discNumber = int(song_info['discNumber']) if 'discNumber' in song_info else None
|
||||||
self.albumId = song_info['albumId']
|
self.albumId = song_info['albumId']
|
||||||
self.artistId = song_info['artistId']
|
self.artistId = song_info['artistId']
|
||||||
self.comment = song_info['comment']
|
self.comment = song_info['comment']
|
||||||
@@ -180,7 +177,7 @@ class SubpyConn:
|
|||||||
return ArtistIndexes(requests.get(url + self._encodePayload(self._getBasePayload())).content)
|
return ArtistIndexes(requests.get(url + self._encodePayload(self._getBasePayload())).content)
|
||||||
|
|
||||||
def _getMusicDirectory(self, id: str):
|
def _getMusicDirectory(self, id: str):
|
||||||
url = self.base_url + '/rest/getMusicDirectory?'
|
url = 'https://' + self.base_url + '/rest/getMusicDirectory?'
|
||||||
|
|
||||||
payload = self._getBasePayload()
|
payload = self._getBasePayload()
|
||||||
payload['id'] = id
|
payload['id'] = id
|
||||||
@@ -195,8 +192,6 @@ class SubpyConn:
|
|||||||
albums: list[Album] = []
|
albums: list[Album] = []
|
||||||
|
|
||||||
for item in root.iter():
|
for item in root.iter():
|
||||||
# print(item.tag, dict(item.items()))
|
|
||||||
|
|
||||||
if item.tag == '{http://subsonic.org/restapi}child':
|
if item.tag == '{http://subsonic.org/restapi}child':
|
||||||
albums.append(Album(dict(item.items())))
|
albums.append(Album(dict(item.items())))
|
||||||
|
|
||||||
@@ -204,6 +199,9 @@ class SubpyConn:
|
|||||||
|
|
||||||
def getAlbumSongs(self, album_id: str) -> list[Song]:
|
def getAlbumSongs(self, album_id: str) -> list[Song]:
|
||||||
r: requests.Response = self._getMusicDirectory(album_id)
|
r: requests.Response = self._getMusicDirectory(album_id)
|
||||||
|
logger.debug(f"Requesting all songs for album {album_id} using url {self.base_url}")
|
||||||
|
logger.debug(f'Got response with status code {r.status_code}')
|
||||||
|
|
||||||
xml_data: str = r.content.decode('utf-8')
|
xml_data: str = r.content.decode('utf-8')
|
||||||
|
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
@@ -212,6 +210,7 @@ class SubpyConn:
|
|||||||
root: xml.etree.ElementTree.Element = xml.etree.ElementTree.fromstring(xml_data)
|
root: xml.etree.ElementTree.Element = xml.etree.ElementTree.fromstring(xml_data)
|
||||||
songs: list[Song] = []
|
songs: list[Song] = []
|
||||||
|
|
||||||
|
logger.debug(f"Request Status: {root.get('status')}")
|
||||||
if root.get('status') != 'ok':
|
if root.get('status') != 'ok':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -257,92 +256,3 @@ class SubpyConn:
|
|||||||
if item.tag == '{http://subsonic.org/restapi}song':
|
if item.tag == '{http://subsonic.org/restapi}song':
|
||||||
return Song(dict(item.items()))
|
return Song(dict(item.items()))
|
||||||
|
|
||||||
# conn = SubpyConn(u, p, "https://music.0x01fe.net")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# artistIndexes = conn.getIndexes()
|
|
||||||
# for artistIndex in artistIndexes.indexes:
|
|
||||||
# print(artistIndex.name, artistIndex.artists)
|
|
||||||
|
|
||||||
helios_id = 'c69cdc593487ed813f1e11ecb270b228'
|
|
||||||
yakui_id = "7f6ca9951af294420382442061a2d6ee"
|
|
||||||
# r = conn.getMusicDirectory(helios_id)
|
|
||||||
# with open("getHelios.xml", 'wb') as file:
|
|
||||||
# file.write(r.content)
|
|
||||||
|
|
||||||
# yakui_albums = conn.getArtistAlbums(yakui_id)
|
|
||||||
|
|
||||||
# for album in yakui_albums:
|
|
||||||
# print(album.name)
|
|
||||||
|
|
||||||
# helios_songs = conn.getAlbumSongs(helios_id)
|
|
||||||
# for song in helios_songs:
|
|
||||||
# print(song.trackNumber, song.title)
|
|
||||||
|
|
||||||
# artists = conn.getArtists()
|
|
||||||
|
|
||||||
# for artist in artists:
|
|
||||||
# print(artist.name)
|
|
||||||
|
|
||||||
echidna_id = "937004317800a879f0cddd471fb82387"
|
|
||||||
|
|
||||||
# print(conn.stream(echidna_id).content)
|
|
||||||
|
|
||||||
|
|
||||||
# def getArtists() -> list[Artist]:
|
|
||||||
# tree = xml.etree.ElementTree.parse("artist_response.xml")
|
|
||||||
# root = tree.getroot()
|
|
||||||
|
|
||||||
# artists: list[Artist] = []
|
|
||||||
|
|
||||||
# if root.get('status') != 'ok':
|
|
||||||
# return []
|
|
||||||
|
|
||||||
# for item in root.iter():
|
|
||||||
# print(item.tag, dict(item.items()))
|
|
||||||
|
|
||||||
# if item.tag == '{http://subsonic.org/restapi}artist':
|
|
||||||
# a = Artist()
|
|
||||||
# a.fromDict(dict(item.items()))
|
|
||||||
|
|
||||||
# artists.append(a)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# return artists
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# artists = getArtists()
|
|
||||||
|
|
||||||
# for artist in artists:
|
|
||||||
# print(artist.name)
|
|
||||||
|
|
||||||
|
|
||||||
# conn = libsonic.Connection('https://music.0x01fe.net', 'pseudo', p, port=443)
|
|
||||||
|
|
||||||
|
|
||||||
# song = conn.getRandomSongs(1)['randomSongs']['song'][0]
|
|
||||||
# song_id = song['id']
|
|
||||||
# rate = song['samplingRate']
|
|
||||||
|
|
||||||
# print(song_id)
|
|
||||||
|
|
||||||
# stream = conn.stream(song_id)
|
|
||||||
|
|
||||||
# while not stream.fp:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
# pya = pyaudio.PyAudio()
|
|
||||||
# pya_stream = pya.open(format=)
|
|
||||||
# wave_obj = simpleaudio.WaveObject(stream.fp)
|
|
||||||
# play_obj = wave_obj.play()
|
|
||||||
# play_obj.wait_done()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Whitespace-only changes.
@@ -0,0 +1,89 @@
|
|||||||
|
# conn = SubpyConn(u, p, "https://music.0x01fe.net")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# artistIndexes = conn.getIndexes()
|
||||||
|
# for artistIndex in artistIndexes.indexes:
|
||||||
|
# print(artistIndex.name, artistIndex.artists)
|
||||||
|
|
||||||
|
helios_id = 'c69cdc593487ed813f1e11ecb270b228'
|
||||||
|
yakui_id = "7f6ca9951af294420382442061a2d6ee"
|
||||||
|
# r = conn.getMusicDirectory(helios_id)
|
||||||
|
# with open("getHelios.xml", 'wb') as file:
|
||||||
|
# file.write(r.content)
|
||||||
|
|
||||||
|
# yakui_albums = conn.getArtistAlbums(yakui_id)
|
||||||
|
|
||||||
|
# for album in yakui_albums:
|
||||||
|
# print(album.name)
|
||||||
|
|
||||||
|
# helios_songs = conn.getAlbumSongs(helios_id)
|
||||||
|
# for song in helios_songs:
|
||||||
|
# print(song.trackNumber, song.title)
|
||||||
|
|
||||||
|
# artists = conn.getArtists()
|
||||||
|
|
||||||
|
# for artist in artists:
|
||||||
|
# print(artist.name)
|
||||||
|
|
||||||
|
echidna_id = "937004317800a879f0cddd471fb82387"
|
||||||
|
|
||||||
|
# print(conn.stream(echidna_id).content)
|
||||||
|
|
||||||
|
|
||||||
|
# def getArtists() -> list[Artist]:
|
||||||
|
# tree = xml.etree.ElementTree.parse("artist_response.xml")
|
||||||
|
# root = tree.getroot()
|
||||||
|
|
||||||
|
# artists: list[Artist] = []
|
||||||
|
|
||||||
|
# if root.get('status') != 'ok':
|
||||||
|
# return []
|
||||||
|
|
||||||
|
# for item in root.iter():
|
||||||
|
# print(item.tag, dict(item.items()))
|
||||||
|
|
||||||
|
# if item.tag == '{http://subsonic.org/restapi}artist':
|
||||||
|
# a = Artist()
|
||||||
|
# a.fromDict(dict(item.items()))
|
||||||
|
|
||||||
|
# artists.append(a)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# return artists
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# artists = getArtists()
|
||||||
|
|
||||||
|
# for artist in artists:
|
||||||
|
# print(artist.name)
|
||||||
|
|
||||||
|
|
||||||
|
# conn = libsonic.Connection('https://music.0x01fe.net', 'pseudo', p, port=443)
|
||||||
|
|
||||||
|
|
||||||
|
# song = conn.getRandomSongs(1)['randomSongs']['song'][0]
|
||||||
|
# song_id = song['id']
|
||||||
|
# rate = song['samplingRate']
|
||||||
|
|
||||||
|
# print(song_id)
|
||||||
|
|
||||||
|
# stream = conn.stream(song_id)
|
||||||
|
|
||||||
|
# while not stream.fp:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
# pya = pyaudio.PyAudio()
|
||||||
|
# pya_stream = pya.open(format=)
|
||||||
|
# wave_obj = simpleaudio.WaveObject(stream.fp)
|
||||||
|
# play_obj = wave_obj.play()
|
||||||
|
# play_obj.wait_done()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<subsonic-response xmlns="http://subsonic.org/restapi" status="failed" version="1.16.1"
|
||||||
|
type="navidrome" serverVersion="0.54.3 (734eb30a)" openSubsonic="true">
|
||||||
|
<error code="70" message="Directory not found"></error>
|
||||||
|
</subsonic-response>
|
||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
LOG_FILE = 'textual.log'
|
||||||
|
LOGGING_LEVEL = logging.DEBUG
|
||||||
|
LOG_FORMAT = '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
||||||
|
|
||||||
|
# Clear Log File
|
||||||
|
os.remove(LOG_FILE)
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOG_FILE, level=LOGGING_LEVEL, format=LOG_FORMAT)
|
||||||
|
|
||||||
|
import SubSonic
|
||||||
|
|
||||||
|
|
||||||
|
from textual import on, events
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Footer, Header, OptionList, DataTable
|
||||||
|
from textual.widgets.option_list import Option
|
||||||
|
|
||||||
|
u = 'pseudo'
|
||||||
|
p = 'telegram9dock-MICHEL'
|
||||||
|
|
||||||
|
conn = SubSonic.SubpyConn(u, p, "music.0x01fe.net")
|
||||||
|
|
||||||
|
class Menus(Enum):
|
||||||
|
MAIN = "MAIN"
|
||||||
|
ARTISTS = "Artists"
|
||||||
|
ARTISTSONGS = "ArtistSongs"
|
||||||
|
ARTISTALBUMS = "ArtistAlbums"
|
||||||
|
ALBUMS = "Albums"
|
||||||
|
ALBUMSONGS = "AlbumSongs"
|
||||||
|
|
||||||
|
class TextualSubpyPlayer(App):
|
||||||
|
|
||||||
|
BINDINGS = [
|
||||||
|
('backspace', 'menu_back()', 'Go back a menu')
|
||||||
|
]
|
||||||
|
|
||||||
|
artists: list[SubSonic.Artist]
|
||||||
|
selectedArtist: SubSonic.Artist
|
||||||
|
albums: list[SubSonic.Album]
|
||||||
|
conn: SubSonic.SubpyConn
|
||||||
|
|
||||||
|
currentMenu: int
|
||||||
|
lastMenu: int
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
self.currentMenu = Menus.MAIN
|
||||||
|
self.lastMenu = Menus.MAIN
|
||||||
|
yield OptionList(
|
||||||
|
Option("Browse Artists", id="BrowseArtists"),
|
||||||
|
Option("Browse Albums"),
|
||||||
|
Option("Browse Songs"),
|
||||||
|
Option("Browse Playlists"),
|
||||||
|
id=Menus.MAIN.value
|
||||||
|
)
|
||||||
|
|
||||||
|
async def action_menu_back(self) -> None:
|
||||||
|
logging.debug('Menu Back Called')
|
||||||
|
match self.lastMenu:
|
||||||
|
case Menus.MAIN:
|
||||||
|
await self.main_menu()
|
||||||
|
case Menus.ARTISTALBUMS:
|
||||||
|
await self.selectArtistAlbumView(self.selectedArtist.id)
|
||||||
|
case Menus.ARTISTS:
|
||||||
|
await self.selectArtistsView()
|
||||||
|
|
||||||
|
def on_key(self, event: events.Key) -> None:
|
||||||
|
logging.debug(f'Key Pressed {event.name}')
|
||||||
|
|
||||||
|
|
||||||
|
def action_toggle_dark(self) -> None:
|
||||||
|
"""An action to toggle dark mode."""
|
||||||
|
self.theme = (
|
||||||
|
"textual-dark" if self.theme == "textual-light" else "textual-light"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _getArtistMenu(self) -> OptionList:
|
||||||
|
self.artists = self.conn.getArtists()
|
||||||
|
|
||||||
|
return OptionList(id="ArtistList")
|
||||||
|
|
||||||
|
|
||||||
|
@on(OptionList.OptionSelected, "#mainMenu")
|
||||||
|
async def selectMainMenu(self, event: OptionList.OptionSelected) -> None:
|
||||||
|
match event.option_id:
|
||||||
|
case "BrowseArtists":
|
||||||
|
logging.debug('View Artists Selected')
|
||||||
|
await self.selectArtistsView()
|
||||||
|
|
||||||
|
|
||||||
|
@on(OptionList.OptionSelected, "#ArtistList")
|
||||||
|
async def artistSelected(self, event: OptionList.OptionSelected) -> None:
|
||||||
|
logging.debug(f'Artist {event.option.prompt} Selected')
|
||||||
|
self.lastMenu = Menus.ARTISTS
|
||||||
|
|
||||||
|
for artist in self.artists:
|
||||||
|
if event.option_id == artist.id:
|
||||||
|
self.selectedArtist = artist
|
||||||
|
|
||||||
|
await self.selectArtistAlbumView(self.selectedArtist.id)
|
||||||
|
|
||||||
|
|
||||||
|
@on(DataTable.RowSelected, "#ArtistAlbums")
|
||||||
|
async def ArtistAlbumSelected(self, event: DataTable.RowSelected) -> None:
|
||||||
|
self.lastMenu = Menus.ARTISTALBUMS
|
||||||
|
row_key = event.row_key
|
||||||
|
row = event.data_table.get_row(row_key)
|
||||||
|
logging.debug(f'Album {row[0]} Selected')
|
||||||
|
|
||||||
|
await self.selectAlbumSongsView(row_key.value)
|
||||||
|
|
||||||
|
async def selectAlbumSongsView(self, album_id: str) -> None:
|
||||||
|
logging.debug("Setting up album songs DataTable")
|
||||||
|
self.currentMenu = Menus.ALBUMSONGS
|
||||||
|
|
||||||
|
songs = conn.getAlbumSongs(album_id)
|
||||||
|
albumSongs = DataTable(id="AlbumSongs")
|
||||||
|
|
||||||
|
await self.mount(albumSongs)
|
||||||
|
|
||||||
|
albumSongs.cursor_type = 'row'
|
||||||
|
albumSongs.add_columns("#", "Title", "Artist", "Duration", "Bitrate")
|
||||||
|
|
||||||
|
for song in songs:
|
||||||
|
albumSongs.add_row(song.trackNumber, song.title, song.artist, song.duration, song.bitRate, key=song.id)
|
||||||
|
|
||||||
|
self.query_one(f"#{self.lastMenu}").remove()
|
||||||
|
|
||||||
|
async def selectArtistAlbumView(self, artist_id: str) -> None:
|
||||||
|
logging.debug("Setting up album DataTable")
|
||||||
|
self.currentMenu = Menus.ARTISTALBUMS
|
||||||
|
|
||||||
|
albumTable = DataTable(id="ArtistAlbums")
|
||||||
|
|
||||||
|
await self.mount(albumTable)
|
||||||
|
|
||||||
|
albumTable.cursor_type = 'row'
|
||||||
|
|
||||||
|
albumTable.add_columns("Title", "Songs")
|
||||||
|
|
||||||
|
self.albums = conn.getArtistAlbums(artist_id)
|
||||||
|
for album in self.albums:
|
||||||
|
albumTable.add_row(album.title, album.songCount, key=album.id)
|
||||||
|
|
||||||
|
self.query_one(f"#{self.lastMenu}").remove()
|
||||||
|
|
||||||
|
async def main_menu(self) -> None:
|
||||||
|
await self.mount(OptionList(
|
||||||
|
Option("Browse Artists", id="BrowseArtists"),
|
||||||
|
Option("Browse Albums"),
|
||||||
|
Option("Browse Songs"),
|
||||||
|
Option("Browse Playlists"),
|
||||||
|
id=Menus.MAIN.value
|
||||||
|
))
|
||||||
|
|
||||||
|
self.query_one(f"#{self.lastMenu}").remove()
|
||||||
|
|
||||||
|
async def selectArtistsView(self) -> None:
|
||||||
|
artistList: OptionList = self._getArtistMenu()
|
||||||
|
|
||||||
|
await self.mount(artistList)
|
||||||
|
|
||||||
|
for artist in self.artists:
|
||||||
|
artistList.add_option(Option(artist.name, artist.id))
|
||||||
|
|
||||||
|
self.query_one(f"#{self.lastMenu.value}").remove()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = TextualSubpyPlayer()
|
||||||
|
app.conn = SubSonic.SubpyConn(u, p, "https://music.0x01fe.net")
|
||||||
|
app.run()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[2025-01-19 13:15:06,036] DEBUG in proactor_events: Using proactor: IocpProactor
|
||||||
|
[2025-01-19 13:15:06,436] DEBUG in textual-test: Key Pressed enter
|
||||||
|
[2025-01-19 13:15:06,563] DEBUG in textual-test: Key Pressed enter
|
||||||
|
[2025-01-19 13:15:06,800] DEBUG in textual-test: Key Pressed backspace
|
||||||
|
[2025-01-19 13:15:06,800] DEBUG in textual-test: Menu Back Called
|
||||||
Reference in new issue
Block a user