StreamGear automates transcoding workflow for generating Ultra-Low Latency, High-Quality, Dynamic & Adaptive Streaming Formats (such as MPEG-DASH and HLS) in just few lines of python code. StreamGear provides a standalone, highly extensible, and flexible wrapper around FFmpeg multimedia framework for generating chunked-encoded media segments of the content.
SteamGear easily transcodes source videos/audio files & real-time video-frames and breaks them into a sequence of multiple smaller chunks/segments of suitable length. These segments make it possible to stream videos at different quality levels (different bitrate or spatial resolutions) and can be switched in the middle of a video from one quality level to another - if bandwidth permits - on a per-segment basis. A user can serve these segments on a web server that makes it easier to download them through HTTP standard-compliant GET requests.
SteamGear also creates a Manifest/Playlist file (such as MPD in-case of DASH and M3U8 in-case of HLS) besides segments that describe these segment information (timing, URL, media characteristics like video resolution and bit rates) and is provided to the client before the streaming session.
SteamGear currently supports MPEG-DASH (Dynamic Adaptive Streaming over HTTP, ISO/IEC 23009-1) and Apple HLS (HTTP live streaming).
classStreamGear:""" StreamGear automates transcoding workflow for generating Ultra-Low Latency, High-Quality, Dynamic & Adaptive Streaming Formats (such as MPEG-DASH and HLS) in just few lines of python code. StreamGear provides a standalone, highly extensible, and flexible wrapper around FFmpeg multimedia framework for generating chunked-encoded media segments of the content. SteamGear easily transcodes source videos/audio files & real-time video-frames and breaks them into a sequence of multiple smaller chunks/segments of suitable length. These segments make it possible to stream videos at different quality levels _(different bitrate or spatial resolutions)_ and can be switched in the middle of a video from one quality level to another - if bandwidth permits - on a per-segment basis. A user can serve these segments on a web server that makes it easier to download them through HTTP standard-compliant GET requests. SteamGear also creates a Manifest/Playlist file (such as MPD in-case of DASH and M3U8 in-case of HLS) besides segments that describe these segment information (timing, URL, media characteristics like video resolution and bit rates) and is provided to the client before the streaming session. SteamGear currently supports MPEG-DASH (Dynamic Adaptive Streaming over HTTP, ISO/IEC 23009-1) and Apple HLS (HTTP live streaming). """def__init__(self,output:str="",format:str="dash",custom_ffmpeg:str="",logging:bool=False,**stream_params:dict):""" This constructor method initializes the object state and attributes of the StreamGear class. Parameters: output (str): sets the valid filename/path for generating the StreamGear assets. format (str): select the adaptive HTTP streaming format(DASH and HLS). custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executables. logging (bool): enables/disables logging. stream_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properties. """# enable logging if specifiedself.__logging=loggingifisinstance(logging,bool)elseFalse# print current versionlogcurr_vidgear_ver(logging=self.__logging)# checks if machine in-use is running windows os or notself.__os_windows=Trueifos.name=="nt"elseFalse# initialize various class variables# handles user-defined parametersself.__params={}# handle input video/frame resolution and channelsself.__inputheight=Noneself.__inputwidth=Noneself.__inputchannels=Noneself.__sourceframerate=None# handle process to be frames writtenself.__process=None# handle valid FFmpeg assets locationself.__ffmpeg=""# handle one time process for valid process initializationself.__initiate_stream=True# cleans and reformat user-defined parametersself.__params={str(k).strip():(v.strip()ifisinstance(v,str)elsev)fork,vinstream_params.items()}# handle where to save the downloaded FFmpeg Static assets on Windows(if specified)__ffmpeg_download_path=self.__params.pop("-ffmpeg_download_path","")ifnotisinstance(__ffmpeg_download_path,(str)):# reset improper values__ffmpeg_download_path=""# validate the FFmpeg assets and return location (also downloads static assets on windows)self.__ffmpeg=get_valid_ffmpeg_path(str(custom_ffmpeg),self.__os_windows,ffmpeg_download_path=__ffmpeg_download_path,logging=self.__logging,)# check if valid FFmpeg path returnedifself.__ffmpeg:self.__loggingandlogger.debug("Found valid FFmpeg executables: `{}`.".format(self.__ffmpeg))else:# else raise errorraiseRuntimeError("[StreamGear:ERROR] :: Failed to find FFmpeg assets on this system. Kindly compile/install FFmpeg or provide a valid custom FFmpeg binary path!")# handle streaming formatsupported_formats=["dash","hls"]# TODO will be extended in futureifformatandisinstance(format,str):_format=format.strip().lower()if_formatinsupported_formats:self.__format=_formatlogger.info("StreamGear will generate asset files for {} streaming format.".format(self.__format.upper()))elifdifflib.get_close_matches(_format,supported_formats):raiseValueError("[StreamGear:ERROR] :: Incorrect `format` parameter value! Did you mean `{}`?".format(difflib.get_close_matches(_format,supported_formats)[0]))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value `{}` not valid/supported!".format(format))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value is Missing or Invalid!")# handle Audio-Inputaudio=self.__params.pop("-audio",False)ifaudioandisinstance(audio,str):ifos.path.isfile(audio):self.__audio=os.path.abspath(audio)elifis_valid_url(self.__ffmpeg,url=audio,logging=self.__logging):self.__audio=audioelse:self.__audio=Falseelifaudioandisinstance(audio,list):self.__audio=audioelse:self.__audio=False# log external audio sourceself.__audioandself.__loggingandlogger.debug("External audio source `{}` detected.".format(self.__audio))# handle Video-Source inputsource=self.__params.pop("-video_source",False)# Check if input is valid stringifsourceandisinstance(source,str)andlen(source)>1:# Differentiate inputifos.path.isfile(source):self.__video_source=os.path.abspath(source)elifis_valid_url(self.__ffmpeg,url=source,logging=self.__logging):self.__video_source=sourceelse:# discard the value otherwiseself.__video_source=False# Validate inputifself.__video_source:validation_results=validate_video(self.__ffmpeg,video_path=self.__video_source)assertnot(validation_resultsisNone),"[StreamGear:ERROR] :: Given `{}` video_source is Invalid, Check Again!".format(self.__video_source)self.__aspect_source=validation_results["resolution"]self.__fps_source=validation_results["framerate"]# log itself.__loggingandlogger.debug("Given video_source is valid and has {}x{} resolution, and a framerate of {} fps.".format(self.__aspect_source[0],self.__aspect_source[1],self.__fps_source,))else:# log warninglogger.warning("Discarded invalid `-video_source` value provided.")else:ifsource:# log warning if source providedlogger.warning("Invalid `-video_source` value provided.")else:# log normallylogger.info("No `-video_source` value provided.")# discard the value otherwiseself.__video_source=False# handle user-defined framerateself.__inputframerate=self.__params.pop("-input_framerate",0.0)ifisinstance(self.__inputframerate,(float,int)):# must be floatself.__inputframerate=float(self.__inputframerate)else:# reset improper valuesself.__inputframerate=0.0# handle old assetsclear_assets=self.__params.pop("-clear_prev_assets",False)ifisinstance(clear_assets,bool):self.__clear_assets=clear_assets# log if clearing assets is enabledclear_assetsandlogger.info("The `-clear_prev_assets` parameter is enabled successfully. All previous StreamGear API assets for `{}` format will be removed for this run.".format(self.__format.upper()))else:# reset improper valuesself.__clear_assets=False# handle whether to livestream?livestreaming=self.__params.pop("-livestream",False)ifisinstance(livestreaming,bool)andlivestreaming:# NOTE: `livestream` is only available with real-time mode.self.__livestreaming=livestreamingifnot(self.__video_source)elseFalseifself.__video_source:logger.error("Live-Streaming is only available with Real-time Mode. Refer docs for more information.")else:# log if live streaming is enabledlivestreamingandlogger.info("Live-Streaming is successfully enabled for this run.")else:# reset improper valuesself.__livestreaming=False# handle the special-case of forced-terminationenable_force_termination=self.__params.pop("-enable_force_termination",False)# check if value is validifisinstance(enable_force_termination,bool):self.__forced_termination=enable_force_termination# log if forced termination is enabledself.__forced_terminationandlogger.warning("Forced termination is enabled for this run. This may result in corrupted output in certain scenarios!")else:# handle improper valuesself.__forced_termination=False# handle streaming formatsupported_formats=["dash","hls"]# TODO will be extended in futureifformatandisinstance(format,str):_format=format.strip().lower()if_formatinsupported_formats:self.__format=_formatlogger.info("StreamGear will generate asset files for {} streaming format.".format(self.__format.upper()))elifdifflib.get_close_matches(_format,supported_formats):raiseValueError("[StreamGear:ERROR] :: Incorrect `format` parameter value! Did you mean `{}`?".format(difflib.get_close_matches(_format,supported_formats)[0]))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value `{}` not valid/supported!".format(format))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value is Missing or Invalid!")# handles output asset filenamesifoutput:# validate this class has the access rights to specified directory or notabs_path=os.path.abspath(output)# check if given output is a valid system pathifcheck_WriteAccess(os.path.dirname(abs_path),is_windows=self.__os_windows,logging=self.__logging,):# get all assets extensionsvalid_extension="mpd"ifself.__format=="dash"else"m3u8"assets_exts=[("chunk-stream",".m4s"),# filename prefix, extension("chunk-stream",".ts"),# filename prefix, extension".{}".format(valid_extension),]# add source file extension tooself.__video_sourceandassets_exts.append(("chunk-stream",os.path.splitext(self.__video_source)[1],)# filename prefix, extension)# handle output# check if path is a directoryifos.path.isdir(abs_path):# clear previous assets if specifiedself.__clear_assetsanddelete_ext_safe(abs_path,assets_exts,logging=self.__logging)# auto-assign valid name and adds it to pathabs_path=os.path.join(abs_path,"{}-{}.{}".format(self.__format,time.strftime("%Y%m%d-%H%M%S"),valid_extension,),)# or check if path is a fileelifos.path.isfile(abs_path)andself.__clear_assets:# clear previous assets if specifieddelete_ext_safe(os.path.dirname(abs_path),assets_exts,logging=self.__logging,)# check if path has valid file extensionassertabs_path.endswith(valid_extension),"Given `{}` path has invalid file-extension w.r.t selected format: `{}`!".format(output,self.__format.upper())self.__loggingandlogger.debug("Output Path:`{}` is successfully configured for generating streaming assets.".format(abs_path))# workaround patch for Windows only,# others platforms will not be affectedself.__out_file=abs_path.replace("\\","/")# check if given output is a valid URLelifis_valid_url(self.__ffmpeg,url=output,logging=self.__logging):self.__loggingandlogger.debug("URL:`{}` is valid and successfully configured for generating streaming assets.".format(output))self.__out_file=output# raise ValueError otherwiseelse:raiseValueError("[StreamGear:ERROR] :: The output parameter value:`{}` is not valid/supported!".format(output))else:# raise ValueError otherwiseraiseValueError("[StreamGear:ERROR] :: Kindly provide a valid `output` parameter value. Refer Docs for more information.")# log Mode of operationself.__video_sourceandlogger.info("StreamGear has been successfully configured for {} Mode.".format("Single-Source"ifself.__video_sourceelse"Real-time Frames"))@deprecated(parameter="rgb_mode",message="The `rgb_mode` parameter is deprecated and will be removed in a future version. Only BGR format frames will be supported going forward.",)defstream(self,frame:NDArray,rgb_mode:bool=False)->None:""" Pipes `ndarray` frames to FFmpeg Pipeline for transcoding them into chunked-encoded media segments of streaming formats such as MPEG-DASH and HLS. !!! warning "[DEPRECATION NOTICE]: The `rgb_mode` parameter is deprecated and will be removed in a future version." Parameters: frame (ndarray): a valid numpy frame rgb_mode (boolean): enable this flag to activate RGB mode _(i.e. specifies that incoming frames are of RGB format instead of default BGR)_. """# check if function is called in correct contextifself.__video_source:raiseRuntimeError("[StreamGear:ERROR] :: The `stream()` method cannot be used when streaming from a `-video_source` input file. Kindly refer vidgear docs!")# None-Type frames will be skippedifframeisNone:return# extract height, width and number of channels of frameheight,width=frame.shape[:2]channels=frame.shape[-1]ifframe.ndim==3else1# assign values to class variables on first runifself.__initiate_stream:self.__inputheight=heightself.__inputwidth=widthself.__inputchannels=channelsself.__sourceframerate=(25.0ifnot(self.__inputframerate)elseself.__inputframerate)self.__loggingandlogger.debug("InputFrame => Height:{} Width:{} Channels:{}".format(self.__inputheight,self.__inputwidth,self.__inputchannels))# validate size of frameifheight!=self.__inputheightorwidth!=self.__inputwidth:raiseValueError("[StreamGear:ERROR] :: All frames must have same size!")# validate number of channelsifchannels!=self.__inputchannels:raiseValueError("[StreamGear:ERROR] :: All frames must have same number of channels!")# initiate FFmpeg process on first runifself.__initiate_stream:# launch pre-processingself.__PreProcess(channels=channels,rgb=rgb_mode)# Check status of the processassertself.__processisnotNone# write the frame to pipelinetry:self.__process.stdin.write(frame.tobytes())except(OSError,IOError):# log something is wrong!logger.error("BrokenPipeError caught, Wrong values passed to FFmpeg Pipe, Kindly Refer Docs!")raiseValueError# for testing purpose onlydeftranscode_source(self)->None:""" Transcodes an entire video file _(with or without audio)_ into chunked-encoded media segments of streaming formats such as MPEG-DASH and HLS. """# check if function is called in correct contextifnot(self.__video_source):raiseRuntimeError("[StreamGear:ERROR] :: The `transcode_source()` method cannot be used without a valid `-video_source` input. Kindly refer vidgear docs!")# assign height, width and framerateself.__inputheight=int(self.__aspect_source[1])self.__inputwidth=int(self.__aspect_source[0])self.__sourceframerate=float(self.__fps_source)# launch pre-processingself.__PreProcess()def__PreProcess(self,channels=0,rgb=False):""" Internal method that pre-processes default FFmpeg parameters before starting pipelining. Parameters: channels (int): Number of channels rgb (boolean): activates RGB mode _(if enabled)_. """# turn off initiate flagself.__initiate_stream=False# initialize I/O parametersinput_parameters=OrderedDict()output_parameters=OrderedDict()# pre-assign default codec parameters (if not assigned by user).default_codec="libx264rgb"ifrgbelse"libx264"output_vcodec=self.__params.pop("-vcodec",default_codec)# enforce default encoder if stream copy specified# in Real-time Frames Modeoutput_parameters["-vcodec"]=(default_codecifoutput_vcodec=="copy"and(not(self.__video_source)or"-streams"inself.__params)elseoutput_vcodec)# enforce compatibility with stream copyifoutput_parameters["-vcodec"]!="copy":# NOTE: these parameters only supported when stream copy not definedoutput_parameters["-vf"]=self.__params.pop("-vf","format=yuv420p")# Non-essential `-aspect` parameter is removed from the default pipeline.else:# log warnings if stream copy specified in Real-time Frames Modenot(self.__video_source)andlogger.error("Stream copy is not compatible with Real-time Frames Mode as it require re-encoding of incoming frames. Discarding the `-vcodec copy` parameter!")("-streams"inself.__params)andlogger.error("Stream copying is incompatible with Custom Streams as it require re-encoding for each additional stream. Discarding the `-vcodec copy` parameter!")# log warnings for these parametersself.__params.pop("-vf",False)andlogger.warning("Filtering and stream copy cannot be used together. Discarding specified `-vf` parameter!")self.__params.pop("-aspect",False)andlogger.warning("Overriding aspect ratio with stream copy may produce invalid files. Discarding specified `-aspect` parameter!")# enable optimizations w.r.t selected codec### OPTIMIZATION-1 ###ifoutput_parameters["-vcodec"]in["libx264","libx264rgb","libx265","libvpx-vp9",]:output_parameters["-crf"]=self.__params.pop("-crf","20")### OPTIMIZATION-2 ###ifoutput_parameters["-vcodec"]=="libx264":ifnot(self.__video_source):output_parameters["-profile:v"]=self.__params.pop("-profile:v","high")### OPTIMIZATION-3 ###ifoutput_parameters["-vcodec"]in["libx264","libx264rgb"]:output_parameters["-tune"]=self.__params.pop("-tune","zerolatency")output_parameters["-preset"]=self.__params.pop("-preset","veryfast")### OPTIMIZATION-4 ###ifoutput_parameters["-vcodec"]=="libx265":output_parameters["-x265-params"]=self.__params.pop("-x265-params","lossless=1")# enable audio (if present)ifself.__audio:# validate audio sourcebitrate=validate_audio(self.__ffmpeg,source=self.__audio)ifbitrate:logger.info("Detected External Audio Source is valid, and will be used for generating streams.")# assign audio sourceoutput_parameters["{}".format("-core_asource"ifisinstance(self.__audio,list)else"-i")]=self.__audio# assign audio codecoutput_parameters["-acodec"]=self.__params.pop("-acodec","aac")output_parameters["a_bitrate"]=bitrate# temporary handleroutput_parameters["-core_audio"]=(["-map","1:a:0"]ifself.__format=="dash"else[])else:# discard invalid audiologger.warning("Audio source `{}` is not valid, Skipped!".format(self.__audio))self.__audio=False# validate input video's audio source if availableelifself.__video_source:bitrate=validate_audio(self.__ffmpeg,source=self.__video_source)ifbitrate:logger.info("Input video's audio source will be used for this run.")# assign audio codecoutput_parameters["-acodec"]=self.__params.pop("-acodec","aac"if("-streams"inself.__params)else"copy",)ifoutput_parameters["-acodec"]!="copy":output_parameters["a_bitrate"]=bitrate# temporary handlerelse:logger.info("No valid audio source available in the input video. Disabling audio while generating streams.")else:logger.info("No valid audio source provided. Disabling audio while generating streams.")# enable audio optimizations based on audio codecif"-acodec"inoutput_parametersandoutput_parameters["-acodec"]=="aac":output_parameters["-movflags"]="+faststart"# set input framerateifself.__sourceframerate>0.0andnot(self.__video_source):# set input framerateself.__loggingandlogger.debug("Setting Input framerate: {}".format(self.__sourceframerate))input_parameters["-framerate"]=str(self.__sourceframerate)# handle input resolution and pixel formatifnot(self.__video_source):dimensions="{}x{}".format(self.__inputwidth,self.__inputheight)input_parameters["-video_size"]=str(dimensions)# handles pix_fmt based on channels(HACK)ifchannels==1:input_parameters["-pix_fmt"]="gray"elifchannels==2:input_parameters["-pix_fmt"]="ya8"elifchannels==3:input_parameters["-pix_fmt"]="rgb24"ifrgbelse"bgr24"elifchannels==4:input_parameters["-pix_fmt"]="rgba"ifrgbelse"bgra"else:raiseValueError("[StreamGear:ERROR] :: Frames with channels outside range 1-to-4 are not supported!")# process assigned format parametersprocess_params=self.__handle_streams(input_params=input_parameters,output_params=output_parameters)# check if processing completed successfullyassertnot(process_paramsisNone),"[StreamGear:ERROR] :: `{}` stream cannot be initiated properly!".format(self.__format.upper())# Finally start FFmpeg pipeline and process everythingself.__Build_n_Execute(process_params[0],process_params[1])def__handle_streams(self,input_params,output_params):""" An internal function that parses various streams and its parameters. Parameters: input_params (dict): Input FFmpeg parameters output_params (dict): Output FFmpeg parameters """# handle bit-per-pixelsbpp=self.__params.pop("-bpp",0.1000)ifisinstance(bpp,float)andbpp>=0.001:bpp=float(bpp)else:# reset to default if invalidbpp=0.1000# log itbppandself.__loggingandlogger.debug("Setting bit-per-pixels: {} for this stream.".format(bpp))# handle gopgop=self.__params.pop("-gop",2*int(self.__sourceframerate))ifisinstance(gop,(int,float))andgop>=0:gop=int(gop)else:# reset to some recommended valuegop=2*int(self.__sourceframerate)# log itgopandself.__loggingandlogger.debug("Setting GOP: {} for this stream.".format(gop))# define default stream and its mappingifself.__format=="hls":output_params["-corev0"]=["-map","0:v"]if"-acodec"inoutput_params:output_params["-corea0"]=["-map","{}:a".format(1if"-core_audio"inoutput_paramselse0),]else:output_params["-map"]=0# assign default output resolutionif"-s:v:0"inself.__params:# prevent duplicatesdelself.__params["-s:v:0"]ifoutput_params["-vcodec"]!="copy":output_params["-s:v:0"]="{}x{}".format(self.__inputwidth,self.__inputheight)# assign default output video-bitrateif"-b:v:0"inself.__params:# prevent duplicatesdelself.__params["-b:v:0"]ifoutput_params["-vcodec"]!="copy":output_params["-b:v:0"]=(str(get_video_bitrate(int(self.__inputwidth),int(self.__inputheight),self.__sourceframerate,bpp,))+"k")# assign default output audio-bitrateif"-b:a:0"inself.__params:# prevent duplicatesdelself.__params["-b:a:0"]# extract and assign audio-bitrate from temporary handlera_bitrate=output_params.pop("a_bitrate",False)if"-acodec"inoutput_paramsanda_bitrate:output_params["-b:a:0"]=a_bitrate# handle user-defined streamsstreams=self.__params.pop("-streams",{})output_params=self.__evaluate_streams(streams,output_params,bpp)# define additional streams optimization parametersifoutput_params["-vcodec"]in["libx264","libx264rgb"]:ifnot"-bf"inself.__params:output_params["-bf"]=1ifnot"-sc_threshold"inself.__params:output_params["-sc_threshold"]=0ifnot"-keyint_min"inself.__params:output_params["-keyint_min"]=gopif(output_params["-vcodec"]in["libx264","libx264rgb","libvpx-vp9"]andnot"-g"inself.__params):output_params["-g"]=gopifoutput_params["-vcodec"]=="libx265":output_params["-core_x265"]=["-x265-params","keyint={}:min-keyint={}".format(gop,gop),]# process given dash/hls stream and return itifself.__format=="dash":processed_params=self.__generate_dash_stream(input_params=input_params,output_params=output_params,)else:processed_params=self.__generate_hls_stream(input_params=input_params,output_params=output_params,)returnprocessed_paramsdef__evaluate_streams(self,streams,output_params,bpp):""" Internal function that Extracts, Evaluates & Validates user-defined streams Parameters: streams (dict): Individual streams formatted as list of dict. output_params (dict): Output FFmpeg parameters """# temporary streams count variableoutput_params["stream_count"]=1# default is 1# check if streams are emptyifnotstreams:logger.info("No additional `-streams` are provided.")returnoutput_params# check if streams are validifisinstance(streams,list)andall(isinstance(x,dict)forxinstreams):# keep track of streamsstream_count=1# calculate source aspect-ratiosource_aspect_ratio=self.__inputwidth/self.__inputheight# log the processself.__loggingandlogger.debug("Processing {} streams.".format(len(streams)))# iterate over given streamsforidx,streaminenumerate(streams):# log stream processingself.__loggingandlogger.debug("Processing Stream: #{}".format(idx))# make copystream_copy=stream.copy()# handle intermediate stream data as dictionaryintermediate_dict={}# define and map stream to intermediate dictifself.__format=="hls":intermediate_dict["-corev{}".format(stream_count)]=["-map","0:v"]if"-acodec"inoutput_params:intermediate_dict["-corea{}".format(stream_count)]=["-map","{}:a".format(1if"-core_audio"inoutput_paramselse0),]else:intermediate_dict["-core{}".format(stream_count)]=["-map","0"]# extract resolution & individual dimension of streamresolution=stream.pop("-resolution","")dimensions=(resolution.lower().split("x")if(resolutionandisinstance(resolution,str))else[])# validate resolutionif(len(dimensions)==2anddimensions[0].isnumeric()anddimensions[1].isnumeric()):# verify resolution is w.r.t source aspect-ratioexpected_width=math.floor(int(dimensions[1])*source_aspect_ratio)ifint(dimensions[0])!=expected_width:logger.warning("The provided stream resolution '{}' does not align with the source aspect ratio. Output stream may appear distorted!".format(resolution))# assign stream resolution to intermediate dictintermediate_dict["-s:v:{}".format(stream_count)]=resolutionelse:# otherwise log error and skip streamlogger.error("Missing `-resolution` value. Invalid stream `{}` Skipped!".format(stream_copy))continue# verify given stream video-bitratevideo_bitrate=stream.pop("-video_bitrate","")if(video_bitrateandisinstance(video_bitrate,str)andvideo_bitrate.endswith(("k","M"))):# assign itintermediate_dict["-b:v:{}".format(stream_count)]=video_bitrateelse:# otherwise calculate video-bitratefps=stream.pop("-framerate",0.0)ifdimensionsandisinstance(fps,(float,int))andfps>0:intermediate_dict["-b:v:{}".format(stream_count)]="{}k".format(get_video_bitrate(int(dimensions[0]),int(dimensions[1]),fps,bpp))else:# If everything fails, log and skip the stream!logger.error("Unable to determine Video-Bitrate for the stream `{}`. Skipped!".format(stream_copy))continue# verify given stream audio-bitrateaudio_bitrate=stream.pop("-audio_bitrate","")if"-acodec"inoutput_params:ifaudio_bitrateandaudio_bitrate.endswith(("k","M")):intermediate_dict["-b:a:{}".format(stream_count)]=audio_bitrateelse:# otherwise calculate audio-bitrateifdimensions:aspect_width=int(dimensions[0])intermediate_dict["-b:a:{}".format(stream_count)]="{}k".format(128if(aspect_width>800)else96)# update output parametersoutput_params.update(intermediate_dict)# clear intermediate dictintermediate_dict.clear()# clear stream copystream_copy.clear()# increment to next streamstream_count+=1# log stream processingself.__loggingandlogger.debug("Processed #{} stream successfully.".format(idx))# store stream countoutput_params["stream_count"]=stream_count# log streams processingself.__loggingandlogger.debug("All streams processed successfully!")else:# skip and loglogger.warning("Invalid type `-streams` skipped!")returnoutput_paramsdef__generate_hls_stream(self,input_params,output_params):""" An internal function that parses user-defined parameters and generates suitable FFmpeg Terminal Command for transcoding input into HLS Stream. Parameters: input_params (dict): Input FFmpeg parameters output_params (dict): Output FFmpeg parameters """# validate `hls_segment_type`default_hls_segment_type=self.__params.pop("-hls_segment_type","mpegts")ifisinstance(default_hls_segment_type,str)anddefault_hls_segment_type.strip()in["fmp4","mpegts"]:output_params["-hls_segment_type"]=default_hls_segment_type.strip()else:# otherwise reset to defaultlogger.warning("Invalid `-hls_segment_type` value skipped!")output_params["-hls_segment_type"]="mpegts"# gather required parametersifself.__livestreaming:# `hls_list_size` must be greater than or equal to 0default_hls_list_size=self.__params.pop("-hls_list_size",6)ifisinstance(default_hls_list_size,int)anddefault_hls_list_size>=0:output_params["-hls_list_size"]=default_hls_list_sizeelse:# otherwise reset to defaultlogger.warning("Invalid `-hls_list_size` value skipped!")output_params["-hls_list_size"]=6# `hls_init_time` must be greater than or equal to 0default_hls_init_time=self.__params.pop("-hls_init_time",4)ifisinstance(default_hls_init_time,int)anddefault_hls_init_time>=0:output_params["-hls_init_time"]=default_hls_init_timeelse:# otherwise reset to defaultlogger.warning("Invalid `-hls_init_time` value skipped!")output_params["-hls_init_time"]=4# `hls_time` must be greater than or equal to 0default_hls_time=self.__params.pop("-hls_time",4)ifisinstance(default_hls_time,int)anddefault_hls_time>=0:output_params["-hls_time"]=default_hls_timeelse:# otherwise reset to defaultlogger.warning("Invalid `-hls_time` value skipped!")output_params["-hls_time"]=6# `hls_flags` must be stringdefault_hls_flags=self.__params.pop("-hls_flags","delete_segments+discont_start+split_by_time")ifisinstance(default_hls_flags,str):output_params["-hls_flags"]=default_hls_flagselse:# otherwise reset to defaultlogger.warning("Invalid `-hls_flags` value skipped!")output_params["-hls_flags"]="delete_segments+discont_start+split_by_time"# clean everything at exit?remove_at_exit=self.__params.pop("-remove_at_exit",0)ifisinstance(remove_at_exit,int)andremove_at_exitin[0,1,]:output_params["-remove_at_exit"]=remove_at_exitelse:# otherwise reset to defaultlogger.warning("Invalid `-remove_at_exit` value skipped!")output_params["-remove_at_exit"]=0else:# enforce "contain all the segments"output_params["-hls_list_size"]=0output_params["-hls_playlist_type"]="vod"# handle base URL for absolute pathshls_base_url=self.__params.pop("-hls_base_url","")ifisinstance(hls_base_url,str):output_params["-hls_base_url"]=hls_base_urlelse:# otherwise reset to defaultlogger.warning("Invalid `-hls_base_url` value skipped!")output_params["-hls_base_url"]=""# Hardcoded HLS parameters (Refer FFmpeg docs for more info.)output_params["-allowed_extensions"]="ALL"# Handling <hls_segment_filename># Here filename will be based on `stream_count` dict parameter that# would be used to check whether stream is multi-variant(>1) or single(0-1)segment_template=("{}-stream%v-%03d.{}"ifoutput_params["stream_count"]>1else"{}-stream-%03d.{}")output_params["-hls_segment_filename"]=segment_template.format(os.path.join(os.path.dirname(self.__out_file),"chunk"),"m4s"ifoutput_params["-hls_segment_type"]=="fmp4"else"ts",)# Hardcoded HLS parameters (Refer FFmpeg docs for more info.)output_params["-hls_allow_cache"]=0# enable hls formattingoutput_params["-f"]="hls"# return HLS paramsreturn(input_params,output_params)def__generate_dash_stream(self,input_params,output_params):""" An internal function that parses user-defined parameters and generates suitable FFmpeg Terminal Command for transcoding input into MPEG-dash Stream. Parameters: input_params (dict): Input FFmpeg parameters output_params (dict): Output FFmpeg parameters """# Check if live-streaming or not?ifself.__livestreaming:# `extra_window_size` must be greater than or equal to 0window_size=self.__params.pop("-window_size",5)ifisinstance(window_size,int)andwindow_size>=0:output_params["-window_size"]=window_sizeelse:# otherwise reset to defaultlogger.warning("Invalid `-window_size` value skipped!")output_params["-window_size"]=5# `extra_window_size` must be greater than or equal to 0extra_window_size=self.__params.pop("-extra_window_size",5)ifisinstance(extra_window_size,int)andextra_window_size>=0:output_params["-extra_window_size"]=window_sizeelse:# otherwise reset to defaultlogger.warning("Invalid `-extra_window_size` value skipped!")output_params["-extra_window_size"]=5# clean everything at exit?remove_at_exit=self.__params.pop("-remove_at_exit",0)ifisinstance(remove_at_exit,int)andremove_at_exitin[0,1,]:output_params["-remove_at_exit"]=remove_at_exitelse:# otherwise reset to defaultlogger.warning("Invalid `-remove_at_exit` value skipped!")output_params["-remove_at_exit"]=0# `seg_duration` must be greater than or equal to 0seg_duration=self.__params.pop("-seg_duration",20)ifisinstance(seg_duration,int)andseg_duration>=0:output_params["-seg_duration"]=seg_durationelse:# otherwise reset to defaultlogger.warning("Invalid `-seg_duration` value skipped!")output_params["-seg_duration"]=20# Disable (0) the use of a SegmentTimeline inside a SegmentTemplate.output_params["-use_timeline"]=0else:# `seg_duration` must be greater than or equal to 0seg_duration=self.__params.pop("-seg_duration",5)ifisinstance(seg_duration,int)andseg_duration>=0:output_params["-seg_duration"]=seg_durationelse:# otherwise reset to defaultlogger.warning("Invalid `-seg_duration` value skipped!")output_params["-seg_duration"]=5# Enable (1) the use of a SegmentTimeline inside a SegmentTemplate.output_params["-use_timeline"]=1# Finally, some hardcoded DASH parameters (Refer FFmpeg docs for more info.)output_params["-use_template"]=1output_params["-adaptation_sets"]="id=0,streams=v {}".format("id=1,streams=a"if("-acodec"inoutput_params)else"")# enable dash formattingoutput_params["-f"]="dash"# return DASH paramsreturn(input_params,output_params)def__Build_n_Execute(self,input_params,output_params):""" An Internal function that launches FFmpeg subprocess and pipelines commands. Parameters: input_params (dict): Input FFmpeg parameters output_params (dict): Output FFmpeg parameters """# handle audio source if present"-core_asource"inoutput_paramsandoutput_params.move_to_end("-core_asource",last=False)# handle `-i` parameter"-i"inoutput_paramsandoutput_params.move_to_end("-i",last=False)# copy streams countstream_count=output_params.pop("stream_count",1)# convert input parameters to listinput_commands=dict2Args(input_params)# convert output parameters to listoutput_commands=dict2Args(output_params)# convert any additional parameters to liststream_commands=dict2Args(self.__params)# create exclusive HLS paramshls_commands=[]# handle HLS multi-variant streamsifself.__format=="hls"andstream_count>1:stream_map=""forcountinrange(0,stream_count):stream_map+="v:{}{} ".format(count,",a:{}".format(count)if"-acodec"inoutput_paramselse",")hls_commands+=["-master_pl_name",os.path.basename(self.__out_file),"-var_stream_map",stream_map.strip(),os.path.join(os.path.dirname(self.__out_file),"stream_%v.m3u8"),]# log it if enabledself.__loggingandlogger.debug("User-Defined Output parameters: `{}`".format(" ".join(output_commands)ifoutput_commandselseNone))self.__loggingandlogger.debug("Additional parameters: `{}`".format(" ".join(stream_commands)ifstream_commandselseNone))# build FFmpeg command from parametersffmpeg_cmd=None# ensuring less cluttering if silent modehide_banner=[]ifself.__loggingelse["-hide_banner"]# format commandsifself.__video_source:ffmpeg_cmd=([self.__ffmpeg,"-y"]+(["-re"]ifself.__livestreamingelse[])# pseudo live-streaming+hide_banner+["-i",self.__video_source]+input_commands+output_commands+stream_commands)else:ffmpeg_cmd=([self.__ffmpeg,"-y"]+hide_banner+["-f","rawvideo","-vcodec","rawvideo"]+input_commands+["-i","-"]+output_commands+stream_commands)# format outputsffmpeg_cmd.extend([self.__out_file]ifnot(hls_commands)elsehls_commands)# Launch the FFmpeg pipeline with built commandlogger.critical("Transcoding streaming chunks. Please wait...")# log itself.__process=sp.Popen(ffmpeg_cmd,stdin=sp.PIPE,stdout=(sp.DEVNULLif(notself.__video_sourceandnotself.__logging)elsesp.PIPE),stderr=Noneifself.__loggingelsesp.STDOUT,)# post handle progress bar and runtime errors in case of video_sourceifself.__video_source:return_code=0pbar=Nonesec_prev=0ifself.__logging:self.__process.communicate()return_code=self.__process.returncodeelse:# iterate until stdout runs outwhileTrue:# read and process datadata=self.__process.stdout.readline()ifdata:data=data.decode("utf-8")# extract duration and time-leftifpbarisNoneand"Duration:"indata:# extract time in secondssec_duration=extract_time(data)# initiate progress barpbar=tqdm(total=sec_duration,desc="Processing Frames",unit="frame",)elif"time="indata:# extract time in secondssec_current=extract_time(data)# update progress barifsec_current:pbar.update(sec_current-sec_prev)sec_prev=sec_currentelse:# poll if no dataifself.__process.poll()isnotNone:breakreturn_code=self.__process.poll()# close progress barnot(pbarisNone)andpbar.close()# handle return_codeifreturn_code!=0:# log and raise error if return_code is `1`logger.error("StreamGear failed to initiate stream for this video source!")raisesp.CalledProcessError(return_code,ffmpeg_cmd)else:# log if successfullogger.critical("Transcoding Ended. {} Streaming assets are successfully generated at specified path.".format(self.__format.upper()))def__enter__(self):""" Handles entry with the `with` statement. See [PEP343 -- The 'with' statement'](https://peps.python.org/pep-0343/). **Returns:** Returns a reference to the StreamGear Class """returnselfdef__exit__(self,exc_type,exc_val,exc_tb):""" Handles exit with the `with` statement. See [PEP343 -- The 'with' statement'](https://peps.python.org/pep-0343/). """self.close()@deprecated(message="The `terminate()` method will be removed in the next release. Kindly use `close()` method instead.")defterminate(self)->None:""" !!! warning "[DEPRECATION NOTICE]: This method is now deprecated and will be removed in a future release." This function ensures backward compatibility for the `terminate()` method to maintain the API on existing systems. It achieves this by calling the new `close()` method to terminate various StreamGear processes. """self.close()defclose(self)->None:""" Safely terminates various StreamGear process. """# log terminationself.__loggingandlogger.debug("Terminating StreamGear Processes.")# return if no process was initiated at first placeifself.__processisNoneornot(self.__process.poll()isNone):return# close `stdin` outputself.__process.stdinandself.__process.stdin.close()# close `stdout` outputself.__process.stdoutandself.__process.stdout.close()# forced termination if specified.ifself.__forced_termination:self.__process.terminate()# handle device audio streamselifself.__audioandisinstance(self.__audio,list):# send `CTRL_BREAK_EVENT` signal if Windows else `SIGINT`self.__process.send_signal(signal.CTRL_BREAK_EVENTifself.__os_windowselsesignal.SIGINT)# wait if process is still processingself.__process.wait()# discard processself.__process=None
def__init__(self,output:str="",format:str="dash",custom_ffmpeg:str="",logging:bool=False,**stream_params:dict):""" This constructor method initializes the object state and attributes of the StreamGear class. Parameters: output (str): sets the valid filename/path for generating the StreamGear assets. format (str): select the adaptive HTTP streaming format(DASH and HLS). custom_ffmpeg (str): assigns the location of custom path/directory for custom FFmpeg executables. logging (bool): enables/disables logging. stream_params (dict): provides the flexibility to control supported internal parameters and FFmpeg properties. """# enable logging if specifiedself.__logging=loggingifisinstance(logging,bool)elseFalse# print current versionlogcurr_vidgear_ver(logging=self.__logging)# checks if machine in-use is running windows os or notself.__os_windows=Trueifos.name=="nt"elseFalse# initialize various class variables# handles user-defined parametersself.__params={}# handle input video/frame resolution and channelsself.__inputheight=Noneself.__inputwidth=Noneself.__inputchannels=Noneself.__sourceframerate=None# handle process to be frames writtenself.__process=None# handle valid FFmpeg assets locationself.__ffmpeg=""# handle one time process for valid process initializationself.__initiate_stream=True# cleans and reformat user-defined parametersself.__params={str(k).strip():(v.strip()ifisinstance(v,str)elsev)fork,vinstream_params.items()}# handle where to save the downloaded FFmpeg Static assets on Windows(if specified)__ffmpeg_download_path=self.__params.pop("-ffmpeg_download_path","")ifnotisinstance(__ffmpeg_download_path,(str)):# reset improper values__ffmpeg_download_path=""# validate the FFmpeg assets and return location (also downloads static assets on windows)self.__ffmpeg=get_valid_ffmpeg_path(str(custom_ffmpeg),self.__os_windows,ffmpeg_download_path=__ffmpeg_download_path,logging=self.__logging,)# check if valid FFmpeg path returnedifself.__ffmpeg:self.__loggingandlogger.debug("Found valid FFmpeg executables: `{}`.".format(self.__ffmpeg))else:# else raise errorraiseRuntimeError("[StreamGear:ERROR] :: Failed to find FFmpeg assets on this system. Kindly compile/install FFmpeg or provide a valid custom FFmpeg binary path!")# handle streaming formatsupported_formats=["dash","hls"]# TODO will be extended in futureifformatandisinstance(format,str):_format=format.strip().lower()if_formatinsupported_formats:self.__format=_formatlogger.info("StreamGear will generate asset files for {} streaming format.".format(self.__format.upper()))elifdifflib.get_close_matches(_format,supported_formats):raiseValueError("[StreamGear:ERROR] :: Incorrect `format` parameter value! Did you mean `{}`?".format(difflib.get_close_matches(_format,supported_formats)[0]))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value `{}` not valid/supported!".format(format))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value is Missing or Invalid!")# handle Audio-Inputaudio=self.__params.pop("-audio",False)ifaudioandisinstance(audio,str):ifos.path.isfile(audio):self.__audio=os.path.abspath(audio)elifis_valid_url(self.__ffmpeg,url=audio,logging=self.__logging):self.__audio=audioelse:self.__audio=Falseelifaudioandisinstance(audio,list):self.__audio=audioelse:self.__audio=False# log external audio sourceself.__audioandself.__loggingandlogger.debug("External audio source `{}` detected.".format(self.__audio))# handle Video-Source inputsource=self.__params.pop("-video_source",False)# Check if input is valid stringifsourceandisinstance(source,str)andlen(source)>1:# Differentiate inputifos.path.isfile(source):self.__video_source=os.path.abspath(source)elifis_valid_url(self.__ffmpeg,url=source,logging=self.__logging):self.__video_source=sourceelse:# discard the value otherwiseself.__video_source=False# Validate inputifself.__video_source:validation_results=validate_video(self.__ffmpeg,video_path=self.__video_source)assertnot(validation_resultsisNone),"[StreamGear:ERROR] :: Given `{}` video_source is Invalid, Check Again!".format(self.__video_source)self.__aspect_source=validation_results["resolution"]self.__fps_source=validation_results["framerate"]# log itself.__loggingandlogger.debug("Given video_source is valid and has {}x{} resolution, and a framerate of {} fps.".format(self.__aspect_source[0],self.__aspect_source[1],self.__fps_source,))else:# log warninglogger.warning("Discarded invalid `-video_source` value provided.")else:ifsource:# log warning if source providedlogger.warning("Invalid `-video_source` value provided.")else:# log normallylogger.info("No `-video_source` value provided.")# discard the value otherwiseself.__video_source=False# handle user-defined framerateself.__inputframerate=self.__params.pop("-input_framerate",0.0)ifisinstance(self.__inputframerate,(float,int)):# must be floatself.__inputframerate=float(self.__inputframerate)else:# reset improper valuesself.__inputframerate=0.0# handle old assetsclear_assets=self.__params.pop("-clear_prev_assets",False)ifisinstance(clear_assets,bool):self.__clear_assets=clear_assets# log if clearing assets is enabledclear_assetsandlogger.info("The `-clear_prev_assets` parameter is enabled successfully. All previous StreamGear API assets for `{}` format will be removed for this run.".format(self.__format.upper()))else:# reset improper valuesself.__clear_assets=False# handle whether to livestream?livestreaming=self.__params.pop("-livestream",False)ifisinstance(livestreaming,bool)andlivestreaming:# NOTE: `livestream` is only available with real-time mode.self.__livestreaming=livestreamingifnot(self.__video_source)elseFalseifself.__video_source:logger.error("Live-Streaming is only available with Real-time Mode. Refer docs for more information.")else:# log if live streaming is enabledlivestreamingandlogger.info("Live-Streaming is successfully enabled for this run.")else:# reset improper valuesself.__livestreaming=False# handle the special-case of forced-terminationenable_force_termination=self.__params.pop("-enable_force_termination",False)# check if value is validifisinstance(enable_force_termination,bool):self.__forced_termination=enable_force_termination# log if forced termination is enabledself.__forced_terminationandlogger.warning("Forced termination is enabled for this run. This may result in corrupted output in certain scenarios!")else:# handle improper valuesself.__forced_termination=False# handle streaming formatsupported_formats=["dash","hls"]# TODO will be extended in futureifformatandisinstance(format,str):_format=format.strip().lower()if_formatinsupported_formats:self.__format=_formatlogger.info("StreamGear will generate asset files for {} streaming format.".format(self.__format.upper()))elifdifflib.get_close_matches(_format,supported_formats):raiseValueError("[StreamGear:ERROR] :: Incorrect `format` parameter value! Did you mean `{}`?".format(difflib.get_close_matches(_format,supported_formats)[0]))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value `{}` not valid/supported!".format(format))else:raiseValueError("[StreamGear:ERROR] :: The `format` parameter value is Missing or Invalid!")# handles output asset filenamesifoutput:# validate this class has the access rights to specified directory or notabs_path=os.path.abspath(output)# check if given output is a valid system pathifcheck_WriteAccess(os.path.dirname(abs_path),is_windows=self.__os_windows,logging=self.__logging,):# get all assets extensionsvalid_extension="mpd"ifself.__format=="dash"else"m3u8"assets_exts=[("chunk-stream",".m4s"),# filename prefix, extension("chunk-stream",".ts"),# filename prefix, extension".{}".format(valid_extension),]# add source file extension tooself.__video_sourceandassets_exts.append(("chunk-stream",os.path.splitext(self.__video_source)[1],)# filename prefix, extension)# handle output# check if path is a directoryifos.path.isdir(abs_path):# clear previous assets if specifiedself.__clear_assetsanddelete_ext_safe(abs_path,assets_exts,logging=self.__logging)# auto-assign valid name and adds it to pathabs_path=os.path.join(abs_path,"{}-{}.{}".format(self.__format,time.strftime("%Y%m%d-%H%M%S"),valid_extension,),)# or check if path is a fileelifos.path.isfile(abs_path)andself.__clear_assets:# clear previous assets if specifieddelete_ext_safe(os.path.dirname(abs_path),assets_exts,logging=self.__logging,)# check if path has valid file extensionassertabs_path.endswith(valid_extension),"Given `{}` path has invalid file-extension w.r.t selected format: `{}`!".format(output,self.__format.upper())self.__loggingandlogger.debug("Output Path:`{}` is successfully configured for generating streaming assets.".format(abs_path))# workaround patch for Windows only,# others platforms will not be affectedself.__out_file=abs_path.replace("\\","/")# check if given output is a valid URLelifis_valid_url(self.__ffmpeg,url=output,logging=self.__logging):self.__loggingandlogger.debug("URL:`{}` is valid and successfully configured for generating streaming assets.".format(output))self.__out_file=output# raise ValueError otherwiseelse:raiseValueError("[StreamGear:ERROR] :: The output parameter value:`{}` is not valid/supported!".format(output))else:# raise ValueError otherwiseraiseValueError("[StreamGear:ERROR] :: Kindly provide a valid `output` parameter value. Refer Docs for more information.")# log Mode of operationself.__video_sourceandlogger.info("StreamGear has been successfully configured for {} Mode.".format("Single-Source"ifself.__video_sourceelse"Real-time Frames"))
defclose(self)->None:""" Safely terminates various StreamGear process. """# log terminationself.__loggingandlogger.debug("Terminating StreamGear Processes.")# return if no process was initiated at first placeifself.__processisNoneornot(self.__process.poll()isNone):return# close `stdin` outputself.__process.stdinandself.__process.stdin.close()# close `stdout` outputself.__process.stdoutandself.__process.stdout.close()# forced termination if specified.ifself.__forced_termination:self.__process.terminate()# handle device audio streamselifself.__audioandisinstance(self.__audio,list):# send `CTRL_BREAK_EVENT` signal if Windows else `SIGINT`self.__process.send_signal(signal.CTRL_BREAK_EVENTifself.__os_windowselsesignal.SIGINT)# wait if process is still processingself.__process.wait()# discard processself.__process=None
@deprecated(parameter="rgb_mode",message="The `rgb_mode` parameter is deprecated and will be removed in a future version. Only BGR format frames will be supported going forward.",)defstream(self,frame:NDArray,rgb_mode:bool=False)->None:""" Pipes `ndarray` frames to FFmpeg Pipeline for transcoding them into chunked-encoded media segments of streaming formats such as MPEG-DASH and HLS. !!! warning "[DEPRECATION NOTICE]: The `rgb_mode` parameter is deprecated and will be removed in a future version." Parameters: frame (ndarray): a valid numpy frame rgb_mode (boolean): enable this flag to activate RGB mode _(i.e. specifies that incoming frames are of RGB format instead of default BGR)_. """# check if function is called in correct contextifself.__video_source:raiseRuntimeError("[StreamGear:ERROR] :: The `stream()` method cannot be used when streaming from a `-video_source` input file. Kindly refer vidgear docs!")# None-Type frames will be skippedifframeisNone:return# extract height, width and number of channels of frameheight,width=frame.shape[:2]channels=frame.shape[-1]ifframe.ndim==3else1# assign values to class variables on first runifself.__initiate_stream:self.__inputheight=heightself.__inputwidth=widthself.__inputchannels=channelsself.__sourceframerate=(25.0ifnot(self.__inputframerate)elseself.__inputframerate)self.__loggingandlogger.debug("InputFrame => Height:{} Width:{} Channels:{}".format(self.__inputheight,self.__inputwidth,self.__inputchannels))# validate size of frameifheight!=self.__inputheightorwidth!=self.__inputwidth:raiseValueError("[StreamGear:ERROR] :: All frames must have same size!")# validate number of channelsifchannels!=self.__inputchannels:raiseValueError("[StreamGear:ERROR] :: All frames must have same number of channels!")# initiate FFmpeg process on first runifself.__initiate_stream:# launch pre-processingself.__PreProcess(channels=channels,rgb=rgb_mode)# Check status of the processassertself.__processisnotNone# write the frame to pipelinetry:self.__process.stdin.write(frame.tobytes())except(OSError,IOError):# log something is wrong!logger.error("BrokenPipeError caught, Wrong values passed to FFmpeg Pipe, Kindly Refer Docs!")raiseValueError# for testing purpose only
[DEPRECATION NOTICE]: This method is now deprecated and will be removed in a future release.
This function ensures backward compatibility for the terminate() method to maintain the API on existing systems. It achieves this by calling the new close() method to terminate various StreamGear processes.
@deprecated(message="The `terminate()` method will be removed in the next release. Kindly use `close()` method instead.")defterminate(self)->None:""" !!! warning "[DEPRECATION NOTICE]: This method is now deprecated and will be removed in a future release." This function ensures backward compatibility for the `terminate()` method to maintain the API on existing systems. It achieves this by calling the new `close()` method to terminate various StreamGear processes. """self.close()
deftranscode_source(self)->None:""" Transcodes an entire video file _(with or without audio)_ into chunked-encoded media segments of streaming formats such as MPEG-DASH and HLS. """# check if function is called in correct contextifnot(self.__video_source):raiseRuntimeError("[StreamGear:ERROR] :: The `transcode_source()` method cannot be used without a valid `-video_source` input. Kindly refer vidgear docs!")# assign height, width and framerateself.__inputheight=int(self.__aspect_source[1])self.__inputwidth=int(self.__aspect_source[0])self.__sourceframerate=float(self.__fps_source)# launch pre-processingself.__PreProcess()