WebGear is a powerful ASGI Video-Broadcaster API ideal for transmitting Motion-JPEG-frames from a single source to multiple recipients via the browser.
WebGear API works on Starlette's ASGI application and provides a highly extensible and flexible async wrapper around its complete framework. WebGear can flexibly interact with Starlette's ecosystem of shared middleware, mountable applications, Response classes, Routing tables, Static Files, Templating engine(with Jinja2), etc.
WebGear API uses an intraframe-only compression scheme under the hood where the sequence of video-frames are first encoded as JPEG-DIB (JPEG with Device-Independent Bit compression) and then streamed over HTTP using Starlette's Multipart Streaming Response and a Uvicorn ASGI Server. This method imposes lower processing and memory requirements, but the quality is not the best, since JPEG compression is not very efficient for motion video.
In layman's terms, WebGear acts as a powerful Video Broadcaster that transmits live video-frames to any web-browser in the network. Additionally, WebGear API also provides internal wrapper around VideoGear, which itself provides internal access to both CamGear and PiGear APIs, thereby granting it exclusive power for transferring frames incoming from any source to the network.
classWebGear:""" WebGear is a powerful ASGI Video-Broadcaster API ideal for transmitting Motion-JPEG-frames from a single source to multiple recipients via the browser. WebGear API works on Starlette's ASGI application and provides a highly extensible and flexible async wrapper around its complete framework. WebGear can flexibly interact with Starlette's ecosystem of shared middleware, mountable applications, Response classes, Routing tables, Static Files, Templating engine(with Jinja2), etc. WebGear API uses an intraframe-only compression scheme under the hood where the sequence of video-frames are first encoded as JPEG-DIB (JPEG with Device-Independent Bit compression) and then streamed over HTTP using Starlette's Multipart Streaming Response and a Uvicorn ASGI Server. This method imposes lower processing and memory requirements, but the quality is not the best, since JPEG compression is not very efficient for motion video. In layman's terms, WebGear acts as a powerful Video Broadcaster that transmits live video-frames to any web-browser in the network. Additionally, WebGear API also provides internal wrapper around VideoGear, which itself provides internal access to both CamGear and PiGear APIs, thereby granting it exclusive power for transferring frames incoming from any source to the network. """def__init__(self,enablePiCamera:bool=False,stabilize:bool=False,source:Any=None,camera_num:int=0,stream_mode:bool=False,backend:int=0,colorspace:str=None,resolution:Tuple[int,int]=(640,480),framerate:Union[int,float]=25,logging:bool=False,time_delay:int=0,**options:dict):""" This constructor method initializes the object state and attributes of the WebGear class. Parameters: enablePiCamera (bool): provide access to PiGear(if True) or CamGear(if False) APIs respectively. stabilize (bool): enable access to Stabilizer Class for stabilizing frames. camera_num (int): selects the camera module index which will be used as Rpi source. resolution (tuple): sets the resolution (i.e. `(width,height)`) of the Rpi source. framerate (int/float): sets the framerate of the Rpi source. source (based on input): defines the source for the input stream. stream_mode (bool): controls the exclusive YouTube Mode. backend (int): selects the backend for OpenCV's VideoCapture class. colorspace (str): selects the colorspace of the input stream. logging (bool): enables/disables logging. time_delay (int): time delay (in sec) before start reading the frames. options (dict): provides ability to alter Tweak Parameters of WebGear, CamGear, PiGear & Stabilizer. """# enable logging if specifiedself.__logging=loggingifisinstance(logging,bool)elseFalse# print current versionlogcurr_vidgear_ver(logging=self.__logging)# raise error(s) for critical Class importsimport_dependency_safe("starlette"ifstarletteisNoneelse"")import_dependency_safe("simplejpeg"ifsimplejpegisNoneelse"",min_version="1.6.1")# initialize global paramsself.__skip_generate_webdata=False# generate webgear data by default# define frame-compression handlerself.__jpeg_compression_quality=90# 90% qualityself.__jpeg_compression_fastdct=True# fastest DCT on by defaultself.__jpeg_compression_fastupsample=False# fastupsample off by defaultself.__jpeg_compression_colorspace="BGR"# use BGR colorspace by defaultself.__frame_size_reduction=25# use 25% reduction# retrieve interpolation for reductionself.__interpolation=retrieve_best_interpolation(["INTER_LINEAR_EXACT","INTER_LINEAR","INTER_AREA"])custom_video_endpoint=""# custom video endpoint pathcustom_data_location=""# path to save data-files to custom locationdata_path=""# path to WebGear data-filesoverwrite_default=Falseself.__enable_inf=False# continue frames even when video ends.# reformat dictionaryoptions={str(k).strip():vfork,vinoptions.items()}# assign values to global variables if specified and validifoptions:# check whether to disable Data-Files Auto-Generation WorkFlowif"skip_generate_webdata"inoptions:value=options["skip_generate_webdata"]# enable jpeg fastdctifisinstance(value,bool):self.__skip_generate_webdata=valueelse:logger.warning("Skipped invalid `skip_generate_webdata` value!")deloptions["skip_generate_webdata"]# cleanif"jpeg_compression_colorspace"inoptions:value=options["jpeg_compression_colorspace"]ifisinstance(value,str)andvalue.strip().upper()in["RGB","BGR","RGBX","BGRX","XBGR","XRGB","GRAY","RGBA","BGRA","ABGR","ARGB","CMYK",]:# set encoding colorspaceself.__jpeg_compression_colorspace=value.strip().upper()else:logger.warning("Skipped invalid `jpeg_compression_colorspace` value!")deloptions["jpeg_compression_colorspace"]# cleanif"jpeg_compression_quality"inoptions:value=options["jpeg_compression_quality"]# set valid jpeg qualityifisinstance(value,(int,float))andvalue>=10andvalue<=100:self.__jpeg_compression_quality=int(value)else:logger.warning("Skipped invalid `jpeg_compression_quality` value!")deloptions["jpeg_compression_quality"]# cleanif"jpeg_compression_fastdct"inoptions:value=options["jpeg_compression_fastdct"]# enable jpeg fastdctifisinstance(value,bool):self.__jpeg_compression_fastdct=valueelse:logger.warning("Skipped invalid `jpeg_compression_fastdct` value!")deloptions["jpeg_compression_fastdct"]# cleanif"jpeg_compression_fastupsample"inoptions:value=options["jpeg_compression_fastupsample"]# enable jpeg fastupsampleifisinstance(value,bool):self.__jpeg_compression_fastupsample=valueelse:logger.warning("Skipped invalid `jpeg_compression_fastupsample` value!")deloptions["jpeg_compression_fastupsample"]# cleanif"frame_size_reduction"inoptions:value=options["frame_size_reduction"]ifisinstance(value,(int,float))andvalue>=0andvalue<=90:self.__frame_size_reduction=valueelse:logger.warning("Skipped invalid `frame_size_reduction` value!")deloptions["frame_size_reduction"]# cleanif"custom_video_endpoint"inoptions:value=options["custom_video_endpoint"]ifvalueandisinstance(value,str)andvalue.strip().isalnum():custom_video_endpoint=value.strip()loggingandlogger.critical("Using custom video endpoint path: `/{}`".format(custom_video_endpoint))else:logger.warning("Skipped invalid `custom_video_endpoint` value!")deloptions["custom_video_endpoint"]# cleanif"custom_data_location"inoptions:value=options["custom_data_location"]ifvalueandisinstance(value,str):assertos.access(value,os.W_OK),"[WebGear:ERROR] :: Permission Denied!, cannot write WebGear data-files to '{}' directory!".format(value)assertos.path.isdir(os.path.abspath(value)),"[WebGear:ERROR] :: `custom_data_location` value must be the path to a directory and not to a file!"custom_data_location=os.path.abspath(value)else:logger.warning("Skipped invalid `custom_data_location` value!")deloptions["custom_data_location"]# cleanif"overwrite_default_files"inoptions:value=options["overwrite_default_files"]ifisinstance(value,bool):overwrite_default=valueelse:logger.warning("Skipped invalid `overwrite_default_files` value!")deloptions["overwrite_default_files"]# cleanif"enable_infinite_frames"inoptions:value=options["enable_infinite_frames"]ifisinstance(value,bool):self.__enable_inf=valueelse:logger.warning("Skipped invalid `enable_infinite_frames` value!")deloptions["enable_infinite_frames"]# clean# check if disable Data-Files Auto-Generation WorkFlow is disabledifnotself.__skip_generate_webdata:# check if custom data path is specifiedifcustom_data_location:data_path=generate_webdata(custom_data_location,c_name="webgear",overwrite_default=overwrite_default,logging=logging,)else:# otherwise generate suitable pathdata_path=generate_webdata(os.path.join(expanduser("~"),".vidgear"),c_name="webgear",overwrite_default=overwrite_default,logging=logging,)# log itself.__loggingandlogger.debug("`{}` is the default location for saving WebGear data-files.".format(data_path))# define Jinja2 templates handlerself.__templates=Jinja2Templates(directory="{}/templates".format(data_path))# define routing tablesself.routes=[Route("/",endpoint=self.__homepage),Route("/{}".format(custom_video_endpointifcustom_video_endpointelse"video"),endpoint=self.__video,),Mount("/static",app=StaticFiles(directory="{}/static".format(data_path)),name="static",),]else:# log itself.__loggingandlogger.critical("WebGear Data-Files Auto-Generation WorkFlow has been manually disabled.")# define routing tablesself.routes=[Route("/{}".format(custom_video_endpointifcustom_video_endpointelse"video"),endpoint=self.__video,),]# log exceptionsself.__loggingandlogger.warning("Only `/video` route is available for this instance.")# define custom exception handlersself.__exception_handlers={404:self.__not_found,500:self.__server_error}# define middleware supportself.middleware=[]# Handle video sourceifsourceisNone:self.config={"generator":None}self.__stream=Noneelse:# define stream with necessary paramsself.__stream=VideoGear(enablePiCamera=enablePiCamera,stabilize=stabilize,source=source,camera_num=camera_num,stream_mode=stream_mode,backend=backend,colorspace=colorspace,resolution=resolution,framerate=framerate,logging=logging,time_delay=time_delay,**options)# define default frame generator in configurationself.config={"generator":self.__producer}# log if specifiedifself.__logging:ifsourceisNone:logger.warning("Given source is of NoneType. Therefore, JPEG Frame-Compression is disabled!")else:logger.debug("Enabling JPEG Frame-Compression with Colorspace:`{}`, Quality:`{}`%, Fastdct:`{}`, and Fastupsample:`{}`.".format(self.__jpeg_compression_colorspace,self.__jpeg_compression_quality,"enabled"ifself.__jpeg_compression_fastdctelse"disabled",("enabled"ifself.__jpeg_compression_fastupsampleelse"disabled"),))# copying original routing tables for further validationself.__rt_org_copy=self.routes[:]# initialize blank frameself.blank_frame=None# keeps check if producer loop should be runningself.__isrunning=Truedef__call__(self)->Starlette:""" Implements a custom Callable method for WebGear application. """# validate routing tablesassertnot(self.routesisNone),"Routing tables are NoneType!"ifnotisinstance(self.routes,list)ornotall(xinself.routesforxinself.__rt_org_copy):raiseRuntimeError("[WebGear:ERROR] :: Routing tables are not valid!")# validate middlewaresassertnot(self.middlewareisNone),"Middlewares are NoneType!"ifself.middlewareand(notisinstance(self.middleware,list)ornotall(isinstance(x,Middleware)forxinself.middleware)):raiseRuntimeError("[WebGear:ERROR] :: Middlewares are not valid!")# validate assigned frame generator in WebGear configurationifisinstance(self.config,dict)and"generator"inself.config:# check if its assigned value is a asynchronous generatorifself.config["generator"]isNoneornotinspect.isasyncgen(self.config["generator"]()):# otherwise raise errorraiseValueError("[WebGear:ERROR] :: Invalid configuration. Assigned generator must be a asynchronous generator function/method only!")else:# raise error if validation failsraiseRuntimeError("[WebGear:ERROR] :: Assigned configuration is invalid!")# initiate streamself.__loggingandlogger.debug("Initiating Video Streaming.")ifnot(self.__streamisNone):self.__stream.start()# return Starlette applicationself.__loggingandlogger.debug("Running Starlette application.")returnStarlette(debug=(Trueifself.__loggingelseFalse),routes=self.routes,middleware=self.middleware,exception_handlers=self.__exception_handlers,lifespan=self.__lifespan,)asyncdef__producer(self):""" WebGear's default asynchronous frame producer/generator. """# loop over frameswhileself.__isrunning:# read frameframe=self.__stream.read()# display blank if NoneTypeifframeisNone:frame=(self.blank_frameifself.blank_frameisNoneelseself.blank_frame[:])ifnotself.__enable_inf:self.__isrunning=Falseelse:# create blankifself.blank_frameisNone:self.blank_frame=create_blank_frame(frame=frame,text="No Input"ifself.__enable_infelse"The End",logging=self.__logging,)# reducer frames size if specifiedifself.__frame_size_reduction:frame=awaitreducer(frame,percentage=self.__frame_size_reduction,interpolation=self.__interpolation,)# handle JPEG encodingifself.__jpeg_compression_colorspace=="GRAY":ifframe.ndim==2:# patch for https://gitlab.com/jfolz/simplejpeg/-/issues/11frame=np.expand_dims(frame,axis=2)encodedImage=simplejpeg.encode_jpeg(frame,quality=self.__jpeg_compression_quality,colorspace=self.__jpeg_compression_colorspace,fastdct=self.__jpeg_compression_fastdct,)else:encodedImage=simplejpeg.encode_jpeg(frame,quality=self.__jpeg_compression_quality,colorspace=self.__jpeg_compression_colorspace,colorsubsampling="422",fastdct=self.__jpeg_compression_fastdct,)# yield frame in byte formatyield(b"--frame\r\nContent-Type:image/jpeg\r\n\r\n"+encodedImage+b"\r\n")# sleep for sometime.awaitasyncio.sleep(0)asyncdef__video(self,scope):""" Returns a async video streaming response. """assertscope["type"]in["http","https"]returnStreamingResponse(self.config["generator"](),media_type="multipart/x-mixed-replace; boundary=frame",)asyncdef__homepage(self,request):""" Returns an HTML index page. """return(self.__templates.TemplateResponse(request,"index.html")ifnotself.__skip_generate_webdataelseJSONResponse({"detail":"MESSAGE : WebGear Data-Files Auto-Generation WorkFlow is disabled!"},status_code=404,))asyncdef__not_found(self,request,exc):""" Returns an HTML 404 page. """return(self.__templates.TemplateResponse(request,"404.html",status_code=404)ifnotself.__skip_generate_webdataelseJSONResponse({"detail":"ERROR : {} :: MESSAGE : WebGear Data-Files Auto-Generation WorkFlow is disabled.".format(exc.detail)},status_code=404,))asyncdef__server_error(self,request,exc):""" Returns an HTML 500 page. """return(self.__templates.TemplateResponse(request,"500.html",status_code=500)ifnotself.__skip_generate_webdataelseJSONResponse({"detail":"ERROR : {} :: MESSAGE : WebGear Data-Files Auto-Generation WorkFlow is disabled.".format(exc.detailifhasattr(exc,"detail")elserepr(exc))},status_code=500,))@contextlib.asynccontextmanagerasyncdef__lifespan(self,context):try:yieldfinally:# close Video Serverself.shutdown()defshutdown(self)->None:""" Implements a Callable to be run on application shutdown """ifnot(self.__streamisNone):self.__loggingandlogger.debug("Closing Video Streaming.")# stops producerself.__isrunning=False# stops VideoGear streamself.__stream.stop()# prevent any re-iterationself.__stream=None
def__call__(self)->Starlette:""" Implements a custom Callable method for WebGear application. """# validate routing tablesassertnot(self.routesisNone),"Routing tables are NoneType!"ifnotisinstance(self.routes,list)ornotall(xinself.routesforxinself.__rt_org_copy):raiseRuntimeError("[WebGear:ERROR] :: Routing tables are not valid!")# validate middlewaresassertnot(self.middlewareisNone),"Middlewares are NoneType!"ifself.middlewareand(notisinstance(self.middleware,list)ornotall(isinstance(x,Middleware)forxinself.middleware)):raiseRuntimeError("[WebGear:ERROR] :: Middlewares are not valid!")# validate assigned frame generator in WebGear configurationifisinstance(self.config,dict)and"generator"inself.config:# check if its assigned value is a asynchronous generatorifself.config["generator"]isNoneornotinspect.isasyncgen(self.config["generator"]()):# otherwise raise errorraiseValueError("[WebGear:ERROR] :: Invalid configuration. Assigned generator must be a asynchronous generator function/method only!")else:# raise error if validation failsraiseRuntimeError("[WebGear:ERROR] :: Assigned configuration is invalid!")# initiate streamself.__loggingandlogger.debug("Initiating Video Streaming.")ifnot(self.__streamisNone):self.__stream.start()# return Starlette applicationself.__loggingandlogger.debug("Running Starlette application.")returnStarlette(debug=(Trueifself.__loggingelseFalse),routes=self.routes,middleware=self.middleware,exception_handlers=self.__exception_handlers,lifespan=self.__lifespan,)
def__init__(self,enablePiCamera:bool=False,stabilize:bool=False,source:Any=None,camera_num:int=0,stream_mode:bool=False,backend:int=0,colorspace:str=None,resolution:Tuple[int,int]=(640,480),framerate:Union[int,float]=25,logging:bool=False,time_delay:int=0,**options:dict):""" This constructor method initializes the object state and attributes of the WebGear class. Parameters: enablePiCamera (bool): provide access to PiGear(if True) or CamGear(if False) APIs respectively. stabilize (bool): enable access to Stabilizer Class for stabilizing frames. camera_num (int): selects the camera module index which will be used as Rpi source. resolution (tuple): sets the resolution (i.e. `(width,height)`) of the Rpi source. framerate (int/float): sets the framerate of the Rpi source. source (based on input): defines the source for the input stream. stream_mode (bool): controls the exclusive YouTube Mode. backend (int): selects the backend for OpenCV's VideoCapture class. colorspace (str): selects the colorspace of the input stream. logging (bool): enables/disables logging. time_delay (int): time delay (in sec) before start reading the frames. options (dict): provides ability to alter Tweak Parameters of WebGear, CamGear, PiGear & Stabilizer. """# enable logging if specifiedself.__logging=loggingifisinstance(logging,bool)elseFalse# print current versionlogcurr_vidgear_ver(logging=self.__logging)# raise error(s) for critical Class importsimport_dependency_safe("starlette"ifstarletteisNoneelse"")import_dependency_safe("simplejpeg"ifsimplejpegisNoneelse"",min_version="1.6.1")# initialize global paramsself.__skip_generate_webdata=False# generate webgear data by default# define frame-compression handlerself.__jpeg_compression_quality=90# 90% qualityself.__jpeg_compression_fastdct=True# fastest DCT on by defaultself.__jpeg_compression_fastupsample=False# fastupsample off by defaultself.__jpeg_compression_colorspace="BGR"# use BGR colorspace by defaultself.__frame_size_reduction=25# use 25% reduction# retrieve interpolation for reductionself.__interpolation=retrieve_best_interpolation(["INTER_LINEAR_EXACT","INTER_LINEAR","INTER_AREA"])custom_video_endpoint=""# custom video endpoint pathcustom_data_location=""# path to save data-files to custom locationdata_path=""# path to WebGear data-filesoverwrite_default=Falseself.__enable_inf=False# continue frames even when video ends.# reformat dictionaryoptions={str(k).strip():vfork,vinoptions.items()}# assign values to global variables if specified and validifoptions:# check whether to disable Data-Files Auto-Generation WorkFlowif"skip_generate_webdata"inoptions:value=options["skip_generate_webdata"]# enable jpeg fastdctifisinstance(value,bool):self.__skip_generate_webdata=valueelse:logger.warning("Skipped invalid `skip_generate_webdata` value!")deloptions["skip_generate_webdata"]# cleanif"jpeg_compression_colorspace"inoptions:value=options["jpeg_compression_colorspace"]ifisinstance(value,str)andvalue.strip().upper()in["RGB","BGR","RGBX","BGRX","XBGR","XRGB","GRAY","RGBA","BGRA","ABGR","ARGB","CMYK",]:# set encoding colorspaceself.__jpeg_compression_colorspace=value.strip().upper()else:logger.warning("Skipped invalid `jpeg_compression_colorspace` value!")deloptions["jpeg_compression_colorspace"]# cleanif"jpeg_compression_quality"inoptions:value=options["jpeg_compression_quality"]# set valid jpeg qualityifisinstance(value,(int,float))andvalue>=10andvalue<=100:self.__jpeg_compression_quality=int(value)else:logger.warning("Skipped invalid `jpeg_compression_quality` value!")deloptions["jpeg_compression_quality"]# cleanif"jpeg_compression_fastdct"inoptions:value=options["jpeg_compression_fastdct"]# enable jpeg fastdctifisinstance(value,bool):self.__jpeg_compression_fastdct=valueelse:logger.warning("Skipped invalid `jpeg_compression_fastdct` value!")deloptions["jpeg_compression_fastdct"]# cleanif"jpeg_compression_fastupsample"inoptions:value=options["jpeg_compression_fastupsample"]# enable jpeg fastupsampleifisinstance(value,bool):self.__jpeg_compression_fastupsample=valueelse:logger.warning("Skipped invalid `jpeg_compression_fastupsample` value!")deloptions["jpeg_compression_fastupsample"]# cleanif"frame_size_reduction"inoptions:value=options["frame_size_reduction"]ifisinstance(value,(int,float))andvalue>=0andvalue<=90:self.__frame_size_reduction=valueelse:logger.warning("Skipped invalid `frame_size_reduction` value!")deloptions["frame_size_reduction"]# cleanif"custom_video_endpoint"inoptions:value=options["custom_video_endpoint"]ifvalueandisinstance(value,str)andvalue.strip().isalnum():custom_video_endpoint=value.strip()loggingandlogger.critical("Using custom video endpoint path: `/{}`".format(custom_video_endpoint))else:logger.warning("Skipped invalid `custom_video_endpoint` value!")deloptions["custom_video_endpoint"]# cleanif"custom_data_location"inoptions:value=options["custom_data_location"]ifvalueandisinstance(value,str):assertos.access(value,os.W_OK),"[WebGear:ERROR] :: Permission Denied!, cannot write WebGear data-files to '{}' directory!".format(value)assertos.path.isdir(os.path.abspath(value)),"[WebGear:ERROR] :: `custom_data_location` value must be the path to a directory and not to a file!"custom_data_location=os.path.abspath(value)else:logger.warning("Skipped invalid `custom_data_location` value!")deloptions["custom_data_location"]# cleanif"overwrite_default_files"inoptions:value=options["overwrite_default_files"]ifisinstance(value,bool):overwrite_default=valueelse:logger.warning("Skipped invalid `overwrite_default_files` value!")deloptions["overwrite_default_files"]# cleanif"enable_infinite_frames"inoptions:value=options["enable_infinite_frames"]ifisinstance(value,bool):self.__enable_inf=valueelse:logger.warning("Skipped invalid `enable_infinite_frames` value!")deloptions["enable_infinite_frames"]# clean# check if disable Data-Files Auto-Generation WorkFlow is disabledifnotself.__skip_generate_webdata:# check if custom data path is specifiedifcustom_data_location:data_path=generate_webdata(custom_data_location,c_name="webgear",overwrite_default=overwrite_default,logging=logging,)else:# otherwise generate suitable pathdata_path=generate_webdata(os.path.join(expanduser("~"),".vidgear"),c_name="webgear",overwrite_default=overwrite_default,logging=logging,)# log itself.__loggingandlogger.debug("`{}` is the default location for saving WebGear data-files.".format(data_path))# define Jinja2 templates handlerself.__templates=Jinja2Templates(directory="{}/templates".format(data_path))# define routing tablesself.routes=[Route("/",endpoint=self.__homepage),Route("/{}".format(custom_video_endpointifcustom_video_endpointelse"video"),endpoint=self.__video,),Mount("/static",app=StaticFiles(directory="{}/static".format(data_path)),name="static",),]else:# log itself.__loggingandlogger.critical("WebGear Data-Files Auto-Generation WorkFlow has been manually disabled.")# define routing tablesself.routes=[Route("/{}".format(custom_video_endpointifcustom_video_endpointelse"video"),endpoint=self.__video,),]# log exceptionsself.__loggingandlogger.warning("Only `/video` route is available for this instance.")# define custom exception handlersself.__exception_handlers={404:self.__not_found,500:self.__server_error}# define middleware supportself.middleware=[]# Handle video sourceifsourceisNone:self.config={"generator":None}self.__stream=Noneelse:# define stream with necessary paramsself.__stream=VideoGear(enablePiCamera=enablePiCamera,stabilize=stabilize,source=source,camera_num=camera_num,stream_mode=stream_mode,backend=backend,colorspace=colorspace,resolution=resolution,framerate=framerate,logging=logging,time_delay=time_delay,**options)# define default frame generator in configurationself.config={"generator":self.__producer}# log if specifiedifself.__logging:ifsourceisNone:logger.warning("Given source is of NoneType. Therefore, JPEG Frame-Compression is disabled!")else:logger.debug("Enabling JPEG Frame-Compression with Colorspace:`{}`, Quality:`{}`%, Fastdct:`{}`, and Fastupsample:`{}`.".format(self.__jpeg_compression_colorspace,self.__jpeg_compression_quality,"enabled"ifself.__jpeg_compression_fastdctelse"disabled",("enabled"ifself.__jpeg_compression_fastupsampleelse"disabled"),))# copying original routing tables for further validationself.__rt_org_copy=self.routes[:]# initialize blank frameself.blank_frame=None# keeps check if producer loop should be runningself.__isrunning=True
defshutdown(self)->None:""" Implements a Callable to be run on application shutdown """ifnot(self.__streamisNone):self.__loggingandlogger.debug("Closing Video Streaming.")# stops producerself.__isrunning=False# stops VideoGear streamself.__stream.stop()# prevent any re-iterationself.__stream=None