thumbnailUrl
This commit is contained in:
@@ -14,3 +14,4 @@ exports.lyrics = require("./src/lyrics");
|
||||
exports.cover = require("./src/cover");
|
||||
exports.projects = require("./src/project");
|
||||
exports.hls = require("./src/hls");
|
||||
exports.thumbnail = require("./src/thumbnail");
|
||||
|
||||
Generated
+138
-46
@@ -6,12 +6,14 @@
|
||||
"": {
|
||||
"name": "functions",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@genkit-ai/googleai": "^1.18.0",
|
||||
"@google-cloud/storage": "^7.17.1",
|
||||
"axios": "^1.11.0",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"firebase-admin": "^12.7.0",
|
||||
"firebase-functions": "^6.4.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"genkit": "^1.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -128,6 +130,132 @@
|
||||
"integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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",
|
||||
"@ffmpeg-installer/linux-arm64": "4.1.4",
|
||||
"@ffmpeg-installer/linux-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/linux-x64": "4.1.0",
|
||||
"@ffmpeg-installer/win32-ia32": "4.1.0",
|
||||
"@ffmpeg-installer/win32-x64": "4.1.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"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==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "GPLv3",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@firebase/app-check-interop-types": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz",
|
||||
@@ -521,7 +649,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
|
||||
"integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"arrify": "^2.0.0",
|
||||
"extend": "^3.0.2"
|
||||
@@ -545,7 +672,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
|
||||
"integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -555,17 +681,15 @@
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
|
||||
"integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@google-cloud/storage": {
|
||||
"version": "7.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.0.tgz",
|
||||
"integrity": "sha512-5m9GoZqKh52a1UqkxDBu/+WVFDALNtHg5up5gNmNbXQWBcV813tzJKsyDtKjOPrlR1em1TxtD7NSPCrObH7koQ==",
|
||||
"version": "7.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.1.tgz",
|
||||
"integrity": "sha512-2FMQbpU7qK+OtBPaegC6n+XevgZksobUGo6mGKnXNmeZpvLiAo1gTAE3oTKsrMGDV4VtL8Zzpono0YsK/Q7Iqg==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@google-cloud/paginator": "^5.0.0",
|
||||
"@google-cloud/projectify": "^4.0.0",
|
||||
@@ -592,7 +716,6 @@
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
@@ -2240,7 +2363,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
@@ -2276,8 +2398,7 @@
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
|
||||
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.38",
|
||||
@@ -2440,7 +2561,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz",
|
||||
"integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/caseless": "*",
|
||||
"@types/node": "*",
|
||||
@@ -2453,7 +2573,6 @@
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
|
||||
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
@@ -2507,8 +2626,7 @@
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/triple-beam": {
|
||||
"version": "1.3.5",
|
||||
@@ -2529,7 +2647,6 @@
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
@@ -2696,7 +2813,6 @@
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
|
||||
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -2722,7 +2838,6 @@
|
||||
"resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
|
||||
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"retry": "0.13.1"
|
||||
}
|
||||
@@ -3239,7 +3354,6 @@
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
|
||||
"integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.4.1",
|
||||
"inherits": "^2.0.3",
|
||||
@@ -3289,7 +3403,6 @@
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
@@ -3563,7 +3676,6 @@
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -3714,7 +3826,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"strnum": "^1.1.1"
|
||||
},
|
||||
@@ -4520,8 +4631,7 @@
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
@@ -4550,7 +4660,6 @@
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
@@ -4565,7 +4674,6 @@
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -5163,7 +5271,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
@@ -5348,7 +5455,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
@@ -5395,7 +5501,6 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
@@ -5832,7 +5937,6 @@
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
@@ -5842,7 +5946,6 @@
|
||||
"resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
|
||||
"integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/request": "^2.48.8",
|
||||
"extend": "^3.0.2",
|
||||
@@ -6177,7 +6280,6 @@
|
||||
"resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
|
||||
"integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"stubs": "^3.0.0"
|
||||
}
|
||||
@@ -6186,8 +6288,7 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
|
||||
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
@@ -6252,15 +6353,13 @@
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stubs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
|
||||
"integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
@@ -6292,7 +6391,6 @@
|
||||
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
|
||||
"integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
@@ -6309,7 +6407,6 @@
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -6322,7 +6419,6 @@
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
@@ -6336,7 +6432,6 @@
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -6361,7 +6456,6 @@
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
@@ -6708,7 +6802,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xorshift": {
|
||||
@@ -6784,7 +6877,6 @@
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
||||
@@ -14,13 +14,15 @@
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@genkit-ai/googleai": "^1.18.0",
|
||||
"@google-cloud/storage": "^7.17.1",
|
||||
"axios": "^1.11.0",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"firebase-admin": "^12.7.0",
|
||||
"firebase-functions": "^6.4.0",
|
||||
"genkit": "^1.18.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"ffmpeg-static": "^5.2.0"
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"genkit": "^1.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.15.0",
|
||||
|
||||
+43
-43
@@ -1,20 +1,20 @@
|
||||
const { onObjectFinalized } = require('firebase-functions/v2/storage');
|
||||
const functions = require('firebase-functions');
|
||||
const admin = require('firebase-admin');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const { onObjectFinalized } = require("firebase-functions/v2/storage");
|
||||
const functions = require("firebase-functions");
|
||||
const admin = require("firebase-admin");
|
||||
const path = require("path");
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
|
||||
// These deps must be installed in functions/: ffmpeg-static, fluent-ffmpeg
|
||||
let ffmpeg;
|
||||
let ffmpegPath;
|
||||
try {
|
||||
ffmpeg = require('fluent-ffmpeg');
|
||||
ffmpegPath = require('ffmpeg-static');
|
||||
ffmpeg = require("fluent-ffmpeg");
|
||||
ffmpegPath = require("ffmpeg-static");
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'ffmpeg modules not installed yet — deploy after npm i',
|
||||
"ffmpeg modules not installed yet — deploy after npm i",
|
||||
e?.message
|
||||
);
|
||||
}
|
||||
@@ -23,56 +23,56 @@ const bucket = admin.storage().bucket();
|
||||
|
||||
exports.transcodePlaybackToHLS = onObjectFinalized(
|
||||
{
|
||||
region: 'europe-west1',
|
||||
memory: '2GiB',
|
||||
region: "europe-west1",
|
||||
memory: "2GiB",
|
||||
timeoutSeconds: 540,
|
||||
cpu: 2,
|
||||
},
|
||||
async (event) => {
|
||||
const object = event.data;
|
||||
const filePath = object?.name || ''; // e.g., musics/{projectId}/source.mp4
|
||||
const contentType = object?.contentType || '';
|
||||
const filePath = object?.name || ""; // e.g., musics/{projectId}/source.mp4
|
||||
const contentType = object?.contentType || "";
|
||||
|
||||
try {
|
||||
// Only process our target path + video files
|
||||
const match = filePath.match(/^musics\/(.+?)\/source\.mp4$/i);
|
||||
if (!match || !contentType.startsWith('video/')) {
|
||||
if (!match || !contentType.startsWith("video/")) {
|
||||
return;
|
||||
}
|
||||
const projectId = match[1];
|
||||
const tempDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), `hls_${projectId}_`)
|
||||
);
|
||||
const localSource = path.join(tempDir, 'source.mp4');
|
||||
const hlsDir = path.join(tempDir, 'hls');
|
||||
const localSource = path.join(tempDir, "source.mp4");
|
||||
const hlsDir = path.join(tempDir, "hls");
|
||||
fs.mkdirSync(hlsDir);
|
||||
|
||||
// Download source
|
||||
await bucket.file(filePath).download({ destination: localSource });
|
||||
|
||||
if (!ffmpeg) {
|
||||
console.warn('ffmpeg not available; skip transcode');
|
||||
console.warn("ffmpeg not available; skip transcode");
|
||||
return;
|
||||
}
|
||||
|
||||
// Transcode to HLS (single rendition for simplicity)
|
||||
const masterPath = path.join(hlsDir, 'master.m3u8');
|
||||
const segmentPattern = path.join(hlsDir, 'segment_%03d.ts');
|
||||
const masterPath = path.join(hlsDir, "master.m3u8");
|
||||
const segmentPattern = path.join(hlsDir, "segment_%03d.ts");
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
ffmpeg(localSource)
|
||||
.outputOptions([
|
||||
'-profile:v baseline',
|
||||
'-level 3.0',
|
||||
'-start_number 0',
|
||||
'-hls_time 4',
|
||||
'-hls_list_size 0',
|
||||
'-f hls',
|
||||
"-profile:v baseline",
|
||||
"-level 3.0",
|
||||
"-start_number 0",
|
||||
"-hls_time 4",
|
||||
"-hls_list_size 0",
|
||||
"-f hls",
|
||||
`-hls_segment_filename ${segmentPattern}`,
|
||||
])
|
||||
.output(masterPath)
|
||||
.on('end', resolve)
|
||||
.on('error', reject)
|
||||
.on("end", resolve)
|
||||
.on("error", reject)
|
||||
.run();
|
||||
});
|
||||
|
||||
@@ -82,28 +82,28 @@ exports.transcodePlaybackToHLS = onObjectFinalized(
|
||||
|
||||
// Upload all segments first
|
||||
const uploads = fileNames
|
||||
.filter((n) => n.endsWith('.ts'))
|
||||
.filter((n) => n.endsWith(".ts"))
|
||||
.map((name) => {
|
||||
const local = path.join(hlsDir, name);
|
||||
const dest = `${destPrefix}${name}`;
|
||||
return bucket.upload(local, {
|
||||
destination: dest,
|
||||
metadata: {
|
||||
contentType: 'video/MP2T',
|
||||
cacheControl: 'public, max-age=3600',
|
||||
contentType: "video/MP2T",
|
||||
cacheControl: "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
});
|
||||
await Promise.all(uploads);
|
||||
|
||||
// Generate signed URLs for segments and rewrite playlist with absolute URLs
|
||||
const segmentNames = fileNames.filter((n) => n.endsWith('.ts'));
|
||||
const segmentNames = fileNames.filter((n) => n.endsWith(".ts"));
|
||||
const signedSegments = {};
|
||||
await Promise.all(
|
||||
segmentNames.map(async (name) => {
|
||||
const file = bucket.file(`${destPrefix}${name}`);
|
||||
const [url] = await file.getSignedUrl({
|
||||
action: 'read',
|
||||
action: "read",
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
|
||||
});
|
||||
signedSegments[name] = url;
|
||||
@@ -111,24 +111,24 @@ exports.transcodePlaybackToHLS = onObjectFinalized(
|
||||
);
|
||||
|
||||
// Rewrite master playlist to use absolute signed segment URLs
|
||||
let masterContent = fs.readFileSync(masterPath, 'utf8');
|
||||
let masterContent = fs.readFileSync(masterPath, "utf8");
|
||||
masterContent = masterContent
|
||||
.split(/\r?\n/)
|
||||
.map((line) =>
|
||||
line.endsWith('.ts') && signedSegments[line]
|
||||
line.endsWith(".ts") && signedSegments[line]
|
||||
? signedSegments[line]
|
||||
: line
|
||||
)
|
||||
.join('\n');
|
||||
const rewrittenMasterLocal = path.join(hlsDir, 'master.abs.m3u8');
|
||||
fs.writeFileSync(rewrittenMasterLocal, masterContent, 'utf8');
|
||||
.join("\n");
|
||||
const rewrittenMasterLocal = path.join(hlsDir, "master.abs.m3u8");
|
||||
fs.writeFileSync(rewrittenMasterLocal, masterContent, "utf8");
|
||||
|
||||
// Upload rewritten playlist
|
||||
await bucket.upload(rewrittenMasterLocal, {
|
||||
destination: `${destPrefix}master.m3u8`,
|
||||
metadata: {
|
||||
contentType: 'application/vnd.apple.mpegurl',
|
||||
cacheControl: 'public, max-age=3600',
|
||||
contentType: "application/vnd.apple.mpegurl",
|
||||
cacheControl: "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -136,15 +136,15 @@ exports.transcodePlaybackToHLS = onObjectFinalized(
|
||||
const [signedUrl] = await bucket
|
||||
.file(`${destPrefix}master.m3u8`)
|
||||
.getSignedUrl({
|
||||
action: 'read',
|
||||
action: "read",
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365,
|
||||
});
|
||||
|
||||
// Update Firestore project doc with playbackUrl
|
||||
await admin.firestore().collection('projects').doc(projectId).set(
|
||||
await admin.firestore().collection("projects").doc(projectId).set(
|
||||
{
|
||||
playbackUrl: signedUrl,
|
||||
playbackStatus: 'ready',
|
||||
playbackStatus: "ready",
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
@@ -155,7 +155,7 @@ exports.transcodePlaybackToHLS = onObjectFinalized(
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.error('HLS transcode error', e);
|
||||
console.error("HLS transcode error", e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
const { onObjectFinalized } = require("firebase-functions/v2/storage");
|
||||
const logger = require("firebase-functions/logger");
|
||||
const admin = require("firebase-admin");
|
||||
const ffmpeg = require("fluent-ffmpeg");
|
||||
const ffmpegInstaller = require("@ffmpeg-installer/ffmpeg");
|
||||
const fs = require("node:fs/promises");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
const crypto = require("node:crypto");
|
||||
|
||||
ffmpeg.setFfmpegPath(ffmpegInstaller.path);
|
||||
|
||||
exports.generateVideoThumbnail = onObjectFinalized(
|
||||
{
|
||||
region: "europe-west1",
|
||||
timeoutSeconds: 180,
|
||||
memory: "1GiB",
|
||||
cpu: 1,
|
||||
},
|
||||
async (event) => {
|
||||
const file = event.data || {};
|
||||
const bucketName = file.bucket;
|
||||
const objectName = file.name || "";
|
||||
const contentType = file.contentType || "";
|
||||
|
||||
if (!bucketName) return;
|
||||
if (!contentType.startsWith("video/")) return;
|
||||
if (!objectName) return;
|
||||
if (/_thumb9x16\.jpg$/i.test(objectName)) return;
|
||||
|
||||
const bucket = admin.storage().bucket(bucketName);
|
||||
const playbackMatch = objectName.match(/^musics\/([^/]+)\/playback\.mp4$/i);
|
||||
const projectId = playbackMatch ? playbackMatch[1] : null;
|
||||
|
||||
// Use unique folder under /tmp to avoid name collisions
|
||||
const tmpDir = path.join(
|
||||
os.tmpdir(),
|
||||
`thumb-${Date.now()}-${Math.round(Math.random() * 1000)}`
|
||||
);
|
||||
const baseName = path.basename(objectName);
|
||||
const dirName = path.posix.dirname(objectName);
|
||||
const localVideoPath = path.join(tmpDir, baseName);
|
||||
|
||||
const thumbBase = baseName.replace(/\.[^.]+$/, "") + "_thumb9x16.jpg";
|
||||
const localThumbPath = path.join(tmpDir, thumbBase);
|
||||
const remoteThumbPath =
|
||||
dirName && dirName !== "."
|
||||
? path.posix.join(dirName, thumbBase)
|
||||
: thumbBase;
|
||||
|
||||
try {
|
||||
await fs.mkdir(tmpDir, { recursive: true });
|
||||
|
||||
// Télécharger la vidéo depuis le bucket
|
||||
await bucket.file(objectName).download({ destination: localVideoPath });
|
||||
|
||||
// Extraire 1 frame à 1 seconde, forcer 9:16 (1080x1920) par scale+crop centré
|
||||
await new Promise((resolve, reject) => {
|
||||
ffmpeg(localVideoPath)
|
||||
.inputOptions(["-ss 1"])
|
||||
.frames(1)
|
||||
.outputOptions([
|
||||
"-vf",
|
||||
"scale='if(gt(a,9/16),1080,-2)':'if(gt(a,9/16),-2,1920)',crop=1080:1920",
|
||||
"-q:v",
|
||||
"2",
|
||||
])
|
||||
.output(localThumbPath)
|
||||
.on("end", resolve)
|
||||
.on("error", reject)
|
||||
.run();
|
||||
});
|
||||
|
||||
// Upload du thumbnail avec un token de téléchargement public Firebase
|
||||
const downloadToken = crypto.randomUUID();
|
||||
await bucket.upload(localThumbPath, {
|
||||
destination: remoteThumbPath,
|
||||
metadata: {
|
||||
contentType: "image/jpeg",
|
||||
cacheControl: "public, max-age=86400",
|
||||
metadata: {
|
||||
original: objectName,
|
||||
aspect: "9:16",
|
||||
t: "1s",
|
||||
firebaseStorageDownloadTokens: downloadToken,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const encodedPath = encodeURIComponent(remoteThumbPath);
|
||||
const thumbnailUrl = `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodedPath}?alt=media&token=${downloadToken}`;
|
||||
|
||||
if (projectId) {
|
||||
await admin.firestore().collection("projects").doc(projectId).set(
|
||||
{
|
||||
thumbnailUrl,
|
||||
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
||||
},
|
||||
{ merge: true }
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("✅ [Thumbnail] Uploaded", {
|
||||
objectName,
|
||||
remoteThumbPath,
|
||||
projectId,
|
||||
thumbnailUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("❌ [Thumbnail] Failed", {
|
||||
objectName,
|
||||
error: error?.message || String(error),
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user