2022-04-08 09:20:44 -06:00
|
|
|
#!/usr/bin/env python
|
2023-09-14 10:46:17 -06:00
|
|
|
|
|
|
|
"""
|
|
|
|
sync_mpv_server.py
|
|
|
|
|
|
|
|
Copyright 2022, 2023 <midnightman@protonmail.com>
|
|
|
|
Copyright 2023, Jeff Moe <moe@spacecruft.org>
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
"""
|
|
|
|
|
2023-09-13 14:21:19 -06:00
|
|
|
from Cryptodome.Cipher import AES
|
|
|
|
from Cryptodome.Util.Padding import pad, unpad
|
2022-04-08 09:20:44 -06:00
|
|
|
from configparser import ConfigParser
|
|
|
|
import socket
|
|
|
|
import select
|
|
|
|
import hashlib
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
import errno
|
|
|
|
import time
|
|
|
|
import os
|
2023-09-14 11:15:53 -06:00
|
|
|
import argparse
|
|
|
|
|
|
|
|
|
|
|
|
# Parse command line options
|
|
|
|
parser = argparse.ArgumentParser(description="Run mpv synchronization Server")
|
|
|
|
parser.add_argument(
|
|
|
|
"-i",
|
|
|
|
"--ip",
|
|
|
|
help="Server IP address (default 0.0.0.0)",
|
|
|
|
type=str,
|
|
|
|
required=False,
|
|
|
|
default="0.0.0.0",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"-p",
|
|
|
|
"--port",
|
|
|
|
help="Server network port (default 51984)",
|
|
|
|
type=int,
|
|
|
|
required=False,
|
|
|
|
default="51984",
|
|
|
|
)
|
|
|
|
parser.add_argument("-u", "--url", help="URL to play", type=str, required=False)
|
|
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
|
IP = args.ip
|
|
|
|
PORT = args.port
|
|
|
|
URL = args.url
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
HEADER_LENGTH = 32
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
FORMAT = "utf-8"
|
|
|
|
DISCONNECT_MESSAGE = "!DISCONNECT"
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
def prepare_concatenation(msg):
|
2022-04-08 09:20:44 -06:00
|
|
|
concat = str(msg).encode("utf-8")
|
2023-09-14 10:51:07 -06:00
|
|
|
concat += b" " * (HEADER_LENGTH - len(concat))
|
2022-04-08 09:20:44 -06:00
|
|
|
return concat
|
|
|
|
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
def send(clientsocket, msg):
|
2022-04-08 09:20:44 -06:00
|
|
|
global KEY
|
|
|
|
|
|
|
|
cipher = AES.new(KEY, AES.MODE_CBC)
|
|
|
|
|
|
|
|
if type(msg) is not bytes:
|
|
|
|
msg = msg.encode("utf-8")
|
|
|
|
|
|
|
|
msg = encrypt_message(msg)
|
|
|
|
send_length = prepare_concatenation(len(msg))
|
|
|
|
|
|
|
|
clientsocket.sendall(send_length)
|
|
|
|
clientsocket.sendall(msg)
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
def encrypt_message(msg):
|
|
|
|
global KEY
|
|
|
|
|
|
|
|
cipher = AES.new(KEY, AES.MODE_CBC)
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
encrypted_msg = cipher.encrypt(pad(msg, AES.block_size))
|
2022-04-08 09:20:44 -06:00
|
|
|
encrypted_msg = cipher.iv + encrypted_msg
|
|
|
|
|
|
|
|
return encrypted_msg
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
def decrypt_message(msg, IV):
|
|
|
|
global KEY
|
|
|
|
|
|
|
|
cipher = AES.new(KEY, AES.MODE_CBC, IV)
|
|
|
|
decrypted_msg = unpad(cipher.decrypt(msg), AES.block_size)
|
|
|
|
|
|
|
|
try:
|
|
|
|
decrypted_msg = decrypted_msg.decode("utf-8")
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
return decrypted_msg
|
|
|
|
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
def receive_message(client_socket):
|
2022-04-08 09:20:44 -06:00
|
|
|
try:
|
|
|
|
message_header = client_socket.recv(HEADER_LENGTH)
|
|
|
|
|
|
|
|
except:
|
|
|
|
return False
|
|
|
|
|
|
|
|
message_length = int(message_header)
|
|
|
|
|
|
|
|
msg = client_socket.recv(message_length)
|
|
|
|
decrypted_msg = decrypt_message(msg[16:], msg[:16])
|
|
|
|
|
|
|
|
return decrypted_msg
|
|
|
|
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
def parse_config(parser, configfile):
|
2022-04-08 09:20:44 -06:00
|
|
|
parser.read(configfile)
|
2023-09-14 10:51:07 -06:00
|
|
|
return parser.get("connection", "password")
|
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
def initialize(parser, configfile):
|
2023-09-14 10:51:07 -06:00
|
|
|
print(
|
|
|
|
"Decide for a 16-digit password.\nPassword will be saved under '~/.config/sync-mpv/serverpassword.conf'\n"
|
|
|
|
)
|
2022-04-08 09:20:44 -06:00
|
|
|
while True:
|
|
|
|
PASSWORD = input("Password: ")
|
|
|
|
if len(PASSWORD) == 16:
|
|
|
|
break
|
|
|
|
print(f"\nPassword is {len(PASSWORD)} digits long. Choose another.")
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
parser["connection"] = {
|
|
|
|
"password": "%s" % PASSWORD,
|
2022-04-08 09:20:44 -06:00
|
|
|
}
|
2023-09-14 10:51:07 -06:00
|
|
|
with open(configfile, "w") as f:
|
2022-04-08 09:20:44 -06:00
|
|
|
parser.write(f)
|
|
|
|
return PASSWORD
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
def main():
|
|
|
|
global KEY
|
|
|
|
|
|
|
|
configfolder = os.path.expanduser("~/.config/sync-mpv/")
|
|
|
|
configfile = os.path.expanduser("~/.config/sync-mpv/serverpassword.conf")
|
|
|
|
parser = ConfigParser()
|
|
|
|
|
|
|
|
if os.path.exists(configfolder):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
os.mkdir(configfolder)
|
|
|
|
|
|
|
|
if os.path.exists(configfile):
|
|
|
|
PASSWORD = parse_config(parser, configfile)
|
|
|
|
else:
|
|
|
|
PASSWORD = initialize(parser, configfile)
|
|
|
|
|
|
|
|
KEY = hashlib.sha256(PASSWORD.encode()).digest()
|
|
|
|
|
|
|
|
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
server_socket.bind((IP, PORT))
|
|
|
|
server_socket.listen()
|
|
|
|
|
|
|
|
sockets_list = [server_socket]
|
|
|
|
clients = {}
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
print(f"Listening for connections on {IP}:{PORT}...")
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
readycounter = 0
|
2023-09-14 11:15:53 -06:00
|
|
|
last_video = URL
|
2022-04-08 09:20:44 -06:00
|
|
|
while True:
|
2023-09-14 10:51:07 -06:00
|
|
|
read_sockets, _, exception_sockets = select.select(
|
|
|
|
sockets_list, [], sockets_list
|
|
|
|
)
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
for notified_socket in read_sockets:
|
|
|
|
# If notified socket is a server socket - new connection, accept it
|
|
|
|
if notified_socket == server_socket:
|
|
|
|
readycounter = 0
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
client_socket, client_address = server_socket.accept()
|
|
|
|
|
|
|
|
user = receive_message(client_socket)
|
|
|
|
|
|
|
|
# If False - client disconnected before he sent his name
|
2023-09-14 10:51:07 -06:00
|
|
|
if user is False or user == b"":
|
2022-04-08 09:20:44 -06:00
|
|
|
continue
|
|
|
|
|
|
|
|
sockets_list.append(client_socket)
|
|
|
|
clients[client_socket] = user
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
print(
|
|
|
|
"Accepted new connection from {}:{}, username: {}".format(
|
|
|
|
*client_address, user
|
|
|
|
)
|
|
|
|
)
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
for client in clients:
|
|
|
|
send(client, f"number of clients {len(clients)}")
|
|
|
|
|
|
|
|
if client != server_socket and client != client_socket:
|
|
|
|
send(client, f"userconnected {user}")
|
|
|
|
|
|
|
|
if last_video is not None:
|
|
|
|
send(client, f"mpv new {last_video}")
|
|
|
|
|
|
|
|
# Else existing socket is sending a message
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
message = receive_message(notified_socket)
|
|
|
|
|
|
|
|
# If False, client disconnected, cleanup
|
|
|
|
except:
|
2023-09-14 10:51:07 -06:00
|
|
|
# print('Closed connection from: {}'.format(clients[notified_socket]))
|
|
|
|
# sockets_list.remove(notified_socket)
|
2022-04-08 09:20:44 -06:00
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
# del clients[notified_socket]
|
|
|
|
# readycounter = 0
|
2022-04-08 09:20:44 -06:00
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
# for client in clients:
|
2022-04-08 09:20:44 -06:00
|
|
|
# send(client, "number of clients %s"%len(clients))
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if message != False:
|
2023-09-14 10:51:07 -06:00
|
|
|
# Get user by notified socket, so we will know who sent the message
|
2022-04-08 09:20:44 -06:00
|
|
|
user = clients[notified_socket]
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
print(f"Received message from {user}: {message}")
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
if "mpv new" in message:
|
|
|
|
readycounter = 0
|
|
|
|
last_video = message[8:]
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
# if "mpv skip" in message:
|
2022-04-08 09:20:44 -06:00
|
|
|
# readycounter = 0
|
|
|
|
|
|
|
|
if "ready" in message:
|
|
|
|
print(len(clients))
|
|
|
|
readycounter += 1
|
|
|
|
print("readycounter ", readycounter)
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
else:
|
2022-04-08 09:20:44 -06:00
|
|
|
readycounter = 0
|
|
|
|
|
|
|
|
if message == "!DISCONNECT":
|
|
|
|
for client_socket in clients:
|
|
|
|
if client_socket != notified_socket:
|
2023-09-14 10:51:07 -06:00
|
|
|
send(
|
|
|
|
client_socket,
|
|
|
|
f"{clients[notified_socket]} disconnected. , {user}",
|
|
|
|
)
|
2022-04-08 09:20:44 -06:00
|
|
|
|
|
|
|
send(notified_socket, message)
|
|
|
|
sockets_list.remove(notified_socket)
|
|
|
|
del clients[notified_socket]
|
|
|
|
|
|
|
|
if readycounter == len(clients):
|
|
|
|
for client_socket in clients:
|
|
|
|
send(client_socket, "mpv playback")
|
|
|
|
readycounter = 0
|
|
|
|
|
|
|
|
for client_socket in clients:
|
|
|
|
if "!DISCONNECT" != message:
|
|
|
|
if client_socket != notified_socket:
|
|
|
|
send(client_socket, f"{message} , {user}")
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
# It's not really necessary to have this, but will handle some socket exceptions just in case
|
|
|
|
for notified_socket in exception_sockets:
|
|
|
|
# Remove from list for socket.socket()
|
|
|
|
sockets_list.remove(notified_socket)
|
|
|
|
|
|
|
|
# Remove from our list of users
|
|
|
|
del clients[notified_socket]
|
|
|
|
|
2023-09-14 10:51:07 -06:00
|
|
|
|
2022-04-08 09:20:44 -06:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|