Search code examples
pythontkinter

missing 1 required positional argument: 'out' (Tkinter start and stop screen recording with buttons and class functions not working)


i want to create a simple UI for screenrecording. In my UI i have two buttons "Start Screen Recording" and "Stop Screen Recording". I can start the screen recording but when i click the stop button the recording is not stopped and i'm getting the following output:

Screenrecorder.stop_recording() missing 1 required positional argument: 'out'

This is my class Screenrecorder:

import cv2
import numpy as np
import pyautogui

class Screenrecorder():

    def start_recording():
        # display screen resolution, get it using pyautogui itself
        SCREEN_SIZE = tuple(pyautogui.size())

        # define the codec
        fourcc = cv2.VideoWriter_fourcc(*"XVID")

        # frames per second
        fps = 12.0

        # create the video write object
        out = cv2.VideoWriter("output.avi", fourcc, fps, (SCREEN_SIZE))

        status = True
        while status:
            # make a screenshot
            img = pyautogui.screenshot()
            # convert these pixels to a proper numpy array to work with OpenCV
            frame = np.array(img)
            # convert colors from BGR to RGB
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # write the frame
            out.write(frame)

            return out


    def stop_recording(out):
        # make sure everything is closed when exitedq
        cv2.destroyAllWindows()
        out.release()

Shouldn't my function "stop_recording" receive the "return out"? What am i doing wrong here? I've read that using return is one solution to pass variables to other functions and i dont want to use global variables.

The UI code:

# ScreenRecorder
# from PyscreenrecClass import MyScreenRecorder
from CV2Class import ScreenRecorder
# import pyscreenrec
# Screenshot
from ScreenshotClass import Screenshot

from tkinter import *
from tkinter import ttk

root = Tk()

root.title("Recorder")

root.geometry("400x900")

mainframe = ttk.Frame(root)
root.iconbitmap("icon.ico")

tabControl = ttk.Notebook(root) 
  
tab1 = ttk.Frame(tabControl) 
tab2 = ttk.Frame(tabControl) 

tabControl.add(tab1, text ='Screen Recording') 
tabControl.add(tab2, text ='Settings') 
tabControl.pack(expand = 1, fill ="both") 

sr = ScreenRecorder() # Right place?

Button(tab1, text="Activate Screenshot Mode", command=Screenshot.TakeScreenshot).pack()

Button(tab1, text="Start Screen Recording", command=sr.start_recording()).pack()

Button(tab1, text="Stop Screen Recording", command=sr.stop_recording()).pack()



root.mainloop()

Solution

  • Since you have already tried an OOP approach, I have taken the liberty of improving it a little so that you do not have to pass out, but this is an instance variable (and thus accessible via self):

    import cv2
    import numpy as np
    import pyautogui
    
    
    class ScreenRecorder:
        def __init__(self):
            self.out = None  # initialize out as instance variable
    
        def start_recording(self):
            # display screen resolution, get it using pyautogui itself
            SCREEN_SIZE = tuple(pyautogui.size())
    
            # define the codec
            fourcc = cv2.VideoWriter_fourcc(*"XVID")
    
            # frames per second
            fps = 12.0
    
            # assign the video write object to the instance variable
            self.out = cv2.VideoWriter("output.avi", fourcc, fps, (SCREEN_SIZE))
    
            status = True
            while status:
                # make a screenshot
                img = pyautogui.screenshot()
                # convert these pixels to a proper numpy array to work with OpenCV
                frame = np.array(img)
                # convert colors from BGR to RGB
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                # write the frame
                self.out.write(frame)
    
        def stop_recording(self):
            # make sure everything is closed when exited
            cv2.destroyAllWindows()
            self.out.release()  # access instance variable
            self.out = None  # reset instance variable to None
    
    

    PS:

    A retrun value is returned when the function is called, but must then also be "caught" (out = Screenrecorder.start_recording()). Without such an assignment, the return value is not used in another function. It would be possible, for example: Screenrecorder.stop_recording(Screenrecorder.start_recording()) to pass out directly to stop_recording. However, this obviously makes no sense as a button function, especially because then out is not the same object as the retrun value from pressing the start button. Rather, the first recording would continue to run and a new recording would be started and stopped immediately when stop is pressed.


    EDIT

    I changed the ode a little bit to work with tkinter as GUI. It is important to notice, that the tkinter mainloop() must be in the main thread and the GUI wont be responding if you call a funtion with a endless loop itself (while True in your recording function). Instead I outsourced the recording loop to a simple thread:

    import cv2
    import numpy as np
    import pyautogui
    from tkinter import Tk, ttk, Button
    from _thread import start_new_thread
    
    
    class ScreenRecorder:
        def __init__(self):
            self.out = None  # initialize out as instance variable
            self.status = False
    
        def start_recording(self):
            # display screen resolution, get it using pyautogui itself
            SCREEN_SIZE = tuple(pyautogui.size())
            # define the codec
            fourcc = cv2.VideoWriter_fourcc(*"XVID")
            # frames per second
            fps = 12.0
            # assign the video write object to the instance variable
            self.out = cv2.VideoWriter("output.avi", fourcc, fps, (SCREEN_SIZE))
            # set status to True
            self.status = True
            # start record loop in new thread
            start_new_thread(self._record_loop, tuple())
    
        def _record_loop(self):
            while self.status:
                # make a screenshot
                img = pyautogui.screenshot()
                # convert these pixels to a proper numpy array to work with OpenCV
                frame = np.array(img)
                # convert colors from BGR to RGB
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                # write the frame if out still exists
                if self.out is not None:
                    self.out.write(frame)
    
        def stop_recording(self):
            if self.status:  # make it only stop if a recording is going on
                # set status to False again
                self.status = False
                # make sure everything is closed when exited
                cv2.destroyAllWindows()
                self.out.release()  # access instance variable
                self.out = None  # reset instance variable to None
    
    
    class GUI(Tk):
        def __init__(self):
            super().__init__()
            self.title("Recorder")
            self.geometry("400x900")
            mainframe = ttk.Frame(self)
            #self.iconbitmap("icon.ico")
            tabControl = ttk.Notebook(self)
            tab1 = ttk.Frame(tabControl)
            tab2 = ttk.Frame(tabControl)
            tabControl.add(tab1, text='Screen Recording')
            tabControl.add(tab2, text='Settings')
            tabControl.pack(expand=1, fill="both")
            sr = ScreenRecorder()
            #Button(tab1, text="Activate Screenshot Mode", command=Screenshot.TakeScreenshot).pack()
            Button(tab1, text="Start Screen Recording", command=sr.start_recording).pack()
            Button(tab1, text="Stop Screen Recording", command=sr.stop_recording).pack()
    
    
    if __name__ == '__main__':
        ui = GUI()
        ui.mainloop()