Carga automatizada de videos de YouTube con programación de intervalos de 12 horas en la zona horaria JST
Este es unSocial Mediaflujo de automatización del dominio deautomatización que contiene 14 nodos.Utiliza principalmente nodos como Code, YouTube, ManualTrigger, ReadWriteFile, ExecuteCommand. Subida automatizada de videos de YouTube, programada en intervalos de 12 horas en la zona horaria JST
- •No hay requisitos previos especiales, puede importar y usarlo directamente
Nodos utilizados (14)
Categoría
{
"meta": {
"instanceId": "4acf4aaf155ef1444d1a0e89138bdc36df3deeb328e5be768fe60b8b9b336e06",
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "8614b18c-d37b-44cf-859a-61963f81d495",
"name": "Disparador manual",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-272,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2a864e15-008d-460c-b002-c7e6369b393a",
"name": "List Video Files",
"type": "n8n-nodes-base.executeCommand",
"position": [
-64,
0
],
"parameters": {
"command": "find /opt/downloads/单词卡/A1-A2 -type f -name '*.mp4' -print0"
},
"typeVersion": 1
},
{
"id": "89a41e82-e8e5-41d1-9077-55441e0f44b9",
"name": "Ordenar and Generate Items",
"type": "n8n-nodes-base.code",
"position": [
144,
0
],
"parameters": {
"jsCode": "const raw = $input.first().json.stdout ?? '';\nconst paths = raw.split('\\u0000').filter(Boolean);\n\nfunction extractDayNum(p) {\n const name = p.split('/').pop();\n const m = name.match(/day[-_ ]*(\\d+)/i);\n return m ? parseInt(m[1], 10) : Number.POSITIVE_INFINITY;\n}\n\nconst sorted = paths\n .filter(p => p.toLowerCase().endsWith('.mp4'))\n .map(p => ({\n path: p,\n filename: p.split('/').pop(),\n day: extractDayNum(p),\n }))\n .sort((a, b) => (a.day - b.day) || a.filename.localeCompare(b.filename));\n\nreturn sorted.map((it, i) => ({\n json: {\n path: it.path,\n filename: it.filename,\n day: it.day,\n order: i\n }\n}));\n"
},
"typeVersion": 2
},
{
"id": "ab5c0131-dc14-4207-9256-b181343b0118",
"name": "Calculate Publish Schedule (+12h Interval)",
"type": "n8n-nodes-base.code",
"position": [
368,
0
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// === Parameters (can be changed) ===\nconst SPAN_HOURS = 12; // Each interval hour\nconst TZ_OFFSET_MS = 9 * 3600e3; // JST +09:00\nconst BUFFER_MIN = 30; // Buffer minutes for processing the platform\n\n// === Read and check the serial number ===\nconst rawOrder = $json.order ?? $json.index ?? 0;\nconst order = Number(rawOrder);\nif (!Number.isFinite(order) || order < 0) {\n throw new Error(`Invalid order/index: ${rawOrder}`);\n}\n\n// === Calculate \"the next whole point of JST + buffer\" in pure milliseconds ===\n// Move the current UTC time to the \"wall clock time\" of JST\nconst nowUtcMs = Date.now();\nconst nowJstMs = nowUtcMs + TZ_OFFSET_MS;\n\n// Align to the next whole point of JST (up to the next hour)\nconst HOUR_MS = 3600e3;\nconst MIN_MS = 60e3;\nconst nextHourJstMs = Math.ceil(nowJstMs / HOUR_MS) * HOUR_MS;\n\n// Add buffer minutes\nconst baseJstMs = nextHourJstMs + BUFFER_MIN * MIN_MS;\n\n// JST release time of the current entry (overlay interval by serial number)\nconst publishJstMs = baseJstMs + order * SPAN_HOURS * HOUR_MS;\n\n// Then move back to UTC and give YouTube's publishAt\nconst publishUtcMs = publishJstMs - TZ_OFFSET_MS;\nconst publishAtUtc = new Date(publishUtcMs).toISOString();\n\n// Log only (JST readable)\nconst publishAtLocal = new Date(publishJstMs).toISOString().slice(0,19) + '+09:00';\n\nreturn {\n publishAtUtc, // To the YouTube node\n publishAtLocal, // Log only\n filename: ($json.path ?? '').split('/').pop(),\n order\n};\n"
},
"typeVersion": 2
},
{
"id": "b4fafaac-c6aa-46a2-bba7-67d9552ca667",
"name": "Split in Batches (1 per video)",
"type": "n8n-nodes-base.splitInBatches",
"position": [
576,
0
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "b077ea9f-5e76-48da-a3d0-09cf548c0da4",
"name": "Read Video File",
"type": "n8n-nodes-base.readWriteFile",
"position": [
832,
16
],
"parameters": {
"options": {},
"fileSelector": "=/opt/downloads/单词卡/A1-A2/{{ $json.filename }}"
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "3e2cd625-012a-4451-aa2a-f3f049f82c0f",
"name": "Upload to YouTube (Scheduled)",
"type": "n8n-nodes-base.youTube",
"position": [
1072,
16
],
"parameters": {
"title": "A1–A2单词打卡计划 #轻松学英语 #英语学习 #英语单词 #英语打卡 #零基础英语",
"options": {
"tags": "轻松学英语,英语学习,英语单词,英语打卡,零基础英语",
"publishAt": "={{ $('Split in Batches (1 per video)').item.json.publishAtUtc }}",
"description": "=A1–A2 英语单词学习系列 | 零基础英语 | 每天10分钟掌握核心词汇 帮助初学者系统学习基础英语词汇,从A1到A2逐步提升。 轻松学英语、英语打卡、自学复习必备。 #英语学习 #英语单词 #零基础英语 #EnglishVocabulary #A1A2English",
"privacyStatus": "private"
},
"resource": "video",
"operation": "upload",
"categoryId": "27",
"regionCode": "CN"
},
"credentials": {
"youTubeOAuth2Api": {
"id": "VM0cz4mwPyGDwQHo",
"name": "YouTube account"
}
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 3000
},
{
"id": "ee967e9e-66b7-475c-b4c6-cb1f194a2304",
"name": "Add to Playlist",
"type": "n8n-nodes-base.youTube",
"position": [
1280,
16
],
"parameters": {
"options": {},
"videoId": "={{ $json.uploadId }}",
"resource": "playlistItem",
"playlistId": "PLJ3aD-smyb90ZmBJ9jcuW7AcnGeHF_jfF"
},
"credentials": {
"youTubeOAuth2Api": {
"id": "VM0cz4mwPyGDwQHo",
"name": "YouTube account"
}
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 3000
},
{
"id": "a52f2b0e-546a-4dd9-b929-a3ed35a28b31",
"name": "Nota adhesiva",
"type": "n8n-nodes-base.stickyNote",
"position": [
-672,
-464
],
"parameters": {
"color": 5,
"width": 480,
"height": 224,
"content": "# Overview\n\n\nThis workflow automates video publishing to YouTube.\nIt lists local video files, generates upload metadata, schedules each upload every 12 hours, then uploads and adds them to a YouTube playlist."
},
"typeVersion": 1
},
{
"id": "7bfa8a61-3eab-42eb-a3fe-52bf180cf7b0",
"name": "Nota adhesiva1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-176,
-208
],
"parameters": {
"color": 4,
"width": 320,
"height": 176,
"content": "## List & Prepare Files\n\nRetrieves all video files from the local folder and prepares them as workflow items.\nEach item includes the file path and basic information for later processing."
},
"typeVersion": 1
},
{
"id": "2ff489ca-c9ff-4f99-b0ce-849a05b21ccc",
"name": "Nota adhesiva2",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
176
],
"parameters": {
"color": 4,
"width": 368,
"height": 192,
"content": "## Sort & Schedule Uploads\n\nSorts videos in the correct order and calculates a publish schedule.\nEach video is assigned a publishAt time spaced 12 hours apart from the previous one."
},
"typeVersion": 1
},
{
"id": "9d4dec38-c545-4e79-8607-eaddf83ff697",
"name": "Nota adhesiva3",
"type": "n8n-nodes-base.stickyNote",
"position": [
448,
-208
],
"parameters": {
"color": 4,
"width": 368,
"height": 192,
"content": "## Process in Batches\n\nEnsures videos are handled one by one.\nPrevents large uploads from overloading memory and makes error recovery easier."
},
"typeVersion": 1
},
{
"id": "e1ea7552-4668-4d16-bd9c-d47464bdd3d9",
"name": "Nota adhesiva4",
"type": "n8n-nodes-base.stickyNote",
"position": [
944,
240
],
"parameters": {
"color": 4,
"width": 368,
"height": 192,
"content": "## Upload to YouTube\n\nReads each video from disk and uploads it to YouTube as a scheduled private upload.\nThe video becomes public automatically at its scheduled publishAt time."
},
"typeVersion": 1
},
{
"id": "f9380df2-f7e4-439c-a07c-cdf679ff01e7",
"name": "Nota adhesiva5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1168,
-208
],
"parameters": {
"color": 4,
"width": 368,
"height": 192,
"content": "## Add to Playlist\n\nAfter upload, each video is automatically added to the specified YouTube playlist to keep the channel content organized."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "2a864e15-008d-460c-b002-c7e6369b393a",
"type": "main",
"index": 0
}
]
]
},
"ee967e9e-66b7-475c-b4c6-cb1f194a2304": {
"main": [
[
{
"node": "b4fafaac-c6aa-46a2-bba7-67d9552ca667",
"type": "main",
"index": 0
}
]
]
},
"b077ea9f-5e76-48da-a3d0-09cf548c0da4": {
"main": [
[
{
"node": "3e2cd625-012a-4451-aa2a-f3f049f82c0f",
"type": "main",
"index": 0
}
]
]
},
"2a864e15-008d-460c-b002-c7e6369b393a": {
"main": [
[
{
"node": "Sort and Generate Items",
"type": "main",
"index": 0
}
]
]
},
"Sort and Generate Items": {
"main": [
[
{
"node": "ab5c0131-dc14-4207-9256-b181343b0118",
"type": "main",
"index": 0
}
]
]
},
"3e2cd625-012a-4451-aa2a-f3f049f82c0f": {
"main": [
[
{
"node": "ee967e9e-66b7-475c-b4c6-cb1f194a2304",
"type": "main",
"index": 0
}
]
]
},
"b4fafaac-c6aa-46a2-bba7-67d9552ca667": {
"main": [
[],
[
{
"node": "b077ea9f-5e76-48da-a3d0-09cf548c0da4",
"type": "main",
"index": 0
}
]
]
},
"ab5c0131-dc14-4207-9256-b181343b0118": {
"main": [
[
{
"node": "b4fafaac-c6aa-46a2-bba7-67d9552ca667",
"type": "main",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Intermedio - Redes sociales
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Zane
@zaneCompartir este flujo de trabajo