mirror of
https://github.com/iAmInActions/random-scripts.git
synced 2024-11-21 19:40:12 +00:00
Add python ASCII video (and audio) socket server and client
This commit is contained in:
parent
1b758cb627
commit
ebc593ffc9
140
py-ascii-video-socket/client.py
Normal file
140
py-ascii-video-socket/client.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import pyaudio
|
||||||
|
import threading
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
|
|
||||||
|
# Global variable for volume control
|
||||||
|
volume = 1.0
|
||||||
|
|
||||||
|
def play_audio(audio_socket):
|
||||||
|
""" Function to continuously play audio data received from the server """
|
||||||
|
global volume
|
||||||
|
p = pyaudio.PyAudio()
|
||||||
|
stream = p.open(format=pyaudio.paUInt8, # 8-bit unsigned format
|
||||||
|
channels=1, # Mono audio
|
||||||
|
rate=8000, # 8000 Hz sample rate
|
||||||
|
output=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
audio_chunk = audio_socket.recv(1024)
|
||||||
|
if not audio_chunk:
|
||||||
|
break
|
||||||
|
# Adjust the volume of the audio chunk
|
||||||
|
adjusted_chunk = bytes(min(int(sample * volume), 255) for sample in audio_chunk)
|
||||||
|
stream.write(adjusted_chunk)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error playing audio: {e}")
|
||||||
|
finally:
|
||||||
|
stream.stop_stream()
|
||||||
|
stream.close()
|
||||||
|
p.terminate()
|
||||||
|
|
||||||
|
def handle_key_input(video_socket, audio_socket):
|
||||||
|
""" Function to handle key input for quitting and volume control """
|
||||||
|
global volume
|
||||||
|
|
||||||
|
# Save the terminal settings
|
||||||
|
original_settings = termios.tcgetattr(sys.stdin)
|
||||||
|
tty.setcbreak(sys.stdin.fileno()) # Set the terminal to cbreak mode for unbuffered input
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
key = sys.stdin.read(1) # Read a single character
|
||||||
|
if key.lower() == 'q': # Quit on 'q' or 'Q'
|
||||||
|
print("Quitting...")
|
||||||
|
video_socket.close()
|
||||||
|
audio_socket.close()
|
||||||
|
break
|
||||||
|
elif key == '+': # Increase volume on '+'
|
||||||
|
volume = min(volume + 0.1, 2.0) # Cap volume at 2.0
|
||||||
|
print(f"Volume increased to {volume}")
|
||||||
|
elif key == '-': # Decrease volume on '-'
|
||||||
|
volume = max(volume - 0.1, 0.0) # Floor volume at 0.0
|
||||||
|
print(f"Volume decreased to {volume}")
|
||||||
|
finally:
|
||||||
|
# Restore the terminal settings
|
||||||
|
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, original_settings)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="ASCII Video Client")
|
||||||
|
parser.add_argument("video_url", help="URL of the video to stream")
|
||||||
|
parser.add_argument("server_ip", help="IP address of the server")
|
||||||
|
parser.add_argument("port", type=int, help="Port number of the server")
|
||||||
|
parser.add_argument("contrast", type=float, nargs="?", default=1.0, help="Contrast adjustment (default: 1.0)")
|
||||||
|
parser.add_argument("brightness", type=float, nargs="?", default=0.0, help="Brightness adjustment (default: 0.0)")
|
||||||
|
parser.add_argument("-c", "--color", action="store_true", help="Enable color mode using ANSI color codes")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Get the terminal size
|
||||||
|
terminal_size = os.get_terminal_size()
|
||||||
|
width = terminal_size.columns
|
||||||
|
height = terminal_size.lines
|
||||||
|
|
||||||
|
# Connect to the server for video
|
||||||
|
try:
|
||||||
|
video_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
video_socket.connect((args.server_ip, args.port))
|
||||||
|
print("Connected to video server successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect to video server: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send the video URL, contrast, brightness, color flag, and terminal size
|
||||||
|
color_mode = "1" if args.color else "0"
|
||||||
|
video_socket.send(f"{args.video_url} {args.contrast} {args.brightness} {color_mode} {width} {height}".encode())
|
||||||
|
|
||||||
|
# Connect to the server for audio
|
||||||
|
try:
|
||||||
|
audio_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
audio_socket.connect((args.server_ip, args.port + 1))
|
||||||
|
print("Connected to audio server successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to connect to audio server: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Start a separate thread for playing audio
|
||||||
|
audio_thread = threading.Thread(target=play_audio, args=(audio_socket,))
|
||||||
|
audio_thread.start()
|
||||||
|
|
||||||
|
# Handle key input in the main thread
|
||||||
|
key_thread = threading.Thread(target=handle_key_input, args=(video_socket, audio_socket))
|
||||||
|
key_thread.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Receive the frame size
|
||||||
|
frame_size_data = video_socket.recv(4)
|
||||||
|
if not frame_size_data:
|
||||||
|
break
|
||||||
|
frame_size = struct.unpack("I", frame_size_data)[0]
|
||||||
|
|
||||||
|
# Receive the full frame data
|
||||||
|
frame_data = b""
|
||||||
|
while len(frame_data) < frame_size:
|
||||||
|
chunk = video_socket.recv(frame_size - len(frame_data))
|
||||||
|
if not chunk:
|
||||||
|
print("Failed to receive full frame data. Connection may be closed.")
|
||||||
|
return
|
||||||
|
frame_data += chunk
|
||||||
|
|
||||||
|
# Clear the terminal and display the frame
|
||||||
|
os.system('clear' if os.name == 'posix' else 'cls')
|
||||||
|
print(frame_data.decode(), end="\r")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error during video reception: {e}")
|
||||||
|
finally:
|
||||||
|
video_socket.close()
|
||||||
|
audio_socket.close()
|
||||||
|
audio_thread.join()
|
||||||
|
key_thread.join()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
154
py-ascii-video-socket/server.py
Normal file
154
py-ascii-video-socket/server.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
import threading
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
FPS = 8
|
||||||
|
# ASCII_CHARS = '$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^. ' # For inverted terminals
|
||||||
|
ASCII_CHARS = ' .^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'
|
||||||
|
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
ANSI_COLORS = [
|
||||||
|
"\033[38;5;16m", # Black
|
||||||
|
"\033[38;5;88m", # Dark Red
|
||||||
|
"\033[38;5;22m", # Dark Green
|
||||||
|
"\033[38;5;130m", # Brown
|
||||||
|
"\033[38;5;19m", # Dark Blue
|
||||||
|
"\033[38;5;54m", # Dark Magenta
|
||||||
|
"\033[38;5;58m", # Dark Cyan
|
||||||
|
"\033[38;5;244m", # Gray
|
||||||
|
"\033[38;5;250m", # Light Gray
|
||||||
|
"\033[38;5;196m", # Red
|
||||||
|
"\033[38;5;46m", # Green
|
||||||
|
"\033[38;5;226m", # Yellow
|
||||||
|
"\033[38;5;21m", # Blue
|
||||||
|
"\033[38;5;201m", # Magenta
|
||||||
|
"\033[38;5;51m", # Cyan
|
||||||
|
"\033[38;5;231m" # White
|
||||||
|
]
|
||||||
|
RESET_COLOR = "\033[0m"
|
||||||
|
|
||||||
|
def frame_to_ascii(frame, color_mode):
|
||||||
|
img = Image.fromarray(frame).convert("RGB").resize((frame.shape[1], frame.shape[0]))
|
||||||
|
ascii_frame = ""
|
||||||
|
for y in range(img.height):
|
||||||
|
for x in range(img.width):
|
||||||
|
r, g, b = img.getpixel((x, y))
|
||||||
|
gray = int(0.3 * r + 0.59 * g + 0.11 * b)
|
||||||
|
index = min(gray // 25, len(ASCII_CHARS) - 1) # Ensure the index is within bounds
|
||||||
|
char = ASCII_CHARS[index]
|
||||||
|
|
||||||
|
if color_mode:
|
||||||
|
color_index = (r // 51) * 36 + (g // 51) * 6 + (b // 51)
|
||||||
|
color_code = f"\033[38;5;{16 + color_index}m"
|
||||||
|
ascii_frame += f"{color_code}{char}{RESET_COLOR}"
|
||||||
|
else:
|
||||||
|
ascii_frame += char
|
||||||
|
ascii_frame += "\n"
|
||||||
|
return ascii_frame
|
||||||
|
|
||||||
|
def process_video(url, contrast, brightness, color_mode, width, height, video_conn, sync_event):
|
||||||
|
ffmpeg_command = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-re",
|
||||||
|
"-i", url,
|
||||||
|
"-vf", f"scale={width}:{height},eq=contrast={contrast}:brightness={brightness}",
|
||||||
|
"-r", str(FPS),
|
||||||
|
"-f", "rawvideo",
|
||||||
|
"-pix_fmt", "rgb24",
|
||||||
|
"pipe:1"
|
||||||
|
]
|
||||||
|
video_process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
frame_size = width * height * 3
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
frame_bytes = video_process.stdout.read(frame_size)
|
||||||
|
if not frame_bytes:
|
||||||
|
break
|
||||||
|
|
||||||
|
frame = np.frombuffer(frame_bytes, np.uint8).reshape((height, width, 3))
|
||||||
|
ascii_frame = frame_to_ascii(frame, color_mode)
|
||||||
|
frame_data = ascii_frame.encode()
|
||||||
|
|
||||||
|
# Send the frame size followed by the frame data
|
||||||
|
video_conn.sendall(struct.pack("I", len(frame_data)) + frame_data)
|
||||||
|
sync_event.wait(1 / FPS)
|
||||||
|
finally:
|
||||||
|
video_process.stdout.close()
|
||||||
|
video_conn.close()
|
||||||
|
|
||||||
|
def process_audio(url, audio_conn, sync_event):
|
||||||
|
ffmpeg_audio_command = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-re",
|
||||||
|
"-i", url,
|
||||||
|
"-f", "wav",
|
||||||
|
"-ar", "8000",
|
||||||
|
"-ac", "1",
|
||||||
|
"-acodec", "pcm_u8",
|
||||||
|
"pipe:1"
|
||||||
|
]
|
||||||
|
audio_process = subprocess.Popen(ffmpeg_audio_command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
audio_chunk = audio_process.stdout.read(1024)
|
||||||
|
if not audio_chunk:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
audio_conn.sendall(audio_chunk)
|
||||||
|
except BrokenPipeError:
|
||||||
|
print("Audio connection closed by client.")
|
||||||
|
break
|
||||||
|
sync_event.set()
|
||||||
|
sync_event.clear()
|
||||||
|
finally:
|
||||||
|
audio_process.stdout.close()
|
||||||
|
audio_conn.close()
|
||||||
|
|
||||||
|
def handle_client(video_conn, audio_conn):
|
||||||
|
data = video_conn.recv(1024).decode().split()
|
||||||
|
url = data[0]
|
||||||
|
contrast = float(data[1])
|
||||||
|
brightness = float(data[2])
|
||||||
|
color_mode = bool(int(data[3]))
|
||||||
|
width = int(data[4])
|
||||||
|
height = int(data[5])
|
||||||
|
print(f"Received URL: {url} with contrast={contrast}, brightness={brightness}, color_mode={color_mode}, width={width}, height={height}")
|
||||||
|
|
||||||
|
sync_event = threading.Event()
|
||||||
|
video_thread = threading.Thread(target=process_video, args=(url, contrast, brightness, color_mode, width, height, video_conn, sync_event))
|
||||||
|
audio_thread = threading.Thread(target=process_audio, args=(url, audio_conn, sync_event))
|
||||||
|
video_thread.start()
|
||||||
|
audio_thread.start()
|
||||||
|
video_thread.join()
|
||||||
|
audio_thread.join()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="ASCII Video Streaming Server")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
server_socket_video = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server_socket_audio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server_socket_video.bind(("0.0.0.0", 12345))
|
||||||
|
server_socket_audio.bind(("0.0.0.0", 12346))
|
||||||
|
server_socket_video.listen(5)
|
||||||
|
server_socket_audio.listen(5)
|
||||||
|
|
||||||
|
print("Server listening on ports 12345 (video) and 12346 (audio)")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
video_conn, video_addr = server_socket_video.accept()
|
||||||
|
audio_conn, audio_addr = server_socket_audio.accept()
|
||||||
|
print(f"Connected by {video_addr} for video and {audio_addr} for audio")
|
||||||
|
handle_client(video_conn, audio_conn)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user