pythonsocketspygamescreenshotscreensharing

Screen sharing in python


Hi I'm stuck and I did not find anything helpful on the internet. I'm trying to make a screen-sharing program in python. The problem is that I can't send the screen in at least 24 fps, because when I take a screenshot with PIL (ImageGrab), something delays there. my client will get the picture (screenshot) from the server and "blit" it to the screen using pygame.

Server:

# -*- coding: utf-8 -*-


import socket
import os
import threading
from PIL import ImageGrab
def RetrFile(name, sock):

    while 1:
        img = ImageGrab.grab()
        img.save("PATH_TO_PIC")

        filename = "PATH_TO_PIC"
        sock.send(str(os.path.getsize(filename)))
        with open('PATH_TO_PIC', 'rb') as f:
            bytesToSend = f.read(1024)
            sock.send(bytesToSend)
            while bytesToSend != "":
                bytesToSend = f.read(1024)
                sock.send(bytesToSend)

def Main():
   host = '0.0.0.0'
   port = 5000

   s = socket.socket()
   s.bind((host,port))

   s.listen(5)
   print "Server Started."

   while True:
       c, addr = s.accept()
       print "Client connected ip: <"+ str(addr) + ">"
       t = threading.Thread(target = RetrFile, args = ("retrThread", c))
       t.start()
   s.close()

if __name__ == '__main__':
    Main()

Client:

import socket
import pygame as pg
def Main():
    host = '127.0.0.1'
    port = 5000


    pg.init()
    display_screen = pg.display.set_mode((1900, 1000))



    clock = pg.time.Clock()

    s = socket.socket()
    s.connect((host,port))
    filename =  "PATH_TO_PIC"
    isExit = False
    while not isExit:

        for event in pg.event.get():
            if event.type == pg.QUIT:
                isExit = True
        data = s.recv(1024)
        print data
        filesize = long(data)

        f = open(filename, 'wb')
        data = s.recv(1024)
        totalRecv  =  len(data)
        f.write(data)
        while totalRecv < filesize:
            data = s.recv(1024)
            totalRecv += len(data)
            f.write(data)
        showImg = pg.image.load('PATH_TO_PIC')
        display_screen.blit(showImg, (0,0))
        pg.display.flip()
        clock.tick(60)
    s.close()

if __name__ == '__main__':
    Main()

Basically my question is: how to share a screen between 2 computers, I don't know if the way of sending a lot of pictures using PIL is efficient and right. is there a more efficient way? that casts the screen of computer no.1 and shows it at computer no.2 ?


Solution

  • I just tried and it seems to work pretty well (Python 3). Let me know if you find this acceptable, I am using the MSS module to prevent I/O.

    server.py

    from socket import socket
    from threading import Thread
    from zlib import compress
    
    from mss import mss
    
    
    WIDTH = 1900
    HEIGHT = 1000
    
    
    def retreive_screenshot(conn):
        with mss() as sct:
            # The region to capture
            rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}
    
            while 'recording':
                # Capture the screen
                img = sct.grab(rect)
                # Tweak the compression level here (0-9)
                pixels = compress(img.rgb, 6)
    
                # Send the size of the pixels length
                size = len(pixels)
                size_len = (size.bit_length() + 7) // 8
                conn.send(bytes([size_len]))
    
                # Send the actual pixels length
                size_bytes = size.to_bytes(size_len, 'big')
                conn.send(size_bytes)
    
                # Send pixels
                conn.sendall(pixels)
    
    
    def main(host='0.0.0.0', port=5000):
        sock = socket()
        sock.bind((host, port))
        try:
            sock.listen(5)
            print('Server started.')
    
            while 'connected':
                conn, addr = sock.accept()
                print('Client connected IP:', addr)
                thread = Thread(target=retreive_screenshot, args=(conn,))
                thread.start()
        finally:
            sock.close()
    
    
    if __name__ == '__main__':
        main()
    

    client.py

    from socket import socket
    from zlib import decompress
    
    import pygame
    
    WIDTH = 1900
    HEIGHT = 1000
    
    
    def recvall(conn, length):
        """ Retreive all pixels. """
    
        buf = b''
        while len(buf) < length:
            data = conn.recv(length - len(buf))
            if not data:
                return data
            buf += data
        return buf
    
    
    def main(host='127.0.0.1', port=5000):
        pygame.init()
        screen = pygame.display.set_mode((WIDTH, HEIGHT))
        clock = pygame.time.Clock()
        watching = True    
    
        sock = socket()
        sock.connect((host, port))
        try:
            while watching:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        watching = False
                        break
    
                # Retreive the size of the pixels length, the pixels length and pixels
                size_len = int.from_bytes(sock.recv(1), byteorder='big')
                size = int.from_bytes(sock.recv(size_len), byteorder='big')
                pixels = decompress(recvall(sock, size))
    
                # Create the Surface from raw pixels
                img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')
    
                # Display the picture
                screen.blit(img, (0, 0))
                pygame.display.flip()
                clock.tick(60)
        finally:
            sock.close()
    
    
    if __name__ == '__main__':
        main()
    

    You could improve by using another compression algorithm like LZ4, which have a Python implementation. You will need to try out :)