From ee11b560cb83d03e3f11bf2309e555a445928a19 Mon Sep 17 00:00:00 2001 From: kc1r74p <7422353+kc1r74p@users.noreply.github.com> Date: Wed, 17 Jul 2024 21:52:00 +0200 Subject: [PATCH] fix: more progress logs, gpu flag, deps --- package-lock.json | 991 ++++++++++++++++++++++++++++++++-------------- package.json | 16 +- src/index.ts | 955 ++++++++++++++++++++++---------------------- 3 files changed, 1191 insertions(+), 771 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7248eb9..d20caa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,67 @@ { "name": "auto_render", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@ffmpeg-installer/darwin-arm64": { + "packages": { + "": { + "name": "auto_render", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "@ffprobe-installer/ffprobe": "^1.4.1", + "canvas": "^2.11.2", + "ffprobe-client": "^1.1.6", + "fluent-ffmpeg": "^2.1.3", + "gopro-telemetry": "^1.2.6", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "typescript": "^5.5.3", + "tz-lookup": "^6.1.25" + }, + "devDependencies": { + "@types/fluent-ffmpeg": "^2.1.24", + "@types/moment": "^2.13.0", + "@types/moment-timezone": "^0.5.30", + "@types/node": "^20.14.11", + "@types/tz-lookup": "^6.1.2" + } + }, + "node_modules/@ffmpeg-installer/darwin-arm64": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", - "optional": true - }, - "@ffmpeg-installer/darwin-x64": { + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/darwin-x64": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", - "optional": true - }, - "@ffmpeg-installer/ffmpeg": { + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "LGPL-2.1", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/ffmpeg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", - "requires": { + "license": "LGPL-2.1", + "optionalDependencies": { "@ffmpeg-installer/darwin-arm64": "4.1.5", "@ffmpeg-installer/darwin-x64": "4.1.0", "@ffmpeg-installer/linux-arm": "4.1.3", @@ -31,59 +72,122 @@ "@ffmpeg-installer/win32-x64": "4.1.0" } }, - "@ffmpeg-installer/linux-arm": { + "node_modules/@ffmpeg-installer/linux-arm": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", - "optional": true - }, - "@ffmpeg-installer/linux-arm64": { + "cpu": [ + "arm" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-arm64": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", - "optional": true - }, - "@ffmpeg-installer/linux-ia32": { + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-ia32": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", - "optional": true - }, - "@ffmpeg-installer/linux-x64": { + "cpu": [ + "ia32" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-x64": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", - "optional": true - }, - "@ffmpeg-installer/win32-ia32": { + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/win32-ia32": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", - "optional": true - }, - "@ffmpeg-installer/win32-x64": { + "cpu": [ + "ia32" + ], + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffmpeg-installer/win32-x64": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", - "optional": true - }, - "@ffprobe-installer/darwin-arm64": { + "cpu": [ + "x64" + ], + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffprobe-installer/darwin-arm64": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", "integrity": "sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg==", - "optional": true - }, - "@ffprobe-installer/darwin-x64": { + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "LGPL-2.1", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffprobe-installer/darwin-x64": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-x64/-/darwin-x64-5.0.0.tgz", "integrity": "sha512-Zl0UkZ+wW/eyMKBPLTUCcNQch2VDnZz/cBn1DXv3YtCBVbYd9aYzGj4MImdxgWcoE0+GpbfbO6mKGwMq5HCm6A==", - "optional": true - }, - "@ffprobe-installer/ffprobe": { + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "GPL-3.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffprobe-installer/ffprobe": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@ffprobe-installer/ffprobe/-/ffprobe-1.4.1.tgz", "integrity": "sha512-3WJvxU0f4d7IOZdzoVCAj9fYtiQNC6E0521FJFe9iP5Ej8auTXU7TsrUzIAG1CydeQI+BnM3vGog92SCcF9KtA==", - "requires": { + "license": "LGPL-2.1", + "optionalDependencies": { "@ffprobe-installer/darwin-arm64": "5.0.1", "@ffprobe-installer/darwin-x64": "5.0.0", "@ffprobe-installer/linux-arm": "5.0.0", @@ -94,47 +198,94 @@ "@ffprobe-installer/win32-x64": "5.0.0" } }, - "@ffprobe-installer/linux-arm": { + "node_modules/@ffprobe-installer/linux-arm": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm/-/linux-arm-5.0.0.tgz", "integrity": "sha512-mM1PPxP2UX5SUvhy0urcj5U8UolwbYgmnXA/eBWbW78k6N2Wk1COvcHYzOPs6c5yXXL6oshS2rZHU1kowigw7g==", - "optional": true - }, - "@ffprobe-installer/linux-arm64": { + "cpu": [ + "arm" + ], + "hasInstallScript": true, + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-arm64": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-arm64/-/linux-arm64-5.0.0.tgz", "integrity": "sha512-IwFbzhe1UydR849YXLPP0RMpHgHXSuPO1kznaCHcU5FscFBV5gOZLkdD8e/xrcC8g/nhKqy0xMjn5kv6KkFQlQ==", - "optional": true - }, - "@ffprobe-installer/linux-ia32": { + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-ia32": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-ia32/-/linux-ia32-5.0.0.tgz", "integrity": "sha512-c3bWlWEDMST59SAZycVh0oyc2eNS/CxxeRjoNryGRgqcZX3EJWJJQL1rAXbpQOMLMi8to1RqnmMuwPJgLLjjUA==", - "optional": true - }, - "@ffprobe-installer/linux-x64": { + "cpu": [ + "ia32" + ], + "hasInstallScript": true, + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/linux-x64": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/linux-x64/-/linux-x64-5.0.0.tgz", "integrity": "sha512-zgLnWJFvMGCaw1txGtz84sMEQt6mQUzdw86ih9S/kZOWnp06Gj/ams/EXxEkAxgAACCVM6/O0mkDe/6biY5tgA==", - "optional": true - }, - "@ffprobe-installer/win32-ia32": { + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "GPL-3.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffprobe-installer/win32-ia32": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-ia32/-/win32-ia32-5.0.0.tgz", "integrity": "sha512-NnDdAZD6ShFXzJeCkAFl2ZjAv7GcJWYudLA+0T/vjZwvskBop+sq1PGfdmVltfFDcdQiomoThRhn9Xiy9ZC71g==", - "optional": true - }, - "@ffprobe-installer/win32-x64": { + "cpu": [ + "ia32" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffprobe-installer/win32-x64": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@ffprobe-installer/win32-x64/-/win32-x64-5.0.0.tgz", "integrity": "sha512-P4ZMRFxVMnfMsOyTfBM/+nkTodLeOUfXNPo+X1bKEWBiZxRErqX/IHS5sLA0yAH8XmtKZcL7Cu6M26ztGcQYxw==", - "optional": true - }, - "@mapbox/node-pre-gyp": { + "cpu": [ + "x64" + ], + "license": "GPL-3.0", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "requires": { + "license": "BSD-3-Clause", + "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", @@ -144,209 +295,288 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" } }, - "@types/fluent-ffmpeg": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.20.tgz", - "integrity": "sha512-B+OvhCdJ3LgEq2PhvWNOiB/EfwnXLElfMCgc4Z1K5zXgSfo9I6uGKwR/lqmNPFQuebNnes7re3gqkV77SyypLg==", + "node_modules/@types/fluent-ffmpeg": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.24.tgz", + "integrity": "sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "@types/node": "*" } }, - "@types/moment": { + "node_modules/@types/moment": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", - "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=", + "integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==", + "deprecated": "This is a stub types definition for Moment (https://github.com/moment/moment). Moment provides its own type definitions, so you don't need @types/moment installed!", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "moment": "*" } }, - "@types/moment-timezone": { + "node_modules/@types/moment-timezone": { "version": "0.5.30", "resolved": "https://registry.npmjs.org/@types/moment-timezone/-/moment-timezone-0.5.30.tgz", "integrity": "sha512-aDVfCsjYnAQaV/E9Qc24C5Njx1CoDjXsEgkxtp9NyXDpYu4CCbmclb6QhWloS9UTU/8YROUEEdEkWI0D7DxnKg==", + "deprecated": "This is a stub types definition. moment-timezone provides its own type definitions, so you do not need this installed.", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "moment-timezone": "*" } }, - "@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "node_modules/@types/node": { + "version": "20.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", + "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "undici-types": "~5.26.4" } }, - "@types/tz-lookup": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/tz-lookup/-/tz-lookup-6.1.0.tgz", - "integrity": "sha512-e+TvKH1exSsa4eM/C/BODjvSwc6pdxjPlMe/jiO3eYSRtHl4B/tPPRhEXYfEfGTQb1/l6/ubkY8JHe+Kgld3ag==", - "dev": true + "node_modules/@types/tz-lookup": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@types/tz-lookup/-/tz-lookup-6.1.2.tgz", + "integrity": "sha512-9y31Xf/8FHXrCHjvVjGZLcsayAa6ABNc8bZlk6MPOQLLlr41tICSqW3TRPRIx2nodbzdKs5N7ipHWBrUsWUiAA==", + "dev": true, + "license": "MIT" }, - "abbrev": { + "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" }, - "agent-base": { + "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { + "license": "MIT", + "dependencies": { "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "aproba": { + "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" }, - "are-we-there-yet": { + "node_modules/are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "requires": { + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" } }, - "async": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", - "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + "node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, - "binary-parser": { + "node_modules/binary-parser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/binary-parser/-/binary-parser-2.2.1.tgz", - "integrity": "sha512-5ATpz/uPDgq5GgEDxTB4ouXCde7q2lqAQlSdBRQVl/AJnxmQmhIfyxJx+0MGu//D5rHQifkfGbWWlaysG0o9NA==" + "integrity": "sha512-5ATpz/uPDgq5GgEDxTB4ouXCde7q2lqAQlSdBRQVl/AJnxmQmhIfyxJx+0MGu//D5rHQifkfGbWWlaysG0o9NA==", + "license": "MIT", + "engines": { + "node": ">=12" + } }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { + "license": "MIT", + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "canvas": { + "node_modules/canvas": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", - "requires": { + "hasInstallScript": true, + "license": "MIT", + "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.17.0", "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" } }, - "chownr": { + "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "color-support": { + "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, - "console-control-strings": { + "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "decompress-response": { + "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { + "license": "MIT", + "dependencies": { "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "delegates": { + "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } }, - "emoji-regex": { + "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, - "ffprobe-client": { + "node_modules/ffprobe-client": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/ffprobe-client/-/ffprobe-client-1.1.6.tgz", - "integrity": "sha512-n+wNlD9QC0nHsBUJGYljYAB1gwtuNxXQh0V4Am6M2T3h8Is7qR2hwrrC5Pjc9YNE4MpIslaQUe7+c9wPCarVJQ==" - }, - "fluent-ffmpeg": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=", - "requires": { - "async": ">=0.2.9", + "integrity": "sha512-n+wNlD9QC0nHsBUJGYljYAB1gwtuNxXQh0V4Am6M2T3h8Is7qR2hwrrC5Pjc9YNE4MpIslaQUe7+c9wPCarVJQ==", + "license": "MIT" + }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "license": "MIT", + "dependencies": { + "async": "^0.2.9", "which": "^1.1.1" + }, + "engines": { + "node": ">=18" } }, - "fs-minipass": { + "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { + "license": "ISC", + "dependencies": { "minipass": "^3.0.0" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" }, - "gauge": { + "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "requires": { + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", "console-control-strings": "^1.0.0", @@ -356,360 +586,545 @@ "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" } }, - "glob": { + "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "gopro-telemetry": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/gopro-telemetry/-/gopro-telemetry-1.2.1.tgz", - "integrity": "sha512-PRf8nG9VMKsCE36WN+qm2wJvKWrZos675ehDn0nclHqhtqzghYaZok4HFfNL3SijSM/1xyzEei/JwJBsZZ3oHg==", - "requires": { + "node_modules/gopro-telemetry": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/gopro-telemetry/-/gopro-telemetry-1.2.6.tgz", + "integrity": "sha512-z8SIsYfrngOUzvAKoLgh+Eu5hH6pqe9hCStgkfcZL+WV0k4D+61Es4pYBuf4Z/K05VCbiaFD7N4XLTIqC8ZY2w==", + "license": "ISC", + "dependencies": { "binary-parser": "^2.2.1" + }, + "peerDependencies": { + "egm96-universal": "^1.1.0" + }, + "peerDependenciesMeta": { + "egm96-universal": { + "optional": true + } } }, - "has-unicode": { + "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" }, - "https-proxy-agent": { + "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { + "license": "MIT", + "dependencies": { "agent-base": "6", "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, - "make-dir": { + "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { + "license": "MIT", + "dependencies": { "semver": "^6.0.0" }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "mimic-response": { + "node_modules/mimic-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { + "license": "ISC", + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minipass": { + "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } }, - "minizlib": { + "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { + "license": "MIT", + "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "mkdirp": { + "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "moment-timezone": { - "version": "0.5.43", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", - "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", - "requires": { + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "license": "MIT", + "dependencies": { "moment": "^2.29.4" + }, + "engines": { + "node": "*" } }, - "ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" }, - "nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "license": "MIT" }, - "node-fetch": { + "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { + "license": "MIT", + "dependencies": { "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "nopt": { + "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "requires": { + "license": "ISC", + "dependencies": { "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" } }, - "npmlog": { + "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "requires": { + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", "gauge": "^3.0.0", "set-blocking": "^2.0.0" } }, - "object-assign": { + "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { + "license": "ISC", + "dependencies": { "wrappy": "1" } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "readable-stream": { + "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "requires": { + "license": "MIT", + "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "set-blocking": { + "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" }, - "signal-exit": { + "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, - "simple-concat": { + "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "simple-get": { + "node_modules/simple-get": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "requires": { + "license": "MIT", + "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, - "string-width": { + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { + "license": "MIT", + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { + "license": "MIT", + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "tar": { + "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "requires": { + "license": "ISC", + "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "tr46": { + "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "tz-lookup": { + "node_modules/tz-lookup": { "version": "6.1.25", "resolved": "https://registry.npmjs.org/tz-lookup/-/tz-lookup-6.1.25.tgz", - "integrity": "sha512-fFewT9o1uDzsW1QnUU1ValqaihFnwiUiiHr1S79/fxOzKXYYvX+EHeRnpvQJ9B3Qg67wPXT6QF2Esc4pFOrvLg==" + "integrity": "sha512-fFewT9o1uDzsW1QnUU1ValqaihFnwiUiiHr1S79/fxOzKXYYvX+EHeRnpvQJ9B3Qg67wPXT6QF2Esc4pFOrvLg==", + "license": "CC0-1.0" }, - "undici-types": { + "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "dev": true, + "license": "MIT" }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, - "webidl-conversions": { + "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, - "whatwg-url": { + "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { + "license": "MIT", + "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "which": { + "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { + "license": "ISC", + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "wide-align": { + "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { + "license": "ISC", + "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" } } } diff --git a/package.json b/package.json index 98ce19c..de14043 100644 --- a/package.json +++ b/package.json @@ -17,18 +17,18 @@ "@ffprobe-installer/ffprobe": "^1.4.1", "canvas": "^2.11.2", "ffprobe-client": "^1.1.6", - "fluent-ffmpeg": "^2.1.2", - "gopro-telemetry": "^1.2.1", - "moment": "^2.29.4", - "moment-timezone": "^0.5.43", - "typescript": "^5.0.4", + "fluent-ffmpeg": "^2.1.3", + "gopro-telemetry": "^1.2.6", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "typescript": "^5.5.3", "tz-lookup": "^6.1.25" }, "devDependencies": { - "@types/fluent-ffmpeg": "^2.1.20", + "@types/fluent-ffmpeg": "^2.1.24", "@types/moment": "^2.13.0", "@types/moment-timezone": "^0.5.30", - "@types/node": "^20.12.12", - "@types/tz-lookup": "^6.1.0" + "@types/node": "^20.14.11", + "@types/tz-lookup": "^6.1.2" } } diff --git a/src/index.ts b/src/index.ts index df7f710..0c19f6f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,475 +1,480 @@ -import * as ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; -import * as ffprobeInstaller from '@ffprobe-installer/ffprobe'; -import { createCanvas } from 'canvas'; -import ffprobe from 'ffprobe-client'; -import ffmpeg from 'fluent-ffmpeg'; -import * as fs from 'fs'; -import goproTelemetry from 'gopro-telemetry'; -import moment from 'moment-timezone'; -import * as path from 'path'; -import tzlookup from 'tz-lookup'; -import * as util from 'util'; - -// setup env -process.env.FFPROBE_PATH = ffprobeInstaller.path; -ffmpeg.setFfmpegPath(ffmpegInstaller.path); - -const overlayFPS = 2; -const globalTZ = 'Europe/Berlin'; - -const extractGPMF = async (videoFile: any) => { - const ffData = await ffprobe(videoFile); - for (let i = 0; i < ffData.streams.length; i++) { - if (ffData.streams[i].codec_tag_string === 'gpmd') { - return [await extractGPMFAt(videoFile, i), ffData]; - } - } - console.error('[Invalid file] No data stream (gpmd) found in: ' + videoFile); - return [null, null]; -}; - -const extractGPMFAt = async (videoFile: any, stream: number) => { - let rawData = Buffer.alloc(0); - await new Promise((resolve) => { - ffmpeg(videoFile) - .outputOption('-y') - .outputOptions('-codec copy') - .outputOptions(`-map 0:${stream}`) - .outputOption('-f rawvideo') - .pipe() - .on('data', (chunk) => { - rawData = Buffer.concat([rawData, chunk]); - }) - .on('end', async () => {await sleep(100); return resolve({})}); - }); - return rawData; -}; - -function getSamplefromTime(time: moment.Moment, samples: any[]) { - const searchFor = time.valueOf(); - const closest = samples.reduce((prev, curr) => { - if (!curr || !curr.date) { return; } - if (!prev) { return curr; } - return (Math.abs(moment(curr.date).valueOf() - searchFor) - < Math.abs(moment(prev.date).valueOf() - searchFor) - ? curr : prev); - }); - return closest; -} - -let lastValidBound: any = null; -function getBoundingRect(data: any) { - let left = Infinity; - let right = -Infinity; - let top = Infinity; - let bottom = -Infinity; - - for (let { value } of data) { - if (value) { lastValidBound = value; } - if (!value) { value = lastValidBound; } - const [lat, long, hgt, spd, inc] = value; - if (left > long) { left = long; } - if (top > lat) { top = lat; } - if (right < long) { right = long; } - if (bottom < lat) { bottom = lat; } - } - return { x: left, y: top, width: right - left, height: bottom - top }; -} - -let lastValidRoute: any = null; -function drawRoute(x: number, y: number, w: number, h: number, ctx: any, data: any) { - const boundingRect = getBoundingRect(data); - for (let { value } of data) { - if (value) { lastValidRoute = value; } - if (!value) { value = lastValidRoute; } - const [lat, long, hgt, spd, inc] = value; - let xx = (long - boundingRect.x) / boundingRect.width * w; - let yy = (lat - boundingRect.y) / boundingRect.height * h; - yy *= -1; - yy += h; - xx += x; - yy += y; - ctx.fillRect(xx, yy, 1, 1); - } -} - -function drawRoutePosition(x: number, y: number, w: number, h: number, ctx: any, data: any, lat: number, long: number) { - const boundingRect = getBoundingRect(data); - let xx = (long - boundingRect.x) / boundingRect.width * w; - let yy = (lat - boundingRect.y) / boundingRect.height * h; - yy *= -1; - yy += h; - - xx += x; - yy += y; - - // track crosshair - ctx.fillRect(xx, y + 0, 1, h); - ctx.fillRect(x + 0, yy, w, 1); -} - -// SRC: https://www.geodatasource.com/developers/javascript -function distance(lat1: number, lon1: number, lat2: number, lon2: number, unit: string) { - if ((lat1 === lat2) && (lon1 === lon2)) { - return 0; - } else { - const radlat1 = Math.PI * lat1 / 180; - const radlat2 = Math.PI * lat2 / 180; - const theta = lon1 - lon2; - const radtheta = Math.PI * theta / 180; - let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); - if (dist > 1) { - dist = 1; - } - dist = Math.acos(dist); - dist = dist * 180 / Math.PI; - dist = dist * 60 * 1.1515; - if (unit === 'K') { dist = dist * 1.609344; } - if (unit === 'N') { dist = dist * 0.8684; } - return dist; - } -} - -function roundRect(ctx: any, x: any, y: any, width: any, height: any, radius: any, fill: boolean, stroke: boolean) { - if (typeof stroke === 'undefined') { - stroke = true; - } - if (typeof radius === 'undefined') { - radius = 5; - } - if (typeof radius === 'number') { - radius = { tl: radius, tr: radius, br: radius, bl: radius }; - } else { - const defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; - for (const side in defaultRadius) { - if (side in radius) { - radius[side] = radius[side]; - } - } - } - ctx.beginPath(); - ctx.moveTo(x + radius.tl, y); - ctx.lineTo(x + width - radius.tr, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); - ctx.lineTo(x + width, y + height - radius.br); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); - ctx.lineTo(x + radius.bl, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); - ctx.lineTo(x, y + radius.tl); - ctx.quadraticCurveTo(x, y, x + radius.tl, y); - ctx.closePath(); - if (fill) { - ctx.fill(); - } - if (stroke) { - ctx.stroke(); - } -} -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - -async function getCompleteTrack(inDir: string, files: any[]) { - const track = await files.reduce(async (prevTrack, f) => { - const ctrack = await prevTrack; - const [raw, ffData]: any = await extractGPMF(inDir + f); - if (!raw) { return ctrack; } - const data = await goproTelemetry({ rawData: raw }); - const key = Object.keys(data).filter((x) => data[x].streams && data[x].streams.GPS5)[0]; - ctrack.push(...data[key].streams.GPS5.samples); - return ctrack; - }, Promise.resolve([])); - return track; -} - -async function getStartDate(inDir: string, files: any[]) { - const min = await files.reduce(async (minP, f) => { - const cmin = await minP; - const [raw, ffData]: any = await extractGPMF(inDir + f); - if (!ffData) { return cmin; } - return moment.min([cmin, moment.utc(ffData.format.tags.creation_time)]); - }, Promise.resolve(moment.utc())); - return min; -} - -function getTrackLen(track: any[], until?: any) { - // calc dist total - let lastValid: any = null; - const trackLength = track.slice(0).reduce((len, pnt, idx, arr) => { - if (idx < 1) { return 0; } - if (lastValid && pnt?.value) { lastValid = pnt; } - if (!pnt?.value) { pnt = lastValid; } - let prevPnt = arr[idx - 1]; - if (!prevPnt?.value) { prevPnt = lastValid; } - if (!pnt || !prevPnt) { return len; } - const [lat1, long1, hgt1, spd1, inc1] = prevPnt.value; - const [lat2, long2, hgt2, spd2, inc2] = pnt.value; - const ll = len + distance(lat1, long1, lat2, long2, 'K'); - // early exit - if (until) { - const [lat3, long3, hgt3, spd3, inc3] = until.value; - if (lat1 === lat3 && long1 === long3) { - arr.splice(1); - } - } - return ll; - }, track.slice(-1)[0]); - return trackLength; -} - -async function renderFullTrack(ctx: any, x: number, y: number, w: number, h: number, fullTrack: any) { - ctx.fillStyle = 'white'; - ctx.strokeStyle = 'white'; - ctx.lineWidth = 3; - // ctx.font = '10px Arial'; - - ctx.fillStyle = 'rgba(80,80,80,0.5)'; - roundRect(ctx, x, y, w, h, 10, true, false); - ctx.fillStyle = 'white'; - - ctx.lineWidth = 1; - drawRoute(x + 5, y + 5, w - 10, h - 10, ctx, fullTrack); -} - -async function handleVideo(file: string, fullTrack: any) { - const rawName = path.basename(file); - const [raw, ffData]: any = await extractGPMF(file); - if (!raw) { return; } - const vid = ffData.streams.filter((s: any) => s.codec_type === 'video')[0]; - console.log('File: ' + rawName); - console.log('Size: ' + Math.round(ffData.format.size / 1024 / 1024) + 'MiB'); - console.log('Created: ' + ffData.format.tags.creation_time); - console.log('Length: ' + Math.trunc(ffData.format.duration / 60) + 'min ' + - Math.trunc(ffData.format.duration % 60) + 's'); - console.log('Res: ' + vid.width + 'x' + vid.height + ' @ ' + vid.r_frame_rate); - console.log('---------------------'); - console.log('Render targets:'); - const frames = Math.trunc(ffData.format.duration * 60); - console.log('Total Frames: ' + frames); - console.log('Res: ' + vid.width + 'x' + vid.height + ' @ 60'); - console.log('---------------------'); - - const data = await goproTelemetry({ rawData: raw }); - const key = Object.keys(data).filter((x) => data[x].streams && data[x].streams.GPS5)[0]; - const zeroMark = moment(data[key].streams.GPS5.samples.slice(0, 1)[0].date); - const renderList: any[] = []; - - // SAMPLE FETCH LOOP - for (let i = 0; i < frames; i++) { - if (i % Math.round(60 / overlayFPS) !== 0) { continue; } - const timeMS = (1000 / 60 * i); - const timeTotal = moment(zeroMark).add(timeMS, 'milliseconds'); - const sample = getSamplefromTime(timeTotal, data[key].streams.GPS5.samples); - if (!sample) { continue; } - if (i % Math.trunc(frames / 100) === 0) { - console.log(rawName + ': [' + Math.round(i / frames * 100) + '%] TrgTime: ' + timeTotal.toISOString()); - } - renderList.push(sample); - } - - console.log('Collected target frames: ' + renderList.length); - console.log('Beginning frame rendering...'); - - // uhh - if (!fs.existsSync(__dirname + '/out/' + rawName)) { - fs.mkdirSync(__dirname + '/out/' + rawName); - } - - // RENDER LOOP - for (let i = 0; i < renderList.length; i++) { - const trackInfo = renderList[i]; - await renderSample(i, trackInfo, vid, rawName, fullTrack); - if ((i / renderList.length * 100) % 10 === 0) { - console.log(rawName + ': Frame render [' + Math.round(i / renderList.length * 100) + '%]'); - } - } - - console.log('Rendered overlay frames for file: ' + rawName); -} - -function pad(num: number, size: number) { - let s = num + ''; - while (s.length < size) { s = '0' + s; } - return s; -} - -async function renderSample(frame: number, sample: any, video: any, rawName: string, fullTrack: any[]) { - const [lat, long, hgt, spd, inc] = sample.value; - const spdKMH = (spd * 3.6).toFixed(2) + ' km/h'; - let dist = fullTrack ? getTrackLen(fullTrack, sample) : 0; - dist = dist.toFixed(3) + 'km'; - - const date = moment.utc(sample.date).tz(tzlookup(lat, long) || globalTZ).format('YYYY-MM-DD HH:mm:ss'); - - const canvas = createCanvas(video.width, video.height); - const ctx = canvas.getContext('2d'); - - ctx.fillStyle = 'white'; - ctx.strokeStyle = 'black'; - ctx.lineWidth = 1; - - // TODO: scale all by res / settings - ctx.font = '30px Arial'; - - // date time - ctx.fillText(date, 50, 100); - ctx.strokeText(date, 50, 100); - - // lat long - ctx.fillText(lat, 50, video.height - 100); - ctx.strokeText(lat, 50, video.height - 100); - ctx.fillText(long, 240, video.height - 100); - ctx.strokeText(long, 240, video.height - 100); - - // spd - ctx.fillText(spdKMH, 50, video.height - 150); - ctx.strokeText(spdKMH, 50, video.height - 150); - - // track len - ctx.fillText(dist, 50, video.height - 50); - ctx.strokeText(dist, 50, video.height - 50); - - // minimap - has more or less scaling - const { x, y, w, h } = { - h: (video.width * 0.15) / 2, - w: (video.width * 0.15), - x: video.width - (video.width * 0.15) - 20, - y: video.height - (video.width * 0.15) + (video.width * 0.15) / 2 - 20, - }; - - if (fullTrack) { - renderFullTrack(ctx, x, y, w, h, fullTrack); - drawRoutePosition(x + 5, y + 5, w - 10, h - 10, ctx, fullTrack, lat, long); - } - - async function renderFrameFile(stream: any, iCanvas: any) { - return new Promise((resolve) => { - iCanvas.createPNGStream().pipe(stream); - stream.on('finish', resolve); - }); - } - - const out = fs.createWriteStream(__dirname + '/out/' + rawName + '/' + pad(frame, 4) + '.png'); - await renderFrameFile(out, canvas); -} - -async function asyncForEach(array: any, callback: any) { - for (let index = 0; index < array.length; index++) { - await callback(array[index], index, array); - } -} - -async function renderOverlayedPart(inDir: string, outDir: string, file: string) { - return new Promise((resolve, reject) => { - // for testing add .addOption('-t 5') which will return a 5s video instead of whole duration - const render = ffmpeg(inDir + file) - .addInput(outDir + file + '/%04d.png') - .inputFPS(overlayFPS) - .complexFilter([ - { - filter: 'overlay', - input: '[0:v][1:v]', - }, - ] as any) - .addOption('-c:a copy') - .on('end', resolve) - .on('error', reject) - .on('progress', (progress: { timemark: moment.MomentInput; }) => { - const tm = moment(progress.timemark, 'HH:mm:ss.SS').valueOf(); - if (Math.round(tm / 100) % 10 === 0) { - console.log(file + '] Processing: ' + progress.timemark); - } - }) - .output(outDir + 'rendered_' + file); - render.run(); - }); -} - -async function concatVideos(inDir: string, outDir: string, files: string[], date: moment.Moment) { - if (files.length < 1) { return; } - return new Promise((resolve, reject) => { - const render = ffmpeg(inDir + files[0]); - files.shift(); - files.forEach((f) => render.addInput(inDir + f)); - render.addOption('-safe 0') - .on('end', resolve) - .on('error', reject) - .on('progress', (progress) => { - const tm = moment(progress.timemark, 'HH:mm:ss.SS').valueOf(); - if (Math.round(tm / 100) % 10 === 0) { - console.log('Concat processing: ' + progress.timemark); - } - }); - - render.mergeToFile(outDir + date.format('YYYYMMDD_HHmmss') + '.mp4'); - }); -} - -async function load() { - const startTime = moment(); - console.log('START: ' + startTime.toISOString()); - - const inDir = __dirname + '/in/'; - const outDir = __dirname + '/out/'; - const renderOutDir = __dirname + '/final/'; - const readdir = util.promisify(fs.readdir) as any; - console.log('inDir: ' + inDir); - - await readdir(inDir, async (err: any, files: any[]) => { - console.log('All files: ' + files.join(',')); - let fileArr = files.filter((x) => x.toLowerCase().includes('.mp4')); - console.log('Files for one track: ' + fileArr.join(',')); - - // collect track metadata over all files - const [cTrack, startDate] = await Promise.all([ - await getCompleteTrack(inDir, fileArr), - await getStartDate(inDir, fileArr) - ]); - - const tl = getTrackLen(cTrack); - console.log('Track length: ' + tl.toFixed(3) + 'km'); - console.log('Track start: ' + startDate.toISOString()); - console.log('----------------------------'); - - const videoHandlers: [Promise] = [Promise.resolve()]; - await asyncForEach(fileArr, async (file: string) => { - videoHandlers.push(handleVideo(inDir + file, cTrack)); - }); - - // render overlay images in parallel - await Promise.all(videoHandlers); - - console.log('----------------------------'); - console.log('Overlay frames done'); - console.log('Proceeding with overlaying over raws'); - - await asyncForEach(fileArr, async (file: string) => { - console.log('Starting: ' + file); - await renderOverlayedPart(inDir, outDir, file); - console.log('Done: ' + file); - console.log('----------------------------'); - }); - - // build out files from above, TODO: return out files above instead of this "logic" - fileArr = fileArr.map((x) => { - x = 'rendered_' + x; - return x; - }); - - // render final file by concatinating all rendered parts of track - if (fileArr.length > 1) { - await concatVideos(outDir, renderOutDir, fileArr, startDate); - } else { - fs.copyFileSync(outDir + fileArr[0], renderOutDir + startDate.format('YYYYMMDD_HHmmss') + '.mp4'); - } - - console.log('END: ' + moment().toISOString()); - const duration = moment.utc(moment().diff(moment(startTime, 'HH:mm:ss'))).format('HH:mm:ss'); - console.log('Duration: ' + duration); - }); - -} - -load(); +import * as ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; +import * as ffprobeInstaller from '@ffprobe-installer/ffprobe'; +import { createCanvas } from 'canvas'; +import ffprobe from 'ffprobe-client'; +import ffmpeg from 'fluent-ffmpeg'; +import * as fs from 'fs'; +import goproTelemetry from 'gopro-telemetry'; +import moment from 'moment-timezone'; +import * as path from 'path'; +import tzlookup from 'tz-lookup'; +import * as util from 'util'; + +// setup env +process.env.FFPROBE_PATH = ffprobeInstaller.path; +ffmpeg.setFfmpegPath(ffmpegInstaller.path); + +const overlayFPS = 2; +const globalTZ = 'Europe/Berlin'; + +const extractGPMF = async (videoFile: any) => { + const ffData = await ffprobe(videoFile); + for (let i = 0; i < ffData.streams.length; i++) { + if (ffData.streams[i].codec_tag_string === 'gpmd') { + return [await extractGPMFAt(videoFile, i), ffData]; + } + } + console.error('[Invalid file] No data stream (gpmd) found in: ' + videoFile); + return [null, null]; +}; + +const extractGPMFAt = async (videoFile: any, stream: number) => { + let rawData = Buffer.alloc(0); + await new Promise((resolve) => { + ffmpeg(videoFile) + .outputOption('-y') + .outputOptions('-codec copy') + .outputOptions(`-map 0:${stream}`) + .outputOption('-f rawvideo') + .pipe() + .on('data', (chunk) => { + rawData = Buffer.concat([rawData, chunk]); + }) + .on('end', async () => { await sleep(100); return resolve({}) }); + }); + return rawData; +}; + +function getSamplefromTime(time: moment.Moment, samples: any[]) { + const searchFor = time.valueOf(); + const closest = samples.reduce((prev, curr) => { + if (!curr || !curr.date) { return; } + if (!prev) { return curr; } + return (Math.abs(moment(curr.date).valueOf() - searchFor) + < Math.abs(moment(prev.date).valueOf() - searchFor) + ? curr : prev); + }); + return closest; +} + +let lastValidBound: any = null; +function getBoundingRect(data: any) { + let left = Infinity; + let right = -Infinity; + let top = Infinity; + let bottom = -Infinity; + + for (let { value } of data) { + if (value) { lastValidBound = value; } + if (!value) { value = lastValidBound; } + const [lat, long, hgt, spd, inc] = value; + if (left > long) { left = long; } + if (top > lat) { top = lat; } + if (right < long) { right = long; } + if (bottom < lat) { bottom = lat; } + } + return { x: left, y: top, width: right - left, height: bottom - top }; +} + +let lastValidRoute: any = null; +function drawRoute(x: number, y: number, w: number, h: number, ctx: any, data: any) { + const boundingRect = getBoundingRect(data); + for (let { value } of data) { + if (value) { lastValidRoute = value; } + if (!value) { value = lastValidRoute; } + const [lat, long, hgt, spd, inc] = value; + let xx = (long - boundingRect.x) / boundingRect.width * w; + let yy = (lat - boundingRect.y) / boundingRect.height * h; + yy *= -1; + yy += h; + xx += x; + yy += y; + ctx.fillRect(xx, yy, 1, 1); + } +} + +function drawRoutePosition(x: number, y: number, w: number, h: number, ctx: any, data: any, lat: number, long: number) { + const boundingRect = getBoundingRect(data); + let xx = (long - boundingRect.x) / boundingRect.width * w; + let yy = (lat - boundingRect.y) / boundingRect.height * h; + yy *= -1; + yy += h; + + xx += x; + yy += y; + + // track crosshair + ctx.fillRect(xx, y + 0, 1, h); + ctx.fillRect(x + 0, yy, w, 1); +} + +// SRC: https://www.geodatasource.com/developers/javascript +function distance(lat1: number, lon1: number, lat2: number, lon2: number, unit: string) { + if ((lat1 === lat2) && (lon1 === lon2)) { + return 0; + } else { + const radlat1 = Math.PI * lat1 / 180; + const radlat2 = Math.PI * lat2 / 180; + const theta = lon1 - lon2; + const radtheta = Math.PI * theta / 180; + let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); + if (dist > 1) { + dist = 1; + } + dist = Math.acos(dist); + dist = dist * 180 / Math.PI; + dist = dist * 60 * 1.1515; + if (unit === 'K') { dist = dist * 1.609344; } + if (unit === 'N') { dist = dist * 0.8684; } + return dist; + } +} + +function roundRect(ctx: any, x: any, y: any, width: any, height: any, radius: any, fill: boolean, stroke: boolean) { + if (typeof stroke === 'undefined') { + stroke = true; + } + if (typeof radius === 'undefined') { + radius = 5; + } + if (typeof radius === 'number') { + radius = { tl: radius, tr: radius, br: radius, bl: radius }; + } else { + const defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 }; + for (const side in defaultRadius) { + if (side in radius) { + radius[side] = radius[side]; + } + } + } + ctx.beginPath(); + ctx.moveTo(x + radius.tl, y); + ctx.lineTo(x + width - radius.tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); + ctx.lineTo(x + width, y + height - radius.br); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); + ctx.lineTo(x + radius.bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); + ctx.lineTo(x, y + radius.tl); + ctx.quadraticCurveTo(x, y, x + radius.tl, y); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } +} +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +async function getCompleteTrack(inDir: string, files: any[]) { + const track = await files.reduce(async (prevTrack, f) => { + const ctrack = await prevTrack; + const [raw, ffData]: any = await extractGPMF(inDir + f); + if (!raw) { return ctrack; } + const data = await goproTelemetry({ rawData: raw }); + const key = Object.keys(data).filter((x) => data[x].streams && data[x].streams.GPS5)[0]; + ctrack.push(...data[key].streams.GPS5.samples); + return ctrack; + }, Promise.resolve([])); + return track; +} + +async function getStartDate(inDir: string, files: any[]) { + const min = await files.reduce(async (minP, f) => { + const cmin = await minP; + const [raw, ffData]: any = await extractGPMF(inDir + f); + if (!ffData) { return cmin; } + return moment.min([cmin, moment.utc(ffData.format.tags.creation_time)]); + }, Promise.resolve(moment.utc())); + return min; +} + +function getTrackLen(track: any[], until?: any) { + // calc dist total + let lastValid: any = null; + const trackLength = track.slice(0).reduce((len, pnt, idx, arr) => { + if (idx < 1) { return 0; } + if (lastValid && pnt?.value) { lastValid = pnt; } + if (!pnt?.value) { pnt = lastValid; } + let prevPnt = arr[idx - 1]; + if (!prevPnt?.value) { prevPnt = lastValid; } + if (!pnt || !prevPnt) { return len; } + const [lat1, long1, hgt1, spd1, inc1] = prevPnt.value; + const [lat2, long2, hgt2, spd2, inc2] = pnt.value; + const ll = len + distance(lat1, long1, lat2, long2, 'K'); + // early exit + if (until) { + const [lat3, long3, hgt3, spd3, inc3] = until.value; + if (lat1 === lat3 && long1 === long3) { + arr.splice(1); + } + } + return ll; + }, track.slice(-1)[0]); + return trackLength; +} + +async function renderFullTrack(ctx: any, x: number, y: number, w: number, h: number, fullTrack: any) { + ctx.fillStyle = 'white'; + ctx.strokeStyle = 'white'; + ctx.lineWidth = 3; + // ctx.font = '10px Arial'; + + ctx.fillStyle = 'rgba(80,80,80,0.5)'; + roundRect(ctx, x, y, w, h, 10, true, false); + ctx.fillStyle = 'white'; + + ctx.lineWidth = 1; + drawRoute(x + 5, y + 5, w - 10, h - 10, ctx, fullTrack); +} + +async function handleVideo(file: string, fullTrack: any) { + const rawName = path.basename(file); + const [raw, ffData]: any = await extractGPMF(file); + if (!raw) { return; } + const vid = ffData.streams.filter((s: any) => s.codec_type === 'video')[0]; + console.log('File: ' + rawName); + console.log('Size: ' + Math.round(ffData.format.size / 1024 / 1024) + 'MiB'); + console.log('Created: ' + ffData.format.tags.creation_time); + console.log('Length: ' + Math.trunc(ffData.format.duration / 60) + 'min ' + + Math.trunc(ffData.format.duration % 60) + 's'); + console.log('Res: ' + vid.width + 'x' + vid.height + ' @ ' + vid.r_frame_rate); + console.log('---------------------'); + console.log('Render targets:'); + const frames = Math.trunc(ffData.format.duration * 60); + console.log('Total Frames: ' + frames); + console.log('Res: ' + vid.width + 'x' + vid.height + ' @ 60'); + console.log('---------------------'); + + const data = await goproTelemetry({ rawData: raw }); + const key = Object.keys(data).filter((x) => data[x].streams && data[x].streams.GPS5)[0]; + const zeroMark = moment(data[key].streams.GPS5.samples.slice(0, 1)[0].date); + const renderList: any[] = []; + + // SAMPLE FETCH LOOP + for (let i = 0; i < frames; i++) { + if (i % Math.round(60 / overlayFPS) !== 0) { continue; } + const timeMS = (1000 / 60 * i); + const timeTotal = moment(zeroMark).add(timeMS, 'milliseconds'); + const sample = getSamplefromTime(timeTotal, data[key].streams.GPS5.samples); + if (!sample) { continue; } + if (i % Math.trunc(frames / 100) === 0) { + console.log(rawName + ': [' + Math.round(i / frames * 100) + '%] TrgTime: ' + timeTotal.toISOString()); + } + renderList.push(sample); + } + + console.log('Collected target frames: ' + renderList.length); + console.log('Beginning frame rendering...'); + + // uhh + if (!fs.existsSync(__dirname + '/out/' + rawName)) { + fs.mkdirSync(__dirname + '/out/' + rawName); + } + + // RENDER LOOP + for (let i = 0; i < renderList.length; i++) { + const trackInfo = renderList[i]; + await renderSample(i, trackInfo, vid, rawName, fullTrack); + if ((i / renderList.length * 100) % 10 === 0) { + console.log(rawName + ': Frame render [' + Math.round(i / renderList.length * 100) + '%]'); + } + } + + console.log('Rendered overlay frames for file: ' + rawName); +} + +function pad(num: number, size: number) { + let s = num + ''; + while (s.length < size) { s = '0' + s; } + return s; +} + +async function renderSample(frame: number, sample: any, video: any, rawName: string, fullTrack: any[]) { + const [lat, long, hgt, spd, inc] = sample.value; + const spdKMH = (spd * 3.6).toFixed(2) + ' km/h'; + let dist = fullTrack ? getTrackLen(fullTrack, sample) : 0; + dist = dist.toFixed(3) + 'km'; + + const date = moment.utc(sample.date).tz(tzlookup(lat, long) || globalTZ).format('YYYY-MM-DD HH:mm:ss'); + + const canvas = createCanvas(video.width, video.height); + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'white'; + ctx.strokeStyle = 'black'; + ctx.lineWidth = 1; + + // TODO: scale all by res / settings + ctx.font = '30px Arial'; + + // date time + ctx.fillText(date, 50, 100); + ctx.strokeText(date, 50, 100); + + // lat long + ctx.fillText(lat, 50, video.height - 100); + ctx.strokeText(lat, 50, video.height - 100); + ctx.fillText(long, 240, video.height - 100); + ctx.strokeText(long, 240, video.height - 100); + + // spd + ctx.fillText(spdKMH, 50, video.height - 150); + ctx.strokeText(spdKMH, 50, video.height - 150); + + // track len + ctx.fillText(dist, 50, video.height - 50); + ctx.strokeText(dist, 50, video.height - 50); + + // minimap - has more or less scaling + const { x, y, w, h } = { + h: (video.width * 0.15) / 2, + w: (video.width * 0.15), + x: video.width - (video.width * 0.15) - 20, + y: video.height - (video.width * 0.15) + (video.width * 0.15) / 2 - 20, + }; + + if (fullTrack) { + renderFullTrack(ctx, x, y, w, h, fullTrack); + drawRoutePosition(x + 5, y + 5, w - 10, h - 10, ctx, fullTrack, lat, long); + } + + async function renderFrameFile(stream: any, iCanvas: any) { + return new Promise((resolve) => { + iCanvas.createPNGStream().pipe(stream); + stream.on('finish', resolve); + }); + } + + const out = fs.createWriteStream(__dirname + '/out/' + rawName + '/' + pad(frame, 4) + '.png'); + await renderFrameFile(out, canvas); +} + +async function asyncForEach(array: any, callback: any) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +async function renderOverlayedPart(inDir: string, outDir: string, file: string) { + return new Promise(async (resolve, reject) => { + const [raw, ffData]: any = await extractGPMF(inDir + file); + const formattedLength = moment.utc(ffData.format.duration*1000); + + // for testing add .addOption('-t 5') which will return a 5s video instead of whole duration + const render = ffmpeg(inDir + file) + .addInput(outDir + file + '/%04d.png') + .inputFPS(overlayFPS) + .complexFilter([ + { + filter: 'overlay', + input: '[0:v][1:v]', + }, + ] as any) + .addOption('-c:a copy') + .on('end', resolve) + .on('error', reject) + .on('progress', (progress: { timemark: moment.MomentInput; }) => { + const prog = moment(progress.timemark, 'HH:mm:ss.SS'); + if (Math.round(prog.valueOf() / 100) % 10 === 0) { + console.log(file + '] Processing: ' + prog.format("HH:mm:ss") + '/' + formattedLength.format("HH:mm:ss")); + } + }) + //.withVideoCodec('h264_nvenc') // <- needs more settings for quality, at least will use GPU + .output(outDir + 'rendered_' + file) + render.run(); + }); +} + +async function concatVideos(inDir: string, outDir: string, files: string[], date: moment.Moment) { + if (files.length < 1) { return; } + return new Promise((resolve, reject) => { + const render = ffmpeg(inDir + files[0]); + files.shift(); + files.forEach((f) => render.addInput(inDir + f)); + render.addOption('-safe 0') + // .withVideoCodec('h264_nvenc') // <- needs more settings for quality, at least will use GPU + .on('end', resolve) + .on('error', reject) + .on('progress', (progress) => { + const tm = moment(progress.timemark, 'HH:mm:ss.SS').valueOf(); + if (Math.round(tm / 100) % 10 === 0) { + console.log('Concat processing: ' + progress.timemark); + } + }); + + render.mergeToFile(outDir + date.format('YYYYMMDD_HHmmss') + '.mp4', __dirname + '/out/'); + }); +} + +async function load() { + const startTime = moment(); + console.log('START Current time: ' + startTime.toISOString()); + + const inDir = __dirname + '/in/'; + const outDir = __dirname + '/out/'; + const renderOutDir = __dirname + '/final/'; + const readdir = util.promisify(fs.readdir) as any; + console.log('inDir: ' + inDir); + + await readdir(inDir, async (err: any, files: any[]) => { + console.log('All files: ' + files.join(',')); + let fileArr = files.filter((x) => x.toLowerCase().includes('.mp4')); + console.log('Files for one track: ' + fileArr.join(',')); + + // collect track metadata over all files + const [cTrack, startDate] = await Promise.all([ + await getCompleteTrack(inDir, fileArr), + await getStartDate(inDir, fileArr) + ]); + + const tl = getTrackLen(cTrack); + console.log('Track length: ' + tl.toFixed(3) + 'km'); + console.log('Track start: ' + startDate.toISOString()); + console.log('----------------------------'); + + const videoHandlers: [Promise] = [Promise.resolve()]; + await asyncForEach(fileArr, async (file: string) => { + videoHandlers.push(handleVideo(inDir + file, cTrack)); + }); + + // render overlay images in parallel + await Promise.all(videoHandlers); + + console.log('----------------------------'); + console.log('Overlay frames done'); + console.log('Proceeding with overlaying over raws'); + + await asyncForEach(fileArr, async (file: string) => { + console.log('Starting: ' + file); + await renderOverlayedPart(inDir, outDir, file); + console.log('Done: ' + file); + console.log('----------------------------'); + }); + + // build out files from above, TODO: return out files above instead of this "logic" + fileArr = fileArr.map((x) => { + x = 'rendered_' + x; + return x; + }); + + // render final file by concatinating all rendered parts of track + if (fileArr.length > 1) { + await concatVideos(outDir, renderOutDir, fileArr, startDate); + } else { + fs.copyFileSync(outDir + fileArr[0], renderOutDir + startDate.format('YYYYMMDD_HHmmss') + '.mp4'); + } + + console.log('END: ' + moment().toISOString()); + const duration = moment.utc(moment().diff(moment(startTime, 'HH:mm:ss'))).format('HH:mm:ss'); + console.log('Duration: ' + duration); + }); + +} + +load();