File indexing completed on 2024-05-12 04:52:38
0001 #!/usr/bin/env python3 0002 0003 import subprocess 0004 import sys 0005 import array 0006 from PIL import Image, ImageOps 0007 from PIL import ImageDraw 0008 from PIL import ImageFont 0009 0010 referenceFile = sys.argv[1] 0011 lastRender = sys.argv[2] 0012 testCounter = int(sys.argv[3]) 0013 0014 cmd = [ 0015 "ffmpeg", 0016 "-hide_banner", 0017 "-loglevel", 0018 "error", 0019 "-i", 0020 referenceFile, 0021 "-i", 0022 lastRender, 0023 "-filter_complex", 0024 "psnr=f=-", 0025 "-f", 0026 "null", 0027 "/dev/null", 0028 ] 0029 0030 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) 0031 framesCount = 0 0032 framesError = 0 0033 errorThumb = 0 0034 firstErrorFrame = -1 0035 borderWidth = 10 0036 errorArray = array.array("i") 0037 # lastState remembers the last frame's status (0 = ok, 1 = error) 0038 lastState = 0 0039 maxPnsrValue = 0 0040 for line in proc.stdout: 0041 linestr = str(line, "utf-8") 0042 values = linestr.split() 0043 pnsr = values[1].split(":") 0044 value = float(pnsr[1]) 0045 if value > 10: 0046 maxPnsrValue = max(value, maxPnsrValue) 0047 errorFrame = int(values[0].split(":")[1]) 0048 if lastState == 0: 0049 errorArray.append(errorFrame) 0050 lastState = 1 0051 framesError += 1 0052 # print(str(errorFrame) + ': PNSR=' + str(value)) 0053 if firstErrorFrame < 0: 0054 firstErrorFrame = errorFrame 0055 else: 0056 if lastState == 1: 0057 errorFrame = int(values[0].split(":")[1]) 0058 errorArray.append(errorFrame) 0059 lastState = 0 0060 framesCount += 1 0061 0062 framesCount -= 1 0063 0064 if lastState == 1: 0065 errorArray.append(framesCount) 0066 lastState = 0 0067 0068 # extract thumbnail 0069 if firstErrorFrame > 0: 0070 # Find video file fps to calculate position in seconds 0071 keyword1 = "Stream #" 0072 keyword2 = "Video:" 0073 fps = 25 0074 cmd3 = ["ffmpeg", "-hide_banner", "-i", referenceFile] 0075 proc3 = subprocess.Popen(cmd3, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 0076 for line in proc3.stderr: 0077 linestr = str(line, "utf-8") 0078 if keyword1 in linestr and keyword2 in linestr: 0079 # match 0080 vals = linestr.split(",") 0081 keyword3 = " tbr" 0082 for v in vals: 0083 if keyword3 in v: 0084 fps = int(v.split(" ")[1]) 0085 break 0086 0087 # errorPos = firstErrorFrame + (errorArray[1] - errorArray[0])/2 0088 for x in range(len(errorArray)): 0089 if x % 2 == 0 or errorArray[x] - errorArray[x - 1] > 1: 0090 errorPos = errorArray[x] - 1 0091 thbcmd = [ 0092 "ffmpeg", 0093 "-hide_banner", 0094 "-loglevel", 0095 "error", 0096 "-y", 0097 "-ss", 0098 str(errorPos / fps), 0099 "-i", 0100 referenceFile, 0101 "-frames:v", 0102 "1", 0103 "tmp/ref.png", 0104 ] 0105 proc2 = subprocess.Popen(thbcmd, stdout=subprocess.PIPE) 0106 proc2.wait() 0107 img = Image.open("tmp/ref.png") 0108 0109 thbcmd2 = [ 0110 "ffmpeg", 0111 "-hide_banner", 0112 "-loglevel", 0113 "error", 0114 "-y", 0115 "-ss", 0116 str(errorPos / fps), 0117 "-i", 0118 lastRender, 0119 "-frames:v", 0120 "1", 0121 "tmp/render.png", 0122 ] 0123 proc3 = subprocess.Popen(thbcmd2, stdout=subprocess.PIPE) 0124 proc3.wait() 0125 0126 images = [Image.open(x) for x in ["tmp/ref.png", "tmp/render.png"]] 0127 widths, heights = zip(*(i.size for i in images)) 0128 0129 total_width = sum(widths) + 4 * borderWidth 0130 max_height = max(heights) + 2 * borderWidth 0131 timelineHeight = int(max_height / 6) 0132 # Results text 0133 result = Image.new("RGB", (total_width, timelineHeight)) 0134 I1 = ImageDraw.Draw(result) 0135 textHeight = int(timelineHeight / 3) 0136 result.paste("red", (0, 0, total_width, textHeight + borderWidth)) 0137 myFont = ImageFont.truetype("FreeMono.ttf", textHeight) 0138 I1.text( 0139 (10, 2), 0140 "Reference: " + referenceFile, 0141 font=myFont, 0142 fill="white", 0143 stroke_width=2, 0144 stroke_fill="white", 0145 ) 0146 I1.text( 0147 (total_width / 2 + 10, 2), 0148 "Last render: " + lastRender, 0149 font=myFont, 0150 fill="white", 0151 stroke_width=2, 0152 stroke_fill="white", 0153 ) 0154 I1.text( 0155 (10, timelineHeight / 2 + 10), 0156 "Error at frame : " + str(int(errorPos)), 0157 font=myFont, 0158 fill="yellow", 0159 stroke_width=2, 0160 stroke_fill="yellow", 0161 ) 0162 0163 # timeline of ok and incorrect segments 0164 timeline = Image.new("RGB", (total_width, timelineHeight)) 0165 I2 = ImageDraw.Draw(timeline) 0166 timeline.paste("darkgreen", (0, 0, timeline.size[0], timeline.size[1])) 0167 for y in range(len(errorArray)): 0168 # print(str(x) + " = " + str(errorArray[x]) + " / MAX: " + str(framesCount)) 0169 if y % 2 == 0: 0170 shape = [ 0171 (total_width * errorArray[y] / framesCount, 0), 0172 (total_width * errorArray[y + 1] / framesCount, timelineHeight), 0173 ] 0174 I2.rectangle(shape, fill="orange") 0175 0176 shape = [ 0177 (total_width * errorPos / framesCount, 0), 0178 (total_width * errorPos / framesCount + borderWidth, timelineHeight), 0179 ] 0180 I2.rectangle(shape, fill="darkred") 0181 0182 new_im = Image.new("RGB", (total_width, max_height + (2 * timelineHeight))) 0183 new_im.paste(timeline, (0, max_height + timelineHeight)) 0184 new_im.paste(result, (0, max_height)) 0185 x_offset = 0 0186 for im in images: 0187 img = ImageOps.expand(im, border=borderWidth, fill="red") 0188 new_im.paste(img, (x_offset, 0)) 0189 x_offset += im.size[0] + 2 * borderWidth 0190 0191 outputImage = ( 0192 "tmp/" + str(testCounter) + "-" + str(errorThumb) + "-result.png" 0193 ) 0194 new_im.save(outputImage) 0195 errorThumb += 1 0196 0197 print( 0198 '<input id="collapsible' 0199 + str(testCounter) 0200 + '" class="toggle" type="checkbox">' 0201 ) 0202 print( 0203 '<label for="collapsible' 0204 + str(testCounter) 0205 + '" class="lbl-toggle"><div class="centered"><img src="resources/failed.png" /> Test #' 0206 + str(testCounter) 0207 + " for file <b>" 0208 + referenceFile 0209 + "</b> failed at frame <b>" 0210 + str(firstErrorFrame) 0211 + "</b>, PNSR: " 0212 + f"{maxPnsrValue:.3f}" 0213 + ".</div></label>" 0214 ) 0215 print( 0216 '<div class="collapsible-content"><div class="content-inner"><b>Broken frames: </b>' 0217 ) 0218 counter2 = 0 0219 for z in range(len(errorArray)): 0220 if z % 2 == 0: 0221 errorPos2 = errorArray[z] - 1 0222 outputImage2 = ( 0223 "tmp/" + str(testCounter) + "-" + str(counter2) + "-result.png" 0224 ) 0225 print( 0226 '<a href="javascript:void(0)" onclick="toggleImg0(\'' 0227 + outputImage2 0228 + "')\">" 0229 + str(errorPos2) 0230 ) 0231 counter2 += 1 0232 if errorArray[z + 1] - errorArray[z] < 2: 0233 print("</a> | ") 0234 else: 0235 print("</a>-") 0236 # Second image 0237 errorPos2 = errorArray[z + 1] - 1 0238 outputImage2 = ( 0239 "tmp/" + str(testCounter) + "-" + str(counter2) + "-result.png" 0240 ) 0241 print( 0242 '<a href="javascript:void(0)" onclick="toggleImg0(\'' 0243 + outputImage2 0244 + "')\">" 0245 + str(errorPos2) 0246 ) 0247 print("</a> | ") 0248 counter2 += 1 0249 print("</div></div>") 0250 else: 0251 # job succeded 0252 print( 0253 '<input id="collapsible' 0254 + str(testCounter) 0255 + '" class="toggle" type="checkbox">' 0256 ) 0257 print( 0258 '<label for="collapsible' 0259 + str(testCounter) 0260 + '" class="lbl-toggle2"><div class="centered"><img src="resources/ok.png" /> Test #' 0261 + str(testCounter) 0262 + " for file <b> " 0263 + referenceFile 0264 + " </b> succeded.</div></label>" 0265 ) 0266 0267 0268 # print("First Error: " + str(firstErrorFrame) + ", TOTAL ERRORS: " + str(int(100*framesError/framesCount + 0.5)) + "%")