added naomusic.py and moved all music stuff to it
This commit is contained in:
+147
@@ -0,0 +1,147 @@
|
||||
# naomusic.py
|
||||
# -*- encoding: UTF-8 -*-
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import threading
|
||||
|
||||
class NaoMusicPlayer:
|
||||
def __init__(self, session, nao_ip):
|
||||
self.session = session
|
||||
self.player = session.service("ALAudioPlayer")
|
||||
self.nao_ip = nao_ip
|
||||
self.music_dir = "/home/nao/music"
|
||||
self.local_path = "/tmp/song.mp3"
|
||||
self.remote_path = f"{self.music_dir}/song.mp3"
|
||||
try:
|
||||
subprocess.run(["sshpass", "-p", "nao", "ssh", f"nao@{nao_ip}", f"mkdir -p {self.music_dir}"],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except: pass
|
||||
self.current_file = None
|
||||
self.current_task_id = None
|
||||
self.is_playing = False
|
||||
self.current_title = ""
|
||||
self.is_loading = False
|
||||
self.load_error = None
|
||||
self.status = ""
|
||||
self.progress = 0.0
|
||||
|
||||
def search(self, query):
|
||||
try:
|
||||
cmd = ["yt-dlp", f"ytsearch5:{query}", "--flat-playlist", "--dump-json",
|
||||
"--no-warnings", "--quiet"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
entries = []
|
||||
for line in result.stdout.strip().splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
data = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
vid = data.get("id")
|
||||
url = data.get("url")
|
||||
if not url or not url.startswith("http"):
|
||||
url = f"https://www.youtube.com/watch?v={vid}" if vid else None
|
||||
if url:
|
||||
entries.append({"url": url, "title": data.get("title", "Unknown")})
|
||||
return entries
|
||||
except Exception as e:
|
||||
print(f"Search error: {e}")
|
||||
return []
|
||||
|
||||
_DL_PROGRESS_RE = re.compile(r'\[download\]\s+([\d.]+)%')
|
||||
|
||||
def play(self, url, title):
|
||||
self.stop()
|
||||
self.current_title = title
|
||||
self.is_loading = True
|
||||
self.load_error = None
|
||||
self.status = "Starting download..."
|
||||
self.progress = 0.0
|
||||
try:
|
||||
if os.path.exists(self.local_path):
|
||||
os.remove(self.local_path)
|
||||
except: pass
|
||||
|
||||
try:
|
||||
cmd = ["yt-dlp", url,
|
||||
"-x", "--audio-format", "mp3",
|
||||
"-o", self.local_path,
|
||||
"--no-playlist", "--no-warnings", "--newline"]
|
||||
print(f"\n▶ yt-dlp {url}")
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, bufsize=1)
|
||||
for line in proc.stdout:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
print(f" [yt-dlp] {line}")
|
||||
m = self._DL_PROGRESS_RE.search(line)
|
||||
if m:
|
||||
self.progress = float(m.group(1))
|
||||
self.status = f"Downloading... {self.progress:.0f}%"
|
||||
elif "ExtractAudio" in line or "Extracting audio" in line:
|
||||
self.status = "Converting to mp3..."
|
||||
elif line.startswith("[download] Destination"):
|
||||
self.status = "Downloading..."
|
||||
proc.wait(timeout=120)
|
||||
|
||||
if proc.returncode != 0 or not os.path.exists(self.local_path):
|
||||
print(f"Download failed (exit {proc.returncode})")
|
||||
self.load_error = "Download failed"
|
||||
return False
|
||||
|
||||
size_mb = os.path.getsize(self.local_path) / (1024 * 1024)
|
||||
print(f"▶ scp {self.local_path} -> nao@{self.nao_ip}:{self.remote_path} ({size_mb:.1f} MB)")
|
||||
self.progress = 0.0
|
||||
upload_start = time.time()
|
||||
stop_ticker = threading.Event()
|
||||
|
||||
def ticker():
|
||||
while not stop_ticker.is_set():
|
||||
elapsed = time.time() - upload_start
|
||||
self.status = f"Uploading to NAO ({size_mb:.1f} MB)... {elapsed:.0f}s"
|
||||
stop_ticker.wait(1.0)
|
||||
|
||||
t = threading.Thread(target=ticker, daemon=True)
|
||||
t.start()
|
||||
scp_result = subprocess.run(
|
||||
["sshpass", "-p", "nao", "scp", self.local_path, f"nao@{self.nao_ip}:{self.remote_path}"],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
stop_ticker.set()
|
||||
t.join(timeout=1.0)
|
||||
print(f" [scp] exit {scp_result.returncode}" +
|
||||
(f": {scp_result.stderr.strip()}" if scp_result.stderr.strip() else ""))
|
||||
if scp_result.returncode != 0:
|
||||
print(f"SCP failed: {scp_result.stderr.strip()}")
|
||||
self.load_error = "SCP failed"
|
||||
return False
|
||||
|
||||
self.current_file = self.remote_path
|
||||
self.status = "Playing"
|
||||
self.progress = 100.0
|
||||
self.current_task_id = self.player.playFile(self.remote_path)
|
||||
self.is_playing = True
|
||||
print(f"♪ Playing on NAO: {title} (task {self.current_task_id})")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Playback error: {e}")
|
||||
self.load_error = str(e)
|
||||
return False
|
||||
finally:
|
||||
self.is_loading = False
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self.player.stopAll()
|
||||
except Exception as e:
|
||||
print(f"Stop error: {e}")
|
||||
self.is_playing = False
|
||||
self.current_file = None
|
||||
self.current_task_id = None
|
||||
self.current_title = ""
|
||||
Reference in New Issue
Block a user