我正在尝试创建一个Firebase函数,该函数允许我传入一组图像URL来创建一个蒙太奇,将文件上传到Firebase Storage,然后返回生成的下载URL。这将从我的应用程序调用,所以我使用functions.https.onCall
。
const functions = require("firebase-functions");
const admin = require('firebase-admin');
var gm = require('gm').subClass({imageMagick: true});
admin.initializeApp();
exports.createMontage = functions.https.onCall((data, context) => {
var storageRef = admin.storage().bucket( 'gs://xyz-zyx.appspot.com' );
var createdMontage = storageRef.file('createdMontage.jpg');
function generateMontage(list){
let g = gm()
list.forEach(function(p){
g.montage(p);
})
g.geometry('+81+81')
g.density(5000,5000)
.write(createdMontage, function(err) {
if(!err) console.log("Written montage image.");
});
return true
}
generateMontage(data)
return createdMontage.getDownloadURL();
});
函数generateMontage()
在NodeJ上本地工作(具有本地写入目的地(。
谢谢。
看看文档中的这个例子:
https://cloud.google.com/storage/docs/uploading-objects#storage-上传目标代码样本
2021-01-11更新
下面是一个工作示例。我使用的是常规的Cloud函数,它的局限性在于srcObject
、dstObject
和bucketName
是常量,但它确实创建了蒙太奇,这是你的目标。
PROJECT=[[YOUR-PROJECT]]
BILLING=[[YOUR-BILLING]]
REGION=[[YOUR-REGION]]
FUNCTION=[[YOUR-FUNCTION]]
BUCKET=[[YOUR-BUCKET]]
OBJECT=[[YOUR-OBJECT]] # Path from ${BUCKET} root
gcloud projects create ${PROJECT}
gcloud beta billing projects link ${PROJECT}
--billing-account=${BILLING}
gcloud services enable cloudfunctions.googleapis.com
--project=${PROJECT}
gcloud services enable cloudbuild.googleapis.com
--project=${PROJECT}
gcloud functions deploy ${FUNCTION}
--memory=4gib
--max-instances=1
--allow-unauthenticated
--entry-point=montager
--set-env-vars=BUCKET=${BUCKET},OBJECT=${OBJECT}
--runtime=nodejs12
--trigger-http
--project=${PROJECT}
--region=${REGION}
ENDPOINT=$(
gcloud functions describe ${FUNCTION}
--project=${PROJECT}
--region=${REGION}
--format="value(httpsTrigger.url)")
curl
--request GET
${ENDPOINT}
`package.json`:
```JSON
{
"name": "montage",
"version": "0.0.1",
"dependencies": {
"@google-cloud/storage": "5.7.1",
"gm": "^1.23.1"
}
}
和index.js
:
const { Storage } = require('@google-cloud/storage');
const storage = new Storage();
const gm = require('gm').subClass({ imageMagick: true });
const bucketName = process.env["BUCKET"];
const srcObject = process.env["OBJECT"];
const dstObject = "montage.png";
// Creates 2x2 montage
const list = [
`/tmp/${srcObject}`,
`/tmp/${srcObject}`,
`/tmp/${srcObject}`,
`/tmp/${srcObject}`
];
const montager = async (req, res) => {
// Download GCS `srcObject` to `/tmp`
const f = await storage
.bucket(bucketName)
.file(srcObject)
.download({
destination: `/tmp/${srcObject}`
});
// Creating GCS write stream for montage
const obj = await storage
.bucket(bucketName)
.file(dstObject)
.createWriteStream();
let g = gm();
list.forEach(f => {
g.montage(f);
});
console.log(`Returning`);
g
.geometry('+81+81')
.density(5000, 5000)
.stream()
.pipe(obj)
.on(`finish`, () => {
console.log(`finish`);
res.status(200).send(`ok`);
})
.on(`error`, (err) => {
console.log(`error: ${err}`);
res.status(500).send(`uhoh!`);
});
}
exports.montager = montager;
我从未使用过'gm',但根据其npm页面,它有一个toBuffer
函数。
所以也许这样的东西可以起作用:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const gm = require('gm').subClass({ imageMagick: true });
admin.initializeApp();
exports.createMontage = functions.https.onCall((data, _context) => {
const bucketName = 'xyz-zyx'; // not sure, I've always used the default bucket
const bucket = admin.storage().bucket(bucketName);
const storagePath = 'createdMontage.jpg';
const fileRef = bucket.file(storagePath);
const generateMontage = async (list) => {
const g = gm();
list.forEach(function (p) {
g.montage(p);
});
g.geometry('+81+81');
g.density(5000, 5000);
return new Promise(resolve => {
g.toBuffer('JPG', (_err, buffer) => {
const saveTask = fileRef.save(buffer, { contentType: 'image/jpeg' });
const baseStorageUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/`;
const encodedPath = encodeURIComponent(storagePath);
const postfix = '?alt=media'; // see stackoverflow.com/a/58443247/6002078
const publicUrl = baseStorageUrl + encodedPath + postfix;
saveTask.then(() => resolve(publicUrl));
});
});
};
return generateMontage(data);
});
但这似乎可以更容易地完成。正如Methkal Khalawi评论的那样:
这里有一个完整的示例,介绍如何使用ImageMagic和函数。尽管他们用它来模糊图像,但想法是一样的。这是文档中的一个教程。
我认为您可以通过管道将输出流从gm
模块传输到firebasestorage
对象写入流。
const functions = require("firebase-functions");
const admin = require('firebase-admin');
var gm = require('gm').subClass({imageMagick: true});
admin.initializeApp();
exports.createMontage = functions.https.onCall(async (data, context) => {
var storage = admin.storage().bucket( 'gs://xyz-zyx.appspot.com' );
var downloadURL = await new Promise((resolve, reject) => {
let g = gm()
list.forEach(function(p){
g.montage(p);
})
g.geometry('+81+81')
g.density(5000,5000)
.stream((err, stdout, stderr) => {
if (err) {
reject();
}
stdout.pipe(
storage.file('generatedMotent.png).createWriteStream({
metadata: {
contentType: 'image/png',
},
})
).on('finish', () => {
storage
.file('generatedMotent')
.getSignedUrl({
action: 'read',
expires: '03-09-2491', // Non expring public url
})
.then((url) => {
resolve(url);
});
});
})
});
return downloadURL;
});
仅供参考,Firebase Admin SDK存储对象没有getDownloadURL()
功能。您应该从存储对象生成不过期的公共签名URL。
此外,根据这个问题,它应该会在一段时间后引发另一个问题。为了解决这个问题,你应该用永久服务帐户初始化firebase应用程序。
const admin = require('firebase-admin');
const serviceAccount = require('../your-service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
projectId: JSON.parse(process.env.FIREBASE_CONFIG).projectId,
databaseURL: JSON.parse(process.env.FIREBASE_CONFIG).databaseURL,
storageBucket: JSON.parse(process.env.FIREBASE_CONFIG).storageBucket,
});