browsertime 16.7.0 → 16.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/bin/browsertime.js +3 -0
- package/browsertime/visualmetrics-portable.py +49 -865
- package/lib/chrome/longTaskMetrics.js +15 -0
- package/lib/chrome/webdriver/setupChromiumOptions.js +4 -0
- package/lib/core/engine/command/debug.js +48 -0
- package/lib/core/engine/command/wait.js +23 -0
- package/lib/core/engine/iteration.js +19 -3
- package/lib/core/seleniumRunner.js +27 -17
- package/lib/firefox/webdriver/builder.js +10 -2
- package/lib/firefox/webdriver/firefox.js +14 -13
- package/lib/support/cli.js +27 -1
- package/lib/support/errors.js +9 -1
- package/lib/video/postprocessing/visualmetrics/visualMetrics.js +0 -4
- package/package.json +3 -3
|
@@ -37,19 +37,15 @@ import json
|
|
|
37
37
|
import logging
|
|
38
38
|
import math
|
|
39
39
|
import os
|
|
40
|
-
import platform
|
|
41
40
|
import re
|
|
42
41
|
import sys
|
|
43
42
|
import shutil
|
|
44
43
|
import subprocess
|
|
45
44
|
import tempfile
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else:
|
|
51
|
-
GZIP_TEXT = "w"
|
|
52
|
-
GZIP_READ_TEXT = "r"
|
|
46
|
+
|
|
47
|
+
GZIP_TEXT = "wt"
|
|
48
|
+
GZIP_READ_TEXT = "rt"
|
|
53
49
|
|
|
54
50
|
# Globals
|
|
55
51
|
options = None
|
|
@@ -372,17 +368,11 @@ def video_to_frames(
|
|
|
372
368
|
directory,
|
|
373
369
|
force,
|
|
374
370
|
orange_file,
|
|
375
|
-
white_file,
|
|
376
|
-
gray_file,
|
|
377
|
-
multiple,
|
|
378
371
|
find_viewport,
|
|
379
|
-
viewport_time,
|
|
380
372
|
viewport_retries,
|
|
381
373
|
viewport_min_height,
|
|
382
374
|
viewport_min_width,
|
|
383
|
-
full_resolution
|
|
384
|
-
timeline_file,
|
|
385
|
-
trim_end,
|
|
375
|
+
full_resolution
|
|
386
376
|
):
|
|
387
377
|
"""Extract the video frames"""
|
|
388
378
|
global client_viewport
|
|
@@ -405,7 +395,6 @@ def video_to_frames(
|
|
|
405
395
|
video,
|
|
406
396
|
directory,
|
|
407
397
|
find_viewport,
|
|
408
|
-
viewport_time,
|
|
409
398
|
viewport_retries,
|
|
410
399
|
viewport_min_height,
|
|
411
400
|
viewport_min_width,
|
|
@@ -419,34 +408,16 @@ def video_to_frames(
|
|
|
419
408
|
gc.collect()
|
|
420
409
|
if extract_frames(video, directory, full_resolution, viewport):
|
|
421
410
|
client_viewport = None
|
|
422
|
-
|
|
423
|
-
client_viewport = find_image_viewport(
|
|
424
|
-
os.path.join(directory, "video-000000.png"), is_mobile
|
|
425
|
-
)
|
|
426
|
-
if multiple and orange_file is not None:
|
|
427
|
-
directories = split_videos(directory, orange_file)
|
|
428
|
-
else:
|
|
429
|
-
directories = [directory]
|
|
411
|
+
directories = [directory]
|
|
430
412
|
for dir in directories:
|
|
431
|
-
trim_video_end(dir, trim_end)
|
|
432
413
|
if orange_file is not None:
|
|
433
414
|
remove_frames_before_orange(dir, orange_file)
|
|
434
415
|
remove_orange_frames(dir, orange_file)
|
|
435
|
-
find_first_frame(dir, white_file)
|
|
436
|
-
blank_first_frame(dir)
|
|
437
416
|
find_render_start(
|
|
438
|
-
dir, orange_file,
|
|
417
|
+
dir, orange_file, cropped, is_mobile
|
|
439
418
|
)
|
|
440
|
-
find_last_frame(dir, white_file)
|
|
441
419
|
adjust_frame_times(dir)
|
|
442
|
-
if timeline_file is not None and not multiple:
|
|
443
|
-
synchronize_to_timeline(dir, timeline_file)
|
|
444
420
|
eliminate_duplicate_frames(dir, cropped, is_mobile)
|
|
445
|
-
eliminate_similar_frames(dir)
|
|
446
|
-
# See if we are limiting the number of frames to keep
|
|
447
|
-
# (before processing them to save processing time)
|
|
448
|
-
if options.maxframes > 0:
|
|
449
|
-
cap_frame_count(dir, options.maxframes)
|
|
450
421
|
crop_viewport(dir)
|
|
451
422
|
gc.collect()
|
|
452
423
|
else:
|
|
@@ -492,10 +463,8 @@ def extract_frames(video, directory, full_resolution, viewport):
|
|
|
492
463
|
]
|
|
493
464
|
logging.debug(" ".join(command))
|
|
494
465
|
lines = []
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
else:
|
|
498
|
-
proc = subprocess.Popen(command, stderr=subprocess.PIPE)
|
|
466
|
+
|
|
467
|
+
proc = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="UTF-8")
|
|
499
468
|
while proc.poll() is None:
|
|
500
469
|
lines.extend(iter(proc.stderr.readline, ""))
|
|
501
470
|
|
|
@@ -526,10 +495,7 @@ def find_recording_platform(video):
|
|
|
526
495
|
logging.debug(command)
|
|
527
496
|
|
|
528
497
|
lines = []
|
|
529
|
-
|
|
530
|
-
proc = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="UTF-8")
|
|
531
|
-
else:
|
|
532
|
-
proc = subprocess.Popen(command, stderr=subprocess.PIPE)
|
|
498
|
+
proc = subprocess.Popen(command, stderr=subprocess.PIPE, encoding="UTF-8")
|
|
533
499
|
|
|
534
500
|
while proc.poll() is None:
|
|
535
501
|
lines.extend(iter(proc.stderr.readline, ""))
|
|
@@ -542,51 +508,6 @@ def find_recording_platform(video):
|
|
|
542
508
|
|
|
543
509
|
return is_mobile
|
|
544
510
|
|
|
545
|
-
|
|
546
|
-
def split_videos(directory, orange_file):
|
|
547
|
-
"""Split multiple videos on orange frame separators"""
|
|
548
|
-
logging.debug("Splitting video on orange frames (this may take a while)...")
|
|
549
|
-
directories = []
|
|
550
|
-
current = 0
|
|
551
|
-
found_orange = False
|
|
552
|
-
video_dir = None
|
|
553
|
-
frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
554
|
-
if len(frames):
|
|
555
|
-
for frame in frames:
|
|
556
|
-
if is_color_frame(frame, orange_file):
|
|
557
|
-
if not found_orange:
|
|
558
|
-
found_orange = True
|
|
559
|
-
# Make a copy of the orange frame for the end of the
|
|
560
|
-
# current video
|
|
561
|
-
if video_dir is not None:
|
|
562
|
-
dest = os.path.join(video_dir, os.path.basename(frame))
|
|
563
|
-
shutil.copyfile(frame, dest)
|
|
564
|
-
current += 1
|
|
565
|
-
video_dir = os.path.join(directory, str(current))
|
|
566
|
-
logging.debug(
|
|
567
|
-
"Orange frame found: %s, starting video directory %s",
|
|
568
|
-
frame,
|
|
569
|
-
video_dir,
|
|
570
|
-
)
|
|
571
|
-
if not os.path.isdir(video_dir):
|
|
572
|
-
os.mkdir(video_dir, 0o755)
|
|
573
|
-
if os.path.isdir(video_dir):
|
|
574
|
-
video_dir = os.path.realpath(video_dir)
|
|
575
|
-
clean_directory(video_dir)
|
|
576
|
-
directories.append(video_dir)
|
|
577
|
-
else:
|
|
578
|
-
video_dir = None
|
|
579
|
-
else:
|
|
580
|
-
found_orange = False
|
|
581
|
-
if video_dir is not None:
|
|
582
|
-
dest = os.path.join(video_dir, os.path.basename(frame))
|
|
583
|
-
os.rename(frame, dest)
|
|
584
|
-
else:
|
|
585
|
-
logging.debug("Removing spurious frame %s at the beginning", frame)
|
|
586
|
-
os.remove(frame)
|
|
587
|
-
return directories
|
|
588
|
-
|
|
589
|
-
|
|
590
511
|
def remove_frames_before_orange(directory, orange_file):
|
|
591
512
|
"""Remove stray frames from the start of the video"""
|
|
592
513
|
frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
@@ -612,7 +533,6 @@ def remove_frames_before_orange(directory, orange_file):
|
|
|
612
533
|
logging.debug("Removing pre-orange frame %s", frame)
|
|
613
534
|
os.remove(frame)
|
|
614
535
|
|
|
615
|
-
|
|
616
536
|
def remove_orange_frames(directory, orange_file):
|
|
617
537
|
"""Remove orange frames from the beginning of the video"""
|
|
618
538
|
frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
@@ -714,7 +634,6 @@ def find_video_viewport(
|
|
|
714
634
|
video,
|
|
715
635
|
directory,
|
|
716
636
|
find_viewport,
|
|
717
|
-
viewport_time,
|
|
718
637
|
viewport_retries,
|
|
719
638
|
viewport_min_height,
|
|
720
639
|
viewport_min_width,
|
|
@@ -758,8 +677,6 @@ def find_video_viewport(
|
|
|
758
677
|
os.remove(frame)
|
|
759
678
|
|
|
760
679
|
command = ["ffmpeg", "-i", video]
|
|
761
|
-
if viewport_time:
|
|
762
|
-
command.extend(["-ss", viewport_time])
|
|
763
680
|
|
|
764
681
|
# Pull one frame from the video starting with the frame at
|
|
765
682
|
# the `retries` index
|
|
@@ -771,46 +688,7 @@ def find_video_viewport(
|
|
|
771
688
|
with Image.open(frame) as im:
|
|
772
689
|
width, height = im.size
|
|
773
690
|
logging.debug("%s is %dx%d", frame, width, height)
|
|
774
|
-
if
|
|
775
|
-
im = Image.open(frame)
|
|
776
|
-
pixels = im.load()
|
|
777
|
-
middle = int(math.floor(height / 2))
|
|
778
|
-
# Find the top edge (at ~40% in to deal with browsers that
|
|
779
|
-
# color the notification area)
|
|
780
|
-
x = int(width * 0.4)
|
|
781
|
-
y = 0
|
|
782
|
-
background = pixels[x, y]
|
|
783
|
-
top = None
|
|
784
|
-
while top is None and y < middle:
|
|
785
|
-
if not colors_are_similar(background, pixels[x, y]):
|
|
786
|
-
top = y
|
|
787
|
-
else:
|
|
788
|
-
y += 1
|
|
789
|
-
if top is None:
|
|
790
|
-
top = 0
|
|
791
|
-
logging.debug("Window top edge is {0:d}".format(top))
|
|
792
|
-
|
|
793
|
-
# Find the bottom edge
|
|
794
|
-
x = 0
|
|
795
|
-
y = height - 1
|
|
796
|
-
bottom = None
|
|
797
|
-
while bottom is None and y > middle:
|
|
798
|
-
if not colors_are_similar(background, pixels[x, y]):
|
|
799
|
-
bottom = y
|
|
800
|
-
else:
|
|
801
|
-
y -= 1
|
|
802
|
-
if bottom is None:
|
|
803
|
-
bottom = height - 1
|
|
804
|
-
logging.debug("Window bottom edge is {0:d}".format(bottom))
|
|
805
|
-
|
|
806
|
-
viewport = {
|
|
807
|
-
"x": 0,
|
|
808
|
-
"y": top,
|
|
809
|
-
"width": width,
|
|
810
|
-
"height": (bottom - top),
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
elif find_viewport:
|
|
691
|
+
if find_viewport:
|
|
814
692
|
viewport = find_image_viewport(frame, is_mobile)
|
|
815
693
|
else:
|
|
816
694
|
viewport = {"x": 0, "y": 0, "width": width, "height": height}
|
|
@@ -830,32 +708,6 @@ def find_video_viewport(
|
|
|
830
708
|
|
|
831
709
|
return viewport, cropped
|
|
832
710
|
|
|
833
|
-
|
|
834
|
-
def trim_video_end(directory, trim_time):
|
|
835
|
-
if trim_time > 0:
|
|
836
|
-
logging.debug(
|
|
837
|
-
"Trimming "
|
|
838
|
-
+ str(trim_time)
|
|
839
|
-
+ "ms from the end of the video in "
|
|
840
|
-
+ directory
|
|
841
|
-
)
|
|
842
|
-
frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
843
|
-
if len(frames):
|
|
844
|
-
match = re.compile(r"video-(?P<ms>[0-9]+)\.png")
|
|
845
|
-
m = re.search(match, frames[-1])
|
|
846
|
-
if m is not None:
|
|
847
|
-
frame_time = int(m.groupdict().get("ms"))
|
|
848
|
-
end_time = frame_time - trim_time
|
|
849
|
-
logging.debug("Trimming frames before " + str(end_time) + "ms")
|
|
850
|
-
for frame in frames:
|
|
851
|
-
m = re.search(match, frame)
|
|
852
|
-
if m is not None:
|
|
853
|
-
frame_time = int(m.groupdict().get("ms"))
|
|
854
|
-
if frame_time > end_time:
|
|
855
|
-
logging.debug("Trimming frame " + frame)
|
|
856
|
-
os.remove(frame)
|
|
857
|
-
|
|
858
|
-
|
|
859
711
|
def adjust_frame_times(directory):
|
|
860
712
|
offset = None
|
|
861
713
|
frames = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
@@ -876,108 +728,7 @@ def adjust_frame_times(directory):
|
|
|
876
728
|
dest = os.path.join(directory, "ms_{0:06d}.png".format(new_time))
|
|
877
729
|
os.rename(frame, dest)
|
|
878
730
|
|
|
879
|
-
|
|
880
|
-
def find_first_frame(directory, white_file):
|
|
881
|
-
logging.debug("Finding First Frame...")
|
|
882
|
-
try:
|
|
883
|
-
if options.startwhite:
|
|
884
|
-
files = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
885
|
-
count = len(files)
|
|
886
|
-
if count > 1:
|
|
887
|
-
from PIL import Image
|
|
888
|
-
|
|
889
|
-
for i in range(count):
|
|
890
|
-
if is_white_frame(files[i], white_file):
|
|
891
|
-
break
|
|
892
|
-
else:
|
|
893
|
-
logging.debug(
|
|
894
|
-
"Removing non-white frame {0} from the beginning".format(
|
|
895
|
-
files[i]
|
|
896
|
-
)
|
|
897
|
-
)
|
|
898
|
-
os.remove(files[i])
|
|
899
|
-
elif options.findstart > 0 and options.findstart <= 100:
|
|
900
|
-
files = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
901
|
-
count = len(files)
|
|
902
|
-
if count > 1:
|
|
903
|
-
from PIL import Image
|
|
904
|
-
|
|
905
|
-
blank = files[0]
|
|
906
|
-
with Image.open(blank) as im:
|
|
907
|
-
width, height = im.size
|
|
908
|
-
match_height = int(math.ceil(height * options.findstart / 100.0))
|
|
909
|
-
crop = (width, match_height, 0, 0)
|
|
910
|
-
found_first_change = False
|
|
911
|
-
found_white_frame = False
|
|
912
|
-
found_non_white_frame = False
|
|
913
|
-
first_frame = None
|
|
914
|
-
if white_file is None:
|
|
915
|
-
found_white_frame = True
|
|
916
|
-
for i in range(count):
|
|
917
|
-
if not found_first_change:
|
|
918
|
-
different = not frames_match(
|
|
919
|
-
files[i], files[i + 1], 5, 100, crop, None
|
|
920
|
-
)
|
|
921
|
-
logging.debug(
|
|
922
|
-
"Removing early frame %s from the beginning", files[i]
|
|
923
|
-
)
|
|
924
|
-
os.remove(files[i])
|
|
925
|
-
if different:
|
|
926
|
-
first_frame = files[i + 1]
|
|
927
|
-
found_first_change = True
|
|
928
|
-
elif not found_white_frame:
|
|
929
|
-
if files[i] != first_frame:
|
|
930
|
-
if found_non_white_frame:
|
|
931
|
-
found_white_frame = is_white_frame(files[i], white_file)
|
|
932
|
-
if not found_white_frame:
|
|
933
|
-
logging.debug(
|
|
934
|
-
"Removing early non-white frame {0} from the beginning".format(
|
|
935
|
-
files[i]
|
|
936
|
-
)
|
|
937
|
-
)
|
|
938
|
-
os.remove(files[i])
|
|
939
|
-
else:
|
|
940
|
-
found_non_white_frame = not is_white_frame(
|
|
941
|
-
files[i], white_file
|
|
942
|
-
)
|
|
943
|
-
logging.debug(
|
|
944
|
-
"Removing early pre-non-white frame {0} from the beginning".format(
|
|
945
|
-
files[i]
|
|
946
|
-
)
|
|
947
|
-
)
|
|
948
|
-
os.remove(files[i])
|
|
949
|
-
if found_first_change and found_white_frame:
|
|
950
|
-
break
|
|
951
|
-
except BaseException:
|
|
952
|
-
logging.exception("Error finding first frame")
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
def find_last_frame(directory, white_file):
|
|
956
|
-
logging.debug("Finding Last Frame...")
|
|
957
|
-
try:
|
|
958
|
-
if options.endwhite:
|
|
959
|
-
files = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
960
|
-
count = len(files)
|
|
961
|
-
if count > 2:
|
|
962
|
-
found_end = False
|
|
963
|
-
|
|
964
|
-
for i in range(2, count):
|
|
965
|
-
if found_end:
|
|
966
|
-
logging.debug(
|
|
967
|
-
"Removing frame {0} from the end".format(files[i])
|
|
968
|
-
)
|
|
969
|
-
os.remove(files[i])
|
|
970
|
-
if is_white_frame(files[i], white_file):
|
|
971
|
-
found_end = True
|
|
972
|
-
logging.debug(
|
|
973
|
-
"Removing ending white frame {0}".format(files[i])
|
|
974
|
-
)
|
|
975
|
-
os.remove(files[i])
|
|
976
|
-
except BaseException:
|
|
977
|
-
logging.exception("Error finding last frame")
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
def find_render_start(directory, orange_file, gray_file, cropped, is_mobile):
|
|
731
|
+
def find_render_start(directory, orange_file, cropped, is_mobile):
|
|
981
732
|
logging.debug("Finding Render Start...")
|
|
982
733
|
try:
|
|
983
734
|
if (
|
|
@@ -1050,9 +801,6 @@ def find_render_start(directory, orange_file, gray_file, cropped, is_mobile):
|
|
|
1050
801
|
):
|
|
1051
802
|
logging.debug("Removing orange frame %s", files[i])
|
|
1052
803
|
os.remove(files[i])
|
|
1053
|
-
elif gray_file is not None and is_color_frame(files[i], gray_file):
|
|
1054
|
-
logging.debug("Removing gray frame %s", files[i])
|
|
1055
|
-
os.remove(files[i])
|
|
1056
804
|
else:
|
|
1057
805
|
break
|
|
1058
806
|
except BaseException:
|
|
@@ -1070,13 +818,6 @@ def eliminate_duplicate_frames(directory, cropped, is_mobile):
|
|
|
1070
818
|
blank = files[0]
|
|
1071
819
|
with Image.open(blank) as im:
|
|
1072
820
|
width, height = im.size
|
|
1073
|
-
if options.viewport and options.notification:
|
|
1074
|
-
if (
|
|
1075
|
-
client_viewport["width"] == width
|
|
1076
|
-
and client_viewport["height"] == height
|
|
1077
|
-
):
|
|
1078
|
-
client_viewport = None
|
|
1079
|
-
|
|
1080
821
|
im_width = width
|
|
1081
822
|
im_height = height
|
|
1082
823
|
|
|
@@ -1161,47 +902,6 @@ def eliminate_duplicate_frames(directory, cropped, is_mobile):
|
|
|
1161
902
|
except BaseException:
|
|
1162
903
|
logging.exception("Error processing frames for duplicates")
|
|
1163
904
|
|
|
1164
|
-
|
|
1165
|
-
def eliminate_similar_frames(directory):
|
|
1166
|
-
logging.debug("Removing Similar Frames...")
|
|
1167
|
-
try:
|
|
1168
|
-
# only do this when decimate couldn't be used to eliminate similar
|
|
1169
|
-
# frames
|
|
1170
|
-
if options.notification:
|
|
1171
|
-
files = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1172
|
-
count = len(files)
|
|
1173
|
-
if count > 3:
|
|
1174
|
-
crop = None
|
|
1175
|
-
if client_viewport is not None:
|
|
1176
|
-
crop = (
|
|
1177
|
-
client_viewport["width"],
|
|
1178
|
-
client_viewport["height"],
|
|
1179
|
-
client_viewport["x"],
|
|
1180
|
-
client_viewport["y"],
|
|
1181
|
-
)
|
|
1182
|
-
baseline = files[1]
|
|
1183
|
-
for i in range(2, count - 1):
|
|
1184
|
-
if frames_match(baseline, files[i], 1, 0, crop, None):
|
|
1185
|
-
logging.debug("Removing similar frame {0}".format(files[i]))
|
|
1186
|
-
os.remove(files[i])
|
|
1187
|
-
else:
|
|
1188
|
-
baseline = files[i]
|
|
1189
|
-
except BaseException:
|
|
1190
|
-
logging.exception("Error removing similar frames")
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
def blank_first_frame(directory):
|
|
1194
|
-
try:
|
|
1195
|
-
if options.forceblank:
|
|
1196
|
-
files = sorted(glob.glob(os.path.join(directory, "video-*.png")))
|
|
1197
|
-
count = len(files)
|
|
1198
|
-
if count > 1:
|
|
1199
|
-
blank = blank_frame(files[0])
|
|
1200
|
-
blank.save(files[0])
|
|
1201
|
-
except BaseException:
|
|
1202
|
-
logging.exception("Error blanking first frame")
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
905
|
def crop_viewport(directory):
|
|
1206
906
|
if client_viewport is not None:
|
|
1207
907
|
try:
|
|
@@ -1228,14 +928,7 @@ def crop_viewport(directory):
|
|
|
1228
928
|
def get_decimate_filter():
|
|
1229
929
|
decimate = None
|
|
1230
930
|
try:
|
|
1231
|
-
|
|
1232
|
-
filters = subprocess.check_output(
|
|
1233
|
-
["ffmpeg", "-filters"], stderr=subprocess.STDOUT, encoding="UTF-8"
|
|
1234
|
-
)
|
|
1235
|
-
else:
|
|
1236
|
-
filters = subprocess.check_output(
|
|
1237
|
-
["ffmpeg", "-filters"], stderr=subprocess.STDOUT
|
|
1238
|
-
)
|
|
931
|
+
filters = subprocess.check_output(["ffmpeg", "-filters"], stderr=subprocess.STDOUT, encoding="UTF-8")
|
|
1239
932
|
lines = filters.split("\n")
|
|
1240
933
|
match = re.compile(
|
|
1241
934
|
r"(?P<filter>[\w]*decimate).*V->V.*Remove near-duplicate frames"
|
|
@@ -1312,50 +1005,6 @@ def is_color_frame(file, color_file):
|
|
|
1312
1005
|
frame_cache[file][color_file] = bool(match)
|
|
1313
1006
|
return match
|
|
1314
1007
|
|
|
1315
|
-
|
|
1316
|
-
def is_white_frame(file, white_file):
|
|
1317
|
-
white = False
|
|
1318
|
-
if os.path.isfile(white_file):
|
|
1319
|
-
try:
|
|
1320
|
-
from PIL import Image
|
|
1321
|
-
|
|
1322
|
-
fmt_img = None
|
|
1323
|
-
white_img = Image.open(white_file)
|
|
1324
|
-
|
|
1325
|
-
if options.viewport:
|
|
1326
|
-
with Image.open(file) as im:
|
|
1327
|
-
fmt_img = resize(im, 200, 200)
|
|
1328
|
-
|
|
1329
|
-
else:
|
|
1330
|
-
with Image.open(file) as im:
|
|
1331
|
-
width, height, _ = im.shape
|
|
1332
|
-
fmt_img = crop_im(
|
|
1333
|
-
im, 0.5 * width, 0.33 * height, 0, 0, gravity="center"
|
|
1334
|
-
)
|
|
1335
|
-
fmt_img = resize(fmt_img, 200, 200)
|
|
1336
|
-
|
|
1337
|
-
if client_viewport is not None:
|
|
1338
|
-
with Image.open(file) as im:
|
|
1339
|
-
width, height, _ = im.shape
|
|
1340
|
-
fmt_img = crop_im(
|
|
1341
|
-
im,
|
|
1342
|
-
client_viewport["width"],
|
|
1343
|
-
client_viewport["height"],
|
|
1344
|
-
client_viewport["x"],
|
|
1345
|
-
client_viewport["y"],
|
|
1346
|
-
)
|
|
1347
|
-
fmt_img = resize(fmt_img, 200, 200)
|
|
1348
|
-
except BaseException as e:
|
|
1349
|
-
logging.exception(e)
|
|
1350
|
-
return None
|
|
1351
|
-
|
|
1352
|
-
different_pixels = compare(white_img, fmt_img, fuzz=0.1)
|
|
1353
|
-
if different_pixels < 500:
|
|
1354
|
-
white = True
|
|
1355
|
-
|
|
1356
|
-
return white
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
1008
|
def colors_are_similar(a, b, threshold=15):
|
|
1360
1009
|
similar = True
|
|
1361
1010
|
sum = 0
|
|
@@ -1424,179 +1073,6 @@ def generate_orange_png(orange_file):
|
|
|
1424
1073
|
except BaseException:
|
|
1425
1074
|
logging.exception("Error generating orange png " + orange_file)
|
|
1426
1075
|
|
|
1427
|
-
|
|
1428
|
-
def generate_gray_png(gray_file):
|
|
1429
|
-
try:
|
|
1430
|
-
from PIL import Image, ImageDraw
|
|
1431
|
-
|
|
1432
|
-
im = Image.new("RGB", (200, 200))
|
|
1433
|
-
draw = ImageDraw.Draw(im)
|
|
1434
|
-
draw.rectangle([0, 0, 200, 200], fill=(128, 128, 128))
|
|
1435
|
-
del draw
|
|
1436
|
-
im.save(gray_file, "PNG")
|
|
1437
|
-
except BaseException:
|
|
1438
|
-
logging.exception("Error generating gray png " + gray_file)
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
def generate_white_png(white_file):
|
|
1442
|
-
try:
|
|
1443
|
-
from PIL import Image, ImageDraw
|
|
1444
|
-
|
|
1445
|
-
im = Image.new("RGB", (200, 200))
|
|
1446
|
-
draw = ImageDraw.Draw(im)
|
|
1447
|
-
draw.rectangle([0, 0, 200, 200], fill=(255, 255, 255))
|
|
1448
|
-
del draw
|
|
1449
|
-
im.save(white_file, "PNG")
|
|
1450
|
-
except BaseException:
|
|
1451
|
-
logging.exception("Error generating white png " + white_file)
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
def synchronize_to_timeline(directory, timeline_file):
|
|
1455
|
-
offset = get_timeline_offset(timeline_file)
|
|
1456
|
-
if offset > 0:
|
|
1457
|
-
frames = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1458
|
-
match = re.compile(r"ms_(?P<ms>[0-9]+)\.png")
|
|
1459
|
-
for frame in frames:
|
|
1460
|
-
m = re.search(match, frame)
|
|
1461
|
-
if m is not None:
|
|
1462
|
-
frame_time = int(m.groupdict().get("ms"))
|
|
1463
|
-
new_time = max(frame_time - offset, 0)
|
|
1464
|
-
dest = os.path.join(directory, "ms_{0:06d}.png".format(new_time))
|
|
1465
|
-
if frame != dest:
|
|
1466
|
-
if os.path.isfile(dest):
|
|
1467
|
-
os.remove(dest)
|
|
1468
|
-
os.rename(frame, dest)
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
def get_timeline_offset(timeline_file):
|
|
1472
|
-
offset = 0
|
|
1473
|
-
try:
|
|
1474
|
-
file_name, ext = os.path.splitext(timeline_file)
|
|
1475
|
-
if ext.lower() == ".gz":
|
|
1476
|
-
f = gzip.open(timeline_file, GZIP_READ_TEXT)
|
|
1477
|
-
else:
|
|
1478
|
-
f = open(timeline_file, "r")
|
|
1479
|
-
timeline = json.load(f)
|
|
1480
|
-
f.close()
|
|
1481
|
-
last_paint = None
|
|
1482
|
-
first_navigate = None
|
|
1483
|
-
|
|
1484
|
-
# In the case of a trace instead of a timeline we want the list of
|
|
1485
|
-
# events
|
|
1486
|
-
if "traceEvents" in timeline:
|
|
1487
|
-
timeline = timeline["traceEvents"]
|
|
1488
|
-
|
|
1489
|
-
for timeline_event in timeline:
|
|
1490
|
-
paint_time = get_timeline_event_paint_time(timeline_event)
|
|
1491
|
-
if paint_time is not None:
|
|
1492
|
-
last_paint = paint_time
|
|
1493
|
-
first_navigate = get_timeline_event_navigate_time(timeline_event)
|
|
1494
|
-
if first_navigate is not None:
|
|
1495
|
-
break
|
|
1496
|
-
|
|
1497
|
-
if (
|
|
1498
|
-
last_paint is not None
|
|
1499
|
-
and first_navigate is not None
|
|
1500
|
-
and first_navigate > last_paint
|
|
1501
|
-
):
|
|
1502
|
-
offset = int(round(first_navigate - last_paint))
|
|
1503
|
-
logging.info(
|
|
1504
|
-
"Trimming {0:d}ms from the start of the video based on timeline synchronization".format(
|
|
1505
|
-
offset
|
|
1506
|
-
)
|
|
1507
|
-
)
|
|
1508
|
-
except BaseException:
|
|
1509
|
-
logging.critical("Error processing timeline file " + timeline_file)
|
|
1510
|
-
|
|
1511
|
-
return offset
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
def get_timeline_event_paint_time(timeline_event):
|
|
1515
|
-
paint_time = None
|
|
1516
|
-
if "cat" in timeline_event:
|
|
1517
|
-
if (
|
|
1518
|
-
timeline_event["cat"].find("devtools.timeline") >= 0
|
|
1519
|
-
and "ts" in timeline_event
|
|
1520
|
-
and "name" in timeline_event
|
|
1521
|
-
and (
|
|
1522
|
-
timeline_event["name"].find("Paint") >= 0
|
|
1523
|
-
or timeline_event["name"].find("CompositeLayers") >= 0
|
|
1524
|
-
)
|
|
1525
|
-
):
|
|
1526
|
-
paint_time = float(timeline_event["ts"]) / 1000.0
|
|
1527
|
-
if "dur" in timeline_event:
|
|
1528
|
-
paint_time += float(timeline_event["dur"]) / 1000.0
|
|
1529
|
-
elif "method" in timeline_event:
|
|
1530
|
-
if (
|
|
1531
|
-
timeline_event["method"] == "Timeline.eventRecorded"
|
|
1532
|
-
and "params" in timeline_event
|
|
1533
|
-
and "record" in timeline_event["params"]
|
|
1534
|
-
):
|
|
1535
|
-
paint_time = get_timeline_event_paint_time(
|
|
1536
|
-
timeline_event["params"]["record"]
|
|
1537
|
-
)
|
|
1538
|
-
else:
|
|
1539
|
-
if "type" in timeline_event and (
|
|
1540
|
-
timeline_event["type"] == "Rasterize"
|
|
1541
|
-
or timeline_event["type"] == "CompositeLayers"
|
|
1542
|
-
or timeline_event["type"] == "Paint"
|
|
1543
|
-
):
|
|
1544
|
-
if "endTime" in timeline_event:
|
|
1545
|
-
paint_time = timeline_event["endTime"]
|
|
1546
|
-
elif "startTime" in timeline_event:
|
|
1547
|
-
paint_time = timeline_event["startTime"]
|
|
1548
|
-
|
|
1549
|
-
# Check for any child paint events
|
|
1550
|
-
if "children" in timeline_event:
|
|
1551
|
-
for child in timeline_event["children"]:
|
|
1552
|
-
child_paint_time = get_timeline_event_paint_time(child)
|
|
1553
|
-
if child_paint_time is not None and (
|
|
1554
|
-
paint_time is None or child_paint_time > paint_time
|
|
1555
|
-
):
|
|
1556
|
-
paint_time = child_paint_time
|
|
1557
|
-
|
|
1558
|
-
return paint_time
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
def get_timeline_event_navigate_time(timeline_event):
|
|
1562
|
-
navigate_time = None
|
|
1563
|
-
if "cat" in timeline_event:
|
|
1564
|
-
if (
|
|
1565
|
-
timeline_event["cat"].find("devtools.timeline") >= 0
|
|
1566
|
-
and "ts" in timeline_event
|
|
1567
|
-
and "name" in timeline_event
|
|
1568
|
-
and timeline_event["name"] == "ResourceSendRequest"
|
|
1569
|
-
):
|
|
1570
|
-
navigate_time = float(timeline_event["ts"]) / 1000.0
|
|
1571
|
-
elif "method" in timeline_event:
|
|
1572
|
-
if (
|
|
1573
|
-
timeline_event["method"] == "Timeline.eventRecorded"
|
|
1574
|
-
and "params" in timeline_event
|
|
1575
|
-
and "record" in timeline_event["params"]
|
|
1576
|
-
):
|
|
1577
|
-
navigate_time = get_timeline_event_navigate_time(
|
|
1578
|
-
timeline_event["params"]["record"]
|
|
1579
|
-
)
|
|
1580
|
-
else:
|
|
1581
|
-
if (
|
|
1582
|
-
"type" in timeline_event
|
|
1583
|
-
and timeline_event["type"] == "ResourceSendRequest"
|
|
1584
|
-
and "startTime" in timeline_event
|
|
1585
|
-
):
|
|
1586
|
-
navigate_time = timeline_event["startTime"]
|
|
1587
|
-
|
|
1588
|
-
# Check for any child paint events
|
|
1589
|
-
if "children" in timeline_event:
|
|
1590
|
-
for child in timeline_event["children"]:
|
|
1591
|
-
child_navigate_time = get_timeline_event_navigate_time(child)
|
|
1592
|
-
if child_navigate_time is not None and (
|
|
1593
|
-
navigate_time is None or child_navigate_time < navigate_time
|
|
1594
|
-
):
|
|
1595
|
-
navigate_time = child_navigate_time
|
|
1596
|
-
|
|
1597
|
-
return navigate_time
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
1076
|
##########################################################################
|
|
1601
1077
|
# Histogram calculations
|
|
1602
1078
|
##########################################################################
|
|
@@ -1714,172 +1190,6 @@ def convert_to_jpeg(directory, quality):
|
|
|
1714
1190
|
|
|
1715
1191
|
logging.debug("Done converting video frames to JPEG")
|
|
1716
1192
|
|
|
1717
|
-
|
|
1718
|
-
##########################################################################
|
|
1719
|
-
# Video rendering
|
|
1720
|
-
##########################################################################
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
def render_video(directory, video_file):
|
|
1724
|
-
"""Render the frames to the given mp4 file"""
|
|
1725
|
-
directory = os.path.realpath(directory)
|
|
1726
|
-
files = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1727
|
-
if len(files) > 1:
|
|
1728
|
-
current_image = None
|
|
1729
|
-
with open(os.path.join(directory, files[0]), "rb") as f_in:
|
|
1730
|
-
current_image = f_in.read()
|
|
1731
|
-
if current_image is not None:
|
|
1732
|
-
command = [
|
|
1733
|
-
"ffmpeg",
|
|
1734
|
-
"-f",
|
|
1735
|
-
"image2pipe",
|
|
1736
|
-
"-vcodec",
|
|
1737
|
-
"png",
|
|
1738
|
-
"-r",
|
|
1739
|
-
"30",
|
|
1740
|
-
"-i",
|
|
1741
|
-
"-",
|
|
1742
|
-
"-vcodec",
|
|
1743
|
-
"libx264",
|
|
1744
|
-
"-r",
|
|
1745
|
-
"30",
|
|
1746
|
-
"-crf",
|
|
1747
|
-
"24",
|
|
1748
|
-
"-g",
|
|
1749
|
-
"15",
|
|
1750
|
-
"-preset",
|
|
1751
|
-
"superfast",
|
|
1752
|
-
"-y",
|
|
1753
|
-
video_file,
|
|
1754
|
-
]
|
|
1755
|
-
try:
|
|
1756
|
-
proc = subprocess.Popen(command, stdin=subprocess.PIPE)
|
|
1757
|
-
if proc:
|
|
1758
|
-
match = re.compile(r"ms_([0-9]+)\.")
|
|
1759
|
-
m = re.search(match, files[1])
|
|
1760
|
-
file_index = 0
|
|
1761
|
-
last_index = len(files) - 1
|
|
1762
|
-
if m is not None:
|
|
1763
|
-
next_image_time = int(m.group(1))
|
|
1764
|
-
done = False
|
|
1765
|
-
current_frame = 0
|
|
1766
|
-
while not done:
|
|
1767
|
-
current_frame_time = int(
|
|
1768
|
-
round(float(current_frame) * 1000.0 / 30.0)
|
|
1769
|
-
)
|
|
1770
|
-
if current_frame_time >= next_image_time:
|
|
1771
|
-
file_index += 1
|
|
1772
|
-
with open(
|
|
1773
|
-
os.path.join(directory, files[file_index]), "rb"
|
|
1774
|
-
) as f_in:
|
|
1775
|
-
current_image = f_in.read()
|
|
1776
|
-
if file_index < last_index:
|
|
1777
|
-
m = re.search(match, files[file_index + 1])
|
|
1778
|
-
if m:
|
|
1779
|
-
next_image_time = int(m.group(1))
|
|
1780
|
-
else:
|
|
1781
|
-
done = True
|
|
1782
|
-
proc.stdin.write(current_image)
|
|
1783
|
-
current_frame += 1
|
|
1784
|
-
# hold the end frame for one second so it's actually
|
|
1785
|
-
# visible
|
|
1786
|
-
for i in range(30):
|
|
1787
|
-
proc.stdin.write(current_image)
|
|
1788
|
-
proc.stdin.close()
|
|
1789
|
-
proc.communicate()
|
|
1790
|
-
except Exception:
|
|
1791
|
-
pass
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
##########################################################################
|
|
1795
|
-
# Reduce the number of saved video frames if necessary
|
|
1796
|
-
##########################################################################
|
|
1797
|
-
def cap_frame_count(directory, maxframes):
|
|
1798
|
-
directory = os.path.realpath(directory)
|
|
1799
|
-
frames = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1800
|
-
frame_count = len(frames)
|
|
1801
|
-
if frame_count > maxframes:
|
|
1802
|
-
# First pass, sample all video frames at 10fps instead of 60fps,
|
|
1803
|
-
# keeping the first 20% of the target
|
|
1804
|
-
logging.debug(
|
|
1805
|
-
"Sampling 10fps: Reducing {0:d} frames to target of {1:d}...".format(
|
|
1806
|
-
frame_count, maxframes
|
|
1807
|
-
)
|
|
1808
|
-
)
|
|
1809
|
-
skip_frames = int(maxframes * 0.2)
|
|
1810
|
-
sample_frames(frames, 100, 0, skip_frames)
|
|
1811
|
-
|
|
1812
|
-
frames = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1813
|
-
frame_count = len(frames)
|
|
1814
|
-
if frame_count > maxframes:
|
|
1815
|
-
# Second pass, sample all video frames after the first 5 seconds at
|
|
1816
|
-
# 2fps, keeping the first 40% of the target
|
|
1817
|
-
logging.debug(
|
|
1818
|
-
"Sampling 2fps: Reducing {0:d} frames to target of {1:d}...".format(
|
|
1819
|
-
frame_count, maxframes
|
|
1820
|
-
)
|
|
1821
|
-
)
|
|
1822
|
-
skip_frames = int(maxframes * 0.4)
|
|
1823
|
-
sample_frames(frames, 500, 5000, skip_frames)
|
|
1824
|
-
|
|
1825
|
-
frames = sorted(glob.glob(os.path.join(directory, "ms_*.png")))
|
|
1826
|
-
frame_count = len(frames)
|
|
1827
|
-
if frame_count > maxframes:
|
|
1828
|
-
# Third pass, sample all video frames after the first 10
|
|
1829
|
-
# seconds at 1fps, keeping the first 60% of the target
|
|
1830
|
-
logging.debug(
|
|
1831
|
-
"Sampling 1fps: Reducing {0:d} frames to target of {1:d}...".format(
|
|
1832
|
-
frame_count, maxframes
|
|
1833
|
-
)
|
|
1834
|
-
)
|
|
1835
|
-
skip_frames = int(maxframes * 0.6)
|
|
1836
|
-
sample_frames(frames, 1000, 10000, skip_frames)
|
|
1837
|
-
|
|
1838
|
-
logging.debug(
|
|
1839
|
-
"{0:d} frames final count with a target max of {1:d} frames...".format(
|
|
1840
|
-
frame_count, maxframes
|
|
1841
|
-
)
|
|
1842
|
-
)
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
def sample_frames(frames, interval, start_ms, skip_frames):
|
|
1846
|
-
frame_count = len(frames)
|
|
1847
|
-
if frame_count > 3:
|
|
1848
|
-
# Always keep the first and last frames, only sample in the middle
|
|
1849
|
-
first_frame = frames[0]
|
|
1850
|
-
first_change = frames[1]
|
|
1851
|
-
last_frame = frames[-1]
|
|
1852
|
-
match = re.compile(r"ms_(?P<ms>[0-9]+)\.")
|
|
1853
|
-
m = re.search(match, first_change)
|
|
1854
|
-
first_change_time = 0
|
|
1855
|
-
if m is not None:
|
|
1856
|
-
first_change_time = int(m.groupdict().get("ms"))
|
|
1857
|
-
last_bucket = None
|
|
1858
|
-
logging.debug(
|
|
1859
|
-
"Sapling frames in {0:d}ms intervals after {1:d} ms, skipping {2:d} frames...".format(
|
|
1860
|
-
interval, first_change_time + start_ms, skip_frames
|
|
1861
|
-
)
|
|
1862
|
-
)
|
|
1863
|
-
frame_count = 0
|
|
1864
|
-
for frame in frames:
|
|
1865
|
-
m = re.search(match, frame)
|
|
1866
|
-
if m is not None:
|
|
1867
|
-
frame_count += 1
|
|
1868
|
-
frame_time = int(m.groupdict().get("ms"))
|
|
1869
|
-
frame_bucket = int(math.floor(frame_time / interval))
|
|
1870
|
-
if (
|
|
1871
|
-
frame_time > first_change_time + start_ms
|
|
1872
|
-
and frame_bucket == last_bucket
|
|
1873
|
-
and frame != first_frame
|
|
1874
|
-
and frame != first_change
|
|
1875
|
-
and frame != last_frame
|
|
1876
|
-
and frame_count > skip_frames
|
|
1877
|
-
):
|
|
1878
|
-
logging.debug("Removing sampled frame " + frame)
|
|
1879
|
-
os.remove(frame)
|
|
1880
|
-
last_bucket = frame_bucket
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
1193
|
##########################################################################
|
|
1884
1194
|
# Visual Metrics
|
|
1885
1195
|
##########################################################################
|
|
@@ -2350,13 +1660,8 @@ def check_config():
|
|
|
2350
1660
|
|
|
2351
1661
|
def check_process(command, output):
|
|
2352
1662
|
ok = False
|
|
2353
|
-
try:
|
|
2354
|
-
|
|
2355
|
-
out = subprocess.check_output(
|
|
2356
|
-
command, stderr=subprocess.STDOUT, shell=True, encoding="UTF-8"
|
|
2357
|
-
)
|
|
2358
|
-
else:
|
|
2359
|
-
out = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
|
|
1663
|
+
try:
|
|
1664
|
+
out = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True, encoding="UTF-8")
|
|
2360
1665
|
if out.find(output) > -1:
|
|
2361
1666
|
ok = True
|
|
2362
1667
|
except BaseException:
|
|
@@ -2414,19 +1719,6 @@ def main():
|
|
|
2414
1719
|
"--screenshot",
|
|
2415
1720
|
help="Save the last frame of video as an image to the path provided.",
|
|
2416
1721
|
)
|
|
2417
|
-
parser.add_argument(
|
|
2418
|
-
"-g",
|
|
2419
|
-
"--histogram",
|
|
2420
|
-
help="Histogram file (as input if exists or as output if "
|
|
2421
|
-
"histograms need to be calculated).",
|
|
2422
|
-
)
|
|
2423
|
-
parser.add_argument(
|
|
2424
|
-
"-m",
|
|
2425
|
-
"--timeline",
|
|
2426
|
-
help="Timeline capture from Chrome dev tools. Used to synchronize the video"
|
|
2427
|
-
" start time and only applies when orange frames are removed "
|
|
2428
|
-
"(see --orange). The timeline file can be gzipped if it ends in .gz",
|
|
2429
|
-
)
|
|
2430
1722
|
parser.add_argument(
|
|
2431
1723
|
"-q",
|
|
2432
1724
|
"--quality",
|
|
@@ -2457,37 +1749,6 @@ def main():
|
|
|
2457
1749
|
default=False,
|
|
2458
1750
|
help="Remove orange-colored frames from the beginning of the video.",
|
|
2459
1751
|
)
|
|
2460
|
-
parser.add_argument(
|
|
2461
|
-
"--gray",
|
|
2462
|
-
action="store_true",
|
|
2463
|
-
default=False,
|
|
2464
|
-
help="Remove gray-colored frames from the beginning of the video.",
|
|
2465
|
-
)
|
|
2466
|
-
parser.add_argument(
|
|
2467
|
-
"-w",
|
|
2468
|
-
"--white",
|
|
2469
|
-
action="store_true",
|
|
2470
|
-
default=False,
|
|
2471
|
-
help="Wait for a full white frame after a non-white frame "
|
|
2472
|
-
"at the beginning of the video.",
|
|
2473
|
-
)
|
|
2474
|
-
parser.add_argument(
|
|
2475
|
-
"--multiple",
|
|
2476
|
-
action="store_true",
|
|
2477
|
-
default=False,
|
|
2478
|
-
help="Multiple videos are combined, separated by orange frames."
|
|
2479
|
-
"In this mode only the extraction is done and analysis "
|
|
2480
|
-
"needs to be run separetely on each directory. Numbered "
|
|
2481
|
-
"directories will be created for each video under the output "
|
|
2482
|
-
"directory.",
|
|
2483
|
-
)
|
|
2484
|
-
parser.add_argument(
|
|
2485
|
-
"-n",
|
|
2486
|
-
"--notification",
|
|
2487
|
-
action="store_true",
|
|
2488
|
-
default=False,
|
|
2489
|
-
help="Trim the notification and home bars from the window.",
|
|
2490
|
-
)
|
|
2491
1752
|
parser.add_argument(
|
|
2492
1753
|
"-p",
|
|
2493
1754
|
"--viewport",
|
|
@@ -2495,12 +1756,6 @@ def main():
|
|
|
2495
1756
|
default=False,
|
|
2496
1757
|
help="Locate and use the viewport from the first video frame.",
|
|
2497
1758
|
)
|
|
2498
|
-
parser.add_argument(
|
|
2499
|
-
"-t",
|
|
2500
|
-
"--viewporttime",
|
|
2501
|
-
help="Time of the video frame to use for identifying the viewport "
|
|
2502
|
-
"(in HH:MM:SS.xx format).",
|
|
2503
|
-
)
|
|
2504
1759
|
parser.add_argument(
|
|
2505
1760
|
"--viewportretries",
|
|
2506
1761
|
type=int,
|
|
@@ -2537,13 +1792,6 @@ def main():
|
|
|
2537
1792
|
default=0,
|
|
2538
1793
|
help="End time (in milliseconds) for calculating visual metrics.",
|
|
2539
1794
|
)
|
|
2540
|
-
parser.add_argument(
|
|
2541
|
-
"--findstart",
|
|
2542
|
-
type=int,
|
|
2543
|
-
default=0,
|
|
2544
|
-
help="Find the start of activity by looking at the top X%% "
|
|
2545
|
-
"of the video (like a browser address bar).",
|
|
2546
|
-
)
|
|
2547
1795
|
parser.add_argument(
|
|
2548
1796
|
"--renderignore",
|
|
2549
1797
|
type=int,
|
|
@@ -2551,38 +1799,6 @@ def main():
|
|
|
2551
1799
|
help="Ignore the center X%% of the frame when looking for "
|
|
2552
1800
|
"the first rendered frame (useful for Opera mini).",
|
|
2553
1801
|
)
|
|
2554
|
-
parser.add_argument(
|
|
2555
|
-
"--startwhite",
|
|
2556
|
-
action="store_true",
|
|
2557
|
-
default=False,
|
|
2558
|
-
help="Find the first fully white frame as the start of the video.",
|
|
2559
|
-
)
|
|
2560
|
-
parser.add_argument(
|
|
2561
|
-
"--endwhite",
|
|
2562
|
-
action="store_true",
|
|
2563
|
-
default=False,
|
|
2564
|
-
help="Find the first fully white frame after render start as the "
|
|
2565
|
-
"end of the video.",
|
|
2566
|
-
)
|
|
2567
|
-
parser.add_argument(
|
|
2568
|
-
"--forceblank",
|
|
2569
|
-
action="store_true",
|
|
2570
|
-
default=False,
|
|
2571
|
-
help="Force the first frame to be blank white.",
|
|
2572
|
-
)
|
|
2573
|
-
parser.add_argument(
|
|
2574
|
-
"--trimend",
|
|
2575
|
-
type=int,
|
|
2576
|
-
default=0,
|
|
2577
|
-
help="Time to trim from the end of the video (in milliseconds).",
|
|
2578
|
-
)
|
|
2579
|
-
parser.add_argument(
|
|
2580
|
-
"--maxframes",
|
|
2581
|
-
type=int,
|
|
2582
|
-
default=0,
|
|
2583
|
-
help="Maximum number of video frames before reducing by "
|
|
2584
|
-
"sampling (to 10fps, 1fps, etc).",
|
|
2585
|
-
)
|
|
2586
1802
|
parser.add_argument(
|
|
2587
1803
|
"-k",
|
|
2588
1804
|
"--perceptual",
|
|
@@ -2620,7 +1836,6 @@ def main():
|
|
|
2620
1836
|
not options.check
|
|
2621
1837
|
and not options.dir
|
|
2622
1838
|
and not options.video
|
|
2623
|
-
and not options.histogram
|
|
2624
1839
|
):
|
|
2625
1840
|
parser.error(
|
|
2626
1841
|
"A video, Directory of images or histograms file needs to be provided.\n\n"
|
|
@@ -2639,10 +1854,7 @@ def main():
|
|
|
2639
1854
|
directory = temp_dir
|
|
2640
1855
|
if options.dir is not None:
|
|
2641
1856
|
directory = options.dir
|
|
2642
|
-
|
|
2643
|
-
histogram_file = options.histogram
|
|
2644
|
-
else:
|
|
2645
|
-
histogram_file = os.path.join(temp_dir, "histograms.json.gz")
|
|
1857
|
+
histogram_file = os.path.join(temp_dir, "histograms.json.gz")
|
|
2646
1858
|
|
|
2647
1859
|
# Set up logging
|
|
2648
1860
|
log_level = logging.CRITICAL
|
|
@@ -2666,9 +1878,6 @@ def main():
|
|
|
2666
1878
|
level=log_level, format=options.logformat, datefmt="%H:%M:%S"
|
|
2667
1879
|
)
|
|
2668
1880
|
|
|
2669
|
-
if options.multiple:
|
|
2670
|
-
options.orange = True
|
|
2671
|
-
|
|
2672
1881
|
ok = False
|
|
2673
1882
|
try:
|
|
2674
1883
|
if not options.check:
|
|
@@ -2688,77 +1897,52 @@ def main():
|
|
|
2688
1897
|
if not os.path.isfile(orange_file):
|
|
2689
1898
|
orange_file = os.path.join(colors_temp_dir, "orange.png")
|
|
2690
1899
|
generate_orange_png(orange_file)
|
|
2691
|
-
white_file = None
|
|
2692
|
-
if options.white or options.startwhite or options.endwhite:
|
|
2693
|
-
white_file = os.path.join(
|
|
2694
|
-
os.path.dirname(os.path.realpath(__file__)), "white.png"
|
|
2695
|
-
)
|
|
2696
|
-
if not os.path.isfile(white_file):
|
|
2697
|
-
white_file = os.path.join(colors_temp_dir, "white.png")
|
|
2698
|
-
generate_white_png(white_file)
|
|
2699
|
-
gray_file = None
|
|
2700
|
-
if options.gray:
|
|
2701
|
-
gray_file = os.path.join(
|
|
2702
|
-
os.path.dirname(os.path.realpath(__file__)), "gray.png"
|
|
2703
|
-
)
|
|
2704
|
-
if not os.path.isfile(gray_file):
|
|
2705
|
-
gray_file = os.path.join(colors_temp_dir, "gray.png")
|
|
2706
|
-
generate_gray_png(gray_file)
|
|
2707
1900
|
video_to_frames(
|
|
2708
1901
|
options.video,
|
|
2709
1902
|
directory,
|
|
2710
1903
|
options.force,
|
|
2711
1904
|
orange_file,
|
|
2712
|
-
white_file,
|
|
2713
|
-
gray_file,
|
|
2714
|
-
options.multiple,
|
|
2715
1905
|
options.viewport,
|
|
2716
|
-
options.viewporttime,
|
|
2717
1906
|
options.viewportretries,
|
|
2718
1907
|
options.viewportminheight,
|
|
2719
1908
|
options.viewportminwidth,
|
|
2720
1909
|
options.full,
|
|
2721
|
-
options.timeline,
|
|
2722
|
-
options.trimend,
|
|
2723
|
-
)
|
|
2724
|
-
if not options.multiple:
|
|
2725
|
-
if options.render is not None:
|
|
2726
|
-
render_video(directory, options.render)
|
|
2727
|
-
|
|
2728
|
-
# Calculate the histograms and visual metrics
|
|
2729
|
-
calculate_histograms(directory, histogram_file, options.force)
|
|
2730
|
-
metrics = calculate_visual_metrics(
|
|
2731
|
-
histogram_file,
|
|
2732
|
-
options.start,
|
|
2733
|
-
options.end,
|
|
2734
|
-
options.perceptual,
|
|
2735
|
-
options.contentful,
|
|
2736
|
-
directory,
|
|
2737
|
-
options.progress,
|
|
2738
|
-
options.herodata,
|
|
2739
1910
|
)
|
|
1911
|
+
|
|
1912
|
+
# Calculate the histograms and visual metrics
|
|
1913
|
+
calculate_histograms(directory, histogram_file, options.force)
|
|
1914
|
+
metrics = calculate_visual_metrics(
|
|
1915
|
+
histogram_file,
|
|
1916
|
+
options.start,
|
|
1917
|
+
options.end,
|
|
1918
|
+
options.perceptual,
|
|
1919
|
+
options.contentful,
|
|
1920
|
+
directory,
|
|
1921
|
+
options.progress,
|
|
1922
|
+
options.herodata,
|
|
1923
|
+
)
|
|
2740
1924
|
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
1925
|
+
if options.screenshot is not None:
|
|
1926
|
+
quality = 30
|
|
1927
|
+
if options.quality is not None:
|
|
1928
|
+
quality = options.quality
|
|
1929
|
+
save_screenshot(directory, options.screenshot, quality)
|
|
1930
|
+
# JPEG conversion
|
|
1931
|
+
if options.dir is not None and options.quality is not None:
|
|
1932
|
+
convert_to_jpeg(directory, options.quality)
|
|
1933
|
+
|
|
1934
|
+
if metrics is not None:
|
|
1935
|
+
ok = True
|
|
1936
|
+
if options.json:
|
|
1937
|
+
data = dict()
|
|
1938
|
+
for metric in metrics:
|
|
1939
|
+
data[metric["name"].replace(" ", "")] = metric["value"]
|
|
1940
|
+
if "videoRecordingStart" in globals():
|
|
1941
|
+
data["videoRecordingStart"] = videoRecordingStart
|
|
1942
|
+
print(json.dumps(data))
|
|
1943
|
+
else:
|
|
1944
|
+
for metric in metrics:
|
|
1945
|
+
print("{0}: {1}".format(metric["name"], metric["value"]))
|
|
2762
1946
|
else:
|
|
2763
1947
|
ok = check_config()
|
|
2764
1948
|
except Exception as e:
|