commit
325ff8b550
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,25 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<value>
|
||||
<list size="12">
|
||||
<item index="0" class="java.lang.String" itemvalue="requests-oauthlib" />
|
||||
<item index="1" class="java.lang.String" itemvalue="Werkzeug" />
|
||||
<item index="2" class="java.lang.String" itemvalue="Flask-Assets" />
|
||||
<item index="3" class="java.lang.String" itemvalue="SQLAlchemy" />
|
||||
<item index="4" class="java.lang.String" itemvalue="libsass" />
|
||||
<item index="5" class="java.lang.String" itemvalue="requests" />
|
||||
<item index="6" class="java.lang.String" itemvalue="Flask-SQLAlchemy" />
|
||||
<item index="7" class="java.lang.String" itemvalue="Jinja2" />
|
||||
<item index="8" class="java.lang.String" itemvalue="Flask-Login" />
|
||||
<item index="9" class="java.lang.String" itemvalue="Flask" />
|
||||
<item index="10" class="java.lang.String" itemvalue="typing_extensions" />
|
||||
<item index="11" class="java.lang.String" itemvalue="grpc" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11 (SpotiMIDI)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (SpotiMIDI)" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/SpotiMIDI.iml" filepath="$PROJECT_DIR$/.idea/SpotiMIDI.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,45 @@
|
||||
import threading
|
||||
import time
|
||||
import mido
|
||||
from mido import MidiFile
|
||||
|
||||
from StoppableThread import StoppableThread
|
||||
from Requestor import Requestor
|
||||
|
||||
class MediaPlayer:
|
||||
|
||||
def __init__(self, host, port):
|
||||
self.requestor = Requestor(host, port)
|
||||
self.song_thread = None
|
||||
self.output = mido.open_output()
|
||||
|
||||
def request_song(self, song_name):
|
||||
bytes_song = self.requestor.getSong(song_name)
|
||||
|
||||
with open('received_song.mid', 'wb') as file:
|
||||
file.write(bytes_song)
|
||||
|
||||
if self.song_thread is not None and self.song_thread.is_alive():
|
||||
self.song_thread.stop()
|
||||
|
||||
self.song_thread = StoppableThread(target=self.play_song, args=('received_song.mid',))
|
||||
self.song_thread.start()
|
||||
|
||||
def play_song(self, song_path):
|
||||
mid = MidiFile(song_path)
|
||||
try:
|
||||
for msg in mid.play():
|
||||
if threading.current_thread().stopped():
|
||||
break
|
||||
|
||||
self.output.send(msg)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
finally:
|
||||
self.output.reset()
|
||||
|
||||
def stop_song(self):
|
||||
if self.song_thread is not None and self.song_thread.is_alive():
|
||||
self.song_thread.stop()
|
@ -0,0 +1,32 @@
|
||||
import socket
|
||||
|
||||
|
||||
class Requestor(object):
|
||||
|
||||
def __init__(self, host: str, port: int):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
|
||||
def getSongList(self) -> list:
|
||||
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
client.connect((self.host, self.port))
|
||||
client.send('getSongList'.encode('utf-8'))
|
||||
song_list = client.recv(1024).decode('utf-8')
|
||||
client.close()
|
||||
return song_list.split('\n')
|
||||
|
||||
def getSong(self, song_id: int) -> bytes:
|
||||
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
client.connect((self.host, self.port))
|
||||
client.send(f'getSong:{song_id}'.encode('utf-8'))
|
||||
|
||||
song_data = b""
|
||||
while True:
|
||||
chunk = client.recv(1024)
|
||||
if not chunk:
|
||||
break
|
||||
song_data += chunk
|
||||
|
||||
client.close()
|
||||
return song_data
|
@ -0,0 +1,13 @@
|
||||
import threading
|
||||
|
||||
class StoppableThread(threading.Thread):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._stop_event = threading.Event()
|
||||
self._pause_event = threading.Event()
|
||||
|
||||
def stop(self):
|
||||
self._stop_event.set()
|
||||
|
||||
def stopped(self):
|
||||
return self._stop_event.is_set()
|
@ -0,0 +1,40 @@
|
||||
import os
|
||||
import tkinter as tk
|
||||
|
||||
from MediaPlayer import MediaPlayer
|
||||
|
||||
CLIENT_HOST = "localhost"
|
||||
CLIENT_PORT = 9999
|
||||
media_player = MediaPlayer(CLIENT_HOST, CLIENT_PORT)
|
||||
|
||||
def request_selected_song():
|
||||
selected_song = song_listbox.get(song_listbox.curselection())
|
||||
media_player.request_song(selected_song)
|
||||
|
||||
def stop_song():
|
||||
media_player.stop_song()
|
||||
|
||||
root = tk.Tk()
|
||||
root.title("Song Requestor")
|
||||
|
||||
song_listbox = tk.Listbox(root, height=10, width=50)
|
||||
song_listbox.pack()
|
||||
|
||||
song_list = media_player.requestor.getSongList()
|
||||
for song in song_list:
|
||||
if song: # Check if song is not an empty string
|
||||
song_listbox.insert(tk.END, song)
|
||||
|
||||
request_button = tk.Button(root, text="Play song", command=request_selected_song)
|
||||
request_button.pack()
|
||||
|
||||
play_pause_button = tk.Button(root, text="Stop song", command=stop_song)
|
||||
play_pause_button.pack()
|
||||
|
||||
def on_close():
|
||||
media_player.stop_song()
|
||||
os.remove('received_song.mid')
|
||||
root.destroy()
|
||||
|
||||
root.protocol("WM_DELETE_WINDOW", on_close)
|
||||
root.mainloop()
|
@ -0,0 +1,69 @@
|
||||
import os
|
||||
|
||||
from mido import MetaMessage
|
||||
|
||||
from Song import Song
|
||||
import mido
|
||||
|
||||
class Library(object):
|
||||
|
||||
def __init__(self):
|
||||
self.songs = []
|
||||
|
||||
def initialize(self, songs_dir: str):
|
||||
print("Initializing library")
|
||||
for root, dirs, files in os.walk(songs_dir):
|
||||
for i, file in enumerate(files):
|
||||
if file.endswith('.mid'):
|
||||
name = file.split('-')[0]
|
||||
author = file.split('-')[1].split('.')[0]
|
||||
path = os.path.join(root, file)
|
||||
song = Song(i, name, author, path, self.get_request_count_from_midi_file(path))
|
||||
print(f"Loaded song: {song.name} by {song.author} with {song.total_requests} requests")
|
||||
self.songs.append(song)
|
||||
|
||||
def get_request_count_from_midi_file(self, file_path):
|
||||
mid = mido.MidiFile(file_path)
|
||||
|
||||
for i, track in enumerate(mid.tracks):
|
||||
for msg in track:
|
||||
if msg.type == 'text' and 'Request count:' in msg.text:
|
||||
return int(msg.text.split(': ')[1])
|
||||
|
||||
# If no 'text' MetaMessage with the request count is found, add one with a count of 0
|
||||
msg = MetaMessage('text', text='Request count: 0')
|
||||
mid.tracks[0].append(msg)
|
||||
mid.save(file_path)
|
||||
return 0
|
||||
|
||||
def get_song_list(self) -> str:
|
||||
sorted = self.songs.copy()
|
||||
sorted.sort(key=lambda x: x.total_requests, reverse=True)
|
||||
song_list = ""
|
||||
for song in sorted:
|
||||
song_list += f"{song.id}: {song.name} by {song.author}\n"
|
||||
return song_list
|
||||
|
||||
def get_song_by_id(self, id: int) -> Song:
|
||||
for song in self.songs:
|
||||
if song.id == id:
|
||||
# Increment the request count
|
||||
song.total_requests += 1
|
||||
|
||||
# Load the MIDI file
|
||||
mid = mido.MidiFile(song.path)
|
||||
|
||||
# Remove the old 'text' MetaMessage with the request count
|
||||
for i, track in enumerate(mid.tracks):
|
||||
for j, msg in enumerate(track):
|
||||
if msg.type == 'text' and 'Request count:' in msg.text:
|
||||
del track[j]
|
||||
|
||||
# Add a new 'text' MetaMessage with the updated request count
|
||||
mid.tracks[0].append(MetaMessage('text', text=f'Request count: {song.total_requests}'))
|
||||
|
||||
# Save the MIDI file
|
||||
mid.save(song.path)
|
||||
|
||||
return song
|
||||
return None
|
@ -0,0 +1,17 @@
|
||||
class Song(object):
|
||||
|
||||
def __init__(self, id: int, name: str, author: str, path: str, total_requests: int):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.author = author
|
||||
self.path = path
|
||||
self.total_requests = total_requests
|
||||
|
||||
def get_total_requests(self) -> int:
|
||||
return self.total_requests
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.name
|
||||
|
||||
def get_data(self) -> bytes:
|
||||
return open(self.path, 'rb').read()
|
@ -0,0 +1,39 @@
|
||||
import socket
|
||||
import threading
|
||||
|
||||
from Library import Library
|
||||
from Song import Song
|
||||
import os
|
||||
|
||||
SONGS_DIR = os.path.join(os.path.dirname(__file__), 'songs')
|
||||
|
||||
library = Library()
|
||||
library.initialize(SONGS_DIR)
|
||||
|
||||
def handle_client(client_socket):
|
||||
request = client_socket.recv(1024).decode('utf-8')
|
||||
print(f"Received: {request}")
|
||||
|
||||
if request == 'getSongList':
|
||||
client_socket.send(library.get_song_list().encode('utf-8'))
|
||||
|
||||
elif request.startswith('getSong:'):
|
||||
song_id = int(request.split(':')[1])
|
||||
song: Song = library.get_song_by_id(song_id)
|
||||
print(f"Sending song: {song.name} by {song.author} to {client_socket.getpeername()}")
|
||||
client_socket.send(song.get_data())
|
||||
client_socket.close()
|
||||
|
||||
def start_server():
|
||||
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server.bind(("0.0.0.0", 9999))
|
||||
server.listen(5)
|
||||
print("Listening on port 9999")
|
||||
|
||||
while True:
|
||||
client, addr = server.accept()
|
||||
print(f"Accepted connection from: {addr[0]}:{addr[1]}")
|
||||
client_handler = threading.Thread(target=handle_client, args=(client,))
|
||||
client_handler.start()
|
||||
|
||||
start_server()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue