FastAPI REST API server for controlling system-wide media playback on Windows, Linux, macOS, and Android. Features: - Play/Pause/Stop/Next/Previous track controls - Volume control and mute - Seek within tracks - Current track info (title, artist, album, artwork) - WebSocket real-time status updates - Script execution API - Token-based authentication - Cross-platform support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
152 lines
4.5 KiB
Python
152 lines
4.5 KiB
Python
"""Windows service installer for Media Server.
|
|
|
|
This module allows the media server to be installed as a Windows service
|
|
that starts automatically on boot.
|
|
|
|
Usage:
|
|
Install: python -m media_server.service.install_windows install
|
|
Start: python -m media_server.service.install_windows start
|
|
Stop: python -m media_server.service.install_windows stop
|
|
Remove: python -m media_server.service.install_windows remove
|
|
Debug: python -m media_server.service.install_windows debug
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import socket
|
|
import logging
|
|
|
|
try:
|
|
import win32serviceutil
|
|
import win32service
|
|
import win32event
|
|
import servicemanager
|
|
import win32api
|
|
|
|
WIN32_AVAILABLE = True
|
|
except ImportError:
|
|
WIN32_AVAILABLE = False
|
|
print("pywin32 not installed. Install with: pip install pywin32")
|
|
|
|
|
|
class MediaServerService:
|
|
"""Windows service wrapper for the Media Server."""
|
|
|
|
_svc_name_ = "MediaServer"
|
|
_svc_display_name_ = "Media Server"
|
|
_svc_description_ = "REST API server for controlling system media playback"
|
|
|
|
def __init__(self, args=None):
|
|
if WIN32_AVAILABLE:
|
|
win32serviceutil.ServiceFramework.__init__(self, args)
|
|
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
|
|
self.is_running = False
|
|
self.server = None
|
|
|
|
def SvcStop(self):
|
|
"""Stop the service."""
|
|
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
|
win32event.SetEvent(self.stop_event)
|
|
self.is_running = False
|
|
if self.server:
|
|
self.server.should_exit = True
|
|
|
|
def SvcDoRun(self):
|
|
"""Run the service."""
|
|
servicemanager.LogMsg(
|
|
servicemanager.EVENTLOG_INFORMATION_TYPE,
|
|
servicemanager.PYS_SERVICE_STARTED,
|
|
(self._svc_name_, ""),
|
|
)
|
|
self.is_running = True
|
|
self.main()
|
|
|
|
def main(self):
|
|
"""Main service loop."""
|
|
import uvicorn
|
|
from media_server.main import app
|
|
from media_server.config import settings
|
|
|
|
config = uvicorn.Config(
|
|
app,
|
|
host=settings.host,
|
|
port=settings.port,
|
|
log_level=settings.log_level.lower(),
|
|
)
|
|
self.server = uvicorn.Server(config)
|
|
self.server.run()
|
|
|
|
|
|
if WIN32_AVAILABLE:
|
|
# Dynamically inherit from ServiceFramework when available
|
|
MediaServerService = type(
|
|
"MediaServerService",
|
|
(win32serviceutil.ServiceFramework,),
|
|
dict(MediaServerService.__dict__),
|
|
)
|
|
|
|
|
|
def install_service():
|
|
"""Install the Windows service."""
|
|
if not WIN32_AVAILABLE:
|
|
print("Error: pywin32 is required for Windows service installation")
|
|
print("Install with: pip install pywin32")
|
|
return False
|
|
|
|
try:
|
|
# Get the path to the Python executable
|
|
python_exe = sys.executable
|
|
|
|
# Get the path to this module
|
|
module_path = os.path.abspath(__file__)
|
|
|
|
win32serviceutil.InstallService(
|
|
MediaServerService._svc_name_,
|
|
MediaServerService._svc_name_,
|
|
MediaServerService._svc_display_name_,
|
|
startType=win32service.SERVICE_AUTO_START,
|
|
description=MediaServerService._svc_description_,
|
|
)
|
|
print(f"Service '{MediaServerService._svc_display_name_}' installed successfully")
|
|
print("Start the service with: sc start MediaServer")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to install service: {e}")
|
|
return False
|
|
|
|
|
|
def remove_service():
|
|
"""Remove the Windows service."""
|
|
if not WIN32_AVAILABLE:
|
|
print("Error: pywin32 is required")
|
|
return False
|
|
|
|
try:
|
|
win32serviceutil.RemoveService(MediaServerService._svc_name_)
|
|
print(f"Service '{MediaServerService._svc_display_name_}' removed successfully")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to remove service: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Main entry point for service management."""
|
|
if not WIN32_AVAILABLE:
|
|
print("Error: pywin32 is required for Windows service support")
|
|
print("Install with: pip install pywin32")
|
|
sys.exit(1)
|
|
|
|
if len(sys.argv) == 1:
|
|
# Running as a service
|
|
servicemanager.Initialize()
|
|
servicemanager.PrepareToHostSingle(MediaServerService)
|
|
servicemanager.StartServiceCtrlDispatcher()
|
|
else:
|
|
# Command line management
|
|
win32serviceutil.HandleCommandLine(MediaServerService)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|