You made a TikTok that went viral.
Now you need to post it on YouTube Shorts, Instagram Reels, Twitter, and LinkedIn.
But each platform has different optimal lengths, caption styles, and hashtag strategies. So you end up manually rewriting everything four times.
That's insane.
In this tutorial, we'll build a Cross-Platform Video Repurposer that:
- Extracts the transcript from any viral video
- Generates optimized captions for every major platform
- Suggests platform-specific hashtags and hooks
One video. Five platforms. Zero manual rewriting.
The Platform Differences That Matter
Each platform has its own rules:
| Platform | Max Caption | Hashtags | Tone | Hook Style |
|---|---|---|---|---|
| TikTok | 4,000 chars | 3-5 viral | Casual, trendy | Pattern interrupt |
| YouTube Shorts | 100 chars title | 3-5 in description | Clickbait-y | Curiosity gap |
| Instagram Reels | 2,200 chars | 10-30 | Polished | Story hook |
| Twitter/X | 280 chars | 1-2 | Conversational | Hot take |
| 3,000 chars | 3-5 | Professional | Value prop |
Doing this manually is a nightmare. AI does it in seconds.
The Stack
- Node.js: Runtime
- SociaVault API: To extract video transcripts
- OpenAI API: To generate platform-specific content
Step 1: Setup
mkdir video-repurposer
cd video-repurposer
npm init -y
npm install axios openai dotenv
Create .env:
SOCIAVAULT_API_KEY=your_sociavault_key
OPENAI_API_KEY=your_openai_key
Step 2: Extract Video Transcript
First, we need to know what the video is about. Create index.js:
require('dotenv').config();
const axios = require('axios');
const OpenAI = require('openai');
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const SOCIAVAULT_BASE = 'https://api.sociavault.com';
async function getTikTokTranscript(videoUrl) {
console.log('π₯ Extracting TikTok transcript...');
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/transcript`, {
params: { url: videoUrl },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
return response.data.data;
} catch (error) {
console.error('Error getting transcript:', error.message);
return null;
}
}
async function getYouTubeTranscript(videoUrl) {
console.log('π₯ Extracting YouTube transcript...');
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/youtube/video/transcript`, {
params: { url: videoUrl },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
return response.data.data;
} catch (error) {
console.error('Error getting transcript:', error.message);
return null;
}
}
async function getTwitterVideoTranscript(tweetUrl) {
console.log('π₯ Extracting Twitter video transcript...');
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/twitter/tweet/transcript`, {
params: { url: tweetUrl },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
return response.data.data;
} catch (error) {
console.error('Error getting transcript:', error.message);
return null;
}
}
Step 3: Get Video Metadata
Context helps AI write better captions:
async function getTikTokVideoInfo(videoUrl) {
console.log('π₯ Fetching video metadata...');
try {
const response = await axios.get(`${SOCIAVAULT_BASE}/v1/scrape/tiktok/video-info`, {
params: { url: videoUrl, get_transcript: true },
headers: { 'Authorization': `Bearer ${process.env.SOCIAVAULT_API_KEY}` }
});
const data = response.data.data;
return {
description: data.desc || data.description,
likes: data.diggCount || data.stats?.diggCount || 0,
comments: data.commentCount || data.stats?.commentCount || 0,
shares: data.shareCount || data.stats?.shareCount || 0,
views: data.playCount || data.stats?.playCount || 0,
music: data.music?.title || 'Original Sound',
author: data.author?.uniqueId || data.authorMeta?.name,
hashtags: data.challenges?.map(c => c.title) || []
};
} catch (error) {
console.error('Error fetching video info:', error.message);
return null;
}
}
Step 4: Platform-Specific Generators
Now the fun part. Let's create specialized generators for each platform:
async function generateTikTokCaption(transcript, videoInfo) {
const prompt = `
You are a TikTok content expert. Create an optimized TikTok caption.
Original video transcript: "${transcript}"
Original description: "${videoInfo?.description || 'N/A'}"
Performance: ${videoInfo?.views?.toLocaleString() || 'N/A'} views
TikTok caption rules:
- Start with a hook (question, bold statement, or pattern interrupt)
- Keep it conversational and authentic
- Use line breaks for readability
- End with a CTA (comment, follow, etc.)
- Add 3-5 relevant trending hashtags
- Can be up to 4,000 characters but shorter usually performs better
Return JSON:
{
"caption": "the full caption with line breaks as actual newlines",
"hashtags": ["hashtag1", "hashtag2", "hashtag3", "hashtag4", "hashtag5"],
"cta": "the call to action used",
"hook": "the hook strategy used"
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
async function generateYouTubeShortsContent(transcript, videoInfo) {
const prompt = `
You are a YouTube Shorts expert. Create optimized title and description.
Video transcript: "${transcript}"
Original context: "${videoInfo?.description || 'N/A'}"
YouTube Shorts rules:
- Title: Max 100 characters, curiosity-driven, slightly clickbait-y
- Description: Include searchable keywords
- Hashtags: #Shorts is required, plus 2-4 relevant ones
- Use numbers and power words in title
Return JSON:
{
"title": "attention-grabbing title under 100 chars",
"description": "SEO-optimized description with keywords",
"hashtags": ["Shorts", "other", "relevant", "hashtags"],
"tags": ["searchable", "video", "tags", "for", "youtube"]
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
async function generateInstagramReelsCaption(transcript, videoInfo) {
const prompt = `
You are an Instagram content strategist. Create an optimized Reels caption.
Video transcript: "${transcript}"
Original context: "${videoInfo?.description || 'N/A'}"
Instagram Reels caption rules:
- Start with a strong hook (first line is crucial - it's the preview)
- Tell a mini-story or share a tip
- Use emojis strategically (not excessively)
- Break into short paragraphs
- End with a question or CTA to drive comments
- Hashtags: 10-20 relevant ones, mix of popular and niche
- Max 2,200 characters
Return JSON:
{
"caption": "full caption with line breaks",
"hashtags": ["list", "of", "15", "strategic", "hashtags"],
"cta": "the engagement hook used",
"firstLine": "the crucial first line that shows in preview"
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
async function generateTwitterPost(transcript, videoInfo) {
const prompt = `
You are a Twitter/X viral content expert. Create an optimized tweet.
Video transcript: "${transcript}"
Original context: "${videoInfo?.description || 'N/A'}"
Twitter rules:
- Max 280 characters (including spaces)
- Hot takes and opinions perform well
- Conversational, like talking to a friend
- Can be a thread if needed (indicate with π§΅)
- 1-2 hashtags maximum (or none)
- Questions drive replies
Return JSON:
{
"tweet": "the main tweet under 280 chars",
"threadContinuation": "optional second tweet if needed",
"hashtags": ["max", "two"],
"replyBait": "a question or statement to encourage replies"
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
async function generateLinkedInPost(transcript, videoInfo) {
const prompt = `
You are a LinkedIn content strategist. Create a professional video post.
Video transcript: "${transcript}"
Original context: "${videoInfo?.description || 'N/A'}"
LinkedIn rules:
- Professional but not boring
- Start with a bold statement or insight
- Share a lesson, tip, or observation
- Use line breaks (LinkedIn loves white space)
- Include a personal angle or story if possible
- End with a question or "Agree?"
- 3-5 professional hashtags
- Max 3,000 characters
Return JSON:
{
"post": "full LinkedIn post with line breaks",
"hook": "the first line that appears before 'see more'",
"hashtags": ["professional", "relevant", "hashtags"],
"engagementQuestion": "the closing question"
}
`;
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
response_format: { type: 'json_object' }
});
return JSON.parse(completion.choices[0].message.content);
}
Step 5: The Main Repurposer
Tie it all together:
async function repurposeVideo(videoUrl, platform = 'tiktok') {
console.log('\nπ CROSS-PLATFORM VIDEO REPURPOSER\n');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
// 1. Get transcript and video info
let transcript, videoInfo;
if (platform === 'tiktok') {
transcript = await getTikTokTranscript(videoUrl);
videoInfo = await getTikTokVideoInfo(videoUrl);
} else if (platform === 'youtube') {
transcript = await getYouTubeTranscript(videoUrl);
videoInfo = { description: 'YouTube video' };
} else if (platform === 'twitter') {
transcript = await getTwitterVideoTranscript(videoUrl);
videoInfo = { description: 'Twitter video' };
}
if (!transcript) {
console.log('β Could not extract transcript. The video may not have speech.');
return;
}
console.log('π Transcript extracted');
console.log(` Length: ${transcript.length} characters\n`);
if (videoInfo) {
console.log('π Original Performance:');
console.log(` ${videoInfo.views?.toLocaleString() || 'N/A'} views`);
console.log(` ${videoInfo.likes?.toLocaleString() || 'N/A'} likes\n`);
}
// 2. Generate all platform versions
console.log('π€ Generating platform-specific versions...\n');
const [tiktok, youtube, instagram, twitter, linkedin] = await Promise.all([
generateTikTokCaption(transcript, videoInfo),
generateYouTubeShortsContent(transcript, videoInfo),
generateInstagramReelsCaption(transcript, videoInfo),
generateTwitterPost(transcript, videoInfo),
generateLinkedInPost(transcript, videoInfo)
]);
// 3. Output results
console.log('βββββββββββββββββββββββββββββββββββββββββββ');
console.log('π± TIKTOK');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log(tiktok.caption);
console.log('\nHashtags:', tiktok.hashtags.map(h => `#${h}`).join(' '));
console.log('Hook strategy:', tiktok.hook);
console.log('\nβββββββββββββββββββββββββββββββββββββββββββ');
console.log('π¬ YOUTUBE SHORTS');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log('Title:', youtube.title);
console.log('\nDescription:', youtube.description);
console.log('\nHashtags:', youtube.hashtags.map(h => `#${h}`).join(' '));
console.log('Tags:', youtube.tags.join(', '));
console.log('\nβββββββββββββββββββββββββββββββββββββββββββ');
console.log('πΈ INSTAGRAM REELS');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log(instagram.caption);
console.log('\nHashtags:');
console.log(instagram.hashtags.map(h => `#${h}`).join(' '));
console.log('\nβββββββββββββββββββββββββββββββββββββββββββ');
console.log('π¦ TWITTER/X');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log(twitter.tweet);
if (twitter.threadContinuation) {
console.log('\nπ§΅ Thread:', twitter.threadContinuation);
}
console.log('\nReply bait:', twitter.replyBait);
console.log('\nβββββββββββββββββββββββββββββββββββββββββββ');
console.log('πΌ LINKEDIN');
console.log('βββββββββββββββββββββββββββββββββββββββββββ\n');
console.log(linkedin.post);
console.log('\nHashtags:', linkedin.hashtags.map(h => `#${h}`).join(' '));
return { tiktok, youtube, instagram, twitter, linkedin };
}
Step 6: Run It
async function main() {
const videoUrl = process.argv[2] || 'https://www.tiktok.com/@garyvee/video/7299831234567890';
await repurposeVideo(videoUrl, 'tiktok');
}
main();
Run with:
node index.js https://www.tiktok.com/@creator/video/1234567890
Sample Output
π CROSS-PLATFORM VIDEO REPURPOSER
βββββββββββββββββββββββββββββββββββββββββββ
π Transcript extracted
Length: 847 characters
π Original Performance:
2,450,000 views
312,000 likes
π€ Generating platform-specific versions...
βββββββββββββββββββββββββββββββββββββββββββ
π± TIKTOK
βββββββββββββββββββββββββββββββββββββββββββ
Stop making this mistake with your morning routine π«
I used to wake up and immediately check my phone.
Emails. Notifications. Other people's problems.
By 8am my brain was fried.
Now I do this instead:
β’ 10 min walk (no phone)
β’ Cold water on face
β’ Write 3 priorities for the day
My productivity went up 3x.
Try it tomorrow and comment what happened π
Hashtags: #morningroutine #productivity #lifehacks #growthmindset #habits
Hook strategy: Pattern interrupt with "Stop making this mistake"
βββββββββββββββββββββββββββββββββββββββββββ
π¬ YOUTUBE SHORTS
βββββββββββββββββββββββββββββββββββββββββββ
Title: I Tried This Morning Routine for 30 Days (Life-Changing Results)
Description: The simple morning routine that 10x'd my productivity. No phone for the first hour, cold water therapy, and priority setting. Here's exactly what changed in my life after doing this for 30 days...
Hashtags: #Shorts #MorningRoutine #Productivity #LifeHacks
Tags: morning routine, productivity tips, habits, self improvement, morning habits
βββββββββββββββββββββββββββββββββββββββββββ
πΈ INSTAGRAM REELS
βββββββββββββββββββββββββββββββββββββββββββ
I was sabotaging my entire day before 8am βοΈ
Here's what I mean...
Every morning I'd wake up and immediately grab my phone. Emails, notifications, DMsβeveryone else's priorities flooding my brain before I even got out of bed.
I felt productive. I wasn't.
The switch? π
β’ First hour: Phone stays in another room
β’ 10 minute walk outside (rain or shine)
β’ Cold water on my face
β’ Write down 3 things that would make today a win
Small changes. Massive results.
The best part? My anxiety dropped and my focus went through the roof.
What's your morning routine? Drop it below π
Hashtags:
#morningroutine #productivitytips #healthyhabits #selfimprovement #mindsetshift #dailyhabits #morningmotivation #successhabits #growthmindset #wellnessjourney #mindfulness #personalgrowth #lifestylechange #motivation #healthylifestyle
βββββββββββββββββββββββββββββββββββββββββββ
π¦ TWITTER/X
βββββββββββββββββββββββββββββββββββββββββββ
Hot take: Checking your phone first thing in the morning is why you're unproductive.
Your brain starts the day solving OTHER people's problems instead of your own.
Try: No phone for 60 min after waking. Just did this for a month. Changed everything.
Reply bait: What's the first thing you do when you wake up? Be honest.
βββββββββββββββββββββββββββββββββββββββββββ
πΌ LINKEDIN
βββββββββββββββββββββββββββββββββββββββββββ
I made one change to my morning routine.
It transformed my productivity.
For years, I started every day the same way:
β Wake up
β Grab phone
β Check emails
β Answer Slack messages
By 8am, I was mentally exhausted.
All my creative energy? Gone. Spent on everyone else's priorities.
So I tried something different:
No phone for the first 60 minutes of my day.
Instead:
β’ Short walk outside
β’ Write down 3 priorities
β’ Actually eat breakfast
The results after 30 days:
β
3x more deep work hours
β
Less anxiety
β
Better decisions
β
More energy at 5pm than 9am
The lesson?
How you start your day determines how you finish it.
What's your morning routine look like?
Hashtags: #productivity #leadership #morningroutine #worklifebalance #personaldevelopment
Adding Batch Processing
For content teams repurposing multiple videos:
async function batchRepurpose(videoUrls) {
const results = [];
for (const url of videoUrls) {
console.log(`\n${'β'.repeat(60)}\n`);
const result = await repurposeVideo(url, 'tiktok');
results.push({ url, ...result });
// Rate limiting
await new Promise(r => setTimeout(r, 2000));
}
// Export to JSON for your content calendar
const fs = require('fs');
fs.writeFileSync('repurposed-content.json', JSON.stringify(results, null, 2));
console.log('\nβ
All content exported to repurposed-content.json');
return results;
}
// Usage
batchRepurpose([
'https://www.tiktok.com/@creator/video/111',
'https://www.tiktok.com/@creator/video/222',
'https://www.tiktok.com/@creator/video/333'
]);
Adding a Simple API
Turn it into a service your team can use:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/repurpose', async (req, res) => {
const { url, platform = 'tiktok' } = req.body;
if (!url) {
return res.status(400).json({ error: 'Video URL required' });
}
try {
const results = await repurposeVideo(url, platform);
res.json({ success: true, content: results });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Repurposer API running on http://localhost:3000');
});
Cost Comparison
Manual repurposing:
- Time: 30-60 minutes per video
- If you value your time at $50/hour: $25-50 per video
This tool:
- SociaVault transcript: $0.01
- OpenAI (5 generations): $0.02
- Total: $0.03 per video
That's a 1000x cost reduction.
What You Just Built
Content teams pay for tools like:
- Repurpose.io: $25/month
- Opus Clip: $19/month
- Descript: $24/month
Your version does the caption/copy part for essentially free. Add video editing and you've got a full repurposing suite.
Get Started
- Get your SociaVault API Key
- Add your OpenAI key
- Paste any video URL
- Get all platforms instantly
One viral video deserves to live everywhere.
Time spent manually rewriting captions: 0. Content output: 5x.
Top comments (0)