mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
feat(YouTube Node): Switch upload operation over to streaming and resumable uploads api (#5320)
This commit is contained in:
parent
2b579871b2
commit
3bb1690086
|
@ -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') {
|
||||
|
|
Loading…
Reference in a new issue