diff --git a/qct_parse/qct_parse.py b/qct_parse/qct_parse.py index 380eb5d..7d8058f 100644 --- a/qct_parse/qct_parse.py +++ b/qct_parse/qct_parse.py @@ -92,7 +92,21 @@ def initLog(inputPath): logPath = inputPath + '.log' logging.basicConfig(filename=logPath,level=logging.INFO,format='%(asctime)s %(message)s') logging.info("Started QCT-Parse") - + +def set_logger(input_path): + log_path = f'{input_path}.log' + logger = logging.getLogger() + if logger.hasHandlers(): + logger.handlers.clear() + logger.setLevel(logging.INFO) + + file_handler = logging.FileHandler(filename=log_path) + file_handler.setLevel(logging.INFO) + file_handler.setFormatter(logging.Formatter('%(asctime)s %(message)s')) + logger.addHandler(file_handler) + logger.info("Started QCT-Parse") + + # finds stuff over/under threshold def threshFinder(inFrame,args,startObj,pkt,tag,over,thumbPath,thumbDelay,adhoc_tag): @@ -613,51 +627,9 @@ def color_percentage(value): print(f"Frames With At Least One Fail:\t{overallFrameFail}\t{color}{percent_overall_string}{RESET}\t% of the total # of frames\n") print(f"{BOLD}**************************{RESET}\n") - -def main(): - """ - Main function that parses QCTools XML files, applies analysis, and optionally exports thumbnails. - - This function handles command-line arguments to process a QCTools report, extract frame data from the XML, - apply threshold analysis for broadcast values, optionally detect color bars, and export analysis results to - the console or thumbnails. - - Command-line Arguments: - -i, --input (str): Path to the input QCTools XML.gz file. - -t, --tagname (str): Tag name to analyze, e.g., SATMAX. - -o, --over (float): Overage threshold for the tag specified. - -u, --under (float): Under threshold for the tag specified. - -p, --profile (str): Profile or template name from the qct-parse_config.txt file, e.g., 'default'. - -buff, --buffSize (int): Circular buffer size. Defaults to 11, ensures odd number. - -te, --thumbExport: Export thumbnails if flag is set. - -ted, --thumbExportDelay (int): Minimum number of frames between exported thumbnails. - -tep, --thumbExportPath (str): Path to export thumbnails, defaults to input basename if not provided. - -ds, --durationStart (float): Start time in seconds for analysis. - -de, --durationEnd (float): End time in seconds for analysis. - -bd, --barsDetection: Flag to enable color bars detection. - -pr, --print: Flag to print frame data to the console. - -q, --quiet: Hide ffmpeg output if flag is set. - - Workflow: - 1. Parse command-line arguments. - 2. Optionally load reference threshold values from a profile in `qct-parse_config.txt`. - 3. Initialize buffers, frame counters, and paths for thumbnail export. - 4. Check for `pkt_dts_time` or `pkt_pts_time` in the QCTools XML file. - 5. Set the analysis duration start and end times. - 6. Perform bars detection if enabled, otherwise proceed with general analysis. - 7. Call the `analyzeIt` function to perform frame-by-frame analysis and calculate exceedances. - 8. Print results using `printresults` if applicable. - 9. Handle errors or invalid input (e.g., missing thumbnail export flag but specifying a path). - - Example usage: - python qct-parse.py -i sample.qctools.xml.gz -t SATMAX -o 5.0 -u -5.0 -te - - Returns: - None: The function processes the XML file, performs analysis, and optionally exports thumbnails and prints results to the console. - """ - #### init the stuff from the cli ######## +def get_arg_parser(): parser = argparse.ArgumentParser(description="parses QCTools XML files for frames beyond broadcast values") - parser.add_argument('-i','--input',dest='i', help="the path to the input qctools.xml.gz file") + parser.add_argument('-i','--input',dest='i', action='append', help="the path to the input qctools.xml.gz file") parser.add_argument('-t','--tagname',dest='t', help="the tag name you want to test, e.g. SATMAX") parser.add_argument('-o','--over',dest='o', help="the threshold overage number") parser.add_argument('-u','--under',dest='u', help="the threshold under number") @@ -672,28 +644,23 @@ def main(): parser.add_argument('-be','--barsEvaluation',dest='be',action ='store_true',default=False, help="turns Color Bar Evaluation on and off") parser.add_argument('-pr','--print',dest='pr',action='store_true',default=False, help="print over/under frame data to console window") parser.add_argument('-q','--quiet',dest='q',action='store_true',default=False, help="hide ffmpeg output from console window") - args = parser.parse_args() - - ## Validate required arguments - if not args.i: - parser.error("the following arguments are required: -i/--input [path to QCTools report]") - if args.o and args.u: - parser.error("Both the -o and -u options were used. Cannot set threshold for both over and under, only one at a time.") - - ##### Initialize variables and buffers ###### - startObj = args.i.replace("\\","/") + return parser + + +def parse_single_qc_tools_report(input_file, args): + startObj = input_file.replace("\\","/") extension = os.path.splitext(startObj)[1] - # If qctools report is in an MKV attachment, extract .qctools.xml.gz report + # If qctools report is in an MKV attachment, extract .qctools.xml.gz report if extension.lower().endswith('mkv'): startObj = extract_report_mkv(startObj) buffSize = int(args.buff) # cast the input buffer as an integer if buffSize%2 == 0: buffSize = buffSize + 1 - initLog(startObj) # initialize the log + set_logger(startObj) overcount = 0 # init count of overs undercount = 0 # init count of unders count = 0 # init total frames counter - framesList = collections.deque(maxlen=buffSize) # init framesList + framesList = collections.deque(maxlen=buffSize) # init framesList thumbDelay = int(args.ted) # get a seconds number for the delay in the original file btw exporting tags parentDir = os.path.dirname(startObj) baseName = os.path.basename(startObj) @@ -702,7 +669,7 @@ def main(): durationEnd = args.de # we gotta find out if the qctools report has pkt_dts_time or pkt_pts_time ugh - with gzip.open(startObj) as xml: + with gzip.open(startObj) as xml: for event, elem in etree.iterparse(xml, events=('end',), tag='frame'): # iterparse the xml doc if elem.attrib['media_type'] == "video": # get just the video frames # we gotta find out if the qctools report has pkt_dts_time or pkt_pts_time ugh @@ -712,10 +679,10 @@ def main(): break ###### Initialize values from the Config Parser - # Determine if video values are 10 bit depth + # Determine if video values are 10 bit depth bit_depth_10 = detectBitdepth(startObj,pkt,framesList,buffSize) # init a dictionary where we'll store reference values from our config file - profile = {} + profile = {} # init a list of every tag available in a QCTools Report tagList = ["YMIN","YLOW","YAVG","YHIGH","YMAX","UMIN","ULOW","UAVG","UHIGH","UMAX","VMIN","VLOW","VAVG","VHIGH","VMAX","SATMIN","SATLOW","SATAVG","SATHIGH","SATMAX","HUEMED","HUEAVG","YDIF","UDIF","VDIF","TOUT","VREP","BRNG","mse_y","mse_u","mse_v","mse_avg","psnr_y","psnr_u","psnr_v","psnr_avg"] @@ -727,13 +694,13 @@ def main(): durationStart = float(args.ds) # The duration at which we start analyzing the file if no bar detection is selected elif not args.de == 99999999: durationEnd = float(args.de) # The duration at which we stop analyzing the file if no bar detection is selected - - - # set the path for the thumbnail export + + + # set the path for the thumbnail export if args.tep and not args.te: print("Buddy, you specified a thumbnail export path without specifying that you wanted to export the thumbnails. Please either add '-te' to your cli call or delete '-tep [path]'") exit() - + if args.tep: # if user supplied thumbExportPath, use that thumbPath = str(args.tep) else: @@ -744,12 +711,12 @@ def main(): thumbPath = os.path.join(parentDir, str(args.t) + "." + str(args.u)) else: # if they're using a profile, put all thumbs in 1 dir thumbPath = os.path.join(parentDir, "ThumbExports") - + if args.te: # make the thumb export path if it doesn't already exist if not os.path.exists(thumbPath): os.makedirs(thumbPath) - - + + ######## Iterate Through the XML for Bars detection ######## if args.bd: print(f"\nStarting Bars Detection on {baseName}\n") @@ -770,7 +737,7 @@ def main(): else: durationStart = "" durationEnd = "" - + if args.p is not None: # create list of profiles list_of_templates = args.p @@ -791,7 +758,7 @@ def main(): if not config.has_section(template): print(f"Profile '{template}' does not match any section in the config.") continue # Skip to the next template if section doesn't exist - for t in tagList: # loop thru every tag available and + for t in tagList: # loop thru every tag available and try: # see if it's in the config section profile[t.replace("_",".")] = config.get(template,t) # if it is, replace _ necessary for config file with . which xml attributes use, assign the value in config except: # if no config tag exists, do nothing so we can move faster @@ -801,8 +768,8 @@ def main(): print(f"\nStarting Analysis on {baseName} using assigned profile {template}\n") kbeyond, frameCount, overallFrameFail = analyzeIt(args,profile,startObj,pkt,durationStart,durationEnd,thumbPath,thumbDelay,framesList,adhoc_tag=False) printresults(kbeyond,frameCount,overallFrameFail) - - if args.t and args.o or args.u: + + if args.t and args.o or args.u: profile = {} tag = args.t if args.o: @@ -813,10 +780,65 @@ def main(): print(f"\nStarting Analysis on {baseName} using user specified tag {tag} w/ threshold {over}\n") kbeyond, frameCount, overallFrameFail = analyzeIt(args,profile,startObj,pkt,durationStart,durationEnd,thumbPath,thumbDelay,framesList,adhoc_tag = True) printresults(kbeyond,frameCount,overallFrameFail) - + print(f"\nFinished Processing File: {baseName}.qctools.xml.gz\n") - - return + +def parse_qc_tools_report(args): + ##### Initialize variables and buffers ###### + for input_file in args.i: + parse_single_qc_tools_report(input_file, args) + +def main(): + """ + Main function that parses QCTools XML files, applies analysis, and optionally exports thumbnails. + + This function handles command-line arguments to process a QCTools report, extract frame data from the XML, + apply threshold analysis for broadcast values, optionally detect color bars, and export analysis results to + the console or thumbnails. + + Command-line Arguments: + -i, --input (str): Path to the input QCTools XML.gz file. + -t, --tagname (str): Tag name to analyze, e.g., SATMAX. + -o, --over (float): Overage threshold for the tag specified. + -u, --under (float): Under threshold for the tag specified. + -p, --profile (str): Profile or template name from the qct-parse_config.txt file, e.g., 'default'. + -buff, --buffSize (int): Circular buffer size. Defaults to 11, ensures odd number. + -te, --thumbExport: Export thumbnails if flag is set. + -ted, --thumbExportDelay (int): Minimum number of frames between exported thumbnails. + -tep, --thumbExportPath (str): Path to export thumbnails, defaults to input basename if not provided. + -ds, --durationStart (float): Start time in seconds for analysis. + -de, --durationEnd (float): End time in seconds for analysis. + -bd, --barsDetection: Flag to enable color bars detection. + -pr, --print: Flag to print frame data to the console. + -q, --quiet: Hide ffmpeg output if flag is set. + + Workflow: + 1. Parse command-line arguments. + 2. Optionally load reference threshold values from a profile in `qct-parse_config.txt`. + 3. Initialize buffers, frame counters, and paths for thumbnail export. + 4. Check for `pkt_dts_time` or `pkt_pts_time` in the QCTools XML file. + 5. Set the analysis duration start and end times. + 6. Perform bars detection if enabled, otherwise proceed with general analysis. + 7. Call the `analyzeIt` function to perform frame-by-frame analysis and calculate exceedances. + 8. Print results using `printresults` if applicable. + 9. Handle errors or invalid input (e.g., missing thumbnail export flag but specifying a path). + + Example usage: + python qct-parse.py -i sample.qctools.xml.gz -t SATMAX -o 5.0 -u -5.0 -te + + Returns: + None: The function processes the XML file, performs analysis, and optionally exports thumbnails and prints results to the console. + """ + #### init the stuff from the cli ######## + parser = get_arg_parser() + args = parser.parse_args() + ## Validate required arguments + if not args.i: + parser.error("the following arguments are required: -i/--input [path to QCTools report]") + if args.o and args.u: + parser.error("Both the -o and -u options were used. Cannot set threshold for both over and under, only one at a time.") + parse_qc_tools_report(args) + if __name__ == '__main__': dependencies()