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)) + "%")