AutomationFlowsSocial Media › Automated Youtube Video Uploads with 12h Interval Scheduling in Jst

Automated Youtube Video Uploads with 12h Interval Scheduling in Jst

ByZane @zane on n8n.io

This workflow automates a batch upload of multiple videos to YouTube, spacing each upload 12 hours apart in Japan Standard Time (UTC+9) and automatically adding them to a playlist. Manual Trigger — Starts the workflow manually. List Video Files — Uses a shell command to find all…

Event trigger★★★★☆ complexity14 nodesExecute CommandRead Write FileYouTube
Social Media Trigger: Event Nodes: 14 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #10098 — we link there as the canonical source.

This workflow follows the Executecommand → Readwritefile recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "8614b18c-d37b-44cf-859a-61963f81d495",
      "name": "Manual Trigger",
      "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/\u5355\u8bcd\u5361/A1-A2 -type f -name '*.mp4' -print0"
      },
      "typeVersion": 1
    },
    {
      "id": "89a41e82-e8e5-41d1-9077-55441e0f44b9",
      "name": "Sort 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/\u5355\u8bcd\u5361/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\u2013A2\u5355\u8bcd\u6253\u5361\u8ba1\u5212 #\u8f7b\u677e\u5b66\u82f1\u8bed #\u82f1\u8bed\u5b66\u4e60 #\u82f1\u8bed\u5355\u8bcd #\u82f1\u8bed\u6253\u5361 #\u96f6\u57fa\u7840\u82f1\u8bed",
        "options": {
          "tags": "\u8f7b\u677e\u5b66\u82f1\u8bed,\u82f1\u8bed\u5b66\u4e60,\u82f1\u8bed\u5355\u8bcd,\u82f1\u8bed\u6253\u5361,\u96f6\u57fa\u7840\u82f1\u8bed",
          "publishAt": "={{ $('Split in Batches (1 per video)').item.json.publishAtUtc }}",
          "description": "=A1\u2013A2 \u82f1\u8bed\u5355\u8bcd\u5b66\u4e60\u7cfb\u5217 | \u96f6\u57fa\u7840\u82f1\u8bed | \u6bcf\u592910\u5206\u949f\u638c\u63e1\u6838\u5fc3\u8bcd\u6c47 \u5e2e\u52a9\u521d\u5b66\u8005\u7cfb\u7edf\u5b66\u4e60\u57fa\u7840\u82f1\u8bed\u8bcd\u6c47\uff0c\u4eceA1\u5230A2\u9010\u6b65\u63d0\u5347\u3002 \u8f7b\u677e\u5b66\u82f1\u8bed\u3001\u82f1\u8bed\u6253\u5361\u3001\u81ea\u5b66\u590d\u4e60\u5fc5\u5907\u3002 #\u82f1\u8bed\u5b66\u4e60 #\u82f1\u8bed\u5355\u8bcd #\u96f6\u57fa\u7840\u82f1\u8bed #EnglishVocabulary #A1A2English",
          "privacyStatus": "private"
        },
        "resource": "video",
        "operation": "upload",
        "categoryId": "27",
        "regionCode": "CN"
      },
      "credentials": {
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "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": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 3000
    },
    {
      "id": "a52f2b0e-546a-4dd9-b929-a3ed35a28b31",
      "name": "Sticky Note",
      "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": "Sticky Note1",
      "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": "Sticky Note2",
      "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": "Sticky Note3",
      "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": "Sticky Note4",
      "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": "Sticky Note5",
      "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
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "List Video Files",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to Playlist": {
      "main": [
        [
          {
            "node": "Split in Batches (1 per video)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Video File": {
      "main": [
        [
          {
            "node": "Upload to YouTube (Scheduled)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Video Files": {
      "main": [
        [
          {
            "node": "Sort and Generate Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sort and Generate Items": {
      "main": [
        [
          {
            "node": "Calculate Publish Schedule (+12h Interval)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to YouTube (Scheduled)": {
      "main": [
        [
          {
            "node": "Add to Playlist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split in Batches (1 per video)": {
      "main": [
        [],
        [
          {
            "node": "Read Video File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Publish Schedule (+12h Interval)": {
      "main": [
        [
          {
            "node": "Split in Batches (1 per video)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow automates a batch upload of multiple videos to YouTube, spacing each upload 12 hours apart in Japan Standard Time (UTC+9) and automatically adding them to a playlist. Manual Trigger — Starts the workflow manually. List Video Files — Uses a shell command to find all…

Source: https://n8n.io/workflows/10098/ — original creator credit. Request a take-down →

More Social Media workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Social Media

• Downloads videos/music from YouTube using yt-dlp • Merges assets with dynamic text overlays • Automatically uploads to YouTube as Shorts (9:16 format) • Tracks everything in Google Sheets Install yt

Google Sheets, Execute Command, Read Write File +1
Social Media

This workflow is perfect for digital content creators, marketers, and social media managers who regularly create engaging short-form videos featuring inspirational or motivational quotes. While the wo

Google Sheets, Google Drive, Read Write File +2
Social Media

This n8n workflow template automates the entire process of publishing Instagram Reels from content stored in Google Sheets and Google Drive. It's designed for content creators, social media managers,

Agent, Airtable, Facebook Graph Api +8
Social Media

📘 Multi-Photo Facebook Post (Windows Directory) – How to Use ✅ Requirements To run this automation, make sure you have the following:

Read Write File, Execute Command, Facebook Graph Api +1
Social Media

This workflow automatically mirrors your YouTube to TikTok and Instagram, so you don’t have to manually download and re-upload your content across platforms.

@Blotato/N8N Nodes Blotato, Execute Command, HTTP Request +2