mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-12 15:44:06 -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 type { IExecuteFunctions } from 'n8n-core';
|
||||||
|
import { BINARY_ENCODING } from 'n8n-core';
|
||||||
import type {
|
import type {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
|
@ -9,6 +9,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import type { Readable } from 'stream';
|
||||||
|
|
||||||
import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
|
import { googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
|
||||||
|
|
||||||
|
@ -24,6 +25,8 @@ import { videoCategoryFields, videoCategoryOperations } from './VideoCategoryDes
|
||||||
|
|
||||||
import { countriesCodes } from './CountryCodes';
|
import { countriesCodes } from './CountryCodes';
|
||||||
|
|
||||||
|
const UPLOAD_CHUNK_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
export class YouTube implements INodeType {
|
export class YouTube implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'YouTube',
|
displayName: 'YouTube',
|
||||||
|
@ -851,8 +854,6 @@ export class YouTube implements INodeType {
|
||||||
const options = this.getNodeParameter('options', i);
|
const options = this.getNodeParameter('options', i);
|
||||||
const binaryProperty = this.getNodeParameter('binaryProperty', i);
|
const binaryProperty = this.getNodeParameter('binaryProperty', i);
|
||||||
|
|
||||||
let mimeType;
|
|
||||||
|
|
||||||
// Is binary file to upload
|
// Is binary file to upload
|
||||||
const item = items[i];
|
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(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
`No binary data property "${binaryProperty}" does not exists on item!`,
|
`No binary data property "${binaryProperty}" does not exists on item!`,
|
||||||
|
@ -870,99 +872,92 @@ export class YouTube implements INodeType {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.binary[binaryProperty].mimeType) {
|
let mimeType: string;
|
||||||
mimeType = item.binary[binaryProperty].mimeType;
|
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 = {
|
const payload = {
|
||||||
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,
|
|
||||||
snippet: {
|
snippet: {
|
||||||
title,
|
title,
|
||||||
categoryId,
|
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) {
|
const resumableUpload = await googleApiRequest.call(
|
||||||
//@ts-ignore
|
this,
|
||||||
data.snippet.description = options.description as string;
|
'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) {
|
responseData = { uploadId, ...resumableUpload.body };
|
||||||
//@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);
|
|
||||||
}
|
}
|
||||||
//https://developers.google.com/youtube/v3/docs/playlists/update
|
//https://developers.google.com/youtube/v3/docs/playlists/update
|
||||||
if (operation === 'update') {
|
if (operation === 'update') {
|
||||||
|
|
Loading…
Reference in a new issue