From cbc9254c604e7e645fcd646811cbf1378c5b5a18 Mon Sep 17 00:00:00 2001 From: Minh Date: Sat, 5 Apr 2025 22:46:03 -0700 Subject: [PATCH 1/2] initial-speaker-cli --- fastAPI/audio43.py | 73 +++++++++++++++++++++++++++++++++++ fastAPI/main.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 fastAPI/audio43.py create mode 100644 fastAPI/main.py diff --git a/fastAPI/audio43.py b/fastAPI/audio43.py new file mode 100644 index 0000000..a95f7d0 --- /dev/null +++ b/fastAPI/audio43.py @@ -0,0 +1,73 @@ +import subprocess +import queue +import time +import sys + +def get_youtube_audio_urls(playlist_url): + # Create a list to store the URLs + audio_urls = [] + + # Run the yt-dlp command and capture its output + command = f'yt-dlp -f bestaudio -g "{playlist_url}"' + try: + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + + # Check for errors + if process.returncode != 0: + print(f"Error extracting URLs: {stderr.decode('utf-8')}") + return [] + + # Decode the output and split by newlines to get individual URLs + output = stdout.decode('utf-8') + urls = [url for url in output.strip().split('\n') if url and url.startswith('http')] + + print(f"Found {len(urls)} audio URLs") + return urls + except Exception as e: + print(f"Exception while extracting URLs: {str(e)}") + return [] + +def mpv_queue(audio_urls): + total_count = len(audio_urls) + played_count = 0 + + if total_count == 0: + print("No audio URLs found.") + return + + # Initialize playing queue + playing_queue = audio_urls[:3] # Take the first 3 URLs + remaining_queue = audio_urls[3:] # The rest of the URLs + + print(f"Total videos: {total_count}, Initially in play queue: {len(playing_queue)}") + + # Process each URL in the playing queue + while playing_queue: + audio_url = playing_queue.pop(0) # Get the next URL + played_count += 1 + + print(f"Playing video {played_count}/{total_count}: {audio_url[:50]}...") + + try: + # Play the audio + command = f'mpv --cache=yes --no-video --force-window=no "{audio_url}"' + process = subprocess.Popen(command, shell=True) + process.wait() + + # If there are more URLs in the remaining queue, add one to the playing queue + if remaining_queue: + next_url = remaining_queue.pop(0) + playing_queue.append(next_url) + print(f"Added next video to queue. Remaining: {len(remaining_queue)}") + except Exception as e: + print(f"Error playing {audio_url[:50]}: {str(e)}") + # If an error occurs, still try to continue with the next URL + continue + + print(f"Finished playing {played_count}/{total_count} videos") + +if __name__ == "__main__": + playlist_url = 'https://www.youtube.com/playlist?list=PLyLqO_HeaCB5QxDs04P0un3SAyfGYS-GW' + audio_urls = get_youtube_audio_urls(playlist_url) + mpv_queue(audio_urls) \ No newline at end of file diff --git a/fastAPI/main.py b/fastAPI/main.py new file mode 100644 index 0000000..28fef0d --- /dev/null +++ b/fastAPI/main.py @@ -0,0 +1,95 @@ +from fastapi import FastAPI, BackgroundTasks +import subprocess +import uvicorn +from typing import Dict, List +from pydantic import BaseModel + +app = FastAPI() + +class PlaylistRequest(BaseModel): + url: str + +@app.get("/") +def root(): + return {"Hello": "World"} + +@app.post("/play") +async def play_youtube_playlist(request: PlaylistRequest, background_tasks: BackgroundTasks): + # Get the URLs + audio_urls = get_youtube_audio_urls(request.url) + + # Start playing in the background + background_tasks.add_task(mpv_queue, audio_urls) + + return { + "message": "Started playing playlist", + "playlist_url": request.url, + "videos_found": len(audio_urls) + } + +def get_youtube_audio_urls(playlist_url): + # Create a list to store the URLs + audio_urls = [] + + # Run the yt-dlp command and capture its output + command = f'yt-dlp -f bestaudio -g "{playlist_url}"' + try: + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + + # Check for errors + if process.returncode != 0: + print(f"Error extracting URLs: {stderr.decode('utf-8')}") + return [] + + # Decode the output and split by newlines to get individual URLs + output = stdout.decode('utf-8') + urls = [url for url in output.strip().split('\n') if url and url.startswith('http')] + + print(f"Found {len(urls)} audio URLs") + return urls + except Exception as e: + print(f"Exception while extracting URLs: {str(e)}") + return [] + +def mpv_queue(audio_urls): + total_count = len(audio_urls) + played_count = 0 + + if total_count == 0: + print("No audio URLs found.") + return + + # Initialize playing queue + playing_queue = audio_urls[:3] # Take the first 3 URLs + remaining_queue = audio_urls[3:] # The rest of the URLs + + print(f"Total videos: {total_count}, Initially in play queue: {len(playing_queue)}") + + # Process each URL in the playing queue + while playing_queue: + audio_url = playing_queue.pop(0) # Get the next URL + played_count += 1 + + print(f"Playing video {played_count}/{total_count}: {audio_url[:50]}...") + + try: + # Play the audio + command = f'mpv --cache=yes --no-video --force-window=no "{audio_url}"' + process = subprocess.Popen(command, shell=True) + process.wait() + + # If there are more URLs in the remaining queue, add one to the playing queue + if remaining_queue: + next_url = remaining_queue.pop(0) + playing_queue.append(next_url) + print(f"Added next video to queue. Remaining: {len(remaining_queue)}") + except Exception as e: + print(f"Error playing {audio_url[:50]}: {str(e)}") + # If an error occurs, still try to continue with the next URL + continue + + print(f"Finished playing {played_count}/{total_count} videos") + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file From a3ab878e4f36c53e84e425699a39a1930dd648fb Mon Sep 17 00:00:00 2001 From: Minh Phong Do Date: Mon, 21 Apr 2025 15:50:30 -0700 Subject: [PATCH 2/2] clean up code with updated gitignore and requirements.txt --- .gitignore | 14 ++++++- README.md | 6 +++ fastAPI/audio43.py | 45 +++++++++++++++------ fastAPI/main.py | 85 ++++------------------------------------ fastAPI/requirements.txt | 14 +++++++ 5 files changed, 73 insertions(+), 91 deletions(-) create mode 100644 fastAPI/requirements.txt diff --git a/.gitignore b/.gitignore index 4df48fd..ddc7815 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ node_modules/ -config.json \ No newline at end of file +config.json + +# Virtual Environment +venv/ +env/ +.venv/ + +# Python +__pycache__/ +*.pyc + +# Logs +errors.log \ No newline at end of file diff --git a/README.md b/README.md index 6e2117e..7ec619e 100644 --- a/README.md +++ b/README.md @@ -30,3 +30,9 @@ ctl.!default ctl.custom Copy this into ~/.asoundrc or wherever your alsa sound file is configured `sudo alsactl --no-ucm store` + +#### Installation for testing speaker cli + +Install dependencies: +```bash +pip install -r requirements.txt \ No newline at end of file diff --git a/fastAPI/audio43.py b/fastAPI/audio43.py index a95f7d0..8e023ea 100644 --- a/fastAPI/audio43.py +++ b/fastAPI/audio43.py @@ -1,12 +1,14 @@ import subprocess -import queue -import time -import sys +import argparse +from datetime import datetime + +MAX_URL_DISPLAY_LENGTH = 50 + +# Open error log file in append mode +def get_error_logger(): + return open('errors.log', 'a') def get_youtube_audio_urls(playlist_url): - # Create a list to store the URLs - audio_urls = [] - # Run the yt-dlp command and capture its output command = f'yt-dlp -f bestaudio -g "{playlist_url}"' try: @@ -15,7 +17,11 @@ def get_youtube_audio_urls(playlist_url): # Check for errors if process.returncode != 0: - print(f"Error extracting URLs: {stderr.decode('utf-8')}") + error_msg = f"[{datetime.now()}] Error extracting URLs: {stderr.decode('utf-8')}" + print(error_msg) + with get_error_logger() as error_log: + error_log.write(error_msg + '\n') + error_log.flush() return [] # Decode the output and split by newlines to get individual URLs @@ -25,7 +31,11 @@ def get_youtube_audio_urls(playlist_url): print(f"Found {len(urls)} audio URLs") return urls except Exception as e: - print(f"Exception while extracting URLs: {str(e)}") + error_msg = f"[{datetime.now()}] Exception while extracting URLs: {str(e)}" + print(error_msg) + with get_error_logger() as error_log: + error_log.write(error_msg + '\n') + error_log.flush() return [] def mpv_queue(audio_urls): @@ -47,7 +57,7 @@ def mpv_queue(audio_urls): audio_url = playing_queue.pop(0) # Get the next URL played_count += 1 - print(f"Playing video {played_count}/{total_count}: {audio_url[:50]}...") + print(f"Playing video {played_count}/{total_count}: {audio_url[:MAX_URL_DISPLAY_LENGTH]}...") try: # Play the audio @@ -61,13 +71,24 @@ def mpv_queue(audio_urls): playing_queue.append(next_url) print(f"Added next video to queue. Remaining: {len(remaining_queue)}") except Exception as e: - print(f"Error playing {audio_url[:50]}: {str(e)}") + error_msg = f"[{datetime.now()}] Error playing {audio_url[:MAX_URL_DISPLAY_LENGTH]}: {str(e)}" + print(error_msg) + with get_error_logger() as error_log: + error_log.write(error_msg + '\n') + error_log.flush() # If an error occurs, still try to continue with the next URL continue print(f"Finished playing {played_count}/{total_count} videos") +# This function parses command line arguments +def parse_arguments(): + parser = argparse.ArgumentParser(description='Play audio from YouTube playlists using mpv') + parser.add_argument('--url', '-u', type=str, required=True, + help='YouTube playlist URL to extract audio from') + return parser.parse_args() + if __name__ == "__main__": - playlist_url = 'https://www.youtube.com/playlist?list=PLyLqO_HeaCB5QxDs04P0un3SAyfGYS-GW' - audio_urls = get_youtube_audio_urls(playlist_url) + args = parse_arguments() + audio_urls = get_youtube_audio_urls(args.url) mpv_queue(audio_urls) \ No newline at end of file diff --git a/fastAPI/main.py b/fastAPI/main.py index 28fef0d..94fa379 100644 --- a/fastAPI/main.py +++ b/fastAPI/main.py @@ -1,95 +1,24 @@ from fastapi import FastAPI, BackgroundTasks -import subprocess import uvicorn -from typing import Dict, List -from pydantic import BaseModel +from audio43 import get_youtube_audio_urls, mpv_queue -app = FastAPI() +MAX_URL_DISPLAY_LENGTH = 50 -class PlaylistRequest(BaseModel): - url: str - -@app.get("/") -def root(): - return {"Hello": "World"} +app = FastAPI() @app.post("/play") -async def play_youtube_playlist(request: PlaylistRequest, background_tasks: BackgroundTasks): +async def play_youtube_playlist(url: str, background_tasks: BackgroundTasks): # Get the URLs - audio_urls = get_youtube_audio_urls(request.url) + audio_urls = get_youtube_audio_urls(url) # Start playing in the background background_tasks.add_task(mpv_queue, audio_urls) return { "message": "Started playing playlist", - "playlist_url": request.url, + "playlist_url": url, "videos_found": len(audio_urls) } -def get_youtube_audio_urls(playlist_url): - # Create a list to store the URLs - audio_urls = [] - - # Run the yt-dlp command and capture its output - command = f'yt-dlp -f bestaudio -g "{playlist_url}"' - try: - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = process.communicate() - - # Check for errors - if process.returncode != 0: - print(f"Error extracting URLs: {stderr.decode('utf-8')}") - return [] - - # Decode the output and split by newlines to get individual URLs - output = stdout.decode('utf-8') - urls = [url for url in output.strip().split('\n') if url and url.startswith('http')] - - print(f"Found {len(urls)} audio URLs") - return urls - except Exception as e: - print(f"Exception while extracting URLs: {str(e)}") - return [] - -def mpv_queue(audio_urls): - total_count = len(audio_urls) - played_count = 0 - - if total_count == 0: - print("No audio URLs found.") - return - - # Initialize playing queue - playing_queue = audio_urls[:3] # Take the first 3 URLs - remaining_queue = audio_urls[3:] # The rest of the URLs - - print(f"Total videos: {total_count}, Initially in play queue: {len(playing_queue)}") - - # Process each URL in the playing queue - while playing_queue: - audio_url = playing_queue.pop(0) # Get the next URL - played_count += 1 - - print(f"Playing video {played_count}/{total_count}: {audio_url[:50]}...") - - try: - # Play the audio - command = f'mpv --cache=yes --no-video --force-window=no "{audio_url}"' - process = subprocess.Popen(command, shell=True) - process.wait() - - # If there are more URLs in the remaining queue, add one to the playing queue - if remaining_queue: - next_url = remaining_queue.pop(0) - playing_queue.append(next_url) - print(f"Added next video to queue. Remaining: {len(remaining_queue)}") - except Exception as e: - print(f"Error playing {audio_url[:50]}: {str(e)}") - # If an error occurs, still try to continue with the next URL - continue - - print(f"Finished playing {played_count}/{total_count} videos") - if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file + uvicorn.run(app, host="localhost", port=8000) \ No newline at end of file diff --git a/fastAPI/requirements.txt b/fastAPI/requirements.txt new file mode 100644 index 0000000..964165f --- /dev/null +++ b/fastAPI/requirements.txt @@ -0,0 +1,14 @@ +annotated-types==0.7.0 +anyio==4.9.0 +click==8.1.8 +colorama==0.4.6 +fastapi==0.115.12 +h11==0.14.0 +idna==3.10 +pydantic==2.11.3 +pydantic_core==2.33.1 +sniffio==1.3.1 +starlette==0.46.2 +typing-inspection==0.4.0 +typing_extensions==4.13.2 +uvicorn==0.34.2