← Course Outline

Scheduling & Auto-Publishing

Content calendar with automated publishing across multiple platforms
Automated scheduling publishes your AI-generated videos at optimal times across all platforms

Why Automate Publishing?

Generating videos is only half the battle. If you produce 10 videos per day but upload them manually, the publishing step becomes the bottleneck. Auto-publishing closes the loop — your pipeline generates content and delivers it to audiences without human intervention.

Automated publishing ensures videos go live at the optimal time for each platform, with complete metadata (titles, descriptions, tags, thumbnails), correct privacy settings, and cross-platform distribution — all without you touching a single upload form.

Content Calendar Automation

A content calendar defines what gets published, when, and where. In an automated system, the calendar is a data source (Google Sheet, Notion database, or JSON file) that the pipeline reads to determine today's production queue.

Content Calendar as a Google Sheet Data Source
// Google Sheet columns:
// | Date       | Topic               | Platform  | Time  | Status    |
// | 2025-07-14 | AI Video Trends     | YouTube   | 14:00 | pending   |
// | 2025-07-14 | Quick AI Tips #42   | TikTok    | 09:00 | pending   |
// | 2025-07-14 | AI Video Trends     | Instagram | 18:00 | pending   |

const getCalendarEntries = async (date) => {
  const sheets = google.sheets({ version: 'v4', auth });
  const response = await sheets.spreadsheets.values.get({
    spreadsheetId: SHEET_ID,
    range: 'Calendar!A:E'
  });
  const rows = response.data.values;
  return rows
    .filter(row => row[0] === date && row[4] === 'pending')
    .map(row => ({
      date: row[0],
      topic: row[1],
      platform: row[2],
      time: row[3],
      status: row[4]
    }));
};
Automated content calendar workflow showing data flow from spreadsheet to pipeline to platforms
The calendar drives the pipeline: each row becomes a video production job

YouTube API Auto-Upload

YouTube's Data API v3 allows full programmatic control over video uploads, including setting titles, descriptions, tags, thumbnails, playlists, scheduling, and monetization settings.

📝 Note: YouTube API requires OAuth 2.0 authentication with a verified Google Cloud project. You need the youtube.upload scope. The API has a daily quota of 10,000 units — each video upload costs 1,600 units, allowing approximately 6 uploads per day with the default quota.
Complete YouTube Upload with Metadata
const uploadToYouTube = async (videoFile, thumbnailFile, metadata) => {
  const youtube = google.youtube({ version: 'v3', auth: oauthClient });

  // Step 1: Upload the video
  const videoResponse = await youtube.videos.insert({
    part: 'snippet,status,contentDetails',
    requestBody: {
      snippet: {
        title: metadata.title,           // Max 100 characters
        description: metadata.description, // Max 5000 characters
        tags: metadata.tags,              // Array of strings
        categoryId: '28',                 // Science & Technology
        defaultLanguage: 'en',
        defaultAudioLanguage: 'en'
      },
      status: {
        privacyStatus: 'private',         // Start private, schedule public
        publishAt: metadata.publishAt,     // ISO 8601: '2025-07-14T14:00:00Z'
        selfDeclaredMadeForKids: false,
        license: 'youtube',
        embeddable: true
      }
    },
    media: {
      body: fs.createReadStream(videoFile)
    }
  });

  const videoId = videoResponse.data.id;

  // Step 2: Set custom thumbnail
  await youtube.thumbnails.set({
    videoId: videoId,
    media: {
      body: fs.createReadStream(thumbnailFile)
    }
  });

  // Step 3: Add to playlist
  await youtube.playlistItems.insert({
    part: 'snippet',
    requestBody: {
      snippet: {
        playlistId: metadata.playlistId,
        resourceId: {
          kind: 'youtube#video',
          videoId: videoId
        }
      }
    }
  });

  return { videoId, url: `https://youtube.com/watch?v=${videoId}` };
};

Social Media Scheduling

Each social media platform has different API capabilities, posting requirements, and optimal formats. A multi-platform publishing system must adapt the same content for each destination.

PlatformAPI AvailableMax Video LengthOptimal DimensionsBest Post Time (General)
YouTubeYes (Data API v3)12 hours1920x1080 (16:9)2-4 PM weekdays
TikTokYes (Content Posting API)10 minutes1080x1920 (9:16)7-9 PM daily
Instagram ReelsYes (Graph API)90 seconds1080x1920 (9:16)11 AM - 1 PM, 7-9 PM
X (Twitter)Yes (v2 API)2 min 20 sec1920x1080 or 1080x19209 AM, 12 PM weekdays
LinkedInYes (Marketing API)10 minutes1920x1080 (16:9)8-10 AM Tue-Thu
FacebookYes (Graph API)240 minutes1920x1080 (16:9)1-4 PM weekdays

TikTok Auto-Publishing

TikTok's Content Posting API allows direct video uploads from your server. The process involves initializing an upload, sending the video file, and then publishing with metadata.

TikTok Content Posting API
// Step 1: Initialize the upload
const initResponse = await fetch('https://open.tiktokapis.com/v2/post/publish/video/init/', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${TIKTOK_ACCESS_TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    post_info: {
      title: 'AI Video Generation Tips #aitools #ai',
      privacy_level: 'PUBLIC_TO_EVERYONE',
      disable_duet: false,
      disable_comment: false,
      disable_stitch: false
    },
    source_info: {
      source: 'FILE_UPLOAD',
      video_size: fileSize,
      chunk_size: fileSize,
      total_chunk_count: 1
    }
  })
});

const { upload_url, publish_id } = await initResponse.json();

// Step 2: Upload the video file
await fetch(upload_url, {
  method: 'PUT',
  headers: {
    'Content-Range': `bytes 0-${fileSize - 1}/${fileSize}`,
    'Content-Type': 'video/mp4'
  },
  body: fs.createReadStream(videoPath)
});

// Step 3: Check publish status
const statusResponse = await fetch(
  `https://open.tiktokapis.com/v2/post/publish/status/fetch/`,
  {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${TIKTOK_ACCESS_TOKEN}` },
    body: JSON.stringify({ publish_id })
  }
);

Instagram Reels Publishing

Instagram requires videos to be hosted at a publicly accessible URL before publishing via the Graph API. The flow is: host the video, create a media container, then publish it.

Instagram Reels via Graph API
// Step 1: Create media container (video must be at a public URL)
const containerResponse = await fetch(
  `https://graph.facebook.com/v18.0/${IG_USER_ID}/media`,
  {
    method: 'POST',
    body: new URLSearchParams({
      media_type: 'REELS',
      video_url: publicVideoUrl,
      caption: 'How AI generates videos automatically! #aiautomation #aivideo',
      share_to_feed: 'true',
      access_token: IG_ACCESS_TOKEN
    })
  }
);
const { id: containerId } = await containerResponse.json();

// Step 2: Wait for processing (poll status)
let status = 'IN_PROGRESS';
while (status === 'IN_PROGRESS') {
  await sleep(10000);
  const check = await fetch(
    `https://graph.facebook.com/v18.0/${containerId}?fields=status_code&access_token=${IG_ACCESS_TOKEN}`
  );
  status = (await check.json()).status_code;
}

// Step 3: Publish
await fetch(
  `https://graph.facebook.com/v18.0/${IG_USER_ID}/media_publish`,
  {
    method: 'POST',
    body: new URLSearchParams({
      creation_id: containerId,
      access_token: IG_ACCESS_TOKEN
    })
  }
);

Optimal Posting Times by Platform

Posting at the right time can increase initial engagement by 30-50%. While optimal times vary by audience, research-backed general guidelines exist for each platform.

Heatmap showing optimal posting times for YouTube, TikTok, Instagram, and X across the week
Optimal posting times vary by platform — schedule accordingly for maximum initial engagement
PlatformBest DaysBest Times (EST)Worst TimesWhy
YouTubeThu-Sat2-4 PMMon morningViewers browse after work/school
TikTokTue-Thu7-9 PMEarly morningPeak scroll time in evenings
InstagramTue, Wed, Fri11 AM - 1 PMLate night (11PM-5AM)Lunch break browsing + evening wind-down
X (Twitter)Mon-Fri9 AM, 12 PMWeekendsWork-hours engagement pattern
LinkedInTue-Thu8-10 AMFri afternoon, weekendsProfessional morning routine
FacebookWed-Fri1-4 PMEarly morning, late nightAfternoon break browsing
📝 Note: These are general guidelines. Use your own analytics data (YouTube Studio, TikTok Analytics, Instagram Insights) to find the optimal posting times for YOUR specific audience. Automate the analysis by pulling analytics data via APIs.

Metadata Automation

Metadata — titles, descriptions, tags, and hashtags — is critical for discoverability. AI can generate optimized metadata automatically based on the video's script and target audience.

AI-Generated Metadata via GPT
const generateMetadata = async (script, platform) => {
  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      {
        role: 'system',
        content: `You are a ${platform} SEO expert. Generate optimized metadata for a video based on its script. Return JSON with: title (max 100 chars), description (max 5000 chars for YouTube, 2200 for Instagram), tags (array of 15-30 keywords), hashtags (array of 5-10 for social platforms).`
      },
      {
        role: 'user',
        content: `Platform: ${platform}\nScript:\n${script}`
      }
    ],
    response_format: { type: 'json_object' }
  });
  return JSON.parse(response.choices[0].message.content);
};

// Generate platform-specific metadata
const ytMeta = await generateMetadata(script, 'YouTube');
const tiktokMeta = await generateMetadata(script, 'TikTok');
const igMeta = await generateMetadata(script, 'Instagram Reels');

Thumbnail Generation Automation

Thumbnails are the single biggest factor in click-through rate. Automated thumbnail generation uses AI image generation combined with text overlay to produce eye-catching thumbnails at scale.

Automated Thumbnail Pipeline
const generateThumbnail = async (videoTitle, keyFrame) => {
  // Step 1: Generate background image with DALL-E
  const bgImage = await openai.images.generate({
    model: 'dall-e-3',
    prompt: `YouTube thumbnail background: ${videoTitle}. Bold, vibrant, high-contrast, eye-catching, no text. 16:9 aspect ratio.`,
    size: '1792x1024',
    quality: 'hd'
  });

  // Step 2: Generate click-worthy title text with GPT
  const titleText = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [{
      role: 'user',
      content: `Write a SHORT (3-5 words max) YouTube thumbnail text for: "${videoTitle}". Make it dramatic, curiosity-driven. Examples: "This Changes Everything", "AI Can Do WHAT?", "You Won't Believe This"`
    }]
  });

  // Step 3: Overlay text on image using Sharp + Canvas
  const thumbnail = await sharp(bgImageBuffer)
    .composite([
      { input: textOverlayBuffer, gravity: 'center' },
      { input: brandLogoBuffer, gravity: 'southeast' }
    ])
    .resize(1280, 720)   // YouTube thumbnail standard size
    .jpeg({ quality: 95 })
    .toBuffer();

  return thumbnail;
};
📝 Note: Always generate 3-5 thumbnail variants per video for A/B testing. YouTube allows you to change thumbnails after publishing, so you can test which performs best and swap accordingly.
Exercise:
How many YouTube API upload units does a single video upload consume from the daily quota?
Exercise:
Which platform requires videos to be hosted at a public URL before publishing via its API?
Exercise:
What is the most effective way to determine optimal posting times for YOUR audience?