covers with logo

This commit is contained in:
2025-10-22 17:06:49 +02:00
parent 40e10d1692
commit b4375d04be
9 changed files with 643 additions and 228 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+2 -3
View File
@@ -49,7 +49,7 @@ exports.generatePicturePrompt = (project = {}) => {
const prompt = ` const prompt = `
<BRIEF> <BRIEF>
<OBJECTIF>Créer une pochette d'album abstraite et lumineuse.</OBJECTIF> <OBJECTIF>Créer une pochette lumineuse.</OBJECTIF>
<TITRE>${title || "Sans titre"}</TITRE> <TITRE>${title || "Sans titre"}</TITRE>
<STYLE_MUSICAL>${tagsLine}</STYLE_MUSICAL> <STYLE_MUSICAL>${tagsLine}</STYLE_MUSICAL>
<EXTRAITS_PAROLES>${lyricsLine}</EXTRAITS_PAROLES> <EXTRAITS_PAROLES>${lyricsLine}</EXTRAITS_PAROLES>
@@ -58,7 +58,6 @@ exports.generatePicturePrompt = (project = {}) => {
<CONTEXTES_VISUELS> <CONTEXTES_VISUELS>
<FORMAT>Image carrée 1024x1024 pixels.</FORMAT> <FORMAT>Image carrée 1024x1024 pixels.</FORMAT>
<STYLE>Abstrait, graphique, formes géométriques et dégradés.</STYLE> <STYLE>Abstrait, graphique, formes géométriques et dégradés.</STYLE>
<PALETTE>${paletteHint}</PALETTE>
<COMPOSITION>Remplis toute la surface sans laisser de bordures ou zones vides.</COMPOSITION> <COMPOSITION>Remplis toute la surface sans laisser de bordures ou zones vides.</COMPOSITION>
</CONTEXTES_VISUELS> </CONTEXTES_VISUELS>
@@ -66,7 +65,7 @@ exports.generatePicturePrompt = (project = {}) => {
<INTERDIT>Texte ou typographie.</INTERDIT> <INTERDIT>Texte ou typographie.</INTERDIT>
<INTERDIT>Personnes ou visages.</INTERDIT> <INTERDIT>Personnes ou visages.</INTERDIT>
<INTERDIT>Fond blanc ou bordures.</INTERDIT> <INTERDIT>Fond blanc ou bordures.</INTERDIT>
<INTERDIT>Filigrane ou logo.</INTERDIT> <INTERDIT>Autre logo ou filigrane que celui prévu en bas à droite.</INTERDIT>
</CONTRAINTES> </CONTRAINTES>
`.trim(); `.trim();
+435 -5
View File
@@ -15,7 +15,8 @@
"firebase-admin": "^12.7.0", "firebase-admin": "^12.7.0",
"firebase-functions": "^6.4.0", "firebase-functions": "^6.4.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"genkit": "^1.18.0" "genkit": "^1.18.0",
"sharp": "^0.33.4"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.15.0", "eslint": "^8.15.0",
@@ -62,6 +63,16 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@emnapi/runtime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz",
"integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.8.0", "version": "4.8.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
@@ -799,6 +810,367 @@
"dev": true, "dev": true,
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@js-sdsl/ordered-map": { "node_modules/@js-sdsl/ordered-map": {
"version": "4.4.2", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
@@ -3097,7 +3469,6 @@
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"color-name": "^1.0.0", "color-name": "^1.0.0",
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
@@ -3296,6 +3667,15 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -4842,8 +5222,7 @@
"version": "0.3.2", "version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/is-core-module": { "node_modules/is-core-module": {
"version": "2.16.1", "version": "2.16.1",
@@ -6205,6 +6584,58 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.5",
"@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5"
}
},
"node_modules/sharp/node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -6311,7 +6742,6 @@
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"is-arrayish": "^0.3.1" "is-arrayish": "^0.3.1"
} }
+2 -1
View File
@@ -23,7 +23,8 @@
"firebase-admin": "^12.7.0", "firebase-admin": "^12.7.0",
"firebase-functions": "^6.4.0", "firebase-functions": "^6.4.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"genkit": "^1.18.0" "genkit": "^1.18.0",
"sharp": "^0.33.4"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.15.0", "eslint": "^8.15.0",
+91 -54
View File
@@ -3,11 +3,68 @@ const admin = require("firebase-admin");
const logger = require("firebase-functions/logger"); const logger = require("firebase-functions/logger");
const { const {
generateImageV2, generateImageV2,
CombineCoverAndPicture,
} = require("../helpers/gemini"); } = require("../helpers/gemini");
const { generatePicturePrompt } = require("../helpers/prompts"); const { generatePicturePrompt } = require("../helpers/prompts");
const path = require("path");
const fs = require("fs");
const axios = require("axios");
const sharp = require("sharp");
const db = admin.firestore(); const db = admin.firestore();
const bucket = admin.storage().bucket();
const LOGO_PATH = path.resolve(
__dirname,
"../assets/musicLandProduction.png",
);
async function buildCoverWithLogo(backgroundUrl, targetPath) {
logger.info("🖼️ [Cover] Adding logo to generated background");
const [{ data: backgroundBuffer }, logoBuffer] = await Promise.all([
axios.get(backgroundUrl, { responseType: "arraybuffer" }),
fs.promises.readFile(LOGO_PATH),
]);
const baseImage = sharp(backgroundBuffer);
const { width = 1024, height = 1024 } = await baseImage.metadata();
const desiredWidth = Math.round(width * 0.32);
const margin = Math.round(width * 0.04);
const { data: resizedLogo, info: logoInfo } = await sharp(logoBuffer)
.resize({ width: desiredWidth })
.png()
.toBuffer({ resolveWithObject: true });
const left = Math.max(width - logoInfo.width - margin, 0);
const top = Math.max(height - logoInfo.height - margin, 0);
const stampedBuffer = await baseImage
.ensureAlpha()
.composite([
{
input: resizedLogo,
left,
top,
blend: "over",
},
])
.png()
.toBuffer();
const token = require("crypto").randomUUID();
await bucket.file(targetPath).save(stampedBuffer, {
resumable: false,
metadata: {
contentType: "image/png",
cacheControl: "public, max-age=31536000",
metadata: { firebaseStorageDownloadTokens: token },
},
});
return `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
targetPath,
)}?alt=media&token=${token}`;
}
// Shared core for generating and saving the cover, and updating the project // Shared core for generating and saving the cover, and updating the project
async function performCoverGeneration(project) { async function performCoverGeneration(project) {
@@ -19,12 +76,13 @@ async function performCoverGeneration(project) {
promptPreview: String(prompt).slice(0, 160), promptPreview: String(prompt).slice(0, 160),
}); });
let coverUrl = ""; let coverUrl = "";
const timestamp = Date.now();
try { try {
// Utiliser une taille fixe de 1024 pixels // Utiliser une taille fixe de 1024 pixels
coverUrl = await generateImageV2( coverUrl = await generateImageV2(
prompt, prompt,
1024, 1024,
`users/${project.userId}/projects/${project.id}/generated-${Date.now()}.png`, `users/${project.userId}/projects/${project.id}/generated-${timestamp}.png`,
); );
console.log("coverUrl", coverUrl); console.log("coverUrl", coverUrl);
} catch (e) { } catch (e) {
@@ -38,6 +96,19 @@ async function performCoverGeneration(project) {
throw new Error("Génération d'image échouée (URL vide)"); throw new Error("Génération d'image échouée (URL vide)");
} }
let finalCoverUrl = "";
try {
const stampedPath = `users/${project.userId}/projects/${project.id}/cover-${timestamp}-stamped.png`;
finalCoverUrl = await buildCoverWithLogo(coverUrl, stampedPath);
} catch (e) {
logger.error("❌ [Cover] Logo overlay failed", {
projectId: project.id,
error: e?.message || String(e),
});
// En cas d'échec, on retient au moins la cover générée
finalCoverUrl = coverUrl;
}
// Mettre à jour le projet avec l'URL et le statut // Mettre à jour le projet avec l'URL et le statut
await db await db
.collection("projects") .collection("projects")
@@ -46,6 +117,7 @@ async function performCoverGeneration(project) {
{ {
cover: { cover: {
generatedBackground: coverUrl, generatedBackground: coverUrl,
result: finalCoverUrl,
}, },
coverStatus: "GENERATED", coverStatus: "GENERATED",
updatedAt: admin.firestore.FieldValue.serverTimestamp(), updatedAt: admin.firestore.FieldValue.serverTimestamp(),
@@ -56,56 +128,10 @@ async function performCoverGeneration(project) {
logger.info("✅ [Cover] Saved", { logger.info("✅ [Cover] Saved", {
projectId: project.id, projectId: project.id,
coverUrl, coverUrl,
finalCoverUrl,
ms: Date.now() - t0, ms: Date.now() - t0,
}); });
return coverUrl; return finalCoverUrl;
}
async function performCombineGeneration(project) {
const t0 = Date.now();
const background = project?.cover?.generatedBackground || null;
const foreground = project?.cover?.userForeground || null;
if (!background || !foreground) {
throw new Error(
"Images manquantes: cover.generatedBackground et cover.userForground sont requis",
);
}
logger.info("🧩 [Combine] Inputs detected", {
bgPreview: String(background).slice(0, 80),
fgPreview: String(foreground).slice(0, 80),
});
const combinedUrl = await CombineCoverAndPicture(
background,
foreground,
`users/${project.userId}/projects/${project.id}/combine-${Date.now()}.png`,
);
// Sauvegarder le résultat et marquer comme généré
await db
.collection("projects")
.doc(project.id)
.set(
{
cover: {
...project.cover,
result: combinedUrl,
},
coverStatus: "GENERATED",
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
},
{ merge: true },
);
logger.info("✅ [Cover] Combined image saved", {
projectId: project.id,
combinedUrl,
ms: Date.now() - t0,
});
return combinedUrl;
} }
// Firestore trigger: création d'une tâche de génération de cover // Firestore trigger: création d'une tâche de génération de cover
@@ -132,6 +158,21 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
type, type,
projectId, projectId,
}); });
if (type === "combine") {
logger.warn("⛔ [Task] Combine task ignored (feature disabled)", {
projectId,
taskId: event?.params?.taskId,
});
await event.data.ref.update({
status: "CANCELLED",
error:
"La personnalisation de la pochette avec une photo n'est plus disponible.",
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
});
return;
}
await db.collection("projects").doc(projectId).update({ await db.collection("projects").doc(projectId).update({
coverStatus: "GENERATING", coverStatus: "GENERATING",
updatedAt: admin.firestore.FieldValue.serverTimestamp(), updatedAt: admin.firestore.FieldValue.serverTimestamp(),
@@ -154,10 +195,6 @@ exports.onTaskCreateGenerateCover = onDocumentCreated(
logger.info("🎨 [Task] Start cover generation", { projectId }); logger.info("🎨 [Task] Start cover generation", { projectId });
coverUrl = await performCoverGeneration(project); coverUrl = await performCoverGeneration(project);
logger.info("✅ [Task] Cover generated", { projectId }); logger.info("✅ [Task] Cover generated", { projectId });
} else if (type === "combine") {
logger.info("🧩 [Task] Start combine (cover + photo)", { projectId });
coverUrl = await performCombineGeneration(project);
logger.info("✅ [Task] Combine generated", { projectId, coverUrl });
} }
await event.data.ref.update({ await event.data.ref.update({
+52 -61
View File
@@ -1,30 +1,21 @@
import * as ImagePicker from "expo-image-picker";
import { BlurView } from "expo-blur"; import { BlurView } from "expo-blur";
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { import { Image, Modal, Pressable, StyleSheet, Text, View } from "react-native";
Image,
Modal,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit"; import useMinuit from "react-native-minuit/src/hooks/useMinuit";
import { ai, background } from "../../assets"; import { ai, background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton"; import BorderGradientButton from "../../components/BorderGradientButton";
import FullscreenIntroVideo from "../../components/FullscreenIntroVideo"; import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import { Input } from "../../components/Input"; import { Input } from "../../components/Input";
import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { projectsRef, usersRef } from "../../config/firebase"; import firebase, { projectsRef, usersRef } from "../../config/firebase";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService"; import { goBack, navigate } from "../../navigation/NavigationService";
import { useUserData } from "../../providers/UserDataProvider"; import { useUserData } from "../../providers/UserDataProvider";
import { buildFullName } from "../../utils/artistName";
import { gutters, Palette } from "../../styles"; import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts"; import { FONT_FAMILY } from "../../styles/Fonts";
import { buildFullName } from "../../utils/artistName";
export const ChooseCoverType = () => { export const ChooseCoverType = () => {
const [showIntro, setShowIntro] = useState(true); const [showIntro, setShowIntro] = useState(true);
@@ -52,11 +43,14 @@ export const ChooseCoverType = () => {
return pref === "REAL_NAME" || pref === "CUSTOM"; return pref === "REAL_NAME" || pref === "CUSTOM";
}, [currentUserData?.artistNamePreference, currentUserData?.userName]); }, [currentUserData?.artistNamePreference, currentUserData?.userName]);
const realName = useMemo(() => buildFullName(currentUserData), [ const realName = useMemo(
currentUserData?.firstName, () => buildFullName(currentUserData),
currentUserData?.lastName, [
currentUserData?.displayName, currentUserData?.firstName,
]); currentUserData?.lastName,
currentUserData?.displayName,
]
);
useEffect(() => { useEffect(() => {
if (pseudoVisible) { if (pseudoVisible) {
@@ -122,48 +116,48 @@ export const ChooseCoverType = () => {
[currentUID, projectId] [currentUID, projectId]
); );
const pickUserImage = useCallback(async () => { // const pickUserImage = useCallback(async () => {
try { // try {
await setIsLoading(true); // await setIsLoading(true);
const result = await ImagePicker.launchImageLibraryAsync({ // const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ["images"], // mediaTypes: ["images"],
allowsEditing: true, // allowsEditing: true,
aspect: [1, 1], // aspect: [1, 1],
quality: 1, // quality: 1,
}); // });
const uri = result?.assets?.[0]?.uri || null; // const uri = result?.assets?.[0]?.uri || null;
if (!uri) return; // if (!uri) return;
if (!projectId) return; // if (!projectId) return;
const path = `musics/${projectId}/userSelectedCover.png`; // const path = `musics/${projectId}/userSelectedCover.png`;
const { resultURI } = await uploadFileToFirebase({ // const { resultURI } = await uploadFileToFirebase({
uri, // uri,
path, // path,
shouldCompress: true, // shouldCompress: true,
fileType: "IMAGE", // fileType: "IMAGE",
}); // });
if (!resultURI) { // if (!resultURI) {
throw new Error("Erreur lors de l'upload"); // throw new Error("Erreur lors de l'upload");
} // }
await updateProjectData({ // await updateProjectData({
cover: { // cover: {
result: resultURI, // result: resultURI,
}, // },
}); // });
navigate(Routes.ValidateCover); // navigate(Routes.ValidateCover);
} catch (e) { // } catch (e) {
console.log("onPickUserImage error", e?.message); // console.log("onPickUserImage error", e?.message);
} finally { // } finally {
await setIsLoading(false); // await setIsLoading(false);
} // }
}, [projectId, setIsLoading, updateProjectData]); // }, [projectId, setIsLoading, updateProjectData]);
const handlePickUserImage = useCallback(() => { // const handlePickUserImage = useCallback(() => {
ensureArtistPreference(pickUserImage); // ensureArtistPreference(pickUserImage);
}, [ensureArtistPreference, pickUserImage]); // }, [ensureArtistPreference, pickUserImage]);
const handleGenerateCover = useCallback(() => { const handleGenerateCover = useCallback(() => {
ensureArtistPreference(() => navigate(Routes.PouchReady)); ensureArtistPreference(() => navigate(Routes.PouchReady));
@@ -184,8 +178,7 @@ export const ChooseCoverType = () => {
if (!realName) { if (!realName) {
setTooltip({ setTooltip({
type: "error", type: "error",
text: text: "Renseigne ton prénom et nom dans ton profil ou crée un nom d'artiste.",
"Renseigne ton prénom et nom dans ton profil ou crée un nom d'artiste.",
}); });
setChoiceVisible(false); setChoiceVisible(false);
setPseudoVisible(true); setPseudoVisible(true);
@@ -298,10 +291,10 @@ export const ChooseCoverType = () => {
gap: 12, gap: 12,
}} }}
> >
<BorderGradientButton {/* <BorderGradientButton
title="Choisir une image" title="Choisir une image"
onPress={handlePickUserImage} onPress={handlePickUserImage}
/> /> */}
<GradientButton <GradientButton
title="Générer une pochette" title="Générer une pochette"
onPress={handleGenerateCover} onPress={handleGenerateCover}
@@ -333,9 +326,7 @@ export const ChooseCoverType = () => {
</Text> </Text>
<View style={styles.modalButtons}> <View style={styles.modalButtons}>
<BorderGradientButton <BorderGradientButton
title={ title={savingChoice ? "Chargement..." : "Utiliser prénom/nom"}
savingChoice ? "Chargement..." : "Utiliser prénom/nom"
}
onPress={handleUseRealName} onPress={handleUseRealName}
disabled={savingChoice} disabled={savingChoice}
/> />
+48 -83
View File
@@ -1,15 +1,11 @@
import { Image as ExpoImage } from "expo-image"; import { Image as ExpoImage } from "expo-image";
import * as ImagePicker from "expo-image-picker";
import React from "react"; import React from "react";
import { ActivityIndicator, Text, View } from "react-native"; import { ActivityIndicator, Text, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit";
import { background } from "../../assets"; import { background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton"; import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import firebase, { tasksRef } from "../../config/firebase";
import loaderMessages from "../../config/loaderMessages"; import loaderMessages from "../../config/loaderMessages";
import { uploadFileToFirebase } from "../../helpers/uploadToFirebase";
import { isWeb } from "../../hooks/useLayoutType"; import { isWeb } from "../../hooks/useLayoutType";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
@@ -19,69 +15,30 @@ import { gutters, Palette, Style } from "../../styles";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
const PhotoCover = () => { const PhotoCover = () => {
const { selectedProject, selectedProjectId, updateProjectData } = const { selectedProject } = useUserData();
useUserData();
const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING"; const isGenerating = selectedProject?.coverStatus === "GENERATING";
const coverGenerationMessage = isWeb const coverGenerationMessage = isWeb
? loaderMessages.photoCoverGenerationWeb ? loaderMessages.photoCoverGenerationWeb
: ""; : "";
const onChooseForegroundImage = async () => { const coverUrl =
try { selectedProject?.cover?.result ||
await setIsLoading(true); selectedProject?.cover?.generatedBackground ||
const result = await ImagePicker.launchImageLibraryAsync({ null;
mediaTypes: ["images"],
allowsEditing: true,
aspect: [1, 1],
quality: 1,
});
const uri = result?.assets?.[0]?.uri || null;
if (!uri) return;
const projectId = selectedProject?.id || selectedProjectId;
const path = `musics/${projectId}/userForeground.png`;
const { resultURI } = await uploadFileToFirebase({
uri,
path,
shouldCompress: true,
fileType: "IMAGE",
});
if (!resultURI) {
throw new Error("Erreur lors de l'upload");
}
await updateProjectData({
cover: {
...selectedProject.cover,
userForeground: resultURI,
},
});
await tasksRef.add({
type: "combine",
projectId: selectedProjectId,
status: "PENDING",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
});
} catch (e) {
console.log("onPickUserImage error", e?.message);
} finally {
await setIsLoading(false);
}
};
return ( return (
<Page backgroundImg={background.studioBG2} headerType="NONE"> <Page backgroundImg={background.studioBG2} headerType="NONE">
<MusicLandHeader onPressBack={goBack} progress={81} /> <MusicLandHeader onPressBack={goBack} progress={81} />
<View style={{ flex: 1, marginTop: 16 }}> <View style={{ flex: 1, marginTop: 16 }}>
<CreateLyricsHeader title="Souhaites-tu rajouter une photo de toi sur la pochette?" /> <CreateLyricsHeader
title="Ta pochette MusicLand Production"
subTitle="Le logo est automatiquement ajouté en bas à droite."
/>
<View style={{ flex: 1, ...Style.containerCenter }}> <View style={{ flex: 1, ...Style.containerCenter }}>
<View style={{ width: "80%", position: "relative" }}> <View style={{ width: "80%", position: "relative" }}>
{selectedProject?.cover?.result && !isGenerating ? ( {coverUrl && !isGenerating ? (
<ExpoImage <ExpoImage
source={{ uri: selectedProject?.cover?.result }} source={{ uri: coverUrl }}
cachePolicy="memory-disk" cachePolicy="memory-disk"
priority="high" priority="high"
contentFit="cover" contentFit="cover"
@@ -103,11 +60,11 @@ const PhotoCover = () => {
...Style.containerCenter, ...Style.containerCenter,
}} }}
> >
{isGenerating && ( <View style={{ alignItems: "center", gap: 8 }}>
<View style={{ alignItems: "center", gap: 8 }}> {isGenerating ? (
<ActivityIndicator color={Palette.white} /> <>
{isWeb ? ( <ActivityIndicator color={Palette.white} />
coverGenerationMessage ? ( {isWeb && coverGenerationMessage ? (
<Text <Text
style={{ style={{
color: Palette.white, color: Palette.white,
@@ -118,21 +75,33 @@ const PhotoCover = () => {
> >
{coverGenerationMessage} {coverGenerationMessage}
</Text> </Text>
) : null ) : (
) : ( <Text
<Text style={{
style={{ color: Palette.white,
color: Palette.white, marginTop: 6,
marginTop: 6, textAlign: "center",
textAlign: "center", opacity: 0.9,
opacity: 0.9, }}
}} >
> Génération en cours.
Génération en cours. </Text>
</Text> )}
)} </>
</View> ) : (
)} <Text
style={{
color: Palette.white,
marginTop: 6,
textAlign: "center",
opacity: 0.9,
}}
>
La pochette sera disponible une fois la génération
terminée.
</Text>
)}
</View>
</View> </View>
)} )}
</View> </View>
@@ -147,18 +116,14 @@ const PhotoCover = () => {
}} }}
> >
<BorderGradientButton <BorderGradientButton
onPress={() => navigate(Routes.ValidateCover)} onPress={() => navigate(Routes.PouchReady)}
title={ title="Regénérer la pochette"
isGenerating ? "Génération en cours..." : "Valider la pochette"
}
disabled={isGenerating} disabled={isGenerating}
/> />
<GradientButton <GradientButton
title={ title="Valider la pochette"
isGenerating ? "Génération en cours..." : "Oui choisir une image" onPress={() => navigate(Routes.ValidateCover)}
} disabled={isGenerating || !coverUrl}
onPress={() => onChooseForegroundImage()}
disabled={isGenerating}
/> />
</View> </View>
</Page> </Page>
+12 -20
View File
@@ -19,7 +19,7 @@ import { gutters, Palette, Style } from "../../styles";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
const PouchReady = () => { const PouchReady = () => {
const { selectedProjectId, selectedProject, updateProjectData } = useUser(); const { selectedProjectId, selectedProject } = useUser();
const { setIsLoading } = useMinuit(); const { setIsLoading } = useMinuit();
const isGenerating = selectedProject?.coverStatus === "GENERATING"; const isGenerating = selectedProject?.coverStatus === "GENERATING";
const isFocused = useIsFocused(); const isFocused = useIsFocused();
@@ -44,22 +44,9 @@ const PouchReady = () => {
} }
}, [selectedProjectId, setIsLoading]); }, [selectedProjectId, setIsLoading]);
async function validateCover() { const validateCover = () => {
try { navigate(Routes.ValidateCover);
await setIsLoading(true); };
await updateProjectData({
cover: {
...selectedProject.cover,
result: selectedProject.cover.generatedBackground,
},
});
navigate(Routes.PhotoCover);
} catch (e) {
console.log(e);
} finally {
await setIsLoading(false);
}
}
useEffect(() => { useEffect(() => {
if ( if (
@@ -84,6 +71,11 @@ const PouchReady = () => {
} }
}, [generateCover, isFocused, selectedProject]); }, [generateCover, isFocused, selectedProject]);
const coverPreviewUrl =
selectedProject?.cover?.result ||
selectedProject?.cover?.generatedBackground ||
null;
return ( return (
<Page backgroundImg={background.studioBG2} headerType="NONE"> <Page backgroundImg={background.studioBG2} headerType="NONE">
<MusicLandHeader onPressBack={goBack} progress={72} /> <MusicLandHeader onPressBack={goBack} progress={72} />
@@ -98,9 +90,9 @@ const PouchReady = () => {
/> />
<View style={{ flex: 1, ...Style.containerCenter }}> <View style={{ flex: 1, ...Style.containerCenter }}>
<View style={{ width: "80%", position: "relative" }}> <View style={{ width: "80%", position: "relative" }}>
{selectedProject?.cover?.generatedBackground && !isGenerating ? ( {coverPreviewUrl && !isGenerating ? (
<ExpoImage <ExpoImage
source={{ uri: selectedProject?.cover?.generatedBackground }} source={{ uri: coverPreviewUrl }}
cachePolicy="memory-disk" cachePolicy="memory-disk"
priority="high" priority="high"
contentFit="cover" contentFit="cover"
@@ -215,7 +207,7 @@ const PouchReady = () => {
/> />
<GradientButton <GradientButton
title={isGenerating ? "Veuillez patienter..." : "Valider"} title={isGenerating ? "Veuillez patienter..." : "Valider"}
disabled={isGenerating} disabled={isGenerating || !coverPreviewUrl}
onPress={() => validateCover()} onPress={() => validateCover()}
/> />
</View> </View>
+1 -1
View File
@@ -205,7 +205,7 @@ export const getStageAction = (key, project) => {
} }
if (!hasFinalCover) { if (!hasFinalCover) {
return { route: Routes.PhotoCover }; return { route: Routes.ValidateCover };
} }
return { route: Routes.ChooseCoverType }; return { route: Routes.ChooseCoverType };