Compare commits
No commits in common. "06040a1a0520718051580992d789fdb6dfc78fe2" and "87c8c5c786cb96650f7d953fffc2c44a72833a58" have entirely different histories.
06040a1a05
...
87c8c5c786
@ -1,33 +0,0 @@
|
|||||||
name: Build Docker Image
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Read current version
|
|
||||||
id: get_version
|
|
||||||
run: echo "current_version=$(cat VERSION)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Log in to Gitea Docker Registry
|
|
||||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login https://gitea.suda.codes -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
gitea.suda.codes/sudacode/mpv-youtube-queue-server:${{ env.current_version }}
|
|
||||||
gitea.suda.codes/sudacode/mpv-youtube-queue-server:latest
|
|
||||||
|
|
||||||
- name: Log out from Gitea Docker Registry
|
|
||||||
run: docker logout https://gitea.suda.codes
|
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1 @@
|
|||||||
.env
|
.env
|
||||||
env/*
|
|
||||||
.git
|
|
||||||
|
25
Dockerfile
25
Dockerfile
@ -1,25 +0,0 @@
|
|||||||
# Use an official Python runtime as a parent image
|
|
||||||
FROM python:3.10-slim
|
|
||||||
|
|
||||||
# Set environment variables for the MPV socket and server host/port
|
|
||||||
ENV LISTEN_ADDRESS="0.0.0.0" \
|
|
||||||
LISTEN_PORT=8080
|
|
||||||
|
|
||||||
# Set the working directory in the container
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy the current directory contents into the container at /app
|
|
||||||
COPY server.py requirements.txt /app/
|
|
||||||
|
|
||||||
# Install any needed packages specified in requirements.txt
|
|
||||||
# If there are no external dependencies, you can skip this step
|
|
||||||
# RUN pip install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
# Make port 8080 available to the world outside this container
|
|
||||||
EXPOSE "${PORT_NUMBER}"
|
|
||||||
|
|
||||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
# Run server.py when the container launches
|
|
||||||
# CMD ["python3", "server.py", "--host", "${LISTEN_ADDRESS}", "--port", "${LISTEN_PORT}", "--input-ipc-server", "${MPV_SOCKET}"]
|
|
||||||
CMD gunicorn --bind "${LISTEN_ADDRESS}":"${LISTEN_PORT}" server:app
|
|
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
services:
|
|
||||||
mpv-youtube-queue-server:
|
|
||||||
build: .
|
|
||||||
image: mpv-youtube-queue-server:latest
|
|
||||||
container_name: mpv-youtube-queue-server
|
|
||||||
user: 1000:1000
|
|
||||||
volumes:
|
|
||||||
- /tmp:/tmp
|
|
||||||
ports:
|
|
||||||
- 42069:8080
|
|
||||||
env_file: .env
|
|
||||||
networks:
|
|
||||||
- mpv-youtube-queue-server
|
|
||||||
restart: unless-stopped
|
|
||||||
networks:
|
|
||||||
mpv-youtube-queue-server:
|
|
||||||
external: true
|
|
16
env.example
16
env.example
@ -1,13 +1,3 @@
|
|||||||
# Server config
|
IP=0.0.0.0 # Listen on all interfaces
|
||||||
LISTEN_ADDRESS=0.0.0.0 # Lisen on all interfaces
|
PORT_NUMBER=8080 # You can change this port if needed
|
||||||
LISTEN_PORT=8080 # Internal port number
|
MPV_SOCKET=/mpvsocket
|
||||||
MPV_SOCKET=/tmp/mpvsocket # Path to mpv socket
|
|
||||||
|
|
||||||
# MySQL connection config
|
|
||||||
MYSQL_HOST=localhost
|
|
||||||
MYSQL_USER=mpvuser
|
|
||||||
MYSQL_PASSWORD=SecretPassword
|
|
||||||
MYSQL_DATABASE=mpv
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
LOGLEVEL=warning
|
|
||||||
|
@ -10,10 +10,6 @@ Restart=on-failure
|
|||||||
Environment="MPV_SOCKET=/tmp/mpvsocket"
|
Environment="MPV_SOCKET=/tmp/mpvsocket"
|
||||||
Environment="HOST_NAME=0.0.0.0"
|
Environment="HOST_NAME=0.0.0.0"
|
||||||
Environment="PORT_NUMBER=42069"
|
Environment="PORT_NUMBER=42069"
|
||||||
Environment="MYSQL_HOST=http://localhost"
|
|
||||||
Environment="MYSQL_USER=mpvuser"
|
|
||||||
Environment="MYSQL_PASSWORD=SecretPassword"
|
|
||||||
Environment="MYSQL_PORT=3306"
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
blinker==1.8.2
|
|
||||||
click==8.1.7
|
|
||||||
Flask==3.0.3
|
|
||||||
gunicorn==23.0.0
|
|
||||||
itsdangerous==2.2.0
|
|
||||||
Jinja2==3.1.4
|
|
||||||
MarkupSafe==2.1.5
|
|
||||||
mysql-connector-python==9.0.0
|
|
||||||
packaging==24.1
|
|
||||||
Werkzeug==3.0.4
|
|
242
server.py
242
server.py
@ -1,120 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from datetime import date
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
import mysql.connector
|
|
||||||
from flask import Flask, jsonify, request
|
|
||||||
from mysql.connector import Error
|
|
||||||
|
|
||||||
MAX_RETRIES = 5
|
|
||||||
SOCKET_RETRY_DELAY = 1.5
|
|
||||||
# Configuration
|
# Configuration
|
||||||
MPV_SOCKET: str = os.getenv("MPV_SOCKET", "/tmp/mpvsocket")
|
MPV_SOCKET = os.getenv("MPV_SOCKET", "/tmp/mpvsocket")
|
||||||
HOST_NAME: str = os.getenv("HOST_NAME", "0.0.0.0")
|
HOST_NAME = os.getenv("HOST_NAME", "0.0.0.0")
|
||||||
PORT_NUMBER: int = int(os.getenv("PORT_NUMBER", "8080"))
|
PORT_NUMBER = int(os.getenv("PORT_NUMBER", "8080"))
|
||||||
# MySQL Configuration
|
SOCKET_RETRY_DELAY = 5 # Time in seconds between retries to connect to the socket
|
||||||
MYSQL_HOST: str = os.getenv("MYSQL_HOST", "localhost")
|
MAX_RETRIES = 10 # Maximum number of retries to connect to the socket
|
||||||
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()
|
|
||||||
|
|
||||||
# Initialize Flask
|
# Set up basic logging
|
||||||
app = Flask(__name__)
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
def setup_logging():
|
|
||||||
"""Sets up logging for both the app and flask."""
|
|
||||||
if not app.logger.hasHandlers(): # Check if there are already handlers
|
|
||||||
# Create a formatter
|
|
||||||
formatter = logging.Formatter(
|
|
||||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a console handler
|
|
||||||
console_handler = logging.StreamHandler()
|
|
||||||
console_handler.setFormatter(formatter)
|
|
||||||
|
|
||||||
# Configure the root logger
|
def send_to_mpv(command):
|
||||||
app.logger.addHandler(console_handler)
|
|
||||||
|
|
||||||
# Set the log level based on LOGLEVEL
|
|
||||||
if LOGLEVEL == "DEBUG":
|
|
||||||
app.logger.setLevel(logging.DEBUG)
|
|
||||||
elif LOGLEVEL == "WARNING":
|
|
||||||
app.logger.setLevel(logging.WARNING)
|
|
||||||
elif LOGLEVEL == "ERROR":
|
|
||||||
app.logger.setLevel(logging.ERROR)
|
|
||||||
else:
|
|
||||||
app.logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# Silence noisy logs from certain libraries if necessary
|
|
||||||
logging.getLogger("logger").setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
setup_logging()
|
|
||||||
|
|
||||||
|
|
||||||
def get_mysql_connection():
|
|
||||||
"""
|
|
||||||
Get a MySQL database connection.
|
|
||||||
|
|
||||||
--------
|
|
||||||
Returns
|
|
||||||
--------
|
|
||||||
connection: mysql.connector.connection.MySQLConnection
|
|
||||||
The MySQL connection object if successful, otherwise None.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
connection = mysql.connector.connect(
|
|
||||||
host=MYSQL_HOST,
|
|
||||||
user=MYSQL_USER,
|
|
||||||
password=MYSQL_PASSWORD,
|
|
||||||
port=MYSQL_PORT,
|
|
||||||
)
|
|
||||||
if connection.is_connected():
|
|
||||||
app.logger.debug("Connected to database successfully.")
|
|
||||||
return connection
|
|
||||||
except Error as e:
|
|
||||||
app.logger.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()
|
|
||||||
app.logger.info("Ensured watch_history table exists")
|
|
||||||
except Error as e:
|
|
||||||
app.logger.error(f"Failed to ensure watch_history table exists: {e}")
|
|
||||||
finally:
|
|
||||||
cursor.close()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
def send_to_mpv(command: str):
|
|
||||||
"""Send a command to the mpv socket, retrying up to MAX_RETRIES times if the socket is not available."""
|
"""Send a command to the mpv socket, retrying up to MAX_RETRIES times if the socket is not available."""
|
||||||
attempts = 0
|
attempts = 0
|
||||||
while attempts < MAX_RETRIES:
|
while attempts < MAX_RETRIES:
|
||||||
@ -122,122 +27,63 @@ def send_to_mpv(command: str):
|
|||||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client_socket:
|
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client_socket:
|
||||||
client_socket.connect(MPV_SOCKET)
|
client_socket.connect(MPV_SOCKET)
|
||||||
client_socket.sendall(command.encode("utf-8"))
|
client_socket.sendall(command.encode("utf-8"))
|
||||||
app.logger.info("Command sent to mpv successfully.")
|
logging.info("Command sent to mpv successfully.")
|
||||||
return True
|
return True
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
app.logger.error(
|
logging.error(
|
||||||
f"Failed to connect to socket (attempt {attempts}/{MAX_RETRIES}): {e}. Retrying in {SOCKET_RETRY_DELAY} seconds..."
|
f"Failed to connect to socket (attempt {attempts}/{MAX_RETRIES}): {e}. Retrying in {SOCKET_RETRY_DELAY} seconds..."
|
||||||
)
|
)
|
||||||
time.sleep(SOCKET_RETRY_DELAY)
|
time.sleep(SOCKET_RETRY_DELAY)
|
||||||
|
|
||||||
app.logger.error(f"Exceeded maximum retries ({MAX_RETRIES}). Ignoring the request.")
|
logging.error(f"Exceeded maximum retries ({MAX_RETRIES}). Ignoring the request.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@app.route("/add_video", methods=["POST"])
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
def add_video():
|
def do_GET(self):
|
||||||
"""
|
# Parse the URL and extract the "url" parameter
|
||||||
Adds a video to the mpv queue and the MySQL database.
|
query_components = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
||||||
|
video_url = query_components.get("url", [None])[0]
|
||||||
|
|
||||||
--------
|
|
||||||
Parameters
|
|
||||||
--------
|
|
||||||
data: dict
|
|
||||||
The JSON data containing the video information.
|
|
||||||
|
|
||||||
Required fields:
|
|
||||||
- video_url: str
|
|
||||||
- video_name: str
|
|
||||||
- channel_url: str
|
|
||||||
- channel_name: str
|
|
||||||
|
|
||||||
e.g. {"video_url": "https://www.youtube.com/watch?v=video_id", "video_name": "video_name", "channel_url": "https://www.youtube.com/channel/channel_id", "channel_name": "channel_name"}
|
|
||||||
"""
|
|
||||||
data = request.get_json()
|
|
||||||
|
|
||||||
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 = date.today().strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
if video_url and video_name and channel_url and channel_name and watch_date:
|
|
||||||
app.logger.debug(f"Received data: {data}")
|
|
||||||
app.logger.debug(f"Watch date: {watch_date}")
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
app.logger.info("Data inserted into MySQL database")
|
|
||||||
return (
|
|
||||||
jsonify(message="Data added to mpv queue and database"),
|
|
||||||
200,
|
|
||||||
)
|
|
||||||
except Error as e:
|
|
||||||
app.logger.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:
|
|
||||||
return jsonify(message="Failed to connect to MySQL database"), 500
|
|
||||||
else:
|
|
||||||
app.logger.error("Missing required data fields")
|
|
||||||
return jsonify(message="Missing required data fields"), 400
|
|
||||||
else:
|
|
||||||
app.logger.error("Invalid JSON data")
|
|
||||||
return jsonify(message="Invalid JSON data"), 400
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", methods=["GET"])
|
|
||||||
def handle_request():
|
|
||||||
"""
|
|
||||||
Handle GET requests to the root URL. This function is used to add a video to the mpv queue.
|
|
||||||
"""
|
|
||||||
video_url = request.args.get("url")
|
|
||||||
if video_url:
|
if video_url:
|
||||||
video_url = urllib.parse.unquote(video_url) # Decode the URL
|
video_url = urllib.parse.unquote(video_url) # Decode the URL
|
||||||
app.logger.info(f"Received URL: {video_url}")
|
logging.info(f"Received URL: {video_url}")
|
||||||
|
|
||||||
# Create the command to send to mpv
|
# Create the command to send to mpv
|
||||||
command = f'{{"command": ["script-message", "add_to_youtube_queue", "{video_url}"]}}\n'
|
command = f'{{"command": ["script-message", "add_to_youtube_queue", "{video_url}"]}}\n'
|
||||||
|
|
||||||
# Try to send the command to mpv
|
# Try to send the command to mpv
|
||||||
if send_to_mpv(command):
|
if send_to_mpv(command):
|
||||||
return "URL added to mpv queue", 200
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"URL added to mpv queue")
|
||||||
else:
|
else:
|
||||||
return "Failed to add URL to mpv queue after max retries", 500
|
self.send_response(500)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"Failed to add URL to mpv queue after max retries")
|
||||||
else:
|
else:
|
||||||
app.logger.error("Missing 'url' parameter")
|
logging.error("Missing 'url' parameter")
|
||||||
return "Missing 'url' parameter", 400
|
self.send_response(400)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"Missing 'url' parameter")
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.logger.info(f"Starting server on {HOST_NAME}:{PORT_NUMBER}...")
|
logging.info(f"Starting server on {HOST_NAME}:{PORT_NUMBER}...")
|
||||||
ensure_watch_history_table_exists()
|
|
||||||
try:
|
try:
|
||||||
app.run(host=HOST_NAME, port=PORT_NUMBER)
|
httpd = HTTPServer((HOST_NAME, PORT_NUMBER), MyHandler)
|
||||||
|
logging.info(f"Server running on port {PORT_NUMBER}...")
|
||||||
|
httpd.serve_forever()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.exception(f"Error occurred: {e}")
|
logging.exception(f"Error occurred: {e}")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
app.logger.info("Server is shutting down...")
|
logging.info("Server is shutting down...")
|
||||||
app.logger.info("Server stopped.")
|
httpd.server_close()
|
||||||
|
logging.info("Server stopped.")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user