Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -11,43 +11,19 @@ st.set_page_config(
|
|
11 |
initial_sidebar_state="expanded"
|
12 |
)
|
13 |
|
14 |
-
# Custom CSS
|
15 |
-
st.markdown(
|
16 |
<style>
|
17 |
-
.header {
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
}
|
22 |
-
.
|
23 |
-
background-color: #F0F2F6;
|
24 |
-
}
|
25 |
-
.stProgress > div > div > div > div {
|
26 |
-
background-color: #FF4B4B;
|
27 |
-
}
|
28 |
-
.day-card {
|
29 |
-
padding: 20px;
|
30 |
-
border-radius: 10px;
|
31 |
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
32 |
-
margin-bottom: 20px;
|
33 |
-
background-color: white;
|
34 |
-
}
|
35 |
-
body {
|
36 |
-
background-image: url('https://images.unsplash.com/photo-7NT4EDSI5Ok');
|
37 |
-
background-size: cover;
|
38 |
-
background-position: center;
|
39 |
-
background-repeat: no-repeat;
|
40 |
-
background-attachment: fixed;
|
41 |
-
}
|
42 |
-
.main > div {
|
43 |
-
background-color: rgba(255, 255, 255, 0.9);
|
44 |
-
padding: 20px;
|
45 |
-
border-radius: 10px;
|
46 |
-
}
|
47 |
</style>
|
48 |
""", unsafe_allow_html=True)
|
49 |
|
50 |
-
#
|
51 |
if 'content_plan' not in st.session_state:
|
52 |
st.session_state.content_plan = None
|
53 |
|
@@ -61,41 +37,46 @@ with st.sidebar:
|
|
61 |
|
62 |
# Main content
|
63 |
st.markdown('<div class="header">π₯ YouTube Content Strategist</div>', unsafe_allow_html=True)
|
64 |
-
st.write("Generate data-driven content plans using YouTube audience insights")
|
65 |
|
66 |
# Helper functions
|
67 |
-
def
|
68 |
-
"""Extract channel ID from URL."""
|
69 |
if "@" in channel_url:
|
70 |
-
|
|
|
71 |
elif "channel/" in channel_url:
|
72 |
return channel_url.split("channel/")[-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
return None
|
74 |
|
75 |
-
def get_channel_videos(channel_id):
|
76 |
"""Fetch recent video IDs from the channel."""
|
77 |
-
youtube = googleapiclient.discovery.build("youtube", "v3", developerKey=YOUTUBE_API_KEY)
|
78 |
request = youtube.search().list(
|
79 |
part="id",
|
80 |
channelId=channel_id,
|
81 |
-
maxResults=MAX_VIDEOS,
|
82 |
order="date",
|
83 |
type="video"
|
84 |
)
|
85 |
response = request.execute()
|
86 |
-
return [item['id']['videoId'] for item in response[
|
87 |
|
88 |
-
def get_video_comments(video_id):
|
89 |
"""Fetch comments from a video."""
|
90 |
-
youtube = googleapiclient.discovery.build("youtube", "v3", developerKey=YOUTUBE_API_KEY)
|
91 |
request = youtube.commentThreads().list(
|
92 |
part="snippet",
|
93 |
videoId=video_id,
|
94 |
-
maxResults=COMMENTS_PER_VIDEO,
|
95 |
textFormat="plainText"
|
96 |
)
|
97 |
response = request.execute()
|
98 |
-
return [item['snippet']['topLevelComment']['snippet']['textDisplay'] for item in response[
|
99 |
|
100 |
def analyze_comments(comments):
|
101 |
"""Analyze comments using Gemini API and generate a content plan."""
|
@@ -115,7 +96,7 @@ def analyze_comments(comments):
|
|
115 |
response = model.generate_content(prompt)
|
116 |
return response.text
|
117 |
|
118 |
-
#
|
119 |
channel_url = st.text_input("Enter YouTube Channel URL:", placeholder="https://www.youtube.com/@ChannelName")
|
120 |
|
121 |
if st.button("Generate Content Plan π"):
|
@@ -123,29 +104,24 @@ if st.button("Generate Content Plan π"):
|
|
123 |
st.error("Please fill all required fields!")
|
124 |
else:
|
125 |
try:
|
126 |
-
# Initialize APIs
|
127 |
youtube = googleapiclient.discovery.build("youtube", "v3", developerKey=YOUTUBE_API_KEY)
|
128 |
genai.configure(api_key=GEMINI_API_KEY)
|
129 |
-
model = genai.GenerativeModel('gemini-1.5-pro')
|
130 |
|
131 |
-
# Get channel data
|
132 |
with st.spinner("π Analyzing channel..."):
|
133 |
-
channel_id =
|
134 |
if not channel_id:
|
135 |
-
st.error("Invalid channel URL")
|
136 |
st.stop()
|
|
|
|
|
137 |
|
138 |
-
video_ids = get_channel_videos(channel_id)
|
139 |
-
|
140 |
-
# Get comments
|
141 |
all_comments = []
|
142 |
progress_bar = st.progress(0)
|
143 |
for idx, video_id in enumerate(video_ids):
|
144 |
progress = (idx + 1) / len(video_ids)
|
145 |
progress_bar.progress(progress, text=f"π₯ Collecting comments from video {idx+1}/{len(video_ids)}...")
|
146 |
-
all_comments.extend(get_video_comments(video_id))
|
147 |
-
|
148 |
-
# Generate content plan
|
149 |
with st.spinner("π§ Analyzing comments and generating plan..."):
|
150 |
content_plan = analyze_comments(all_comments)
|
151 |
st.session_state.content_plan = content_plan
|
@@ -158,31 +134,24 @@ if st.button("Generate Content Plan π"):
|
|
158 |
if st.session_state.content_plan:
|
159 |
st.markdown("## π
10-Day Content Plan")
|
160 |
st.success("Here's your personalized content strategy based on audience insights!")
|
161 |
-
|
162 |
-
# Create date cards
|
163 |
start_date = datetime.now()
|
164 |
for day in range(10):
|
165 |
current_date = start_date + timedelta(days=day)
|
166 |
-
with st.expander(f"Day {day+1} - {current_date.strftime('%b %d')}", expanded=True if day==0 else False):
|
167 |
-
|
168 |
-
plan_lines = st.session_state.content_plan.split('\n')
|
169 |
-
|
170 |
-
# Calculate start index, accounting for potential inconsistent line counts
|
171 |
start_index = day * 5
|
172 |
if start_index + 4 < len(plan_lines):
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
""", unsafe_allow_html=True)
|
183 |
|
184 |
-
|
185 |
-
# Download button
|
186 |
st.download_button(
|
187 |
label="π₯ Download Full Plan",
|
188 |
data=st.session_state.content_plan,
|
@@ -190,22 +159,14 @@ if st.session_state.content_plan:
|
|
190 |
mime="text/plain"
|
191 |
)
|
192 |
|
193 |
-
# Instructions
|
194 |
with st.expander("βΉοΈ How to use this tool"):
|
195 |
st.markdown("""
|
196 |
1. **Get API Keys**:
|
197 |
- YouTube Data API: [Get it here](https://console.cloud.google.com/)
|
198 |
- Gemini API: [Get it here](https://makersuite.google.com/)
|
199 |
-
|
200 |
2. **Enter Channel URL**:
|
201 |
-
- Supports both channel IDs (@ChannelName) and custom URLs
|
202 |
-
|
203 |
3. **Adjust Settings**:
|
204 |
-
- Control number of videos/comments analyzed in sidebar
|
205 |
-
|
206 |
4. **Generate Plan**:
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
- Download the plan and track performance in YouTube Analytics
|
211 |
-
""")
|
|
|
11 |
initial_sidebar_state="expanded"
|
12 |
)
|
13 |
|
14 |
+
# Custom CSS
|
15 |
+
st.markdown("""
|
16 |
<style>
|
17 |
+
.header { font-size: 2.5em !important; color: #FF4B4B !important; margin-bottom: 30px !important; }
|
18 |
+
.sidebar .sidebar-content { background-color: #F0F2F6; }
|
19 |
+
.stProgress > div > div > div > div { background-color: #FF4B4B; }
|
20 |
+
.day-card { padding: 20px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); margin-bottom: 20px; background-color: white; }
|
21 |
+
body { background-size: cover; background-position: center; background-repeat: no-repeat; background-attachment: fixed; }
|
22 |
+
.main > div { background-color: rgba(255, 255, 255, 0.9); padding: 20px; border-radius: 10px; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
</style>
|
24 |
""", unsafe_allow_html=True)
|
25 |
|
26 |
+
# Session state for content plan
|
27 |
if 'content_plan' not in st.session_state:
|
28 |
st.session_state.content_plan = None
|
29 |
|
|
|
37 |
|
38 |
# Main content
|
39 |
st.markdown('<div class="header">π₯ YouTube Content Strategist</div>', unsafe_allow_html=True)
|
40 |
+
st.write("Generate data-driven content plans using YouTube audience insights.")
|
41 |
|
42 |
# Helper functions
|
43 |
+
def get_actual_channel_id(youtube, channel_url):
|
44 |
+
"""Extract real channel ID from a handle (@username) or custom URL."""
|
45 |
if "@" in channel_url:
|
46 |
+
username = channel_url.split("@")[-1]
|
47 |
+
request = youtube.channels().list(part="id", forHandle=username)
|
48 |
elif "channel/" in channel_url:
|
49 |
return channel_url.split("channel/")[-1]
|
50 |
+
else:
|
51 |
+
return None
|
52 |
+
|
53 |
+
response = request.execute()
|
54 |
+
if "items" in response and response["items"]:
|
55 |
+
return response["items"][0]["id"]
|
56 |
return None
|
57 |
|
58 |
+
def get_channel_videos(youtube, channel_id):
|
59 |
"""Fetch recent video IDs from the channel."""
|
|
|
60 |
request = youtube.search().list(
|
61 |
part="id",
|
62 |
channelId=channel_id,
|
63 |
+
maxResults=min(MAX_VIDEOS, 50), # Ensure within API limit
|
64 |
order="date",
|
65 |
type="video"
|
66 |
)
|
67 |
response = request.execute()
|
68 |
+
return [item['id']['videoId'] for item in response.get("items", [])]
|
69 |
|
70 |
+
def get_video_comments(youtube, video_id):
|
71 |
"""Fetch comments from a video."""
|
|
|
72 |
request = youtube.commentThreads().list(
|
73 |
part="snippet",
|
74 |
videoId=video_id,
|
75 |
+
maxResults=min(COMMENTS_PER_VIDEO, 100), # Ensure within API limit
|
76 |
textFormat="plainText"
|
77 |
)
|
78 |
response = request.execute()
|
79 |
+
return [item['snippet']['topLevelComment']['snippet']['textDisplay'] for item in response.get("items", [])]
|
80 |
|
81 |
def analyze_comments(comments):
|
82 |
"""Analyze comments using Gemini API and generate a content plan."""
|
|
|
96 |
response = model.generate_content(prompt)
|
97 |
return response.text
|
98 |
|
99 |
+
# Get channel URL
|
100 |
channel_url = st.text_input("Enter YouTube Channel URL:", placeholder="https://www.youtube.com/@ChannelName")
|
101 |
|
102 |
if st.button("Generate Content Plan π"):
|
|
|
104 |
st.error("Please fill all required fields!")
|
105 |
else:
|
106 |
try:
|
|
|
107 |
youtube = googleapiclient.discovery.build("youtube", "v3", developerKey=YOUTUBE_API_KEY)
|
108 |
genai.configure(api_key=GEMINI_API_KEY)
|
|
|
109 |
|
|
|
110 |
with st.spinner("π Analyzing channel..."):
|
111 |
+
channel_id = get_actual_channel_id(youtube, channel_url)
|
112 |
if not channel_id:
|
113 |
+
st.error("Invalid channel URL!")
|
114 |
st.stop()
|
115 |
+
|
116 |
+
video_ids = get_channel_videos(youtube, channel_id)
|
117 |
|
|
|
|
|
|
|
118 |
all_comments = []
|
119 |
progress_bar = st.progress(0)
|
120 |
for idx, video_id in enumerate(video_ids):
|
121 |
progress = (idx + 1) / len(video_ids)
|
122 |
progress_bar.progress(progress, text=f"π₯ Collecting comments from video {idx+1}/{len(video_ids)}...")
|
123 |
+
all_comments.extend(get_video_comments(youtube, video_id))
|
124 |
+
|
|
|
125 |
with st.spinner("π§ Analyzing comments and generating plan..."):
|
126 |
content_plan = analyze_comments(all_comments)
|
127 |
st.session_state.content_plan = content_plan
|
|
|
134 |
if st.session_state.content_plan:
|
135 |
st.markdown("## π
10-Day Content Plan")
|
136 |
st.success("Here's your personalized content strategy based on audience insights!")
|
137 |
+
|
|
|
138 |
start_date = datetime.now()
|
139 |
for day in range(10):
|
140 |
current_date = start_date + timedelta(days=day)
|
141 |
+
with st.expander(f"Day {day+1} - {current_date.strftime('%b %d')}", expanded=True if day == 0 else False):
|
142 |
+
plan_lines = st.session_state.content_plan.split("\n")
|
|
|
|
|
|
|
143 |
start_index = day * 5
|
144 |
if start_index + 4 < len(plan_lines):
|
145 |
+
st.markdown(f"""
|
146 |
+
<div class="day-card">
|
147 |
+
<h3>{plan_lines[start_index] if start_index < len(plan_lines) else 'No Topic'}</h3>
|
148 |
+
<p>π― Objective: {plan_lines[start_index+1] if start_index+1 < len(plan_lines) else 'N/A'}</p>
|
149 |
+
<p>πΉ Format: {plan_lines[start_index+2] if start_index+2 < len(plan_lines) else 'N/A'}</p>
|
150 |
+
<p>π‘ Engagement Strategy: {plan_lines[start_index+3] if start_index+3 < len(plan_lines) else 'N/A'}</p>
|
151 |
+
<p>π Success Metrics: {plan_lines[start_index+4] if start_index+4 < len(plan_lines) else 'N/A'}</p>
|
152 |
+
</div>
|
153 |
+
""", unsafe_allow_html=True)
|
|
|
154 |
|
|
|
|
|
155 |
st.download_button(
|
156 |
label="π₯ Download Full Plan",
|
157 |
data=st.session_state.content_plan,
|
|
|
159 |
mime="text/plain"
|
160 |
)
|
161 |
|
|
|
162 |
with st.expander("βΉοΈ How to use this tool"):
|
163 |
st.markdown("""
|
164 |
1. **Get API Keys**:
|
165 |
- YouTube Data API: [Get it here](https://console.cloud.google.com/)
|
166 |
- Gemini API: [Get it here](https://makersuite.google.com/)
|
|
|
167 |
2. **Enter Channel URL**:
|
|
|
|
|
168 |
3. **Adjust Settings**:
|
|
|
|
|
169 |
4. **Generate Plan**:
|
170 |
+
5. **Download & Track Results**.
|
171 |
+
""")
|
172 |
+
|
|
|
|