From bebeedc0ae5398cf70a202a91cbae659354b4c0d Mon Sep 17 00:00:00 2001 From: Kyle Yasuda Date: Wed, 4 Sep 2024 11:54:05 -0700 Subject: [PATCH] history-db (#1) - update to Flask - add history db for watch history tracking - update service file Co-authored-by: ksyasuda Reviewed-on: https://gitea.suda.codes/sudacode/mpv-youtube-queue-server/pulls/1 --- .gitignore | 2 + Dockerfile | 9 +- docker-compose.yml | 1 + env.example | 13 ++- mpv-youtube-queue-server.service | 4 + requirements.txt | 8 ++ server.py | 185 ++++++++++++++++++++++++------- 7 files changed, 180 insertions(+), 42 deletions(-) create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 4c49bd7..d76b9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .env +env/* +.git diff --git a/Dockerfile b/Dockerfile index 6fb47eb..4188f50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,12 @@ FROM python:3.10-slim # Set environment variables for the MPV socket and server host/port ENV MPV_SOCKET="/tmp/mpvsocket" \ HOST_NAME="0.0.0.0" \ - PORT_NUMBER=8080 + PORT_NUMBER=8080 \ + MYSQL_HOST="localhost" \ + MYSQL_USER="mpvuser" \ + MYSQL_PASSWORD="SecretPassword" \ + MYSQL_DATABASE="mpv" \ + MYSQL_PORT=3306 # Set the working directory in the container WORKDIR /app @@ -19,5 +24,7 @@ COPY server.py /app/server.py # Make port 8080 available to the world outside this container EXPOSE "${PORT_NUMBER}" +RUN pip3 install --no-cache-dir Flask mysql-connector-python + # Run server.py when the container launches CMD ["python3", "server.py", "--host", "${HOST_NAME}", "--port", "${PORT_NUMBER}", "--input-ipc-server", "${MPV_SOCKET}"] diff --git a/docker-compose.yml b/docker-compose.yml index 8f043c0..65adb63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ services: mpv-youtube-queue-server: build: . + image: mpv-youtube-queue-server:latest container_name: mpv-youtube-queue-server user: 1000:1000 volumes: diff --git a/env.example b/env.example index faecdf8..a5fe80e 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,10 @@ -IP=0.0.0.0 -PORT_NUMBER=8080 -MPV_SOCKET=/tmp/mpvsocket +IP=0.0.0.0 # Lisen on all interfaces +PORT_NUMBER=8080 # Internal port number +MPV_SOCKET=/tmp/mpvsocket # Path to mpv socket +LOGLEVEL=info + +# MySQL connection info +MYSQL_HOST=localhost +MYSQL_USER=mpvuser +MYSQL_PASSWORD=SecretPassword +MYSQL_DATABASE=mpv \ No newline at end of file diff --git a/mpv-youtube-queue-server.service b/mpv-youtube-queue-server.service index 3da2c00..99da8b9 100644 --- a/mpv-youtube-queue-server.service +++ b/mpv-youtube-queue-server.service @@ -10,6 +10,10 @@ Restart=on-failure Environment="MPV_SOCKET=/tmp/mpvsocket" Environment="HOST_NAME=0.0.0.0" Environment="PORT_NUMBER=42069" +Environment="MYSQL_HOST=http://localhost" +Environment="MYSQL_USER=mpvuser" +Environment="MYSQL_PASSWORD=SecretPassword" +Environment="MYSQL_PORT=3306" [Install] WantedBy=multi-user.target diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9705fea --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +blinker==1.8.2 +click==8.1.7 +Flask==3.0.3 +itsdangerous==2.2.0 +Jinja2==3.1.4 +MarkupSafe==2.1.5 +mysql-connector-python==9.0.0 +Werkzeug==3.0.4 diff --git a/server.py b/server.py index 5c66e0e..0b89889 100755 --- a/server.py +++ b/server.py @@ -4,20 +4,91 @@ import os import socket import time import urllib.parse -from http.server import BaseHTTPRequestHandler, HTTPServer +from datetime import date -# Configuration -MPV_SOCKET = os.getenv("MPV_SOCKET", "/tmp/mpvsocket") -HOST_NAME = os.getenv("HOST_NAME", "0.0.0.0") -PORT_NUMBER = int(os.getenv("PORT_NUMBER", "8080")) -SOCKET_RETRY_DELAY = 5 # Time in seconds between retries to connect to the socket -MAX_RETRIES = 10 # Maximum number of retries to connect to the socket +import mysql.connector +from flask import Flask, jsonify, request +from mysql.connector import Error # Set up basic logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) +SOCKET_RETRY_DELAY = 5 # Time in seconds between retries to connect to the socket +MAX_RETRIES = 10 # Maximum number of retries to connect to the socket + +# Configuration +MPV_SOCKET: str = os.getenv("MPV_SOCKET", "/tmp/mpvsocket") +HOST_NAME: str = os.getenv("HOST_NAME", "0.0.0.0") +PORT_NUMBER: int = int(os.getenv("PORT_NUMBER", "8080")) +# MySQL Configuration +MYSQL_HOST: str = os.getenv("MYSQL_HOST", "localhost") +MYSQL_DATABASE: str = os.getenv("MYSQL_DATABASE", "your_database") +MYSQL_USER: str = os.getenv("MYSQL_USER", "your_username") +MYSQL_PASSWORD: str = os.getenv("MYSQL_PASSWORD", "your_password") +MYSQL_PORT: int = int(os.getenv("MYSQL_PORT", "3306")) +LOGLEVEL = os.getenv("LOGLEVEL", "INFO").strip().upper() +if LOGLEVEL == "DEBUG": + logging.getLogger().setLevel(logging.DEBUG) +elif LOGLEVEL == "WARNING": + logging.getLogger().setLevel(logging.WARNING) +elif LOGLEVEL == "ERROR": + logging.getLogger().setLevel(logging.ERROR) +else: + logging.getLogger().setLevel(logging.INFO) + + +app = Flask(__name__) + + +def get_mysql_connection(): + """Get a MySQL database connection.""" + try: + logging.debug( + f"Connection information: {MYSQL_HOST}, {MYSQL_USER}, {MYSQL_PORT}" + ) + connection = mysql.connector.connect( + host=MYSQL_HOST, + user=MYSQL_USER, + password=MYSQL_PASSWORD, + port=MYSQL_PORT, + ) + if connection.is_connected(): + logging.info("Connected to MySQL database") + return connection + except Error as e: + logging.error(f"Error while connecting to MySQL: {e}") + return None + + +def ensure_watch_history_table_exists(): + """Ensure the watch_history table exists in the mpv schema, otherwise create it.""" + connection = get_mysql_connection() + if connection: + try: + cursor = connection.cursor() + cursor.execute("CREATE DATABASE IF NOT EXISTS mpv") + cursor.execute( + """ + CREATE TABLE IF NOT EXISTS mpv.watch_history ( + whid INT AUTO_INCREMENT PRIMARY KEY, + video_url VARCHAR(255) NOT NULL, + video_name VARCHAR(255) NOT NULL, + channel_url VARCHAR(255) NOT NULL, + channel_name VARCHAR(255) NOT NULL, + watch_date DATE NOT NULL + ) + """ + ) + connection.commit() + logging.info("Ensured watch_history table exists") + except Error as e: + logging.error(f"Failed to ensure watch_history table exists: {e}") + finally: + cursor.close() + connection.close() + def send_to_mpv(command): """Send a command to the mpv socket, retrying up to MAX_RETRIES times if the socket is not available.""" @@ -40,50 +111,88 @@ def send_to_mpv(command): return False -class MyHandler(BaseHTTPRequestHandler): - def do_GET(self): - # Parse the URL and extract the "url" parameter - query_components = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query) - video_url = query_components.get("url", [None])[0] +@app.route("/add_video", methods=["POST"]) +def add_video(): + data = request.get_json() - if video_url: - video_url = urllib.parse.unquote(video_url) # Decode the URL - logging.info(f"Received URL: {video_url}") + if data: + video_url: str = data.get("video_url") + video_name: str = data.get("video_name") + channel_url: str = data.get("channel_url") + channel_name: str = data.get("channel_name") + watch_date: date = data.get("watch_date") - # Create the command to send to mpv - command = f'{{"command": ["script-message", "add_to_youtube_queue", "{video_url}"]}}\n' + if video_url and video_name and channel_url and channel_name and watch_date: + logging.info(f"Received data: {data}") - # Try to send the command to mpv - if send_to_mpv(command): - self.send_response(200) - self.end_headers() - self.wfile.write(b"URL added to mpv queue") + # Insert the data into the MySQL database + connection = get_mysql_connection() + if connection: + try: + query = """ + INSERT INTO mpv.watch_history (video_url, video_name, channel_url, channel_name, watch_date) + VALUES (%s, %s, %s, %s, %s) + """ + cursor = connection.cursor() + cursor.execute( + query, + ( + video_url, + video_name, + channel_url, + channel_name, + watch_date, + ), + ) + connection.commit() + logging.info("Data inserted into MySQL database") + return ( + jsonify(message="Data added to mpv queue and database"), + 200, + ) + except Error as e: + logging.error(f"Failed to insert data into MySQL database: {e}") + return jsonify(message="Failed to add data to database"), 500 + finally: + cursor.close() + connection.close() else: - self.send_response(500) - self.end_headers() - self.wfile.write(b"Failed to add URL to mpv queue after max retries") + return jsonify(message="Failed to connect to MySQL database"), 500 else: - logging.error("Missing 'url' parameter") - self.send_response(400) - self.end_headers() - self.wfile.write(b"Missing 'url' parameter") + logging.error("Missing required data fields") + return jsonify(message="Missing required data fields"), 400 + else: + logging.error("Invalid JSON data") + return jsonify(message="Invalid JSON data"), 400 - def log_message(self, format, *args): - # Override default log_message method to avoid duplicate logging from BaseHTTPRequestHandler - logging.info(f"{self.address_string()} - {format % args}") + +@app.route("/", methods=["GET"]) +def handle_request(): + video_url = request.args.get("url") + if video_url: + video_url = urllib.parse.unquote(video_url) # Decode the URL + logging.info(f"Received URL: {video_url}") + + # Create the command to send to mpv + command = f'{{"command": ["script-message", "add_to_youtube_queue", "{video_url}"]}}\n' + + # Try to send the command to mpv + if send_to_mpv(command): + return "URL added to mpv queue", 200 + else: + return "Failed to add URL to mpv queue after max retries", 500 + else: + logging.error("Missing 'url' parameter") + return "Missing 'url' parameter", 400 if __name__ == "__main__": logging.info(f"Starting server on {HOST_NAME}:{PORT_NUMBER}...") - + ensure_watch_history_table_exists() try: - httpd = HTTPServer((HOST_NAME, PORT_NUMBER), MyHandler) - logging.info(f"Server running on port {PORT_NUMBER}...") - httpd.serve_forever() + app.run(host=HOST_NAME, port=PORT_NUMBER) except Exception as e: logging.exception(f"Error occurred: {e}") except KeyboardInterrupt: logging.info("Server is shutting down...") - httpd.server_close() logging.info("Server stopped.") -