From d3b5a2120f66a57d8d1d11e2ef584a8139a73ae1 Mon Sep 17 00:00:00 2001 From: 0x01fe <0x01fe@0x01fe.net> Date: Mon, 20 Jan 2025 22:45:47 -0600 Subject: [PATCH] quite a bit of stuff --- SubSonic.py | 108 +++------------------------ echidna.wav | 0 random_test_code.py | 89 ++++++++++++++++++++++ test.xml | 4 + textual-test.py | 176 ++++++++++++++++++++++++++++++++++++++++++++ textual.log | 5 ++ 6 files changed, 283 insertions(+), 99 deletions(-) delete mode 100644 echidna.wav create mode 100644 random_test_code.py create mode 100644 test.xml create mode 100644 textual-test.py create mode 100644 textual.log diff --git a/SubSonic.py b/SubSonic.py index 2094ff3..5eddd07 100644 --- a/SubSonic.py +++ b/SubSonic.py @@ -1,20 +1,17 @@ import random import string import hashlib +import logging import urllib.parse -# import time import xml.etree import xml.etree.ElementTree import requests -# import libsonic -# import simpleaudio -# import pyaudio -# u = 'pseudo' -# p = 'telegram9dock-MICHEL' +logger = logging.getLogger(__name__) + class Album: id: str @@ -65,7 +62,7 @@ class Song: self.duration = int(song_info['duration']) self.bitRate = int(song_info['bitRate']) 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.artistId = song_info['artistId'] self.comment = song_info['comment'] @@ -180,7 +177,7 @@ class SubpyConn: return ArtistIndexes(requests.get(url + self._encodePayload(self._getBasePayload())).content) def _getMusicDirectory(self, id: str): - url = self.base_url + '/rest/getMusicDirectory?' + url = 'https://' + self.base_url + '/rest/getMusicDirectory?' payload = self._getBasePayload() payload['id'] = id @@ -195,8 +192,6 @@ class SubpyConn: albums: list[Album] = [] for item in root.iter(): - # print(item.tag, dict(item.items())) - if item.tag == '{http://subsonic.org/restapi}child': albums.append(Album(dict(item.items()))) @@ -204,6 +199,9 @@ class SubpyConn: def getAlbumSongs(self, album_id: str) -> list[Song]: 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') if r.status_code != 200: @@ -212,6 +210,7 @@ class SubpyConn: root: xml.etree.ElementTree.Element = xml.etree.ElementTree.fromstring(xml_data) songs: list[Song] = [] + logger.debug(f"Request Status: {root.get('status')}") if root.get('status') != 'ok': return [] @@ -257,92 +256,3 @@ class SubpyConn: if item.tag == '{http://subsonic.org/restapi}song': 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() - - - - - - diff --git a/echidna.wav b/echidna.wav deleted file mode 100644 index e69de29..0000000 diff --git a/random_test_code.py b/random_test_code.py new file mode 100644 index 0000000..788d891 --- /dev/null +++ b/random_test_code.py @@ -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() + + + + + + diff --git a/test.xml b/test.xml new file mode 100644 index 0000000..c85d69a --- /dev/null +++ b/test.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/textual-test.py b/textual-test.py new file mode 100644 index 0000000..2fecc87 --- /dev/null +++ b/textual-test.py @@ -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() diff --git a/textual.log b/textual.log new file mode 100644 index 0000000..1dd07df --- /dev/null +++ b/textual.log @@ -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