[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80119":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":9,"pushedAt":9,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":15,"starSnapshotCount":15,"syncStatus":28,"lastSyncTime":29,"discoverSource":30},80119,"skill-autoshorts","mutonby\u002Fskill-autoshorts","mutonby","Daily viral-clip pipeline: Whisper + Gemini 3 Flash + Upload-Post. Picks one long video per day, cuts the best moments, asks you on your phone which to publish, and learns from engagement data over time.",null,"Python",67,13,55,1,0,4,7,9,12,52.84,false,"main",true,[],"2026-06-12 04:01:26","# autoshorts\n\n> Turn long videos into a daily stream of viral short-form clips for TikTok, Instagram Reels and YouTube Shorts — with a human-in-the-loop approval step you can answer from your phone.\n\nThe idea is to keep a small backlog of long videos and forward a new one to your agent every time you record. The agent (Hermes \u002F Openclaw \u002F whatever you're running) saves it into the input folder for you — you don't manage paths yourself, you just send the video in chat. Once a day the skill picks one video from that folder (newest unprocessed first, so the one you just sent jumps the queue), decides which moments are worth clipping, cuts them, overlays a punchy hook on top, and asks you (in the same chat channel) which ones you want to publish. You reply with the clip IDs. It schedules them on TikTok \u002F Instagram \u002F YouTube via [Upload-Post](https:\u002F\u002Fupload-post.com).\n\n**Input videos should already be 9:16 vertical** (1080×1920 ideally) and ready to post on social — with your subtitles already burned in if you want subs. The skill does NOT reformat, crop, or burn subtitles. It only cuts and adds a hook text overlay on top. Treat the input folder as \"near-final long-form footage\", not raw camera dumps.\n\nIt's the same workflow Opus Clip \u002F Vizard sell, but you own every piece of it and you stay in the loop on what actually ships.\n\n---\n\n## Setup prompt\n\nPaste into Claude Code, Codex, Hermes, Openclaw, or any agent with shell access:\n\n> Set up https:\u002F\u002Fgithub.com\u002Fmutonby\u002Fskill-autoshorts for me. Read README.md.\n\n---\n\n## Install\n\nWhen an agent reads this section, here's the full install protocol it should follow:\n\n1. Clone or `git pull` this repo into `~\u002FDocuments\u002Fskill-autoshorts\u002F` (or whatever path the user prefers).\n2. Make sure `ffmpeg` is installed (`brew install ffmpeg` on macOS, `apt install ffmpeg` on Linux). Verify with `ffmpeg -version`.\n3. Create the venv and install Python deps: `python3 -m venv venv && .\u002Fvenv\u002Fbin\u002Fpip install -r requirements.txt`.\n4. Register `SKILL.md` with whichever agent harness is running. For Claude Code, copy or symlink it into `~\u002F.claude\u002Fskills\u002Fautoshorts\u002FSKILL.md`. For Hermes \u002F Openclaw follow their skill registration docs.\n5. Create `.env` from `.env.example` and ask the user to paste the values you need:\n   - `GEMINI_API_KEY` — https:\u002F\u002Faistudio.google.com\u002Fapikey (free tier is enough).\n   - `UPLOAD_POST_API_KEY` and `UPLOAD_POST_PROFILE` — https:\u002F\u002Fapp.upload-post.com → Settings → API Keys + Manage Users (free tier available, no credit card required).\n   - Default `INPUT_FOLDER` and `OUTPUT_FOLDER` to `~\u002FDocuments\u002Fskill-autoshorts\u002Finput` and `...\u002Foutput` unless the user says otherwise. Default `WHISPER_MODEL=medium` and `TIMEZONE=Europe\u002FMadrid` (override if the user is in another timezone).\n6. Verify the Upload-Post key works: `curl -s -H \"Authorization: Apikey $UPLOAD_POST_API_KEY\" https:\u002F\u002Fapi.upload-post.com\u002Fapi\u002Fuploadposts\u002Fusers` should return the user's profile and connected platforms. Surface any platform with `reauth_required: true` so the user can fix it before the first publish.\n7. Read `SKILL.md` end-to-end. That's the canonical daily workflow — visual QA, weekly `learn`, all the operational rules.\n\n**After install, do nothing else.** Don't transcribe, don't call Gemini, don't publish. Tell the user everything is ready, summarize what's connected on Upload-Post, and wait. The user will forward videos to you in chat going forward — your job is to save each one into `INPUT_FOLDER` and invoke the skill (`\u002Fautoshorts` or equivalent) when they ask.\n\n### Notes for the user during install\n\n- The first transcribe call downloads Whisper `medium` (~1.5 GB). Subsequent runs are instant.\n- Upload-Post platform requirements: **TikTok** any account works · **Instagram** must be a Business or Creator account linked to a Facebook Page · **YouTube** any Google account · LinkedIn \u002F Reddit \u002F Threads \u002F X also supported.\n- The `UPLOAD_POST_PROFILE` value is the **profile name** you create in Upload-Post → Manage Users, NOT your social handle.\n\n---\n\n## The interesting part: how the clips are picked\n\nMost \"auto-clip\" tools either (a) work from text only and miss the visual cues that make a moment funny \u002F tense \u002F shareable, or (b) work from video only and produce ragged cuts that chop people mid-word. We sidestep both.\n\n```\n┌─────────────────┐\n│  long video.mp4 │\n└────────┬────────┘\n         │\n         ├──────────────► Whisper (medium, local)\n         │                ─ word-level timestamps\n         │                ─ language auto-detect\n         │                ─ outputs transcript.json\n         │\n         └──────────────► Gemini 3 Flash (multimodal, cloud)\n                          ─ receives the FULL VIDEO + the transcript\n                          ─ sees laughter, gestures, scene changes\n                          ─ MUST snap clip start\u002Fend to word boundaries from the transcript\n                          ─ outputs clips.json with {start, end, hook_text, reason, score}\n\n                                          │\n                                          ▼\n                          ffmpeg cut + Pillow hook overlay\n                                          │\n                                          ▼\n                                 Upload-Post API\n                                  ↳ TikTok (draft)\n                                  ↳ Instagram Reels\n                                  ↳ YouTube Shorts\n```\n\n**Why this combo works:**\n\n1. **Whisper is the clock.** Word-level timestamps mean every cut starts and ends on a clean word boundary. No mid-syllable chops, no half-breaths.\n2. **Gemini Flash is the editor.** It's multimodal — we send the actual video file via the Files API along with the transcript. It can see a punchline land, hear laughter, notice a scene change, react to a chart on screen. Crucially, the prompt forces it to use timestamps from the Whisper transcript, so it can't hallucinate \"20.5s\" and miss by a syllable.\n3. **The pipeline is human-gated.** The model proposes; you dispose. Every candidate is cut and rendered before you see it, so you review the actual final video, not a description. You answer from your phone.\n\n---\n\n## Usage\n\n### Inside an agent harness (recommended — Hermes \u002F Openclaw \u002F similar)\n\nThis is what the skill is built for. An agent harness on a VPS runs the daily cron, pings you on your messenger (\"here are today's candidates, which do I publish?\"), captures your reply, and closes the loop. You drop new videos in chat whenever you record; you get a daily push of clip candidates; you reply with IDs from your phone. Hands-off after install.\n\nThe skill itself doesn't talk to Telegram \u002F WhatsApp \u002F etc. — the harness does. The skill just runs the pipeline and surfaces a candidates table; the harness forwards it to your messenger and pipes your reply back.\n\nFor invocation, the harness either fires `\u002Fautoshorts` on its daily cron, or invokes the equivalent in its own skill system. SKILL.md is the contract.\n\n### As a standalone CLI\n\n```bash\nsource venv\u002Fbin\u002Factivate\n\n# 1. Pick the next unprocessed video\npython autoshorts.py pick\n\n# 2. Transcribe (writes output\u002F\u003Cslug>\u002Ftranscript.json)\npython autoshorts.py transcribe input\u002Fyour-video.mp4\n\n# 3. Analyze with Gemini (writes output\u002F\u003Cslug>\u002Fclips.json)\npython autoshorts.py analyze input\u002Fyour-video.mp4\n\n# 4. For each clip in clips.json:\npython autoshorts.py extract input\u002Fyour-video.mp4 \\\n    --start 12.34 --end 45.67 \\\n    --output output\u002Fyour-video\u002Fclip_1.mp4\n\npython autoshorts.py hook output\u002Fyour-video\u002Fclip_1.mp4 \\\n    --text \"Tu hook aquí\" --duration 3 \\\n    --output output\u002Fyour-video\u002Fclip_1_final.mp4\n\n# 5. Publish (TikTok defaults to draft \u002F MEDIA_UPLOAD)\npython autoshorts.py publish output\u002Fyour-video\u002Fclip_1_final.mp4 \\\n    --platforms tiktok,instagram,youtube \\\n    --title \"general title\" \\\n    --description \"general description\" \\\n    --tiktok-title \"TikTok-specific (max 90 chars, can have emojis + hashtags)\" \\\n    --instagram-title \"Instagram caption (long-form, 500-800 chars + 20-30 hashtags)\" \\\n    --youtube-title \"YouTube title (~40-60 chars, SEO-friendly)\" \\\n    --schedule \"2026-05-01T10:00:00\" \\\n    --timezone \"Europe\u002FMadrid\" \\\n    --tiktok-mode draft\n\n# 6. Mark the source video as consumed (so tomorrow's pick skips it)\npython autoshorts.py mark-processed input\u002Fyour-video.mp4 \\\n    --clips-generated 5 --clips-published 3\n```\n\n### As a daily loop\n\nThe whole thing is designed to run forever, one video per day:\n\n- You drop new long videos into `INPUT_FOLDER` whenever you have one.\n- A cron \u002F systemd \u002F openclaw schedule fires `\u002Fautoshorts` daily.\n- `pick` always chooses the **newest unprocessed** video. Fresh material jumps the queue. Old unprocessed videos still drain over time.\n- `state\u002Fprocessed.json` (sha256-keyed) is the only memory between runs — it's what prevents the same video being clipped twice.\n- If you reject ALL candidates (\"none\"), the video is still marked consumed. To retry, manually remove its entry from `state\u002Fprocessed.json`.\n\n---\n\n## How it learns\n\nThe pipeline gets smarter with every clip you publish. Engagement data flows back from Upload-Post analytics into the Gemini prompt that selects tomorrow's clips.\n\n```\n                          publish ─────► Upload-Post\n                            │                │\n                            ▼                ▼\n                  post-history.jsonl   real platform metrics\n                  (clip → request_id,   (views, likes, comments,\n                   hook, score, …)      shares, saves)\n                            │                │\n                            └───────┬────────┘\n                                    │\n                                    ▼ (weekly)\n                              learn subcommand\n                            ─ z-score per platform\n                            ─ composite = 0.6·views + 0.4·engagement_rate\n                            ─ top 20% = winners\n                            ─ bottom 20% = losers\n                                    │\n                                    ▼\n                              Gemini Flash\n                            \"Here are winners and losers,\n                             plus the current HOT.md.\n                             Output an updated HOT.md\n                             (≤80 lines of patterns).\"\n                                    │\n                                    ▼\n                            learnings\u002FHOT.md\n                                    │\n                                    ▼ (every analyze call, automatically)\n                            prepended to Gemini's analyze prompt\n                                    │\n                                    ▼\n                            tomorrow's clips reflect what worked\n```\n\n### Three CLI commands drive the loop\n\n| Command | Cadence | What it does |\n|---|---|---|\n| `publish` (with `--clip-id --hook-text --viral-score --reason --video-source`) | every approved clip | logs the clip's full context to `learnings\u002Fpost-history.jsonl` so we can correlate it with metrics later |\n| `learn` | weekly | pulls fresh analytics, finds winners\u002Flosers, asks Gemini to refresh `HOT.md` |\n| `reflect` (optional) | when you want | quick qualitative pass — compares which candidates you APPROVED vs REJECTED, no metrics needed |\n\n### Composite metric\n\n`learn` ranks clips by a weighted z-score:\n\n```\ncomposite = 0.6 × z(total_views) + 0.4 × z(engagement_rate)\n\nwhere engagement_rate = (likes + comments + shares + saves) \u002F total_views\n```\n\nBoth weights are flags (`--weight-views`, `--weight-engagement`) — bump engagement higher if you care more about quality than reach, lower if you're optimizing pure volume.\n\n### Soak window\n\n`learn --soak-days 7` (default): clips younger than 7 days are excluded — engagement metrics need time to mature, daily learning would chase noise. Older than 90 days = stale and ignored too.\n\nIf you have fewer than ~5 winners + 5 losers, `learn` skips the synthesis and writes a \"not enough data\" note to `learnings\u002Fruns\u002Flearn-YYYY-MM-DD.md`. Just keep publishing.\n\n### Auditability\n\nEvery `learn` run writes a full audit to `learnings\u002Fruns\u002Flearn-YYYY-MM-DD.md`: which clips were called winners, with their scores, the previous HOT.md, and the new HOT.md side-by-side. The previous HOT.md is also backed up as `HOT.YYYYMMDD-HHMMSS.md.bak`. You can roll back if Gemini synthesizes garbage.\n\n### Reflect (no-metrics qualitative pass)\n\n`reflect --window-days 30` is a faster pass that doesn't wait for engagement data. It compares the clips Gemini OFFERED against the ones you APPROVED and asks Gemini to extract qualitative patterns (\"approves hooks with concrete numbers, rejects question-form hooks\"). Output goes to `learnings\u002Fruns\u002Freflect-...md` and is **not** auto-promoted to HOT.md — it's notes for you to read and curate.\n\n### Why we don't auto-promote everything\n\n`learn` overwrites `HOT.md` based on metrics only — that's safe because the data is real. `reflect` is observational and could lock in your past biases (\"I always reject question hooks\") rather than what actually performs. So reflect output stays in `runs\u002F` for human review.\n\n---\n\n## File layout\n\n```\nskill-autoshorts\u002F\n├── README.md                  ← you are here\n├── autoshorts.py              ← CLI: pick \u002F transcribe \u002F analyze \u002F extract \u002F hook \u002F publish \u002F mark-processed\n├── requirements.txt\n├── .env                       ← secrets (gitignored)\n├── .env.example\n├── input\u002F                     ← drop long videos here\n├── output\u002F\n│   └── \u003Cvideo_slug>\u002F\n│       ├── transcript.json    ← Whisper output (segments + word timestamps)\n│       ├── clips.json         ← Gemini's clip selections\n│       ├── clip_1.mp4         ← raw cut\n│       ├── clip_1_final.mp4   ← cut + hook overlay\n│       └── …\n├── state\u002F\n│   └── processed.json         ← sha256s of videos already processed\n└── learnings\u002F\n    ├── HOT.md                 ← auto-managed by `learn`, prepended to every analyze prompt\n    ├── post-history.jsonl     ← every clip we published (request_id, hook, score, …)\n    ├── candidate-history.jsonl ← every candidate Gemini offered (so reflect can compare)\n    ├── metrics.jsonl          ← analytics snapshots from Upload-Post\n    ├── insights\u002F              ← MANUAL notes (not used by the pipeline)\n    └── runs\u002F\n        ├── learn-YYYY-MM-DD.md\n        └── reflect-YYYY-MM-DD-HHMM.md\n```\n\n---\n\n## Per-platform copy guidance\n\nThe publish helper takes one general `--title` \u002F `--description` plus per-platform overrides. Lengths are asymmetric:\n\n| Platform | Field | Practical sweet spot | Hard limit | Style |\n|---|---|---|---|---|\n| YouTube Shorts | `--youtube-title` | **40–60 chars** | 100 | Short, SEO-friendly with keywords. Truncates on mobile if longer. |\n| TikTok | `--tiktok-title` | 70–85 chars | 90 | Punchy, 1–2 emojis, hashtags at the end |\n| Instagram Reels | `--instagram-title` | 500–800 chars | 2200 | Long-form storytelling: hook line, 2–4 short paragraphs, CTA, then 20–30 hashtags |\n\nDon't reuse the same string across platforms. YouTube wants compression; Instagram wants depth.\n\n---\n\n## Why these tech choices\n\n- **Whisper `medium`** — the sweet spot for accuracy vs. speed on consumer hardware (CPU `int8`). `small` is twice as fast but loses on technical vocabulary; `large-v3` is markedly better but ~3× slower.\n- **Gemini 3 Flash Preview (multimodal)** — has a free tier, accepts video via the Files API, returns strict JSON via `response_mime_type=application\u002Fjson`, and is cheap enough to run daily. Crucially, it can *watch* the video, not just read its transcript.\n- **Pillow + ffmpeg overlay** for the hook — the alternative (ffmpeg's `drawtext` filter) requires `libfreetype` which is missing from many Homebrew ffmpeg builds. Rendering the hook to a transparent PNG with PIL and compositing with the always-available `overlay` filter is more portable and gives nicer text rendering (anti-aliasing, auto word-wrap, multi-line layout). The hook itself uses a TikTok-style **black pill behind each line of text** (78% opacity, rounded corners) so it stays legible regardless of what's underneath — pure white frames, pure black frames, or busy screenshares all work without per-frame analysis.\n- **Upload-Post** — one API for ~10 platforms, OAuth handled in their dashboard, supports scheduling and platform-specific titles. The free tier (10 uploads\u002Fmonth) is enough to validate the pipeline; paid plans for production volume.\n\n## Limitations \u002F things to know\n\n- **Quota**: Upload-Post free tier = 10 uploads\u002Fmonth, where one publish to 3 platforms counts as 3. Paid plans available.\n- **TikTok draft mode (default)**: with `--tiktok-mode draft`, clips land in your TikTok inbox (`post_mode=MEDIA_UPLOAD`) — you finish editing in the TikTok app before publishing. Use `--tiktok-mode direct` if you want immediate publication.\n- **Whisper first-run download**: ~1.5 GB on first transcribe; cached afterwards.\n- **Gemini Files API processing**: a 9-minute video takes ~30–60s of processing on Google's side before it's queryable. The script polls and waits.\n- **Rate-limiting**: the daily-loop design is partly to stay friendly with TikTok \u002F Instagram limits — bulk-publishing a backlog at once is more likely to be flagged than 1\u002Fday.\n- **Newest-first prioritization**: if you keep dropping new videos every day, older ones may sit in the queue indefinitely. That's intentional (fresh content > old backlog) but if you want strict FIFO, swap the sort in `cmd_pick`.\n","该项目是一个自动化生成每日热门短视频的流水线，通过Whisper和Gemini 3 Flash技术从长视频中剪辑精彩片段，并通过手机端确认发布内容。其核心功能包括自动选取并剪辑最佳时刻、添加吸引人的文本叠加效果以及根据用户反馈学习优化。特别适用于需要频繁更新社交平台如TikTok、Instagram Reels和YouTube Shorts内容的内容创作者。通过简单的设置流程，用户可以轻松地将几乎完成的垂直格式长视频转换为多个短小精悍的视频片段，提高工作效率的同时保持对最终发布内容的控制权。",2,"2026-06-11 03:59:19","CREATED_QUERY"]