@jackdbd/eleventy-plugin-text-to-speech
Version:
Eleventy plugin that uses text-to-speech to generate audio assets for your website, then injects audio players in your HTML.
100 lines • 3.94 kB
JavaScript
import { Readable } from 'node:stream';
import defDebug from 'debug';
import { z } from 'zod';
import { Storage } from '@google-cloud/storage';
import { cloud_storage, iam } from '@jackdbd/zod-schemas/gcp';
import { asset_name } from '../schemas/common.js';
import { DEBUG_PREFIX } from '../constants.js';
import { validatedDataOrThrow, validatedResult } from '../validation.js';
const debug = defDebug(`${DEBUG_PREFIX}:cloud-storage`);
export const bucket_config = z.object({
/**
* Name of the Google Cloud Storage bucket to upload the audio file to.
*/
bucketName: cloud_storage.bucket_name
});
export const write_config = z
.object({
/**
* Name of the audio asset to be uploaded to Cloud Storage.
*/
assetName: asset_name,
/**
* Readable stream containing the audio data to be uploaded to Cloud Storage.
*/
readable: z.instanceof(Readable)
})
.describe('Cloud Storage write config');
export const write = (storage, cfg, config) => {
const { bucketName } = cfg;
const result = validatedResult(config, write_config);
if (result.error) {
return { error: result.error };
}
const { assetName, readable } = result.value;
debug(`upload ${assetName} to Cloud Storage bucket ${bucketName}`);
const bucket = storage.bucket(bucketName);
const file = bucket.file(assetName);
const href = file.publicUrl();
const uri = file.cloudStorageURI;
// https://googleapis.dev/nodejs/storage/latest/global.html#UploadOptions
// https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON
// const contentType = 'audio/mpeg'
// TODO: accept more options to customize metadata, timeout, etc
const writable = file.createWriteStream({
// contentType,
// gzip: true,
timeout: 10000, // in ms
// https://cloud.google.com/storage/docs/hashes-etags#validation
// validation: true,
// Some metadata fields are editable, some others are not.
// https://cloud.google.com/storage/docs/metadata#editable
metadata: {
// TODO: double check that setting cache-control takes precedence over the
// caching policy enforced by the bucket storage class.
// 31536000 seconds = 1 year
cacheControl: 'public, max-age=31536000'
// contentType
// contentLanguage: 'en-US'
}
});
readable.pipe(writable);
return new Promise((resolve) => {
writable.on('error', (error) => {
debug(`writable stream errored`);
// should I destroy the readable stream manually?
// readable.destroy()
resolve({ error });
});
writable.on('finish', () => {
debug(`writable stream finished, no more data to flush to ${uri}`);
const message = `asset uploaded to ${uri} and publicly accessible at ${href}`;
resolve({ value: { message, href } });
});
writable.on('close', () => {
debug(`writable stream closed, all underlying resources freed`);
});
});
};
export const auth_options = z.object({
credentials: iam.client_credentials.optional(),
keyFilename: iam.service_account_json_key_filepath.optional()
});
export const client_config = auth_options.merge(bucket_config);
/**
* Client for Google Cloud Storage.
*/
export const defClient = (config) => {
const data = validatedDataOrThrow(config, client_config);
const { credentials, keyFilename, bucketName } = data;
debug(`audio assets will be written to ${bucketName}`);
const storage = new Storage({ credentials, keyFilename });
const writeWithStorageAndBucketConfig = write.bind(null, storage, {
bucketName
});
return {
config: { bucketName },
write: writeWithStorageAndBucketConfig
};
};
//# sourceMappingURL=cloud-storage.js.map