browsertime 16.17.0 → 17.0.0-beta.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 +44 -0
- package/bin/browsertime.js +43 -43
- package/bin/browsertimeWebPageReplay.js +10 -9
- package/browserscripts/pageinfo/generator.js +1 -1
- package/index.js +14 -13
- package/lib/android/gnirehtet.js +14 -15
- package/lib/android/index.js +92 -89
- package/lib/android/root.js +6 -9
- package/lib/chrome/chromeDevtoolsProtocol.js +46 -45
- package/lib/chrome/har.js +12 -13
- package/lib/chrome/longTaskMetrics.js +3 -5
- package/lib/chrome/parseCpuTrace.js +13 -10
- package/lib/chrome/settings/chromeAndroidOptions.js +1 -3
- package/lib/chrome/settings/chromeDesktopOptions.js +1 -3
- package/lib/chrome/settings/traceCategories.js +1 -3
- package/lib/chrome/traceCategoriesParser.js +35 -36
- package/lib/chrome/webdriver/builder.js +18 -26
- package/lib/chrome/webdriver/chromium.js +56 -46
- package/lib/chrome/webdriver/setupChromiumOptions.js +32 -27
- package/lib/connectivity/humble.js +13 -14
- package/lib/connectivity/index.js +74 -77
- package/lib/connectivity/trafficShapeParser.js +31 -35
- package/lib/core/engine/collector.js +22 -22
- package/lib/core/engine/command/addText.js +24 -27
- package/lib/core/engine/command/android.js +7 -11
- package/lib/core/engine/command/cache.js +3 -4
- package/lib/core/engine/command/chromeDevToolsProtocol.js +20 -20
- package/lib/core/engine/command/click.js +48 -49
- package/lib/core/engine/command/debug.js +3 -4
- package/lib/core/engine/command/javaScript.js +18 -19
- package/lib/core/engine/command/measure.js +31 -37
- package/lib/core/engine/command/meta.js +1 -4
- package/lib/core/engine/command/mouse/clickAndHold.js +36 -39
- package/lib/core/engine/command/mouse/contextClick.js +17 -18
- package/lib/core/engine/command/mouse/doubleClick.js +17 -19
- package/lib/core/engine/command/mouse/index.js +5 -9
- package/lib/core/engine/command/mouse/mouseMove.js +19 -23
- package/lib/core/engine/command/mouse/singleClick.js +15 -19
- package/lib/core/engine/command/navigation.js +12 -14
- package/lib/core/engine/command/screenshot.js +1 -4
- package/lib/core/engine/command/scroll.js +4 -7
- package/lib/core/engine/command/select.js +26 -26
- package/lib/core/engine/command/set.js +25 -23
- package/lib/core/engine/command/stopWatch.js +5 -8
- package/lib/core/engine/command/switch.js +18 -20
- package/lib/core/engine/command/util/lcpHighlightScript.js +1 -2
- package/lib/core/engine/command/util/lsHighlightScript.js +1 -2
- package/lib/core/engine/command/wait.js +18 -18
- package/lib/core/engine/index.js +81 -71
- package/lib/core/engine/iteration.js +83 -79
- package/lib/core/engine/run.js +5 -9
- package/lib/core/pageCompleteChecks/defaultPageCompleteCheck.js +1 -2
- package/lib/core/pageCompleteChecks/pageCompleteCheckByInactivity.js +1 -3
- package/lib/core/pageCompleteChecks/spaInactivity.js +1 -3
- package/lib/core/seleniumRunner.js +92 -100
- package/lib/core/webdriver/index.js +24 -21
- package/lib/edge/webdriver/builder.js +21 -26
- package/lib/extensionserver/httpServer.js +17 -21
- package/lib/extensionserver/index.js +8 -12
- package/lib/extensionserver/setup.js +12 -12
- package/lib/firefox/geckoProfiler.js +27 -21
- package/lib/firefox/getHAR.js +7 -8
- package/lib/firefox/memoryReport.js +9 -11
- package/lib/firefox/perfStats.js +5 -7
- package/lib/firefox/settings/disableSafeBrowsingPreferences.js +1 -3
- package/lib/firefox/settings/disableTrackingProtectionPreferences.js +1 -3
- package/lib/firefox/settings/firefoxPreferences.js +2 -4
- package/lib/firefox/settings/geckoProfilerDefaults.js +5 -9
- package/lib/firefox/webdriver/builder.js +66 -63
- package/lib/firefox/webdriver/firefox.js +43 -48
- package/lib/safari/webdriver/builder.js +4 -5
- package/lib/safari/webdriver/safari.js +4 -8
- package/lib/screenshot/defaults.js +1 -3
- package/lib/screenshot/index.js +24 -21
- package/lib/support/browserScript.js +65 -56
- package/lib/support/cli.js +120 -87
- package/lib/support/dns.js +6 -7
- package/lib/support/engineUtils.js +85 -38
- package/lib/support/errors.js +4 -13
- package/lib/support/fileUtil.js +81 -68
- package/lib/support/filters.js +15 -20
- package/lib/support/getViewPort.js +6 -7
- package/lib/support/har/index.js +208 -216
- package/lib/support/images/index.js +54 -26
- package/lib/support/logging.js +20 -17
- package/lib/support/pathToFolder.js +18 -23
- package/lib/support/preURL.js +5 -6
- package/lib/support/processes.js +4 -6
- package/lib/support/setResourceTimingBufferSize.js +2 -4
- package/lib/support/statistics.js +58 -56
- package/lib/support/stop.js +7 -8
- package/lib/support/storageManager.js +44 -44
- package/lib/support/tcpdump.js +22 -16
- package/lib/support/userTiming.js +7 -13
- package/lib/support/util.js +204 -224
- package/lib/support/xvfb.js +11 -13
- package/lib/video/defaults.js +6 -10
- package/lib/video/postprocessing/finetune/addTextToVideo.js +15 -15
- package/lib/video/postprocessing/finetune/convertFps.js +14 -8
- package/lib/video/postprocessing/finetune/getFont.js +7 -9
- package/lib/video/postprocessing/finetune/getTimingMetrics.js +19 -18
- package/lib/video/postprocessing/finetune/index.js +40 -36
- package/lib/video/postprocessing/finetune/removeOrange.js +10 -11
- package/lib/video/postprocessing/visualmetrics/extraMetrics.js +17 -13
- package/lib/video/postprocessing/visualmetrics/getVideoMetrics.js +12 -13
- package/lib/video/postprocessing/visualmetrics/visualMetrics.js +117 -128
- package/lib/video/screenRecording/android/recorder.js +12 -17
- package/lib/video/screenRecording/desktop/convert.js +13 -11
- package/lib/video/screenRecording/desktop/desktopRecorder.js +29 -24
- package/lib/video/screenRecording/desktop/ffmpegRecorder.js +60 -63
- package/lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js +4 -6
- package/lib/video/screenRecording/desktop/osx/getScreen.js +9 -10
- package/lib/video/screenRecording/firefox/firefoxWindowRecorder.js +54 -54
- package/lib/video/screenRecording/ios/convertToMp4.js +10 -11
- package/lib/video/screenRecording/ios/iosRecorder.js +23 -21
- package/lib/video/screenRecording/iosSimulator/convertToMp4.js +10 -11
- package/lib/video/screenRecording/iosSimulator/recorder.js +10 -9
- package/lib/video/screenRecording/recorder.js +8 -9
- package/lib/video/screenRecording/setOrangeBackground.js +5 -6
- package/lib/video/video.js +28 -34
- package/package.json +19 -17
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const getFont = require('./getFont');
|
|
1
|
+
import { isAndroidConfigured } from '../../../android/index.js';
|
|
2
|
+
import getFont from './getFont.js';
|
|
4
3
|
|
|
5
4
|
function isSmallish(options) {
|
|
6
5
|
return (
|
|
@@ -31,26 +30,28 @@ function get(metric, metricName, pos, options) {
|
|
|
31
30
|
},30)':x=(w-tw)/2: y=H-${pos}-h/${x}:fontcolor=white:fontsize=h/${fontSize}:box=1:boxcolor=0x000000AA:boxborderw=2:text='${metricName} ${metric}'`;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
export default function (videoMetrics, timingMetrics, options) {
|
|
35
34
|
let text = '';
|
|
36
35
|
const vm = videoMetrics.visualMetrics;
|
|
37
36
|
const startPosition = 'h/10';
|
|
38
37
|
const metricsAndValues = [];
|
|
39
38
|
|
|
40
39
|
if (options.visualMetrics) {
|
|
41
|
-
metricsAndValues.push(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
metricsAndValues.push(
|
|
41
|
+
{
|
|
42
|
+
name: 'FirstVisualChange',
|
|
43
|
+
value: vm.FirstVisualChange
|
|
44
|
+
},
|
|
45
|
+
{ name: 'SpeedIndex', value: vm.SpeedIndex },
|
|
46
|
+
{
|
|
47
|
+
name: 'VisualComplete85',
|
|
48
|
+
value: vm.VisualComplete85
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'LastVisualChange',
|
|
52
|
+
value: vm.LastVisualChange
|
|
53
|
+
}
|
|
54
|
+
);
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
if (vm.LargestContentfulPaint) {
|
|
@@ -83,4 +84,4 @@ module.exports = function (videoMetrics, timingMetrics, options) {
|
|
|
83
84
|
pos += `-h/${posOffset}`;
|
|
84
85
|
}
|
|
85
86
|
return text;
|
|
86
|
-
}
|
|
87
|
+
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
rename as _rename,
|
|
4
|
+
copyFile as _copyFile,
|
|
5
|
+
unlink as _unlink
|
|
6
|
+
} from 'node:fs';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import intel from 'intel';
|
|
9
|
+
import get from 'lodash.get';
|
|
10
|
+
import addTextToVideo from './addTextToVideo.js';
|
|
11
|
+
import removeOrange from './removeOrange.js';
|
|
12
|
+
import convert from './convertFps.js';
|
|
13
|
+
import { convert as _convert } from '../../defaults.js';
|
|
14
|
+
import { isAndroidConfigured } from '../../../android/index.js';
|
|
15
|
+
const rename = promisify(_rename);
|
|
16
|
+
const copyFile = promisify(_copyFile);
|
|
17
|
+
const unlink = promisify(_unlink);
|
|
18
|
+
const log = intel.getLogger('browsertime.video');
|
|
2
19
|
|
|
3
|
-
|
|
4
|
-
const addTextToVideo = require('./addTextToVideo');
|
|
5
|
-
const removeOrange = require('./removeOrange');
|
|
6
|
-
const convert = require('./convertFps');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const get = require('lodash.get');
|
|
10
|
-
const { promisify } = require('util');
|
|
11
|
-
const rename = promisify(fs.rename);
|
|
12
|
-
const copyFile = promisify(fs.copyFile);
|
|
13
|
-
const unlink = promisify(fs.unlink);
|
|
14
|
-
const defaults = require('../../defaults');
|
|
15
|
-
|
|
16
|
-
module.exports = async function (
|
|
20
|
+
export default async function (
|
|
17
21
|
videoDir,
|
|
18
22
|
videoPath,
|
|
19
23
|
index,
|
|
@@ -22,10 +26,10 @@ module.exports = async function (
|
|
|
22
26
|
options
|
|
23
27
|
) {
|
|
24
28
|
const newStart = videoMetrics.videoRecordingStart / 1000;
|
|
25
|
-
let
|
|
29
|
+
let temporaryFile = join(videoDir, 'tmp.mp4');
|
|
26
30
|
|
|
27
31
|
if (get(options, 'videoParams.keepOriginalVideo', false)) {
|
|
28
|
-
const originalFile =
|
|
32
|
+
const originalFile = join(videoDir, index + '-original.mp4');
|
|
29
33
|
await copyFile(videoPath, originalFile);
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -34,7 +38,7 @@ module.exports = async function (
|
|
|
34
38
|
if (videoMetrics.videoRecordingStart > 0) {
|
|
35
39
|
await removeOrange(
|
|
36
40
|
videoPath,
|
|
37
|
-
|
|
41
|
+
temporaryFile,
|
|
38
42
|
newStart,
|
|
39
43
|
videoMetrics.visualMetrics,
|
|
40
44
|
options
|
|
@@ -48,36 +52,36 @@ module.exports = async function (
|
|
|
48
52
|
videoMetrics
|
|
49
53
|
);
|
|
50
54
|
}
|
|
51
|
-
|
|
55
|
+
temporaryFile = videoPath;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
if (
|
|
55
|
-
options
|
|
56
|
-
get(options, 'videoParams.convert',
|
|
59
|
+
isAndroidConfigured(options) &&
|
|
60
|
+
get(options, 'videoParams.convert', _convert)
|
|
57
61
|
) {
|
|
58
|
-
const
|
|
59
|
-
await convert(
|
|
60
|
-
await unlink(
|
|
61
|
-
await rename(
|
|
62
|
+
const temporaryFile2 = join(videoDir, 'tmp-60fps.mp4');
|
|
63
|
+
await convert(temporaryFile, temporaryFile2, 60);
|
|
64
|
+
await unlink(temporaryFile);
|
|
65
|
+
await rename(temporaryFile2, temporaryFile);
|
|
62
66
|
} else if (options.safari && options.safari.useSimulator) {
|
|
63
|
-
const
|
|
64
|
-
await convert(
|
|
65
|
-
await unlink(
|
|
66
|
-
await rename(
|
|
67
|
+
const temporaryFile2 = join(videoDir, 'tmp-60fps.mp4');
|
|
68
|
+
await convert(temporaryFile, temporaryFile2, 60);
|
|
69
|
+
await unlink(temporaryFile);
|
|
70
|
+
await rename(temporaryFile2, temporaryFile);
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
if (options.videoParams.addTimer) {
|
|
70
|
-
const
|
|
74
|
+
const temporaryFile2 = join(videoDir, 'tmp2.mp4');
|
|
71
75
|
await addTextToVideo(
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
temporaryFile,
|
|
77
|
+
temporaryFile2,
|
|
74
78
|
videoMetrics,
|
|
75
79
|
timingMetrics,
|
|
76
80
|
options
|
|
77
81
|
);
|
|
78
|
-
await rename(
|
|
79
|
-
await unlink(
|
|
82
|
+
await rename(temporaryFile2, videoPath);
|
|
83
|
+
await unlink(temporaryFile);
|
|
80
84
|
} else {
|
|
81
|
-
await rename(
|
|
85
|
+
await rename(temporaryFile, videoPath);
|
|
82
86
|
}
|
|
83
|
-
}
|
|
87
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import intel from 'intel';
|
|
3
|
+
const log = intel.getLogger('browsertime.video');
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const log = require('intel').getLogger('browsertime.video');
|
|
5
|
-
|
|
6
|
-
module.exports = async function (
|
|
5
|
+
export default async function (
|
|
7
6
|
inputFile,
|
|
8
7
|
outputFile,
|
|
9
8
|
newStart,
|
|
@@ -11,14 +10,14 @@ module.exports = async function (
|
|
|
11
10
|
options
|
|
12
11
|
) {
|
|
13
12
|
// Remove the orange frames from the video (and stop the video after last visual change)
|
|
14
|
-
const
|
|
13
|
+
const arguments_ = ['-nostdin', '-ss', newStart, '-i', inputFile];
|
|
15
14
|
if (options.visualMetrics) {
|
|
16
15
|
// End the video on last visual change + 1 s
|
|
17
16
|
const end = newStart + visualMetrics.LastVisualChange / 1000 + 1;
|
|
18
|
-
|
|
17
|
+
arguments_.push('-to', end);
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
arguments_.push('-c', 'copy', outputFile);
|
|
21
20
|
|
|
22
|
-
log.verbose('Removing orange start with FFMPEG ' +
|
|
23
|
-
return execa('ffmpeg',
|
|
24
|
-
}
|
|
21
|
+
log.verbose('Removing orange start with FFMPEG ' + arguments_.join(' '));
|
|
22
|
+
return execa('ffmpeg', arguments_);
|
|
23
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module.exports = function (metrics) {
|
|
1
|
+
export default function (metrics) {
|
|
4
2
|
const videoMetrics = {};
|
|
5
3
|
if (!metrics.VisualReadiness) {
|
|
6
4
|
metrics.VisualReadiness =
|
|
@@ -11,26 +9,32 @@ module.exports = function (metrics) {
|
|
|
11
9
|
const eachLine = metrics.VisualProgress.split(',');
|
|
12
10
|
for (const timeAndPercentage of eachLine) {
|
|
13
11
|
const [timestamp, percent] = timeAndPercentage.split('=');
|
|
14
|
-
if (parseInt(percent, 10) >= 85 && !metrics.VisualComplete85) {
|
|
15
|
-
metrics.VisualComplete85 = parseInt(timestamp, 10);
|
|
12
|
+
if (Number.parseInt(percent, 10) >= 85 && !metrics.VisualComplete85) {
|
|
13
|
+
metrics.VisualComplete85 = Number.parseInt(timestamp, 10);
|
|
16
14
|
}
|
|
17
|
-
if (parseInt(percent, 10) >= 95 && !metrics.VisualComplete95) {
|
|
18
|
-
metrics.VisualComplete95 = parseInt(timestamp, 10);
|
|
15
|
+
if (Number.parseInt(percent, 10) >= 95 && !metrics.VisualComplete95) {
|
|
16
|
+
metrics.VisualComplete95 = Number.parseInt(timestamp, 10);
|
|
19
17
|
}
|
|
20
|
-
if (parseInt(percent, 10) >= 99 && !metrics.VisualComplete99) {
|
|
21
|
-
metrics.VisualComplete99 = parseInt(timestamp, 10);
|
|
18
|
+
if (Number.parseInt(percent, 10) >= 99 && !metrics.VisualComplete99) {
|
|
19
|
+
metrics.VisualComplete99 = Number.parseInt(timestamp, 10);
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
// Oh noo the painting on the screen goes backward
|
|
25
23
|
// see https://github.com/sitespeedio/sitespeed.io/issues/2259#issuecomment-456878707
|
|
26
|
-
if (metrics.VisualComplete85 && parseInt(percent, 10) < 85) {
|
|
24
|
+
if (metrics.VisualComplete85 && Number.parseInt(percent, 10) < 85) {
|
|
27
25
|
metrics.VisualComplete85 = undefined;
|
|
28
26
|
metrics.VisualComplete95 = undefined;
|
|
29
27
|
metrics.VisualComplete95 = undefined;
|
|
30
|
-
} else if (
|
|
28
|
+
} else if (
|
|
29
|
+
metrics.VisualComplete95 &&
|
|
30
|
+
Number.parseInt(percent, 10) < 95
|
|
31
|
+
) {
|
|
31
32
|
metrics.VisualComplete95 = undefined;
|
|
32
33
|
metrics.VisualComplete95 = undefined;
|
|
33
|
-
} else if (
|
|
34
|
+
} else if (
|
|
35
|
+
metrics.VisualComplete99 &&
|
|
36
|
+
Number.parseInt(percent, 10) < 99
|
|
37
|
+
) {
|
|
34
38
|
metrics.VisualComplete99 = undefined;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -40,4 +44,4 @@ module.exports = function (metrics) {
|
|
|
40
44
|
}
|
|
41
45
|
videoMetrics.visualMetrics = metrics;
|
|
42
46
|
return videoMetrics;
|
|
43
|
-
}
|
|
47
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import intel from 'intel';
|
|
3
|
+
import { run } from './visualMetrics.js';
|
|
4
|
+
import extraMetrics from './extraMetrics.js';
|
|
5
|
+
const log = intel.getLogger('browsertime.video');
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const visualMetrics = require('./visualMetrics');
|
|
6
|
-
const extraMetrics = require('./extraMetrics');
|
|
7
|
-
|
|
8
|
-
module.exports = async function (
|
|
7
|
+
export default async function (
|
|
9
8
|
videoDir,
|
|
10
9
|
filmstripDir,
|
|
11
10
|
videoPath,
|
|
@@ -26,12 +25,12 @@ module.exports = async function (
|
|
|
26
25
|
true
|
|
27
26
|
);
|
|
28
27
|
}
|
|
29
|
-
const elementsFile =
|
|
28
|
+
const elementsFile = join(
|
|
30
29
|
storageManager.directory,
|
|
31
30
|
index + '-visualElements.json.gz'
|
|
32
31
|
);
|
|
33
32
|
try {
|
|
34
|
-
const metrics = await
|
|
33
|
+
const metrics = await run(
|
|
35
34
|
videoPath,
|
|
36
35
|
filmstripDir,
|
|
37
36
|
elementsFile,
|
|
@@ -43,13 +42,13 @@ module.exports = async function (
|
|
|
43
42
|
);
|
|
44
43
|
log.debug('Collected metrics ' + JSON.stringify(metrics));
|
|
45
44
|
return extraMetrics(metrics);
|
|
46
|
-
} catch (
|
|
47
|
-
log.error('Could not run Visual Metrics',
|
|
48
|
-
throw
|
|
45
|
+
} catch (error) {
|
|
46
|
+
log.error('Could not run Visual Metrics', error);
|
|
47
|
+
throw error;
|
|
49
48
|
} finally {
|
|
50
49
|
// Remove the file
|
|
51
50
|
if (options.visualElements && visualElements) {
|
|
52
51
|
await storageManager.rm(index + '-visualElements.json.gz');
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
|
-
}
|
|
54
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import intel from 'intel';
|
|
6
|
+
import get from 'lodash.get';
|
|
7
|
+
import { readFile, removeFile, copyFile } from '../../../support/fileUtil.js';
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const log = intel.getLogger('browsertime.video');
|
|
10
|
+
|
|
11
|
+
const SCRIPT_PATH = join(
|
|
10
12
|
__dirname,
|
|
11
13
|
'..',
|
|
12
14
|
'..',
|
|
@@ -16,7 +18,7 @@ const SCRIPT_PATH = path.join(
|
|
|
16
18
|
'visualmetrics.py'
|
|
17
19
|
);
|
|
18
20
|
|
|
19
|
-
const PORTABLE_SCRIPT_PATH =
|
|
21
|
+
const PORTABLE_SCRIPT_PATH = join(
|
|
20
22
|
__dirname,
|
|
21
23
|
'..',
|
|
22
24
|
'..',
|
|
@@ -34,133 +36,120 @@ function getScript(options) {
|
|
|
34
36
|
return SCRIPT_PATH;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
export async function checkDependencies(options) {
|
|
40
|
+
return execa(process.env.PYTHON || 'python', [getScript(options), '--check']);
|
|
41
|
+
}
|
|
42
|
+
export async function run(
|
|
43
|
+
videoPath,
|
|
44
|
+
imageDirPath,
|
|
45
|
+
elementsFile,
|
|
46
|
+
videoDir,
|
|
47
|
+
index,
|
|
48
|
+
pageNumber,
|
|
49
|
+
visitedPageNumber,
|
|
50
|
+
options
|
|
51
|
+
) {
|
|
52
|
+
const filmstripQuality = get(options, 'videoParams.filmstripQuality', 75);
|
|
53
|
+
const createFilmstrip = get(options, 'videoParams.createFilmstrip', true);
|
|
54
|
+
const fullSizeFilmstrip = get(
|
|
55
|
+
options,
|
|
56
|
+
'videoParams.filmstripFullSize',
|
|
57
|
+
false
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const thumbsize = get(options, 'videoParams.thumbsize', 400);
|
|
61
|
+
|
|
62
|
+
const scriptArguments = [
|
|
63
|
+
'--video',
|
|
45
64
|
videoPath,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const scriptArgs = [
|
|
65
|
-
'--video',
|
|
66
|
-
videoPath,
|
|
67
|
-
'--orange',
|
|
68
|
-
'--force',
|
|
69
|
-
'--renderignore',
|
|
70
|
-
5,
|
|
71
|
-
'--json',
|
|
72
|
-
'--viewport',
|
|
73
|
-
'--viewportretries',
|
|
74
|
-
60,
|
|
75
|
-
'--viewportminheight',
|
|
76
|
-
100,
|
|
77
|
-
'--viewportminwidth',
|
|
78
|
-
100
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
if (options.visualMetricsPerceptual) {
|
|
82
|
-
scriptArgs.push('--perceptual');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (options.visualMetricsContentful) {
|
|
86
|
-
scriptArgs.push('--contentful');
|
|
87
|
-
}
|
|
65
|
+
'--orange',
|
|
66
|
+
'--force',
|
|
67
|
+
'--renderignore',
|
|
68
|
+
5,
|
|
69
|
+
'--json',
|
|
70
|
+
'--viewport',
|
|
71
|
+
'--viewportretries',
|
|
72
|
+
60,
|
|
73
|
+
'--viewportminheight',
|
|
74
|
+
100,
|
|
75
|
+
'--viewportminwidth',
|
|
76
|
+
100
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
if (options.visualMetricsPerceptual) {
|
|
80
|
+
scriptArguments.push('--perceptual');
|
|
81
|
+
}
|
|
88
82
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// 11:20:14.951 - Done calculating histograms
|
|
93
|
-
// Since we tuned the borders of the video, I hope it will just work fine
|
|
94
|
-
// without. If not, let us create a upstream issue
|
|
95
|
-
// If it is the first page and no preURL we can use
|
|
96
|
-
// start white, that gives us less variance in metrics in Chrome
|
|
97
|
-
/*
|
|
98
|
-
if (!options.preURL && visitedPageNumber === 0) {
|
|
99
|
-
scriptArgs.push('--startwhite');
|
|
100
|
-
}
|
|
101
|
-
*/
|
|
83
|
+
if (options.visualMetricsContentful) {
|
|
84
|
+
scriptArguments.push('--contentful');
|
|
85
|
+
}
|
|
102
86
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
// There seems to be a bug with --startwhite that makes VM bail out
|
|
88
|
+
// 11:20:14.950 - Calculating image histograms
|
|
89
|
+
// 11:20:14.951 - No video frames found in /private/var/folders/27/xpnvcsbs0nlfbb4qq397z3rh0000gn/T/vis-cn_JMf
|
|
90
|
+
// 11:20:14.951 - Done calculating histograms
|
|
91
|
+
// Since we tuned the borders of the video, I hope it will just work fine
|
|
92
|
+
// without. If not, let us create a upstream issue
|
|
93
|
+
// If it is the first page and no preURL we can use
|
|
94
|
+
// start white, that gives us less variance in metrics in Chrome
|
|
95
|
+
/*
|
|
96
|
+
if (!options.preURL && visitedPageNumber === 0) {
|
|
97
|
+
scriptArgs.push('--startwhite');
|
|
98
|
+
}
|
|
99
|
+
*/
|
|
100
|
+
if (options.visualElements) {
|
|
101
|
+
scriptArguments.push('--herodata', elementsFile);
|
|
102
|
+
}
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
scriptArgs.push('--thumbsize');
|
|
116
|
-
scriptArgs.push(thumbsize);
|
|
117
|
-
}
|
|
104
|
+
if (createFilmstrip) {
|
|
105
|
+
scriptArguments.push('-q', filmstripQuality);
|
|
106
|
+
scriptArguments.unshift('--dir', imageDirPath);
|
|
107
|
+
if (fullSizeFilmstrip) {
|
|
108
|
+
scriptArguments.push('--full');
|
|
109
|
+
} else if (thumbsize !== 400) {
|
|
110
|
+
scriptArguments.push('--thumbsize', thumbsize);
|
|
118
111
|
}
|
|
112
|
+
}
|
|
119
113
|
|
|
120
|
-
|
|
121
|
-
videoDir,
|
|
122
|
-
index + '.visualmetrics.log'
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
scriptArgs.push('--logfile');
|
|
126
|
-
scriptArgs.push(visualMetricsLogFile);
|
|
127
|
-
if (options.verbose >= 1) {
|
|
128
|
-
scriptArgs.push('-vvvv');
|
|
129
|
-
} else {
|
|
130
|
-
// Full -vvvv is really chatty so keep this level for now.
|
|
131
|
-
scriptArgs.push('-vvv');
|
|
132
|
-
}
|
|
114
|
+
const visualMetricsLogFile = join(videoDir, index + '.visualmetrics.log');
|
|
133
115
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (options.verbose < 1) {
|
|
142
|
-
try {
|
|
143
|
-
await removeFile(visualMetricsLogFile);
|
|
144
|
-
} catch (e) {
|
|
145
|
-
log.error(
|
|
146
|
-
'Could not remove the log file from VisualMetrics, does it exist?: %s',
|
|
147
|
-
visualMetricsLogFile
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return JSON.parse(result.stdout);
|
|
152
|
-
} catch (e) {
|
|
153
|
-
log.error('VisualMetrics failed to run', e);
|
|
154
|
-
await copyFile(videoPath, videoPath + '.failed.mp4');
|
|
116
|
+
scriptArguments.push('--logfile', visualMetricsLogFile);
|
|
117
|
+
if (options.verbose >= 1) {
|
|
118
|
+
scriptArguments.push('-vvvv');
|
|
119
|
+
} else {
|
|
120
|
+
// Full -vvvv is really chatty so keep this level for now.
|
|
121
|
+
scriptArguments.push('-vvv');
|
|
122
|
+
}
|
|
155
123
|
|
|
156
|
-
|
|
157
|
-
|
|
124
|
+
scriptArguments.unshift(getScript(options));
|
|
125
|
+
|
|
126
|
+
log.debug('Running visualmetrics.py ' + scriptArguments.join(' '));
|
|
127
|
+
log.info('Get visual metrics from the video');
|
|
128
|
+
try {
|
|
129
|
+
const result = await execa(process.env.PYTHON || 'python', scriptArguments);
|
|
130
|
+
log.debug('visualmetrics.py output:' + result.stdout);
|
|
131
|
+
if (options.verbose < 1) {
|
|
132
|
+
try {
|
|
133
|
+
await removeFile(visualMetricsLogFile);
|
|
134
|
+
} catch {
|
|
135
|
+
log.error(
|
|
136
|
+
'Could not remove the log file from VisualMetrics, does it exist?: %s',
|
|
137
|
+
visualMetricsLogFile
|
|
138
|
+
);
|
|
158
139
|
}
|
|
140
|
+
}
|
|
141
|
+
return JSON.parse(result.stdout);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
log.error('VisualMetrics failed to run', error);
|
|
144
|
+
await copyFile(videoPath, videoPath + '.failed.mp4');
|
|
159
145
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
log.error('Log from VisualMetrics: %s', visualMetricLog);
|
|
163
|
-
throw e;
|
|
146
|
+
if (options.visualElements) {
|
|
147
|
+
await copyFile(elementsFile, elementsFile + '.failed.json');
|
|
164
148
|
}
|
|
149
|
+
|
|
150
|
+
// If something goes wrong, dump the visual metrics log to our log
|
|
151
|
+
const visualMetricLog = await readFile(visualMetricsLogFile);
|
|
152
|
+
log.error('Log from VisualMetrics: %s', visualMetricLog);
|
|
153
|
+
throw error;
|
|
165
154
|
}
|
|
166
|
-
}
|
|
155
|
+
}
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
1
|
+
import { promisify } from 'node:util';
|
|
2
|
+
import { unlink as _unlink } from 'node:fs';
|
|
3
|
+
import get from 'lodash.get';
|
|
4
|
+
import intel from 'intel';
|
|
5
|
+
import { framerate } from '../../defaults.js';
|
|
6
|
+
import { Android } from '../../../android/index.js';
|
|
7
|
+
const unlink = promisify(_unlink);
|
|
8
|
+
const log = intel.getLogger('browsertime.video');
|
|
10
9
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
export default class AndroidRecorder {
|
|
13
12
|
constructor(options) {
|
|
14
13
|
this.waitTime = get(options, 'videoParams.androidVideoWaitTime', 5000);
|
|
15
|
-
this.framerate = get(
|
|
16
|
-
options,
|
|
17
|
-
'videoParams.framerate',
|
|
18
|
-
videoDefaults.framerate
|
|
19
|
-
);
|
|
14
|
+
this.framerate = get(options, 'videoParams.framerate', framerate);
|
|
20
15
|
this.options = options;
|
|
21
16
|
}
|
|
22
17
|
|
|
@@ -35,11 +30,11 @@ module.exports = class AndroidRecorder {
|
|
|
35
30
|
if (this.options.resultDir) {
|
|
36
31
|
try {
|
|
37
32
|
await unlink(destination);
|
|
38
|
-
} catch
|
|
33
|
+
} catch {
|
|
39
34
|
// Nothing to see here
|
|
40
35
|
}
|
|
41
36
|
}
|
|
42
37
|
await this.android.pullVideo(destination);
|
|
43
38
|
return this.android.removeVideo();
|
|
44
39
|
}
|
|
45
|
-
}
|
|
40
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import intel from 'intel';
|
|
3
|
+
const log = intel.getLogger('browsertime.video');
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
module.exports = async function convert(src, dest, crf, threads) {
|
|
7
|
-
const scriptArgs = [
|
|
5
|
+
export default async function convert(source, destination, crf, threads) {
|
|
6
|
+
const scriptArguments = [
|
|
8
7
|
'-nostdin',
|
|
9
8
|
'-i',
|
|
10
|
-
|
|
9
|
+
source,
|
|
11
10
|
'-c:v',
|
|
12
11
|
'libx264',
|
|
13
12
|
'-threads',
|
|
@@ -18,10 +17,13 @@ module.exports = async function convert(src, dest, crf, threads) {
|
|
|
18
17
|
'fast',
|
|
19
18
|
'-vf',
|
|
20
19
|
'format=yuv420p',
|
|
21
|
-
|
|
20
|
+
destination
|
|
22
21
|
];
|
|
23
22
|
|
|
24
|
-
log.debug(
|
|
23
|
+
log.debug(
|
|
24
|
+
'Converting video to viewable format with args %j',
|
|
25
|
+
scriptArguments
|
|
26
|
+
);
|
|
25
27
|
|
|
26
|
-
return execa('ffmpeg',
|
|
27
|
-
}
|
|
28
|
+
return execa('ffmpeg', scriptArguments);
|
|
29
|
+
}
|