Loading...
Loading...
Automate content creation from research to video generation using AI-powered content pipeline with Claude, OpenAI, and Remotion
npx skill4agent add aradotso/marketing-skills marketing-pipeline-automationSkill by ara.so — Marketing Skills collection.
# Node.js 18+ required
node --version
# Package manager
npm --version
# or
pnpm --versiongit clone https://github.com/pennydinh/marketing-pineline-share.git
cd marketing-pineline-share
# Install dependencies
npm install
# or
pnpm install.env.local# AI Provider Keys
ANTHROPIC_API_KEY=your_claude_api_key
OPENAI_API_KEY=your_openai_api_key
# Research & Crawling APIs
RAPIDAPI_KEY=your_rapidapi_key
# Video Rendering (Remotion)
REMOTION_WEBHOOK_SECRET=your_webhook_secret
# Database (if applicable)
DATABASE_URL=postgresql://user:password@localhost:5432/marketing_pipeline
# Next.js Configuration
NEXT_PUBLIC_API_URL=http://localhost:3000# Start development server
npm run dev
# Build for production
npm run build
# Start production server
npm run start
# Run linter
npm run lint# Render single video
npm run remotion:render
# Preview Remotion composition
npm run remotion:preview
# Upgrade Remotion
npm run remotion:upgrademarketing-pineline-share/
├── src/
│ ├── app/ # Next.js app directory
│ ├── components/ # React components
│ ├── lib/
│ │ ├── ai/ # AI integration (Claude, OpenAI)
│ │ ├── crawlers/ # Content crawling modules
│ │ ├── generators/ # Content generation logic
│ │ └── video/ # Video rendering (Remotion)
│ └── utils/ # Utility functions
├── public/ # Static assets
├── remotion/ # Remotion video templates
└── .env.local # Environment variables// src/lib/crawlers/news-crawler.ts
import axios from 'axios';
interface NewsSource {
title: string;
url: string;
publishedAt: string;
content: string;
}
export async function crawlTechNews(keyword: string): Promise<NewsSource[]> {
const options = {
method: 'GET',
url: 'https://news-api.p.rapidapi.com/v1/search',
params: {
q: keyword,
lang: 'en',
sort_by: 'published_at',
from: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Last 24h
},
headers: {
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY!,
'X-RapidAPI-Host': 'news-api.p.rapidapi.com'
}
};
try {
const response = await axios.request(options);
return response.data.articles.slice(0, 10);
} catch (error) {
console.error('News crawling error:', error);
return [];
}
}// src/lib/ai/claude-generator.ts
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
});
interface ContentRequest {
keyword: string;
format: 'toplist' | 'pov' | 'case-study' | 'how-to';
language: 'en' | 'vi';
tone: 'expert' | 'friendly' | 'humorous';
researchData: string;
}
export async function generateContentWithClaude(
request: ContentRequest
): Promise<string> {
const systemPrompt = `You are an expert content creator specializing in ${request.format} format.
Write in ${request.language === 'vi' ? 'Vietnamese' : 'English'} with a ${request.tone} tone.
Use the provided research data to create data-backed, trending content.`;
const userPrompt = `Create a ${request.format} article about "${request.keyword}" using this research:
${request.researchData}
Requirements:
- Include real data and insights from the research
- Make it engaging and trend-focused
- Optimize for social media sharing
- Add relevant statistics and quotes`;
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
system: systemPrompt,
messages: [
{
role: 'user',
content: userPrompt,
},
],
});
return message.content[0].type === 'text' ? message.content[0].text : '';
}// src/lib/ai/openai-generator.ts
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY!,
});
export async function generateContentWithOpenAI(
request: ContentRequest
): Promise<string> {
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{
role: 'system',
content: `You are an expert content creator specializing in ${request.format} format.`,
},
{
role: 'user',
content: `Create a ${request.format} article about "${request.keyword}" using this research:\n\n${request.researchData}`,
},
],
temperature: 0.7,
max_tokens: 3000,
});
return completion.choices[0].message.content || '';
}// src/lib/video/render-video.ts
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import path from 'path';
interface VideoConfig {
title: string;
content: string;
aspectRatio: '9:16' | '16:9' | '1:1'; // TikTok, YouTube, Instagram
}
export async function renderContentVideo(
config: VideoConfig
): Promise<string> {
const compositionId = 'ContentVideo';
const bundleLocation = await bundle({
entryPoint: path.resolve('./remotion/index.ts'),
webpackOverride: (config) => config,
});
const composition = await selectComposition({
serveUrl: bundleLocation,
id: compositionId,
inputProps: {
title: config.title,
content: config.content,
aspectRatio: config.aspectRatio,
},
});
const outputLocation = path.join(
process.cwd(),
'public',
'videos',
`${Date.now()}.mp4`
);
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation,
inputProps: composition.props,
});
return outputLocation;
}// src/lib/pipeline/content-pipeline.ts
import { crawlTechNews } from '../crawlers/news-crawler';
import { generateContentWithClaude } from '../ai/claude-generator';
import { renderContentVideo } from '../video/render-video';
export interface PipelineConfig {
keyword: string;
format: 'toplist' | 'pov' | 'case-study' | 'how-to';
language: 'en' | 'vi';
tone: 'expert' | 'friendly' | 'humorous';
generateVideo: boolean;
videoAspectRatio?: '9:16' | '16:9' | '1:1';
}
export async function runContentPipeline(config: PipelineConfig) {
console.log(`Starting pipeline for keyword: ${config.keyword}`);
// Step 1: Research & Crawl
console.log('Step 1: Crawling news sources...');
const newsArticles = await crawlTechNews(config.keyword);
const researchData = newsArticles
.map(
(article) =>
`Title: ${article.title}\nURL: ${article.url}\nContent: ${article.content}\n---`
)
.join('\n\n');
// Step 2: Generate Content
console.log('Step 2: Generating content with AI...');
const generatedContent = await generateContentWithClaude({
keyword: config.keyword,
format: config.format,
language: config.language,
tone: config.tone,
researchData,
});
// Step 3: Render Video (if enabled)
let videoPath: string | null = null;
if (config.generateVideo) {
console.log('Step 3: Rendering video...');
videoPath = await renderContentVideo({
title: config.keyword,
content: generatedContent,
aspectRatio: config.videoAspectRatio || '16:9',
});
}
return {
content: generatedContent,
videoPath,
sources: newsArticles,
timestamp: new Date().toISOString(),
};
}// src/app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { runContentPipeline } from '@/lib/pipeline/content-pipeline';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const result = await runContentPipeline({
keyword: body.keyword,
format: body.format || 'toplist',
language: body.language || 'en',
tone: body.tone || 'expert',
generateVideo: body.generateVideo || false,
videoAspectRatio: body.videoAspectRatio || '16:9',
});
return NextResponse.json({
success: true,
data: result,
});
} catch (error) {
console.error('Pipeline error:', error);
return NextResponse.json(
{ success: false, error: 'Content generation failed' },
{ status: 500 }
);
}
}// src/components/ContentGenerator.tsx
'use client';
import { useState } from 'react';
export default function ContentGenerator() {
const [keyword, setKeyword] = useState('');
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<any>(null);
const handleGenerate = async () => {
setLoading(true);
try {
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
keyword,
format: 'toplist',
language: 'en',
tone: 'expert',
generateVideo: true,
videoAspectRatio: '16:9',
}),
});
const data = await response.json();
setResult(data.data);
} catch (error) {
console.error('Generation failed:', error);
} finally {
setLoading(false);
}
};
return (
<div className="p-6">
<input
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="Enter keyword..."
className="border p-2 rounded w-full"
/>
<button
onClick={handleGenerate}
disabled={loading}
className="mt-4 bg-blue-600 text-white px-6 py-2 rounded"
>
{loading ? 'Generating...' : 'Generate Content'}
</button>
{result && (
<div className="mt-6">
<h3 className="font-bold">Generated Content:</h3>
<div className="prose">{result.content}</div>
{result.videoPath && (
<video src={result.videoPath} controls className="mt-4" />
)}
</div>
)}
</div>
);
}// remotion/ContentVideo.tsx
import { AbsoluteFill, useCurrentFrame, interpolate } from 'remotion';
interface ContentVideoProps {
title: string;
content: string;
aspectRatio: '9:16' | '16:9' | '1:1';
}
export const ContentVideo: React.FC<ContentVideoProps> = ({
title,
content,
}) => {
const frame = useCurrentFrame();
const opacity = interpolate(frame, [0, 30], [0, 1]);
return (
<AbsoluteFill
style={{
backgroundColor: '#000',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div style={{ opacity }}>
<h1 style={{ color: '#fff', fontSize: '48px' }}>{title}</h1>
<p style={{ color: '#fff', fontSize: '24px', marginTop: '20px' }}>
{content.substring(0, 200)}...
</p>
</div>
</AbsoluteFill>
);
};async function generateBatchContent(keywords: string[]) {
const results = await Promise.all(
keywords.map((keyword) =>
runContentPipeline({
keyword,
format: 'toplist',
language: 'en',
tone: 'expert',
generateVideo: false,
})
)
);
return results;
}// Use with cron or task scheduler
import cron from 'node-cron';
cron.schedule('0 9 * * *', async () => {
// Run daily at 9 AM
const trendingKeywords = ['AI', 'Web3', 'Marketing Automation'];
await generateBatchContent(trendingKeywords);
});// Implement rate limiting
import pLimit from 'p-limit';
const limit = pLimit(3); // Max 3 concurrent requests
const results = await Promise.all(
keywords.map((keyword) =>
limit(() => runContentPipeline({ keyword, /* ... */ }))
)
);// Increase timeout in Remotion config
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation,
timeoutInMilliseconds: 300000, // 5 minutes
});async function generateWithRetry(request: ContentRequest, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await generateContentWithClaude(request);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
}
}
}// src/lib/utils/validate-env.ts
export function validateEnv() {
const required = [
'ANTHROPIC_API_KEY',
'OPENAI_API_KEY',
'RAPIDAPI_KEY',
];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing environment variables: ${missing.join(', ')}`);
}
}