Pipeline Overview
Pipeline Flow
1 · Topic Engine
→
2 · Script Workshop
→
3 · Voiceover Studio
→
4 · Visual Production
→
5 · CapCut Auto
→
6 · TikTok Publisher
→
7 · Feedback Loop
Today's Pipeline Stats
0
Videos in Queue
0
Scripts Ready
0
Drafts in CapCut
0
Posted Today
7-Day Content Calendar
Stage 1 — Topic Engine
Generate and manage TikTok video topics for data governance content.
Topic Categories
Data Quality
Metadata Management
Data Lineage
Data Catalog
Governance Frameworks
Data Privacy/GDPR
Master Data Management
Data Mesh
Data Contracts
Data Products
AI Topic Generator
Topic Backlog Queue
Stage 2 — Script Workshop
Craft TikTok scripts with proven hook formulas.
Hook Formula Library
🔥
Myth-Bust
Everyone says [X] about data governance — they're completely wrong
📊
Stat Hook
84% of data initiatives fail because of this one governance mistake
❓
Question Hook
What's the actual difference between a data owner and a data steward?
😱
Problem Hook
Your data is siloed and nobody knows who owns it. Here's how to fix it in 3 steps.
📖
Story Hook
I audited a Fortune 500 company's data. What I found was terrifying.
Script Generator
Generated Script
CTA Library
Follow for daily data governance tips
Drop your biggest data challenge in the comments 👇
Visit thedatagovernor.com for the full breakdown
Save this if you're building a governance program
Stage 3 — Voiceover Studio
Voice configuration, pacing, and subtitle generation.
ElevenLabsRecommended
Voice: Adam or Rachel
Stability: 0.75
Similarity Boost: 0.85
Style Exaggeration: 0.3
Speaker Boost: Yes
Output: MP3 44.1kHz 192kbps
Play.htAlternative
Voice: Ethan or Olivia
Speed: 0.95
Temperature: 0.8
Output: WAV 44.1kHz
ElevenLabs API Code
from elevenlabs import VoiceSettings, generate, save
def generate_voiceover(script_text: str, output_path: str = "voiceover.mp3"):
api_key = "YOUR_ELEVENLABS_API_KEY"
voice_id = "pNInz6obpgDQGcFmaJgB" # Adam voice
audio = generate(
api_key=api_key,
text=script_text,
voice=voice_id,
model="eleven_turbo_v2",
voice_settings=VoiceSettings(
stability=0.75,
similarity_boost=0.85,
style=0.3,
use_speaker_boost=True,
),
)
save(audio, output_path)
print(f"✅ Voiceover saved to {output_path}")
return output_pathPacing Calculator
SRT Subtitle Generator Code
import whisper
def generate_srt(audio_path: str, output_srt: str = "subtitles.srt"):
model = whisper.load_model("base")
result = model.transcribe(audio_path, task="transcribe")
with open(output_srt, "w") as f:
for i, segment in enumerate(result["segments"], 1):
start = format_time(segment["start"])
end = format_time(segment["end"])
f.write(f"{i}\n{start} --> {end}\n{segment['text'].strip()}\n\n")
print(f"✅ Subtitles saved to {output_srt}")
return output_srt
def format_time(seconds):
h = int(seconds // 3600)
m = int((seconds % 3600) // 60)
s = int(seconds % 60)
ms = int((seconds % 1) * 1000)
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"Stage 4 — Visual Production
B-roll sourcing, brand kit, and visual storyboarding.
B-Roll Source Library
| Source | Free/Paid | Best Search Terms | Notes |
|---|---|---|---|
| Pexels | Free | data, analytics, business, technology, charts, database | Best free option |
| Pixabay | Free | governance, compliance, data management, office | Good variety |
| Videvo | Free/Paid | corporate, fintech, data center, server room | High quality |
| Storyblocks | Paid | enterprise, C-suite, boardroom, risk management | Best for B2B feel |
| Envato Elements | Paid | data visualization, infographic, charts | Premium |
Brand Visual Kit
Color Palette
Font Stack
Poppins Bold (headlines) · Inter Regular (body) · Courier New (code)
Text Overlay Templates
Lower Third
Name + Title bar at bottom
Definition Box
Term + Definition card
Stat Callout
Big number + context
Visual Storyboard
Panel 1 · Hook Visual (0-3s)
Panel 2 · Problem Visual (3-10s)
Panel 3 · Point 1 (10-25s)
Panel 4 · Point 2 (25-45s)
Panel 5 · Point 3 (45-60s)
Panel 6 · CTA Visual (60-90s)
Stage 5 — CapCut Automation
Automated video assembly using CapCut API.
Setup Instructions
✅ Cloned at
C:\Users\clint\VectCutAPI (repo renamed CapCutAPI → VectCutAPI)✅ Dependencies installed in
venv-capcut · config.json created (port 9001, capcut_legacy profile)1. Start server:
cd C:\Users\clint\VectCutAPI && venv-capcut\Scripts\python.exe capcut_server.py (port 9001; 404 on / is normal)2. Ensure CapCut Desktop (v8.3) is running on this machine
📁 CapCut drafts folder:
C:\Users\clint\AppData\Local\CapCut\User Data\Projects\com.lveditor.draftMain Automation Script
import requests, os
from pathlib import Path
CAPCUT_API = "http://localhost:9001"
DRAFTS_DIR = r"C:\Users\clint\AppData\Local\CapCut\User Data\Projects\com.lveditor.draft"
def assemble_dg_video(topic_title, voiceover_path, broll_clips, srt_path, key_term=None, key_stat=None):
print(f"🎬 Creating draft: {topic_title}")
res = requests.post(f"{CAPCUT_API}/create_draft", json={"name": topic_title})
draft_id = res.json()["draft_id"]
timestamp = 0
for clip in broll_clips:
requests.post(f"{CAPCUT_API}/add_video", json={"draft_id": draft_id, "video_path": clip["path"], "start_time": timestamp, "duration": clip["duration"], "transition": "fade"})
timestamp += clip["duration"]
requests.post(f"{CAPCUT_API}/add_audio", json={"draft_id": draft_id, "audio_path": voiceover_path, "volume": 1.0, "track_index": 1})
bg = "./assets/bg_music_corporate.mp3"
if os.path.exists(bg):
requests.post(f"{CAPCUT_API}/add_audio", json={"draft_id": draft_id, "audio_path": bg, "volume": 0.12, "track_index": 2})
requests.post(f"{CAPCUT_API}/add_subtitle", json={"draft_id": draft_id, "subtitle_path": srt_path, "language": "en-US", "font_size": 38, "bold": True, "position": {"x": 0.5, "y": 0.82}})
if key_term:
requests.post(f"{CAPCUT_API}/add_text", json={"draft_id": draft_id, "text": f"📌 {key_term}", "start_time": 10.0, "duration": 5.0, "font_size": 44, "bold": True, "color": "#00d4ff", "position": {"x": 0.5, "y": 0.12}})
if key_stat:
requests.post(f"{CAPCUT_API}/add_text", json={"draft_id": draft_id, "text": key_stat, "start_time": 5.0, "duration": 4.0, "font_size": 64, "bold": True, "color": "#f0a828", "position": {"x": 0.5, "y": 0.25}})
requests.post(f"{CAPCUT_API}/add_image", json={"draft_id": draft_id, "image_path": "./assets/brand_watermark.png", "start_time": 0, "duration": timestamp, "position": {"x": 0.88, "y": 0.06}, "scale": 0.15, "opacity": 0.7})
output_path = str(Path(DRAFTS_DIR) / topic_title.replace(" ", "_"))
requests.post(f"{CAPCUT_API}/save_draft", json={"draft_id": draft_id, "draft_folder": output_path})
print(f"✅ Draft saved: {output_path}")
return draft_idCapCut Config JSON
{
"brand": {
"watermark_path": "./assets/brand_watermark.png",
"bg_music_path": "./assets/bg_music_corporate.mp3",
"bg_music_volume": 0.12,
"subtitle_font_size": 38,
"accent_color": "#00d4ff",
"stat_color": "#f0a828"
},
"video": {"aspect_ratio": "9:16", "resolution": "1080x1920", "fps": 30, "format": "mp4"},
"capcut_server": {"host": "localhost", "port": 9001, "drafts_dir": "C:\\Users\\clint\\AppData\\Local\\CapCut\\User Data\\Projects\\com.lveditor.draft"}
}Stage 6 — TikTok Publisher
API credentials, posting, and hashtag management.
🔑 API Credentials Configuration
Step 1 — Copy from developers.tiktok.com
Flow: [TikTok Dev Portal] → → [Your .env File] → → [Used In Script]
| What to Copy | .env Variable | Used For |
|---|---|---|
| Client Key (app dashboard) | TIKTOK_CLIENT_KEY | token refresh, OAuth |
| Client Secret (app dashboard) | TIKTOK_CLIENT_SECRET | token refresh, OAuth |
| Access Token (OAuth flow) | TIKTOK_ACCESS_TOKEN | every video post |
| Refresh Token (OAuth flow) | TIKTOK_REFRESH_TOKEN | daily auto-renewal |
💡 Access & Refresh Tokens are NOT on the dashboard — generate them by running the OAuth flow in Step 2.
Step 2 — One-Time OAuth Flow
# run_oauth.py — run ONCE to get tokens
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlencode, urlparse, parse_qs
import webbrowser, requests
CLIENT_KEY = "PASTE_YOUR_CLIENT_KEY_HERE"
CLIENT_SECRET = "PASTE_YOUR_CLIENT_SECRET_HERE"
REDIRECT_URI = "http://localhost:8080/callback"
SCOPE = "video.publish"
class OAuthHandler(BaseHTTPRequestHandler):
def do_GET(self):
qs = parse_qs(urlparse(self.path).query)
code = qs.get("code", [None])[0]
if code:
res = requests.post("https://open.tiktokapis.com/v2/oauth/token/", data={
"client_key": CLIENT_KEY, "client_secret": CLIENT_SECRET,
"code": code, "grant_type": "authorization_code",
"redirect_uri": REDIRECT_URI})
tokens = res.json()
print("\n✅ Add these to your .env:\n")
print(f"TIKTOK_ACCESS_TOKEN={tokens['access_token']}")
print(f"TIKTOK_REFRESH_TOKEN={tokens['refresh_token']}")
with open(".env.tokens", "w") as f:
f.write(f"TIKTOK_ACCESS_TOKEN={tokens['access_token']}\n")
f.write(f"TIKTOK_REFRESH_TOKEN={tokens['refresh_token']}\n")
self.send_response(200); self.end_headers()
self.wfile.write(b"Done! Check your terminal."); raise SystemExit
auth_url = "https://www.tiktok.com/v2/auth/authorize/?" + urlencode({
"client_key": CLIENT_KEY, "scope": SCOPE,
"response_type": "code", "redirect_uri": REDIRECT_URI,
"state": "dg-pipeline"})
webbrowser.open(auth_url)
HTTPServer(("localhost", 8080), OAuthHandler).handle_request()1. Paste Client Key + Secret into the script
2. Run:
python run_oauth.py3. Browser opens → login → authorize
4. Copy tokens to .env
5. Run only once (pipeline auto-refreshes daily)
Step 3 — .env File
# .env — pipeline root folder. Add to .gitignore! TIKTOK_CLIENT_KEY=your_client_key_here TIKTOK_CLIENT_SECRET=your_client_secret_here TIKTOK_ACCESS_TOKEN=generated_by_run_oauth.py TIKTOK_REFRESH_TOKEN=generated_by_run_oauth.py ELEVENLABS_API_KEY=your_elevenlabs_key_here PEXELS_API_KEY=your_pexels_key_here
Quick Test
# quick_test.py
from dotenv import load_dotenv; import requests, os; load_dotenv()
token = os.getenv("TIKTOK_ACCESS_TOKEN")
res = requests.get("https://open.tiktokapis.com/v2/user/info/",
headers={"Authorization": f"Bearer {token}"},
params={"fields": "display_name,follower_count"})
data = res.json()
if "data" in data:
print(f"✅ Connected as: {data['data']['user']['display_name']}")
else:
print(f"❌ Auth failed: {data}")API Setup Checklist
TikTok Posting Script
import requests, os
from dotenv import load_dotenv
load_dotenv()
TIKTOK_BASE = "https://open.tiktokapis.com/v2"
DG_HASHTAGS = ["#datagovernance","#datamanagement","#dataquality",
"#dataengineer","#dataanalyst","#MDM","#datalineage",
"#tecktok","#learnontiktok","#datagovernor"]
def post_video(video_url: str, topic_title: str) -> str:
token = os.getenv("TIKTOK_ACCESS_TOKEN")
caption = f"{topic_title} | thedatagovernor.com\n\n" + " ".join(DG_HASHTAGS[:8])
res = requests.post(
f"{TIKTOK_BASE}/post/publish/video/init/",
headers={"Authorization": f"Bearer {token}",
"Content-Type": "application/json"},
json={"post_info": {
"title": caption[:2200],
"privacy_level": "PUBLIC_TO_EVERYONE",
"disable_duet": False, "disable_comment": False,
"disable_stitch": False,
"video_cover_timestamp_ms": 2000},
"source_info": {
"source": "PULL_FROM_URL",
"video_url": video_url}},
)
data = res.json()
if res.status_code == 200:
print(f"✅ Posted! publish_id: {data['data']['publish_id']}")
return data["data"]["publish_id"]
raise Exception(f"❌ Failed: {data}")Hashtag Manager
Optimal Posting Times
| Day | Best Time 1 | Best Time 2 |
|---|---|---|
| Mon | 7:00 AM EDT | 9:00 PM EDT |
| Tue | 9:00 AM EDT | 8:00 PM EDT |
| Wed | 7:00 AM EDT | 11:00 AM EDT |
| Thu | 12:00 PM EDT | 7:00 PM EDT |
| Fri | 5:00 AM EDT | 1:00 PM EDT |
| Sat | 11:00 AM EDT | 7:00 PM EDT |
| Sun | 7:00 AM EDT | 4:00 PM EDT |
Recommended: 7:00 AM EDT daily for B2B audience
Stage 7 — Feedback Loop
Performance tracking, analytics, and content optimization.
Performance Thresholds
Analytics Fetch Code
import requests, os
from dotenv import load_dotenv
load_dotenv()
def fetch_video_metrics(publish_ids: list) -> list:
token = os.getenv("TIKTOK_ACCESS_TOKEN")
headers = {"Authorization": f"Bearer {token}"}
results = []
for pid in publish_ids:
res = requests.get(
f"https://open.tiktokapis.com/v2/video/query/",
headers=headers,
params={"fields": "id,title,view_count,like_count,comment_count,share_count,average_time_watched",
"filters": {"video_ids": [pid]}})
v = res.json()["data"]["videos"][0]
avg_watch_pct = round((v["average_time_watched"] / 75) * 100, 1)
perf = ("🔥 Viral" if v["view_count"]>=10000
else "✅ Good" if v["view_count"]>=2000
else "⚠️ Low" if v["view_count"]>=500
else "❌ Poor")
results.append({
"publish_id": pid, "title": v["title"][:60],
"views": v["view_count"], "likes": v["like_count"],
"avg_watch_pct": avg_watch_pct, "performance": perf})
return resultsWeekly Review
Feedback Automation Rules
IF views ≥ 10K → auto-add follow-up topic with [SERIES] tag
IF avg_watch < 30% → flag script for hook rewrite in Stage 2
IF comments > 50 → extract themes and add to Topic Engine
IF shares > 100 → convert to long-form post for thedatagovernor.com
WEEKLY → remove bottom 20% performers from topic backlog
Daily Scheduler
Automated pipeline timeline and orchestration.
Daily Timeline
Master Orchestrator Script
import schedule, time
def daily_pipeline():
print("🚀 Starting Daily Data Governor Pipeline...")
topic = run_topic_engine()
script = run_script_workshop(topic)
audio = run_voiceover(script["text"],
f"./output/audio/{topic['slug']}.mp3")
broll = run_visual_sourcing(topic["keywords"])
draft = run_capcut_assembly(
topic_title=topic["title"],
voiceover_path=audio["path"],
broll_clips=broll["clips"],
srt_path=audio["srt_path"],
key_term=topic.get("key_term"),
key_stat=topic.get("key_stat"))
print(f"⏸️ HUMAN REVIEW: Open CapCut, review draft, export to ./output/videos/")
video_path = wait_for_export(
f"./output/videos/{topic['slug']}.mp4")
publish_id = run_tiktok_publish(video_path, topic["title"])
run_analytics_fetch()
run_feedback_loop()
print("✅ Pipeline complete!")
schedule.every().day.at("06:00").do(daily_pipeline)
if __name__ == "__main__":
print("⏰ Scheduler active. Runs at 06:00 AM daily.")
while True:
schedule.run_pending()
time.sleep(60)