Communitygithub.com

beachyphotoandfilm-arch/video-skills

Render finished reels out of DaVinci Resolve, write captions in the creator's voice, attach a custom Instagram cover from reel-covers, and schedule them to Instagram, TikTok and YouTube Shorts through Metricool. Handles the Drive upload hop, collision checks against the existing calendar, and cleanup. Trigger on "schedule the reels", "post these", "put these on the calendar", or after reels are approved.

video-skills 是什么?

video-skills is a Claude Code agent skill that render finished reels out of DaVinci Resolve, write captions in the creator's voice, attach a custom Instagram cover from reel-covers, and schedule them to Instagram, TikTok and YouTube Shorts through Metricool. Handles the Drive upload hop, collision checks against the existing calendar, and cleanup. Trigger on "schedule the reels", "post these", "put these on the calendar", or after reels are approved.

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/beachyphotoandfilm-arch/video-skills/tree/HEAD/skills/schedule-social

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Schedule to social

Setup: fill these in

Replace every placeholder below before the first run.

placeholderwhat it is
<YOUR_METRICOOL_BLOG_ID>Your Metricool brand's blogId (from getBrandSettings)
<YOUR_TIMEZONE>IANA timezone for scheduling, e.g. America/Chicago
<YOUR_POST_TIME>Default daily posting time, e.g. 10:00
<YOUR_IG_HANDLE>, <YOUR_TIKTOK_HANDLE>, <YOUR_YOUTUBE_CHANNEL_ID>The accounts connected in Metricool
<YOUR_DRIVE_UPLOAD_FOLDER_ID>A Google Drive folder shared "anyone with the link" (see the media hop)
<YOUR_VIDEO_LIBRARY>Where finished files are kept, e.g. ~/Videos/Finished
<YOUR_STANDING_CTA>Optional closing line you put on every caption
<YOUR_VOICE_FILE>Optional: path to a voice/style guide for captions

Brand: blogId <YOUR_METRICOOL_BLOG_ID>, timezone <YOUR_TIMEZONE>. Connected: Instagram (<YOUR_IG_HANDLE>), TikTok (<YOUR_TIKTOK_HANDLE>), YouTube (<YOUR_YOUTUBE_CHANNEL_ID>).

The media hop (the part that isn't obvious)

Metricool's API takes public URLs only. It cannot accept a local file. The working chain:

  1. Upload the MP4 to a Drive folder such as Metricool Uploads (Claude) (<YOUR_DRIVE_UPLOAD_FOLDER_ID>). Share the folder "anyone with link" once; files inherit that, so no per-file sharing.
    cd <dir with the file> && gws drive files create \
      --params '{"uploadType":"multipart","supportsAllDrives":true}' \
      --json '{"name":"<name>.mp4","mimeType":"video/mp4","parents":["<YOUR_DRIVE_UPLOAD_FOLDER_ID>"]}' \
      --upload "<file>"
    
  2. Schedule with the direct-download URL: https://drive.google.com/uc?id=<fileId>&export=download. The /view link fails with "Failed to normalize media".
  3. Metricool copies the file to its own storage; the response's media flips to static.metricool.com/....
  4. Delete the Drive copy once you see that. Storage never accumulates.

Claude usually cannot make Drive files public (the sandbox blocks it) and cannot edit its own permissions. That is why the shared folder exists. If a file ends up outside the folder, ask the user to move it rather than trying to share it.

Captions

If you keep a voice guide, read <YOUR_VOICE_FILE> first and match it. Otherwise use these defaults.

When the reel has a comment-keyword CTA (a keyword that triggers an automated DM or email), line 1 is the CTA:

  • Line 1: Comment <KEYWORD> and I'll send you <resource name> free 👇, keyword in caps, exactly as spoken in the reel's CTA clip.
  • Then the hook line and the 2-4 teaching lines below.
  • Close with the standing CTA, if you use one.
  • The keyword must match the reel's spoken CTA (reel_cta in the video's state file). One keyword per video.

Format for reels without a keyword CTA:

  • Line 1 is the hook. It's the only line showing in feed. Usually the same line the reel opens on.
  • 2-4 short lines that teach one thing. Line breaks between them, not a paragraph.
  • No hashtag spam. Few or none.
  • Close with <YOUR_STANDING_CTA> verbatim if you have one.

Write from what's actually in the reel, using the creator's words from the transcript, not generic advice. Short but educational: the viewer should learn one usable thing from the caption alone.

Generic examples of the shape:

"Posting every day = BURNOUT / Posting 3 great videos a week = GROWTH"

"Quick tip if you want better audio: put the mic closer, not the gain higher..."

Rendering

Captions are burned in: video-reels puts them on V3 as an alpha overlay clip, with the text-hook card on V2. Nothing to import, no burn-in checkbox.

  1. The user trims the reel. Both overlay tracks are independent clips, so trimming V1 does not move them. If the timing is re-cut, re-render that reel's captions rather than sliding them.
  2. Claude renders them through the Resolve API. Four reels take about 90 seconds.
out = os.path.expanduser("~/Movies/<video>-deliverables/reels-final")
os.makedirs(out, exist_ok=True)
project.DeleteAllRenderJobs()
for i in range(project.GetTimelineCount()):
    t = project.GetTimelineByIndex(i+1)
    if not t.GetName().startswith("Reel"):
        continue
    project.SetCurrentTimeline(t)
    project.SetRenderSettings({"TargetDir": out, "CustomName": t.GetName().replace(" ", "_"),
                               "SelectAllFrames": True, "FormatWidth": 1080, "FormatHeight": 1920,
                               "ExportVideo": True, "ExportAudio": True})
    project.SetCurrentRenderFormatAndCodec("mp4", "H264")
    project.AddRenderJob()

Then start_rendering. Codec string is "H264", not "H.264", and the format is "mp4". Poll the output directory rather than the job status; the files appear as they finish. Expect ~40MB for 30 seconds. 3. Render is the point of no return for timing, so do any trimming first. 4. Spot-check the output with ffmpeg before uploading, one frame inside the hook window and one mid-reel:

ffmpeg -v error -ss 1.5 -i reel.mp4 -frames:v 1 hook.png -y
ffmpeg -v error -ss 8   -i reel.mp4 -frames:v 1 cap.png  -y

Stack them into one proof sheet and actually look at it. This is the last place a missing hook card or a blank caption track can be caught, and after scheduling there is no delete.

Where the finished files go

Everything kept lands in <YOUR_VIDEO_LIBRARY>/, one folder per video:

<YOUR_VIDEO_LIBRARY>/<Video> YYYY-MM-DD/
    Reels/              what posted. 1080x1920 H.264
    Reels/Masters/      same cut, 60 Mbps archive
    Timelines/          a .drt per timeline + the project .drp
    Covers/             the approved Instagram reel covers, 1080x1920 JPG
    Thumbnails/         the full-size options
    YouTube/            the exported long-form, delivery + master
    Posting schedule.txt   what goes out, where, and when

The upload file: as high as Instagram's publishing API allows

Metricool posts through Meta's content-publishing API, which caps what it accepts. Upload the best file that fits inside those caps, made from the 60 Mbps master and not from Resolve's delivery render:

  • H.264 High, yuv420p, 1080x1920, closed GOP, moov at the front (+faststart)
  • video 24 Mbps max (the cap is 25), audio AAC 128 kbps at 48 kHz (Resolve writes 320 kbps, which is over)
  • video and audio streams only. Resolve adds a timecode data track; drop it with -dn
ffmpeg -i "Masters/<n>.mp4" -map 0:v:0 -map 0:a:0 -map_metadata -1 -dn \
  -c:v libx264 -preset slow -crf 11 -maxrate 24M -bufsize 48M -pix_fmt yuv420p -profile:v high \
  -g 48 -keyint_min 48 -sc_threshold 0 -flags +cgop \
  -c:a aac -b:a 128k -ar 48000 -movflags +faststart "Reels/<n>.mp4"

At CRF 16 talking-head footage only used ~8 Mbps, so CRF 11 spends more of the budget. Run it in the background, since several reels take minutes at -preset slow. Encode one file at a time, not in parallel: many parallel encodes can fill the disk mid-write and truncate every file. Instagram still re-compresses on its side; this makes sure what it starts from is as clean as it allows.

Two renders per reel, not one. Same codec, same resolution, different bitrate:

resolutionbitratefor
delivery1080x1920 H.264Resolve pass, then replaced by the upload file aboveuploading
master1080x1920 H.26460 Mbps via "VideoQuality": 60000archive

Same two passes for the long-form at 1920x1080: ~900MB delivery, ~4.5GB master for a 10-minute video.

1080x1920 is the right resolution: Instagram serves 1080 wide regardless, and the reel crop is already oversampled from 4K. Don't offer ProRes or a bigger frame unless asked.

VideoQuality is sticky. Set it on every single pass.

Render settings live on the project, not the job. Leave VideoQuality out of a SetRenderSettings call and the render silently reuses whatever the previous one used. A long-form delivery pass that inherits 60000 from the reel masters comes out ~5x bigger than intended.

project.SetRenderSettings({..., "VideoQuality": 12000})   # delivery, ~12 Mbps
project.SetRenderSettings({..., "VideoQuality": 60000})   # master,  ~60 Mbps

Never rely on the default for the delivery pass. And never trust the job listing: VideoQuality reads back as None from GetRenderJobList() even when it applied. Check the finished file:

ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of csv=p=0 out.mp4

Check free space before queueing a long-form pair. A 10-minute master at 60 Mbps is ~4.5GB.

Export the timelines into the folder too, so the edit is portable without the project database:

for i in range(project.GetTimelineCount()):
    t = project.GetTimelineByIndex(i+1)
    t.Export(os.path.join(d, t.GetName() + ".drt"), resolve.EXPORT_DRT)
resolve.GetProjectManager().ExportProject(project.GetName(), os.path.join(d, project.GetName() + ".drp"))
  • Render straight into it. Set the Resolve TargetDir to <that folder>/Reels, so nothing has to be moved later.
  • Rename off the Resolve timeline names. Reel_2_-_my_best_tip.mp4 becomes 2. my best tip.mp4: numbered so they sort, readable at a glance. Same names for the masters and the .drt files, so the three folders line up.
  • Move, don't copy. ~/Movies/<video>-* holds working files (whisper JSON, SRTs, overlay movs, caption movs). Those stay put for re-renders; the finished exports leave.
  • Write Posting schedule.txt after scheduling, listing each reel's date, time and networks, so the user can see what is already out the door without opening Metricool.

Mention disk space only if a render actually fails for space, or if a long-form pair clearly won't fit.

Covers (Instagram only)

Every reel gets a custom Instagram cover before it's scheduled. Run the reel-covers skill after the captions are approved and before scheduling. It hands back one approved JPG per reel in <Video>/Covers/. Show the covers at the same gate as the captions and slots, so everything is approved in one pass.

Upload each cover through the same Drive hop as the video (same folder <YOUR_DRIVE_UPLOAD_FOLDER_ID>, mimeType: image/jpeg), and pass it as videoThumbnailUrl using the uc?id=...&export=download form. Delete the Drive copy once Metricool has its own copy, the same as with the video.

Scheduling

One post per reel, all three networks, cover included. The cover only matters on Instagram. TikTok ignores it on a personal account, and Shorts only shows it on a verified channel. Don't split the post.

"providers":[{"network":"instagram"},{"network":"tiktok"},{"network":"youtube"}],
"media":["https://drive.google.com/uc?id=<video id>&export=download"],
"videoThumbnailUrl":"https://drive.google.com/uc?id=<cover id>&export=download",
"instagramData":{"type":"REEL","showReelOnFeed":true,"isAiGenerated":false},
"tiktokData":{"privacyOption":"PUBLIC_TO_EVERYONE","title":"<short title>"},
"youtubeData":{"type":"short","privacy":"public","madeForKids":false,"title":"<title, <100 chars>"}
  • Adding a cover to a post that's already scheduled: updateScheduledPost with the post's full original fields (text, providers, media, all network data, date) plus videoThumbnailUrl. It overwrites and has no undo, so copy the fields from getScheduledPosts verbatim. Never replace an existing videoThumbnailUrl or videoCoverMilliseconds: a post that has either already has a chosen cover.
  • If one update fails with "Failed to normalize media" while the others worked, the fetch from Drive failed that once. The post is left unchanged. Upload the file again (new id) and retry. Retrying the same URL won't help.
  • Then check with getScheduledPosts that every post's videoThumbnailUrl now starts with static.metricool.com, and delete the Drive copies.
  • YouTube requires a title and madeForKids. TikTok wants a short title.
  • autoPublish: true lets Metricool post it automatically.

When to schedule (default rule, adjust to taste)

Start the day after today. One post per day. If a day already has content, move to the next free day.

day = tomorrow
for each post in order:
    while day already has a scheduled post:  day += 1
    schedule at <YOUR_POST_TIME> local on day
    day += 1
  • <YOUR_POST_TIME> in <YOUR_TIMEZONE> is the default posting time.
  • Always call getScheduledPosts first, covering tomorrow through tomorrow + (number of posts + 14 days), so you can see the gaps. Skipping this check can land a post on an already-taken slot.
  • A day counts as taken if anything is scheduled that day, drafts included.
  • Don't bunch several posts on one day, and don't leave holes: the point is a steady daily run starting immediately.
  • Never put a test label in the caption text. No "TEST DRAFT", no "safe to delete". The caption is the finished copy, always, even on a test run; the user may want to keep it. Mark test posts with draft: true + autoPublish: false (drafts are visually distinct in the planner) and hand over the plannerUrl plus the post id so it can be deleted in one click.

Long-form YouTube (the main video, not a Short)

Recommended: don't send large long-form files through Metricool. Upload them in YouTube Studio. A ~1GB export sent through Metricool can be stored truncated mid-transfer. Resolve puts the MP4 index (moov) at the end of the file, so a truncated copy is unreadable, and YouTube reports "processing abandoned" after it has already "published". Metricool may not report an error. Hand the user the file, title, description and thumbnail to paste into Studio, rendered in 4K (Studio has no size problem). Everything below is kept for reference.

If Metricool is used for a big file, check the stored copy before trusting it: curl -sI <static.metricool.com url> content-length must match the local file, and ffprobe <url> must open it.

Metricool publishes full videos too, with the thumbnail and title from video-thumbnails.

"providers":[{"network":"youtube"}],
"youtubeData":{"type":"video","title":"<40-65 chars>",
               "privacy":"public","madeForKids":false,
               "category":"EDUCATION","tags":["<topic tag>","<topic tag>"]},
"videoThumbnailUrl":"https://drive.google.com/uc?id=<thumb file id>&export=download"
  • The post text becomes the video description. Write it properly: the hook line, a short what-you'll-learn, your main link or CTA, then timestamps if the video has clear sections (the paper-edit transcript gives you these for free).
  • videoThumbnailUrl must be jpg/png and goes through the same Drive hop as the video. 1920x1080 PNG is fine.
  • Custom thumbnails only display on a verified channel. If the channel isn't verified, YouTube silently uses an auto-frame. Check before promising it landed.
  • Sending a thumbnail where it doesn't apply rejects the whole request with VIDEO_THUMBNAIL_NOT_APPLICABLE, so don't attach one speculatively.
  • Large files (over ~100MB) need a different Drive link (and past ~600MB, see the warning above). Drive answers uc?id=...&export=download with a "can't scan for viruses" page instead of the file, and Metricool fails with "Failed to normalize media". Use https://drive.usercontent.google.com/download?id=<fileId>&export=download&confirm=t, which returns the file itself. Check it first with curl -sIL <url> | grep -i content-length.
  • Long-form and Shorts are separate posts. Don't put a 10-minute video in a post that also has type: "short".

After scheduling: log every reel (optional)

If you track reel performance, add one row per scheduled reel to a tracker file (date, batch, reel, format, hook style, length, CTA, punch-ins) and fill in stats 7 days later. This is how the edits improve from real numbers.

The gate

Captions ship clean. Write every caption as if it is going live, including on test runs.

Show every caption and every time before scheduling. Once autoPublish is true and the date passes, it posts by itself. There is no undo in the API.

After scheduling: confirm each post's media is static.metricool.com, delete the Drive copies, and hand over the planner links.

There is no delete in the API. getScheduledPosts, createScheduledPost and updateScheduledPost are all you get. A post Claude creates can only be removed by the user, in the planner. So:

  • Never create a post you expect to throw away. Test against a real caption on a real free slot, or not at all.
  • When asked to delete one, save its caption to a local drafts folder first, then give the plannerUrl and say it takes one click.
  • Check what the post actually contains before agreeing it is a duplicate. Two drafts that look like "old versions" may hold a different reel entirely.

Related

  • video-reels: makes the reels and the SRTs
  • reel-covers: makes the Instagram covers
  • youtube: the conductor; this is its last stage

Individual skills in this repo

This repo contains 7 individual skills — each has its own dedicated page.

beachyphotoandfilm-arch/video-skills

Sync camera footage to a separate lapel or field-recorder track (e.g. Tascam DR-10L and similar) and lay it up in DaVinci Resolve. Stitches the recorder's split files, finds each clip's exact place in the audio by matching words then waveforms, cuts one lapel slice per clip so every later skill works unchanged, checks for clipping, and builds a synced timeline in a new Resolve project. Trigger on "sync the lapel", "sync my audio", "line up the recorder", "I used a lav", or any shoot with a separate audio folder (live talks, events, workshops). Run it before paper-edit or video-reels.

beachyphotoandfilm-arch/video-skills

Turn raw talking-head footage into an assembled rough cut inside DaVinci Resolve. Pulls the clips off your footage drive, transcribes them locally, removes failed takes, restarts, asides and dead air, then creates a new Resolve project with two paper-edit timelines ready for your fine trim. Trigger on "paper edit", "cut my raw footage", "make a rough cut", "clean up this video", or when the user points at a folder of raw camera files for a YouTube video.

beachyphotoandfilm-arch/video-skills

Design a unique Instagram cover for each reel in your house style. Reads what the reel is about, writes a short hook headline, picks one of your own photos from a tagged photo library, lays it out like your existing covers, and proofs it in grid view at phone size. Renders locally, never with AI image generation. Can be called by a scheduling skill before reels are scheduled. Trigger on "make covers", "reel covers", "cover photo for this reel", "design the IG covers".

beachyphotoandfilm-arch/video-skills

Build animated brand overlays (motion graphics) for a talking-head video, timed to the speaker's exact words, plus a DaVinci Resolve import file. House style is in-scene type (text beside and behind the speaker's head) plus diagrams. Transcribes the rough cut locally, picks the moments, renders transparent .mov clips, and writes an FCPXML that drops every clip onto the timeline already in position. Trigger on "make animations for this video", "add overlays/motion graphics", "animate this", "b-roll graphics for my YouTube video", or when the user shares a rough cut and asks for graphics.

beachyphotoandfilm-arch/video-skills

Cut vertical Instagram reels out of a long-form talking-head video, hook first. Picks the strongest standalone moments from the transcript, opens each reel on its punchiest line, builds vertical timelines in DaVinci Resolve framed on the speaker's face with the LUT applied, and finishes them: a hook (a held text card, or a word-by-word "build" hook with punch-in) and burned-look captions on V3; this-or-that reels get product graphics and a comment-keyword CTA card. You trim and render, nothing to import. Trigger on "make reels", "clip this for Instagram", "cut some verticals", or after a YouTube video is cut.

beachyphotoandfilm-arch/video-skills

Design YouTube thumbnails and titles for a talking-head video. Pulls graded full-res frames out of the raw footage, renders brand-styled variants in two proven layouts, and proofs them at phone size where the click actually gets decided. Trigger on "make thumbnails", "thumbnail for this video", "title and thumbnail", or after a video is cut.

beachyphotoandfilm-arch/video-skills

Run the whole YouTube video pipeline end to end, one stage at a time, stopping for the creator's approval at every gate. Paper edit, punch-ins, reels, reel covers, scheduling the reels, overlays, thumbnail and title, then scheduling the long-form video, all through Metricool. Keeps state per video so you can stop anywhere and pick up later. Trigger on "/youtube", "let's do the whole video", "run the video pipeline", "continue the <name> video", or when handed raw footage for a YouTube video.

相关技能