Tools
Tools: Build a Video-to-Blog Post Converter That Works Across Every Platform
2026-02-23
0 views
admin
Why Every Video Should Become a Blog Post ## The Stack ## Step 1: Setup ## Step 2: Universal Transcript Fetcher ## Step 3: AI Blog Post Generator ## Step 4: Batch Converter for Content Libraries ## Step 5: Content Calendar Generator ## Step 6: Full Pipeline CLI ## Running It ## Sample Output ## The Content Multiplication Strategy ## Cost Comparison ## Get Started ## python #contentcreation #seo #webdev You published a banger 10-minute video last week. Thousands of views. Good engagement. And then it's gone — buried in the algorithm. Meanwhile, a written version of that same content would rank on Google for years. But who has time to manually transcribe and rewrite every video? Let's build a Video-to-Blog Converter that: One video → one blog post → years of organic traffic. Let's go. Here's something most creators miss: video and search audiences barely overlap. Google indexes text, not video. Your YouTube video might get 50K views in the first week and then plateau. But a blog post covering the same topic can compound traffic for years. The beauty here — SociaVault has transcript endpoints for every major platform. One function handles them all. Here's where the transcript becomes a polished blog post: Got a backlog of videos? Convert them all: Plan which videos to convert based on performance: From a 12-minute YouTube tutorial: Here's the real play: One video → 5 pieces of content. Every week. The creators winning right now aren't creating 5x more content. They're repurposing 5x more effectively. The SociaVault transcript costs 1 credit per video. OpenAI rewrite costs about $0.01. Total: pennies per blog post. Your best content is already recorded. It's just stuck in video format where Google can't find it. Every video you've ever made is a blog post waiting to happen. Stop leaving SEO traffic on the table. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK: mkdir video-to-blog cd video-to-blog pip install requests openai python-dotenv CODE_BLOCK: mkdir video-to-blog cd video-to-blog pip install requests openai python-dotenv CODE_BLOCK: mkdir video-to-blog cd video-to-blog pip install requests openai python-dotenv CODE_BLOCK: SOCIAVAULT_API_KEY=your_key_here OPENAI_API_KEY=your_openai_key CODE_BLOCK: SOCIAVAULT_API_KEY=your_key_here OPENAI_API_KEY=your_openai_key CODE_BLOCK: SOCIAVAULT_API_KEY=your_key_here OPENAI_API_KEY=your_openai_key COMMAND_BLOCK: import os import re import json import requests from openai import OpenAI from dotenv import load_dotenv load_dotenv() API_BASE = "https://api.sociavault.com" HEADERS = {"Authorization": f"Bearer {os.getenv('SOCIAVAULT_API_KEY')}"} client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def detect_platform(url: str) -> str: """Auto-detect which platform a URL belongs to.""" url_lower = url.lower() if "tiktok.com" in url_lower: return "tiktok" elif "youtube.com" in url_lower or "youtu.be" in url_lower: return "youtube" elif "instagram.com" in url_lower: return "instagram" elif "twitter.com" in url_lower or "x.com" in url_lower: return "twitter" elif "facebook.com" in url_lower or "fb.watch" in url_lower: return "facebook" else: raise ValueError(f"Unsupported platform: {url}") def get_transcript(url: str) -> dict: """Fetch transcript from any supported platform.""" platform = detect_platform(url) endpoints = { "tiktok": "/v1/scrape/tiktok/transcript", "youtube": "/v1/scrape/youtube/transcript", "instagram": "/v1/scrape/instagram/transcript", "twitter": "/v1/scrape/twitter/transcript", "facebook": "/v1/scrape/facebook/transcript", } endpoint = endpoints.get(platform) if not endpoint: raise ValueError(f"No transcript endpoint for {platform}") print(f"📥 Fetching {platform} transcript...") response = requests.get( f"{API_BASE}{endpoint}", params={"url": url}, headers=HEADERS ) response.raise_for_status() data = response.json().get("data", {}) # Normalize transcript format across platforms transcript_text = "" if isinstance(data, str): transcript_text = data elif isinstance(data, dict): transcript_text = data.get("transcript", "") or data.get("text", "") # Handle timestamped segments if not transcript_text and "segments" in data: transcript_text = " ".join( seg.get("text", "") for seg in data["segments"] ) elif isinstance(data, list): transcript_text = " ".join( item.get("text", "") if isinstance(item, dict) else str(item) for item in data ) word_count = len(transcript_text.split()) print(f" ✓ Got {word_count} words from {platform}") return { "platform": platform, "url": url, "transcript": transcript_text, "word_count": word_count, "raw_data": data, } COMMAND_BLOCK: import os import re import json import requests from openai import OpenAI from dotenv import load_dotenv load_dotenv() API_BASE = "https://api.sociavault.com" HEADERS = {"Authorization": f"Bearer {os.getenv('SOCIAVAULT_API_KEY')}"} client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def detect_platform(url: str) -> str: """Auto-detect which platform a URL belongs to.""" url_lower = url.lower() if "tiktok.com" in url_lower: return "tiktok" elif "youtube.com" in url_lower or "youtu.be" in url_lower: return "youtube" elif "instagram.com" in url_lower: return "instagram" elif "twitter.com" in url_lower or "x.com" in url_lower: return "twitter" elif "facebook.com" in url_lower or "fb.watch" in url_lower: return "facebook" else: raise ValueError(f"Unsupported platform: {url}") def get_transcript(url: str) -> dict: """Fetch transcript from any supported platform.""" platform = detect_platform(url) endpoints = { "tiktok": "/v1/scrape/tiktok/transcript", "youtube": "/v1/scrape/youtube/transcript", "instagram": "/v1/scrape/instagram/transcript", "twitter": "/v1/scrape/twitter/transcript", "facebook": "/v1/scrape/facebook/transcript", } endpoint = endpoints.get(platform) if not endpoint: raise ValueError(f"No transcript endpoint for {platform}") print(f"📥 Fetching {platform} transcript...") response = requests.get( f"{API_BASE}{endpoint}", params={"url": url}, headers=HEADERS ) response.raise_for_status() data = response.json().get("data", {}) # Normalize transcript format across platforms transcript_text = "" if isinstance(data, str): transcript_text = data elif isinstance(data, dict): transcript_text = data.get("transcript", "") or data.get("text", "") # Handle timestamped segments if not transcript_text and "segments" in data: transcript_text = " ".join( seg.get("text", "") for seg in data["segments"] ) elif isinstance(data, list): transcript_text = " ".join( item.get("text", "") if isinstance(item, dict) else str(item) for item in data ) word_count = len(transcript_text.split()) print(f" ✓ Got {word_count} words from {platform}") return { "platform": platform, "url": url, "transcript": transcript_text, "word_count": word_count, "raw_data": data, } COMMAND_BLOCK: import os import re import json import requests from openai import OpenAI from dotenv import load_dotenv load_dotenv() API_BASE = "https://api.sociavault.com" HEADERS = {"Authorization": f"Bearer {os.getenv('SOCIAVAULT_API_KEY')}"} client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) def detect_platform(url: str) -> str: """Auto-detect which platform a URL belongs to.""" url_lower = url.lower() if "tiktok.com" in url_lower: return "tiktok" elif "youtube.com" in url_lower or "youtu.be" in url_lower: return "youtube" elif "instagram.com" in url_lower: return "instagram" elif "twitter.com" in url_lower or "x.com" in url_lower: return "twitter" elif "facebook.com" in url_lower or "fb.watch" in url_lower: return "facebook" else: raise ValueError(f"Unsupported platform: {url}") def get_transcript(url: str) -> dict: """Fetch transcript from any supported platform.""" platform = detect_platform(url) endpoints = { "tiktok": "/v1/scrape/tiktok/transcript", "youtube": "/v1/scrape/youtube/transcript", "instagram": "/v1/scrape/instagram/transcript", "twitter": "/v1/scrape/twitter/transcript", "facebook": "/v1/scrape/facebook/transcript", } endpoint = endpoints.get(platform) if not endpoint: raise ValueError(f"No transcript endpoint for {platform}") print(f"📥 Fetching {platform} transcript...") response = requests.get( f"{API_BASE}{endpoint}", params={"url": url}, headers=HEADERS ) response.raise_for_status() data = response.json().get("data", {}) # Normalize transcript format across platforms transcript_text = "" if isinstance(data, str): transcript_text = data elif isinstance(data, dict): transcript_text = data.get("transcript", "") or data.get("text", "") # Handle timestamped segments if not transcript_text and "segments" in data: transcript_text = " ".join( seg.get("text", "") for seg in data["segments"] ) elif isinstance(data, list): transcript_text = " ".join( item.get("text", "") if isinstance(item, dict) else str(item) for item in data ) word_count = len(transcript_text.split()) print(f" ✓ Got {word_count} words from {platform}") return { "platform": platform, "url": url, "transcript": transcript_text, "word_count": word_count, "raw_data": data, } COMMAND_BLOCK: def transcript_to_blog( transcript_data: dict, target_keyword: str = None, tone: str = "conversational but authoritative", word_count_target: int = 1500, ) -> dict: """Convert a video transcript into an SEO-optimized blog post.""" transcript = transcript_data["transcript"] platform = transcript_data["platform"] if not transcript.strip(): raise ValueError("Empty transcript — video might not have speech") print(f"\n✍️ Converting {transcript_data['word_count']} words to blog post...") keyword_instruction = "" if target_keyword: keyword_instruction = f""" Target SEO keyword: "{target_keyword}" - Include it in the title, first paragraph, and 2-3 subheadings - Use it naturally 3-5 times in the body - Include related long-tail variations """ completion = client.chat.completions.create( model="gpt-4o-mini", messages=[{ "role": "user", "content": f"""Convert this video transcript into a polished blog post. TRANSCRIPT (from {platform}): {transcript[:6000]} INSTRUCTIONS: - Write ~{word_count_target} words - Tone: {tone} - Structure with H2 and H3 headings - Add an engaging introduction that hooks the reader - Break into scannable sections - Add a clear conclusion with a takeaway - Write like a human, not AI — use short sentences, contractions, and personality - Keep the speaker's original insights and examples - Don't add information that wasn't in the transcript - Remove verbal tics (um, uh, like, you know, so basically) {keyword_instruction} Return JSON: {{ "title": "SEO-optimized blog title", "meta_description": "155 character meta description", "slug": "url-friendly-slug", "blog_post": "Full markdown blog post with headings", "tags": ["5 relevant tags"], "estimated_read_time": "X min read", "seo_suggestions": ["3 internal linking opportunities"], "social_snippets": {{ "twitter": "Tweet-length summary", "linkedin": "LinkedIn post summary (2-3 sentences)" }} }}""" }], response_format={"type": "json_object"} ) result = json.loads(completion.choices[0].message.content) actual_words = len(result["blog_post"].split()) print(f" ✓ Generated {actual_words}-word blog post") print(f" 📝 Title: {result['title']}") print(f" 🔗 Slug: /{result['slug']}") print(f" ⏱️ {result['estimated_read_time']}") return result COMMAND_BLOCK: def transcript_to_blog( transcript_data: dict, target_keyword: str = None, tone: str = "conversational but authoritative", word_count_target: int = 1500, ) -> dict: """Convert a video transcript into an SEO-optimized blog post.""" transcript = transcript_data["transcript"] platform = transcript_data["platform"] if not transcript.strip(): raise ValueError("Empty transcript — video might not have speech") print(f"\n✍️ Converting {transcript_data['word_count']} words to blog post...") keyword_instruction = "" if target_keyword: keyword_instruction = f""" Target SEO keyword: "{target_keyword}" - Include it in the title, first paragraph, and 2-3 subheadings - Use it naturally 3-5 times in the body - Include related long-tail variations """ completion = client.chat.completions.create( model="gpt-4o-mini", messages=[{ "role": "user", "content": f"""Convert this video transcript into a polished blog post. TRANSCRIPT (from {platform}): {transcript[:6000]} INSTRUCTIONS: - Write ~{word_count_target} words - Tone: {tone} - Structure with H2 and H3 headings - Add an engaging introduction that hooks the reader - Break into scannable sections - Add a clear conclusion with a takeaway - Write like a human, not AI — use short sentences, contractions, and personality - Keep the speaker's original insights and examples - Don't add information that wasn't in the transcript - Remove verbal tics (um, uh, like, you know, so basically) {keyword_instruction} Return JSON: {{ "title": "SEO-optimized blog title", "meta_description": "155 character meta description", "slug": "url-friendly-slug", "blog_post": "Full markdown blog post with headings", "tags": ["5 relevant tags"], "estimated_read_time": "X min read", "seo_suggestions": ["3 internal linking opportunities"], "social_snippets": {{ "twitter": "Tweet-length summary", "linkedin": "LinkedIn post summary (2-3 sentences)" }} }}""" }], response_format={"type": "json_object"} ) result = json.loads(completion.choices[0].message.content) actual_words = len(result["blog_post"].split()) print(f" ✓ Generated {actual_words}-word blog post") print(f" 📝 Title: {result['title']}") print(f" 🔗 Slug: /{result['slug']}") print(f" ⏱️ {result['estimated_read_time']}") return result COMMAND_BLOCK: def transcript_to_blog( transcript_data: dict, target_keyword: str = None, tone: str = "conversational but authoritative", word_count_target: int = 1500, ) -> dict: """Convert a video transcript into an SEO-optimized blog post.""" transcript = transcript_data["transcript"] platform = transcript_data["platform"] if not transcript.strip(): raise ValueError("Empty transcript — video might not have speech") print(f"\n✍️ Converting {transcript_data['word_count']} words to blog post...") keyword_instruction = "" if target_keyword: keyword_instruction = f""" Target SEO keyword: "{target_keyword}" - Include it in the title, first paragraph, and 2-3 subheadings - Use it naturally 3-5 times in the body - Include related long-tail variations """ completion = client.chat.completions.create( model="gpt-4o-mini", messages=[{ "role": "user", "content": f"""Convert this video transcript into a polished blog post. TRANSCRIPT (from {platform}): {transcript[:6000]} INSTRUCTIONS: - Write ~{word_count_target} words - Tone: {tone} - Structure with H2 and H3 headings - Add an engaging introduction that hooks the reader - Break into scannable sections - Add a clear conclusion with a takeaway - Write like a human, not AI — use short sentences, contractions, and personality - Keep the speaker's original insights and examples - Don't add information that wasn't in the transcript - Remove verbal tics (um, uh, like, you know, so basically) {keyword_instruction} Return JSON: {{ "title": "SEO-optimized blog title", "meta_description": "155 character meta description", "slug": "url-friendly-slug", "blog_post": "Full markdown blog post with headings", "tags": ["5 relevant tags"], "estimated_read_time": "X min read", "seo_suggestions": ["3 internal linking opportunities"], "social_snippets": {{ "twitter": "Tweet-length summary", "linkedin": "LinkedIn post summary (2-3 sentences)" }} }}""" }], response_format={"type": "json_object"} ) result = json.loads(completion.choices[0].message.content) actual_words = len(result["blog_post"].split()) print(f" ✓ Generated {actual_words}-word blog post") print(f" 📝 Title: {result['title']}") print(f" 🔗 Slug: /{result['slug']}") print(f" ⏱️ {result['estimated_read_time']}") return result COMMAND_BLOCK: def batch_convert(urls: list, output_dir: str = "blog_posts") -> list: """Convert multiple videos to blog posts.""" os.makedirs(output_dir, exist_ok=True) results = [] print(f"\n🔄 Converting {len(urls)} videos to blog posts...\n") for i, url in enumerate(urls, 1): print(f"{'─' * 50}") print(f"[{i}/{len(urls)}] {url}") try: transcript = get_transcript(url) if transcript["word_count"] < 50: print(f" ⚠️ Skipping — only {transcript['word_count']} words") continue blog = transcript_to_blog(transcript) # Save as markdown file filename = f"{blog['slug']}.md" filepath = os.path.join(output_dir, filename) frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} source_video: "{url}" source_platform: "{transcript['platform']}" read_time: "{blog['estimated_read_time']}" --- """ with open(filepath, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f" 💾 Saved: {filepath}") results.append({ "url": url, "title": blog["title"], "slug": blog["slug"], "file": filepath, "word_count": len(blog["blog_post"].split()), }) except Exception as e: print(f" ❌ Error: {e}") results.append({"url": url, "error": str(e)}) # Summary successful = [r for r in results if "title" in r] print(f"\n{'═' * 50}") print(f"✅ Converted: {len(successful)}/{len(urls)} videos") total_words = sum(r["word_count"] for r in successful) print(f"📝 Total content: {total_words:,} words") print(f"📁 Output: {output_dir}/") return results COMMAND_BLOCK: def batch_convert(urls: list, output_dir: str = "blog_posts") -> list: """Convert multiple videos to blog posts.""" os.makedirs(output_dir, exist_ok=True) results = [] print(f"\n🔄 Converting {len(urls)} videos to blog posts...\n") for i, url in enumerate(urls, 1): print(f"{'─' * 50}") print(f"[{i}/{len(urls)}] {url}") try: transcript = get_transcript(url) if transcript["word_count"] < 50: print(f" ⚠️ Skipping — only {transcript['word_count']} words") continue blog = transcript_to_blog(transcript) # Save as markdown file filename = f"{blog['slug']}.md" filepath = os.path.join(output_dir, filename) frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} source_video: "{url}" source_platform: "{transcript['platform']}" read_time: "{blog['estimated_read_time']}" --- """ with open(filepath, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f" 💾 Saved: {filepath}") results.append({ "url": url, "title": blog["title"], "slug": blog["slug"], "file": filepath, "word_count": len(blog["blog_post"].split()), }) except Exception as e: print(f" ❌ Error: {e}") results.append({"url": url, "error": str(e)}) # Summary successful = [r for r in results if "title" in r] print(f"\n{'═' * 50}") print(f"✅ Converted: {len(successful)}/{len(urls)} videos") total_words = sum(r["word_count"] for r in successful) print(f"📝 Total content: {total_words:,} words") print(f"📁 Output: {output_dir}/") return results COMMAND_BLOCK: def batch_convert(urls: list, output_dir: str = "blog_posts") -> list: """Convert multiple videos to blog posts.""" os.makedirs(output_dir, exist_ok=True) results = [] print(f"\n🔄 Converting {len(urls)} videos to blog posts...\n") for i, url in enumerate(urls, 1): print(f"{'─' * 50}") print(f"[{i}/{len(urls)}] {url}") try: transcript = get_transcript(url) if transcript["word_count"] < 50: print(f" ⚠️ Skipping — only {transcript['word_count']} words") continue blog = transcript_to_blog(transcript) # Save as markdown file filename = f"{blog['slug']}.md" filepath = os.path.join(output_dir, filename) frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} source_video: "{url}" source_platform: "{transcript['platform']}" read_time: "{blog['estimated_read_time']}" --- """ with open(filepath, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f" 💾 Saved: {filepath}") results.append({ "url": url, "title": blog["title"], "slug": blog["slug"], "file": filepath, "word_count": len(blog["blog_post"].split()), }) except Exception as e: print(f" ❌ Error: {e}") results.append({"url": url, "error": str(e)}) # Summary successful = [r for r in results if "title" in r] print(f"\n{'═' * 50}") print(f"✅ Converted: {len(successful)}/{len(urls)} videos") total_words = sum(r["word_count"] for r in successful) print(f"📝 Total content: {total_words:,} words") print(f"📁 Output: {output_dir}/") return results COMMAND_BLOCK: def suggest_conversion_priority(video_urls_with_views: list) -> list: """Rank videos by conversion priority.""" print("\n📊 Analyzing conversion priority...\n") scored = [] for item in video_urls_with_views: url = item["url"] views = item.get("views", 0) # Higher views = more proven topic # Longer videos = more content to work with transcript = get_transcript(url) word_count = transcript["word_count"] # Score: views (topic validation) × words (content depth) score = 0 if word_count >= 500: score += 40 # Enough content for a full post elif word_count >= 200: score += 20 else: score += 5 if views >= 100000: score += 50 elif views >= 10000: score += 35 elif views >= 1000: score += 20 else: score += 10 scored.append({ "url": url, "views": views, "word_count": word_count, "score": score, "platform": transcript["platform"], }) scored.sort(key=lambda x: x["score"], reverse=True) print("Priority ranking:") for i, item in enumerate(scored, 1): emoji = "🟢" if item["score"] >= 60 else "🟡" if item["score"] >= 30 else "🔴" print(f" {emoji} {i}. [{item['platform']}] Score: {item['score']}") print(f" {item['url']}") print(f" {item['views']:,} views | {item['word_count']} words") print() return scored COMMAND_BLOCK: def suggest_conversion_priority(video_urls_with_views: list) -> list: """Rank videos by conversion priority.""" print("\n📊 Analyzing conversion priority...\n") scored = [] for item in video_urls_with_views: url = item["url"] views = item.get("views", 0) # Higher views = more proven topic # Longer videos = more content to work with transcript = get_transcript(url) word_count = transcript["word_count"] # Score: views (topic validation) × words (content depth) score = 0 if word_count >= 500: score += 40 # Enough content for a full post elif word_count >= 200: score += 20 else: score += 5 if views >= 100000: score += 50 elif views >= 10000: score += 35 elif views >= 1000: score += 20 else: score += 10 scored.append({ "url": url, "views": views, "word_count": word_count, "score": score, "platform": transcript["platform"], }) scored.sort(key=lambda x: x["score"], reverse=True) print("Priority ranking:") for i, item in enumerate(scored, 1): emoji = "🟢" if item["score"] >= 60 else "🟡" if item["score"] >= 30 else "🔴" print(f" {emoji} {i}. [{item['platform']}] Score: {item['score']}") print(f" {item['url']}") print(f" {item['views']:,} views | {item['word_count']} words") print() return scored COMMAND_BLOCK: def suggest_conversion_priority(video_urls_with_views: list) -> list: """Rank videos by conversion priority.""" print("\n📊 Analyzing conversion priority...\n") scored = [] for item in video_urls_with_views: url = item["url"] views = item.get("views", 0) # Higher views = more proven topic # Longer videos = more content to work with transcript = get_transcript(url) word_count = transcript["word_count"] # Score: views (topic validation) × words (content depth) score = 0 if word_count >= 500: score += 40 # Enough content for a full post elif word_count >= 200: score += 20 else: score += 5 if views >= 100000: score += 50 elif views >= 10000: score += 35 elif views >= 1000: score += 20 else: score += 10 scored.append({ "url": url, "views": views, "word_count": word_count, "score": score, "platform": transcript["platform"], }) scored.sort(key=lambda x: x["score"], reverse=True) print("Priority ranking:") for i, item in enumerate(scored, 1): emoji = "🟢" if item["score"] >= 60 else "🟡" if item["score"] >= 30 else "🔴" print(f" {emoji} {i}. [{item['platform']}] Score: {item['score']}") print(f" {item['url']}") print(f" {item['views']:,} views | {item['word_count']} words") print() return scored COMMAND_BLOCK: def main(): import sys if len(sys.argv) < 2: print("Video-to-Blog Converter") print() print("Usage:") print(" python converter.py convert <video_url>") print(" python converter.py convert <url> --keyword 'target keyword'") print(" python converter.py batch urls.txt") print(" python converter.py transcript <url>") print() print("Supported platforms: TikTok, YouTube, Instagram, Twitter/X, Facebook") return command = sys.argv[1] if command == "transcript": url = sys.argv[2] result = get_transcript(url) print(f"\n--- TRANSCRIPT ({result['word_count']} words) ---\n") print(result["transcript"][:2000]) if result["word_count"] > 400: print(f"\n... ({result['word_count'] - 400} more words)") elif command == "convert": url = sys.argv[2] keyword = None if "--keyword" in sys.argv: idx = sys.argv.index("--keyword") keyword = sys.argv[idx + 1] transcript = get_transcript(url) blog = transcript_to_blog(transcript, target_keyword=keyword) # Save it filename = f"{blog['slug']}.md" frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} "{url}" --- """ with open(filename, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f"\n💾 Saved to {filename}") print(f"\n🐦 Twitter: {blog['social_snippets']['twitter']}") print(f"\n💼 LinkedIn: {blog['social_snippets']['linkedin']}") elif command == "batch": filepath = sys.argv[2] with open(filepath) as f: urls = [line.strip() for line in f if line.strip()] batch_convert(urls) if __name__ == "__main__": main() COMMAND_BLOCK: def main(): import sys if len(sys.argv) < 2: print("Video-to-Blog Converter") print() print("Usage:") print(" python converter.py convert <video_url>") print(" python converter.py convert <url> --keyword 'target keyword'") print(" python converter.py batch urls.txt") print(" python converter.py transcript <url>") print() print("Supported platforms: TikTok, YouTube, Instagram, Twitter/X, Facebook") return command = sys.argv[1] if command == "transcript": url = sys.argv[2] result = get_transcript(url) print(f"\n--- TRANSCRIPT ({result['word_count']} words) ---\n") print(result["transcript"][:2000]) if result["word_count"] > 400: print(f"\n... ({result['word_count'] - 400} more words)") elif command == "convert": url = sys.argv[2] keyword = None if "--keyword" in sys.argv: idx = sys.argv.index("--keyword") keyword = sys.argv[idx + 1] transcript = get_transcript(url) blog = transcript_to_blog(transcript, target_keyword=keyword) # Save it filename = f"{blog['slug']}.md" frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} "{url}" --- """ with open(filename, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f"\n💾 Saved to {filename}") print(f"\n🐦 Twitter: {blog['social_snippets']['twitter']}") print(f"\n💼 LinkedIn: {blog['social_snippets']['linkedin']}") elif command == "batch": filepath = sys.argv[2] with open(filepath) as f: urls = [line.strip() for line in f if line.strip()] batch_convert(urls) if __name__ == "__main__": main() COMMAND_BLOCK: def main(): import sys if len(sys.argv) < 2: print("Video-to-Blog Converter") print() print("Usage:") print(" python converter.py convert <video_url>") print(" python converter.py convert <url> --keyword 'target keyword'") print(" python converter.py batch urls.txt") print(" python converter.py transcript <url>") print() print("Supported platforms: TikTok, YouTube, Instagram, Twitter/X, Facebook") return command = sys.argv[1] if command == "transcript": url = sys.argv[2] result = get_transcript(url) print(f"\n--- TRANSCRIPT ({result['word_count']} words) ---\n") print(result["transcript"][:2000]) if result["word_count"] > 400: print(f"\n... ({result['word_count'] - 400} more words)") elif command == "convert": url = sys.argv[2] keyword = None if "--keyword" in sys.argv: idx = sys.argv.index("--keyword") keyword = sys.argv[idx + 1] transcript = get_transcript(url) blog = transcript_to_blog(transcript, target_keyword=keyword) # Save it filename = f"{blog['slug']}.md" frontmatter = f"""--- title: "{blog['title']}" description: "{blog['meta_description']}" tags: {json.dumps(blog['tags'])} "{url}" --- """ with open(filename, "w", encoding="utf-8") as f: f.write(frontmatter + blog["blog_post"]) print(f"\n💾 Saved to {filename}") print(f"\n🐦 Twitter: {blog['social_snippets']['twitter']}") print(f"\n💼 LinkedIn: {blog['social_snippets']['linkedin']}") elif command == "batch": filepath = sys.argv[2] with open(filepath) as f: urls = [line.strip() for line in f if line.strip()] batch_convert(urls) if __name__ == "__main__": main() COMMAND_BLOCK: # Convert a single YouTube video python converter.py convert "https://youtube.com/watch?v=abc123" # Convert with SEO keyword targeting python converter.py convert "https://youtube.com/watch?v=abc123" --keyword "python web scraping" # Just grab the transcript python converter.py transcript "https://tiktok.com/@user/video/123" # Batch convert from a file of URLs python converter.py batch my_videos.txt COMMAND_BLOCK: # Convert a single YouTube video python converter.py convert "https://youtube.com/watch?v=abc123" # Convert with SEO keyword targeting python converter.py convert "https://youtube.com/watch?v=abc123" --keyword "python web scraping" # Just grab the transcript python converter.py transcript "https://tiktok.com/@user/video/123" # Batch convert from a file of URLs python converter.py batch my_videos.txt COMMAND_BLOCK: # Convert a single YouTube video python converter.py convert "https://youtube.com/watch?v=abc123" # Convert with SEO keyword targeting python converter.py convert "https://youtube.com/watch?v=abc123" --keyword "python web scraping" # Just grab the transcript python converter.py transcript "https://tiktok.com/@user/video/123" # Batch convert from a file of URLs python converter.py batch my_videos.txt CODE_BLOCK: 📥 Fetching youtube transcript... ✓ Got 1,847 words from youtube ✍️ Converting 1,847 words to blog post... ✓ Generated 1,623-word blog post 📝 Title: How to Build a REST API with Express.js in 2025 🔗 Slug: /build-rest-api-express-2025 ⏱️ 7 min read 💾 Saved to build-rest-api-express-2025.md 🐦 Twitter: Just converted my Express.js tutorial into a blog post automatically. 1,800 words of video → 1,600 words of SEO-optimized content in 30 seconds. 💼 LinkedIn: Published a comprehensive guide on building REST APIs with Express.js. Originally a video tutorial, now optimized for search with structured headings and examples. CODE_BLOCK: 📥 Fetching youtube transcript... ✓ Got 1,847 words from youtube ✍️ Converting 1,847 words to blog post... ✓ Generated 1,623-word blog post 📝 Title: How to Build a REST API with Express.js in 2025 🔗 Slug: /build-rest-api-express-2025 ⏱️ 7 min read 💾 Saved to build-rest-api-express-2025.md 🐦 Twitter: Just converted my Express.js tutorial into a blog post automatically. 1,800 words of video → 1,600 words of SEO-optimized content in 30 seconds. 💼 LinkedIn: Published a comprehensive guide on building REST APIs with Express.js. Originally a video tutorial, now optimized for search with structured headings and examples. CODE_BLOCK: 📥 Fetching youtube transcript... ✓ Got 1,847 words from youtube ✍️ Converting 1,847 words to blog post... ✓ Generated 1,623-word blog post 📝 Title: How to Build a REST API with Express.js in 2025 🔗 Slug: /build-rest-api-express-2025 ⏱️ 7 min read 💾 Saved to build-rest-api-express-2025.md 🐦 Twitter: Just converted my Express.js tutorial into a blog post automatically. 1,800 words of video → 1,600 words of SEO-optimized content in 30 seconds. 💼 LinkedIn: Published a comprehensive guide on building REST APIs with Express.js. Originally a video tutorial, now optimized for search with structured headings and examples. - Grabs the transcript from any video (TikTok, YouTube, Instagram, Twitter) - Cleans and structures it automatically - Uses AI to rewrite it as an SEO-optimized blog post - Suggests titles, meta descriptions, and internal links - A 10-minute video = ~1,500 words of spoken content - A 1,500-word blog post = competitive for most long-tail keywords - Time to manually transcribe + edit = 2 hours - Time with this tool = 30 seconds - Python: Language - SociaVault API: Multi-platform transcript endpoints - OpenAI: Content transformation - python-dotenv: Config management - Record one video with your expertise - Convert to blog post (this tool) - Extract tweet threads from key sections - Pull LinkedIn posts from the social snippets - Create email newsletter from the summary - Get your API key at sociavault.com - Pick your best-performing video - Convert it and publish — it'll probably rank within a month
toolsutilitiessecurity toolsbuildvideoconverterworksacrosseveryplatform