feat(YouTube Node): Switch upload operation over to streaming and resumable uploads api (#5320)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-02-01 14:49:49 +01:00 committed by GitHub
parent 2b579871b2
commit 3bb1690086
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,5 +1,5 @@
import type { IExecuteFunctions } from 'n8n-core';
import { BINARY_ENCODING } from 'n8n-core';
import type {
IDataObject,
ILoadOptionsFunctions,
@ -9,6 +9,7 @@ import type {
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import type { Readable } from 'stream';
import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
@ -24,6 +25,8 @@ import { videoCategoryFields, videoCategoryOperations } from './VideoCategoryDes
import { countriesCodes } from './CountryCodes';
const UPLOAD_CHUNK_SIZE = 1024 * 1024;
export class YouTube implements INodeType {
description: INodeTypeDescription = {
displayName: 'YouTube',
@ -851,8 +854,6 @@ export class YouTube implements INodeType {
const options = this.getNodeParameter('options', i);
const binaryProperty = this.getNodeParameter('binaryProperty', i);
let mimeType;
// Is binary file to upload
const item = items[i];
@ -862,7 +863,8 @@ export class YouTube implements INodeType {
});
}
if (item.binary[binaryProperty] === undefined) {
const binaryData = item.binary[binaryProperty];
if (binaryData === undefined) {
throw new NodeOperationError(
this.getNode(),
`No binary data property "${binaryProperty}" does not exists on item!`,
@ -870,99 +872,92 @@ export class YouTube implements INodeType {
);
}
if (item.binary[binaryProperty].mimeType) {
mimeType = item.binary[binaryProperty].mimeType;
let mimeType: string;
let contentLength: number;
let fileContent: Buffer | Readable;
if (binaryData.mimeType) {
mimeType = binaryData.mimeType;
}
const body = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
if (binaryData.id) {
// Stream data in 256KB chunks, and upload the via the resumable upload api
fileContent = this.helpers.getBinaryStream(binaryData.id, UPLOAD_CHUNK_SIZE);
const metadata = await this.helpers.getBinaryMetadata(binaryData.id);
contentLength = metadata.fileSize;
mimeType = binaryData.mimeType;
} else {
fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
contentLength = fileContent.length;
mimeType = binaryData.mimeType;
}
const requestOptions = {
headers: {
'Content-Type': mimeType,
},
json: false,
};
const response = await googleApiRequest.call(
this,
'POST',
'/upload/youtube/v3/videos',
body,
qs,
undefined,
requestOptions,
);
const { id } = JSON.parse(response);
qs.part = 'snippet, status, recordingDetails';
const data = {
id,
const payload = {
snippet: {
title,
categoryId,
description: options.description,
tags: (options.tags as string)?.split(','),
defaultLanguage: options.defaultLanguage,
},
status: {
privacyStatus: options.privacyStatus,
embeddable: options.embeddable,
publicStatsViewable: options.publicStatsViewable,
publishAt: options.publishAt,
selfDeclaredMadeForKids: options.selfDeclaredMadeForKids,
license: options.license,
},
recordingDetails: {
recordingDate: options.recordingDate,
},
status: {},
recordingDetails: {},
};
if (options.description) {
//@ts-ignore
data.snippet.description = options.description as string;
const resumableUpload = await googleApiRequest.call(
this,
'POST',
'/upload/youtube/v3/videos',
payload,
{
uploadType: 'resumable',
part: 'snippet,status,recordingDetails',
notifySubscribers: options.notifySubscribers ?? false,
},
undefined,
{
headers: {
'X-Upload-Content-Length': contentLength,
'X-Upload-Content-Type': mimeType,
},
json: true,
resolveWithFullResponse: true,
},
);
const uploadUrl = resumableUpload.headers.location;
let uploadId;
let offset = 0;
for await (const chunk of fileContent) {
const nextOffset = offset + Number(chunk.length);
try {
const response = await this.helpers.httpRequest({
method: 'PUT',
url: uploadUrl,
headers: {
'Content-Length': chunk.length,
'Content-Range': `bytes ${offset}-${nextOffset - 1}/${contentLength}`,
},
body: chunk,
});
uploadId = response.id;
} catch (error) {
if (error.response?.status !== 308) throw error;
}
offset = nextOffset;
}
if (options.privacyStatus) {
//@ts-ignore
data.status.privacyStatus = options.privacyStatus as string;
}
if (options.tags) {
//@ts-ignore
data.snippet.tags = (options.tags as string).split(',');
}
if (options.embeddable) {
//@ts-ignore
data.status.embeddable = options.embeddable as boolean;
}
if (options.publicStatsViewable) {
//@ts-ignore
data.status.publicStatsViewable = options.publicStatsViewable as boolean;
}
if (options.publishAt) {
//@ts-ignore
data.status.publishAt = options.publishAt as string;
}
if (options.recordingDate) {
//@ts-ignore
data.recordingDetails.recordingDate = options.recordingDate as string;
}
if (options.selfDeclaredMadeForKids) {
//@ts-ignore
data.status.selfDeclaredMadeForKids = options.selfDeclaredMadeForKids as boolean;
}
if (options.license) {
//@ts-ignore
data.status.license = options.license as string;
}
if (options.defaultLanguage) {
//@ts-ignore
data.snippet.defaultLanguage = options.defaultLanguage as string;
}
if (options.notifySubscribers) {
qs.notifySubscribers = options.notifySubscribers;
delete options.notifySubscribers;
}
responseData = await googleApiRequest.call(this, 'PUT', '/youtube/v3/videos', data, qs);
responseData = { uploadId, ...resumableUpload.body };
}
//https://developers.google.com/youtube/v3/docs/playlists/update
if (operation === 'update') {