history-db (#1)

- update to Flask
- add history db for watch history tracking
- update service file

Co-authored-by: ksyasuda <ksyasuda@umich.edu>
Reviewed-on: #1
This commit is contained in:
Kyle Yasuda 2024-09-04 11:54:05 -07:00
parent 3fcd7b1706
commit bebeedc0ae
7 changed files with 180 additions and 42 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
.env
env/*
.git

View File

@ -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}"]

View File

@ -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:

View File

@ -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

View File

@ -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

8
requirements.txt Normal file
View File

@ -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

185
server.py
View File

@ -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.")