Fix duplicate video embed when youtube_url is empty string
- Add .trim() checks to all video source conditions - Prevents rendering empty youtube_url as valid video - Fixes double embed card display issue - Update sidebar icon check to use optional chaining with trim 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
34086
Live Zoom - Diskusi Cara Jual Jasa via Online.json
Normal file
34086
Live Zoom - Diskusi Cara Jual Jasa via Online.json
Normal file
File diff suppressed because one or more lines are too long
196
Live Zoom - Diskusi Cara Jual Jasa via Online.md
Normal file
196
Live Zoom - Diskusi Cara Jual Jasa via Online.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# E-Course Curriculum: Cara Jual Jasa via Online
|
||||
## How to Sell Services Online
|
||||
|
||||
**Source Video**: Live Zoom - Diskusi Cara Jual Jasa via Online
|
||||
**Duration**: 2 hours 30 minutes
|
||||
**Speaker**: Dwindi Ramadhana
|
||||
**Language**: Indonesian
|
||||
|
||||
---
|
||||
|
||||
## Course Overview
|
||||
|
||||
This comprehensive course teaches you how to successfully sell services online, with a focus on web development and plugin installation services. Learn from real-world examples, case studies, and practical strategies for building a sustainable service business.
|
||||
|
||||
---
|
||||
|
||||
## Course Structure
|
||||
|
||||
### Chapter 1: Pengenalan & Temukan Pasar [00:00:00 - 00:15:00]
|
||||
|
||||
- [00:04:40 - 00:08:20] Pembukaan & Setup Diskusi
|
||||
- Pengenalan topik cara jual jasa via online
|
||||
- Format diskusi kasual dan interaktif
|
||||
- Ruang lingkup: jasa pembuatan website & instalasi plugin
|
||||
|
||||
- [00:08:20 - 00:15:00] Cara Menemukan Market Anda
|
||||
- Pentingnya bergabung dengan komunitas (Telegram, grup Facebook, forum)
|
||||
- Kisah Abdurrahman bin Auf mencari pasar terlebih dahulu
|
||||
- Mengamati pola komunitas dan masalah umum
|
||||
- Jangan takut untuk bergabung dan mengamati
|
||||
|
||||
### Chapter 2: Discovery Masalah & Eksplorasi [00:15:00 - 00:30:00]
|
||||
|
||||
- [00:15:00 - 00:21:30] Identifikasi Masalah yang Sering Muncul
|
||||
- Menemukan masalah yang sering disebutkan di komunitas
|
||||
- Berpindah dari komunitas ke chat pribadi (Japri)
|
||||
- Membangun jejaring melalui manfaat timbal balik
|
||||
- Fase eksplorasi: trial and error di situs demo
|
||||
|
||||
- [00:21:30 - 00:30:00] Dasar Personal Branding
|
||||
- Melakukan hal-hal bermanfaat untuk orang lain
|
||||
- Pamerkan pengetahuan Anda (jangan disembunyikan)
|
||||
- Memahami funnel AIDA (Awareness, Interest, Desire, Action)
|
||||
- Cold market (grup) vs Warm market (chat pribadi) vs Hot market (klien)
|
||||
|
||||
### Chapter 3: Akuisisi & Manajemen Klien [00:30:00 - 00:45:00]
|
||||
|
||||
- [00:30:00 - 00:35:00] Psikologi Klien
|
||||
- Aturan Klien: Semua orang ingin mengeluh
|
||||
- Semua orang berpikir masalah mereka adalah yang terpenting
|
||||
- Semua orang ingin didengar
|
||||
- Empat kunci: Simak, Rekap, Offer, Deal
|
||||
|
||||
- [00:35:00 - 00:45:00] Strategi Harga
|
||||
- Fokus pada effort Anda, bukan budget klien
|
||||
- Studi kasus: Penetapan harga Elementor Pro
|
||||
- Memahami nilai vs harga komoditas
|
||||
- Contoh: Menjual lisensi Elementor Pro dengan harga Rp1.000/tahun
|
||||
|
||||
### Chapter 4: Deliver Layanan & Relasi Klien [00:45:00 - 01:00:00]
|
||||
|
||||
- [00:45:00 - 00:50:00] Prioritas Pelayanan Klien
|
||||
- Prioritas 1: Respon cepat (jadilah responsif)
|
||||
- Prioritas 2: Selesaikan proyek
|
||||
- Prioritas 3: Eksplorasi hal baru
|
||||
- Pentingnya keberadaan dan ketersediaan
|
||||
|
||||
- [00:50:00 - 01:00:00] Keberlanjutan Bisnis
|
||||
- Mengapa harga rendah bermasalah (undercutting)
|
||||
- Dua risiko utama: persaingan dan komoditisasi skill
|
||||
- Transisi dari layanan dasar ke solusi spesialis
|
||||
|
||||
### Chapter 5: Sesi Tanya Jawab - Bagian 1 [01:00:00 - 01:15:00]
|
||||
|
||||
- [01:00:00 - 01:07:00] Implementasi Funnel
|
||||
- Cara menerapkan AIDA dalam praktik
|
||||
- Personal branding melalui partisipasi komunitas
|
||||
- Menghadapi keberadaan komersial vs membantu di komunitas
|
||||
|
||||
- [01:07:00 - 01:15:00] Strategi Pemasaran
|
||||
- Pemasaran afiliasi (model komisi 30%)
|
||||
- Perbandingan pemasaran organik vs berbayar
|
||||
- Contoh nyata keberhasilan afiliasi
|
||||
|
||||
### Chapter 6: Harga Lanjutan & Negosiasi [01:15:00 - 01:30:00]
|
||||
|
||||
- [01:15:00 - 01:22:00] Pendekatan Berorientasi Solusi
|
||||
- Fokus pada solusi, bukan hanya menjual
|
||||
- Studi kasus: Kustomisasi JetEngine
|
||||
- Kapan merekomendasikan alternatif vs jasa Anda
|
||||
|
||||
- [01:22:00 - 01:30:00] Psikologi Harga
|
||||
- Hindari harga terperinci (contoh 8 poin vs 4 poin)
|
||||
- Memahami keberatan klien
|
||||
- Jelas tentang ruang lingkup sebelum memberi harga
|
||||
|
||||
### Chapter 7: Manajemen Proyek & Kontrak [01:30:00 - 01:45:00]
|
||||
|
||||
- [01:30:00 - 01:37:00] Kejujuran dalam Relasi Klien
|
||||
- Bersikap langsung tentang apa yang mungkin
|
||||
- Contoh: Masalah kompatibilitas tema
|
||||
- Menghindari janji yang berlebihan
|
||||
|
||||
- [01:37:00 - 01:45:00] Esensial Kontrak
|
||||
- Kapan menggunakan kontrak (proyek besar)
|
||||
- Format kontrak sederhana (1-2 halaman)
|
||||
- Syarat dan ketentuan yang jelas
|
||||
- Kebijakan uang muka
|
||||
|
||||
### Chapter 8: Pengembangan Diri & Etos Kerja [01:45:00 - 02:00:00]
|
||||
|
||||
- [01:45:00 - 01:53:00] Membangun Skill Anda
|
||||
- Mindset workaholic vs kerja berkelanjutan
|
||||
- Belajar dari rasa ingin tahu (contoh PSP)
|
||||
- Eksplorasi dan pembelajaran terus-menerus
|
||||
|
||||
- [01:53:00 - 02:00:00] Alur Kerja Manajemen Proyek
|
||||
- Strategi alokasi waktu
|
||||
- Mengganggu saat menangani proyek
|
||||
- Menyeimbangkan banyak klien
|
||||
|
||||
### Chapter 9: Sesi Tanya Jawab - Bagian 2 [02:00:00 - 02:15:00]
|
||||
|
||||
- [02:00:00 - 02:07:00] Lisensi Plugin & Etika
|
||||
- Plugin GPL dari vendor (Envato, dll)
|
||||
- Risiko plugin nulled/bajakan
|
||||
- Mengunduh dari sumber resmi
|
||||
- Edukasi klien tentang lisensi
|
||||
|
||||
- [02:07:00 - 02:15:00] Funnel AIDA Lebih Dalam
|
||||
- Bagaimana eksplorasi memenuhi funnel
|
||||
- Menciptakan kesadaran melalui kebermanfaatan
|
||||
- Memindahkan prospek melalui tahapan
|
||||
|
||||
### Chapter 10: Tanya Jawab Final & Alat [02:15:00 - 02:30:18]
|
||||
|
||||
- [02:15:00 - 02:22:00] Pertanyaan Umum Layanan
|
||||
- Etika pemasaran (hindari spam)
|
||||
- Follow-up tanpa mengganggu
|
||||
- Perjalanan belajar teknis
|
||||
|
||||
- [02:22:00 - 02:30:00] Alat yang Direkomendasikan
|
||||
- Lingkungan pengembangan lokal (LocalWP)
|
||||
- Rekomendasi hosting
|
||||
- Sumber belajar
|
||||
- Penutup dan perpisahan
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
### Prinsip Utama
|
||||
1. **Komunitas Dulu**: Bergabunglah dengan komunitas sebelum mencoba menjual apa pun
|
||||
2. **Fokus pada Masalah**: Identifikasi masalah yang sering disebutkan
|
||||
3. **Eksplorasi & Belajar**: Gunakan situs demo untuk trial and error
|
||||
4. **Bangun Kepercayaan**: Bagikan pengetahuan dengan gratis, jangan sembunyikan
|
||||
5. **Pahami Psikologi**: Klien ingin didengar dan dipahami
|
||||
6. **Harga Berbasis Nilai**: Harga berdasarkan effort dan nilai Anda, bukan budget klien
|
||||
7. **Jadilah Responsif**: Waktu respon cepat sangat krusial
|
||||
8. **Berorientasi Solusi**: Fokus pada memecahkan masalah, bukan hanya menjual jasa
|
||||
9. **Kejujuran Menang**: Bersikap langsung tentang keterbatasan dan kemungkinan
|
||||
10. **Pembelajaran Terus-menerus**: Selalu eksplorasi alat dan teknik baru
|
||||
|
||||
### Studi Kasus Utama
|
||||
- Penjualan Elementor Pro (100+ domain)
|
||||
- Negosiasi harga dengan klien
|
||||
- Membangun personal branding di komunitas
|
||||
- Transisi dari freelancer ke pemilik produk
|
||||
|
||||
---
|
||||
|
||||
## Sumber Tambahan
|
||||
|
||||
### Tools yang Disebutkan
|
||||
- LocalWP (local development)
|
||||
- Elementor Pro
|
||||
- JetEngine
|
||||
- Sejoli Shortcode
|
||||
|
||||
### Komunitas yang Direkomendasikan
|
||||
- Grup WordPress Indonesia
|
||||
- Grup Facebook seputar web development
|
||||
- Forum Telegram untuk diskusi teknis
|
||||
|
||||
---
|
||||
|
||||
## Catatan untuk Kursus
|
||||
|
||||
Kurikulum ini dirancang untuk memecah video 2.5 jam menjadi pelajaran yang mudah dicerna. Setiap chapter berfokus pada topik tertentu dengan durasi 15 menit, ideal untuk pembelajaran online. Materi mencakup contoh nyata, studi kasus, dan strategi praktis yang dapat langsung diterapkan.
|
||||
|
||||
---
|
||||
|
||||
**Dokumen ini dibuat berdasarkan transcript video asli**
|
||||
**Total Durasi Video**: 2 jam 30 menit 18 detik
|
||||
**Jumlah Chapter**: 10
|
||||
**Jumlah Pelajaran**: 20
|
||||
179
analyze_final.py
Normal file
179
analyze_final.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze video transcript to identify topics and create chapter divisions.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from datetime import timedelta
|
||||
|
||||
def seconds_to_timestamp(seconds):
|
||||
"""Convert seconds to readable timestamp."""
|
||||
total_seconds = int(float(seconds))
|
||||
hours, remainder = divmod(total_seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
|
||||
def load_transcript(file_path):
|
||||
"""Load JSON transcript file."""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
def extract_segments(data):
|
||||
"""Extract transcript segments with timestamps."""
|
||||
segments = []
|
||||
|
||||
for track in data[0]['tracks']:
|
||||
if 'transcript' in track:
|
||||
for item in track['transcript']:
|
||||
start = float(item.get('start', 0))
|
||||
dur = float(item.get('dur', 0))
|
||||
text = item.get('text', '').strip()
|
||||
|
||||
if text and text != '\n':
|
||||
segments.append({
|
||||
'start': start,
|
||||
'end': start + dur,
|
||||
'text': text
|
||||
})
|
||||
|
||||
# Sort by start time
|
||||
segments.sort(key=lambda x: x['start'])
|
||||
return segments
|
||||
|
||||
def extract_keywords(text):
|
||||
"""Extract key topics from text."""
|
||||
keywords = {
|
||||
'Market & Community': ['market', 'pasar', 'grup', 'komunitas', 'telegram', 'facebook', 'forum'],
|
||||
'Problem Finding': ['masalah', 'problem', 'kesulitan', 'permasalahan', 'error', 'bermasalah'],
|
||||
'Exploration': ['explor', 'coba', 'trial', 'nyoba', 'eksplor', 'explore'],
|
||||
'Personal Branding': ['branding', 'personal branding', 'show off', 'image', 'eksistensi'],
|
||||
'AIDA/Funnel': ['aida', 'awareness', 'interest', 'desire', 'action', 'funel', 'funnel'],
|
||||
'Trust': ['trust', 'percaya', 'kepercayaan'],
|
||||
'Clients': ['klien', 'client', 'pelanggan', 'customer'],
|
||||
'Pricing': ['harga', 'price', 'bayar', 'budget', 'rp', 'juta', 'ribu', 'dibayar'],
|
||||
'Negotiation': ['tawar', 'negosiasi', 'deal'],
|
||||
'Services': ['jasa', 'service', 'website', 'plugin', 'elementor', 'instal'],
|
||||
'Cold/Warm/Hot Market': ['cold market', 'warm market', 'hot market', 'dingin', 'hangat'],
|
||||
'Network': ['network', 'jaringan', 'koneksi', 'hubungan'],
|
||||
'Sharing': ['sharing', 'share', 'bagi'],
|
||||
'Products': ['produk', 'product', 'template'],
|
||||
'Japri': ['japri', 'private', 'chat pribadi'],
|
||||
}
|
||||
|
||||
found = []
|
||||
text_lower = text.lower()
|
||||
|
||||
for topic, kw_list in keywords.items():
|
||||
count = sum(1 for kw in kw_list if kw.lower() in text_lower)
|
||||
if count > 0:
|
||||
found.append((topic, count))
|
||||
|
||||
return sorted(found, key=lambda x: x[1], reverse=True)
|
||||
|
||||
def analyze_video():
|
||||
"""Analyze the video transcript."""
|
||||
file_path = "/Users/dwindown/CascadeProjects/MeetDwindiCom/access-hub/Live Zoom - Diskusi Cara Jual Jasa via Online.json"
|
||||
|
||||
print("="*80)
|
||||
print("VIDEO TRANSCRIPT ANALYSIS")
|
||||
print("Cara Jual Jasa via Online (How to Sell Services Online)")
|
||||
print("="*80)
|
||||
print()
|
||||
|
||||
data = load_transcript(file_path)
|
||||
segments = extract_segments(data)
|
||||
|
||||
print(f"Total segments: {len(segments)}")
|
||||
|
||||
if not segments:
|
||||
print("No segments found!")
|
||||
return
|
||||
|
||||
total_duration = segments[-1]['end']
|
||||
print(f"Total duration: {seconds_to_timestamp(total_duration)} ({total_duration/60:.1f} minutes)\n")
|
||||
|
||||
# Create time-based groups every 5 minutes
|
||||
print("="*80)
|
||||
print("CONTENT BREAKDOWN BY 5-MINUTE INTERVALS")
|
||||
print("="*80)
|
||||
print()
|
||||
|
||||
window = 300 # 5 minutes
|
||||
current_time = 0
|
||||
section_num = 1
|
||||
|
||||
while current_time < total_duration:
|
||||
window_end = min(current_time + window, total_duration)
|
||||
window_segments = [s for s in segments
|
||||
if current_time <= s['start'] < window_end]
|
||||
|
||||
if window_segments:
|
||||
# Combine text
|
||||
combined_text = ' '.join([s['text'] for s in window_segments])
|
||||
|
||||
# Extract keywords
|
||||
keywords = extract_keywords(combined_text)
|
||||
|
||||
print(f"Section {section_num}: {seconds_to_timestamp(current_time)} - {seconds_to_timestamp(window_end)}")
|
||||
print("-" * 80)
|
||||
|
||||
# Show first 400 characters as preview
|
||||
preview = combined_text[:400]
|
||||
print(f"Content: {preview}...")
|
||||
print()
|
||||
|
||||
if keywords:
|
||||
print("Key topics detected:")
|
||||
for topic, count in keywords[:7]:
|
||||
print(f" • {topic}: {count} mentions")
|
||||
else:
|
||||
print("Key topics: (transition/break section)")
|
||||
|
||||
print()
|
||||
print()
|
||||
|
||||
section_num += 1
|
||||
|
||||
current_time = window_end
|
||||
|
||||
# Now create suggested chapters based on content analysis
|
||||
print("\n")
|
||||
print("="*80)
|
||||
print("SUGGESTED CHAPTER STRUCTURE")
|
||||
print("="*80)
|
||||
print()
|
||||
|
||||
# Create larger 15-minute groups for chapter suggestions
|
||||
chapter_window = 900 # 15 minutes
|
||||
current_time = 0
|
||||
chapter_num = 1
|
||||
|
||||
while current_time < total_duration:
|
||||
chapter_end = min(current_time + chapter_window, total_duration)
|
||||
chapter_segments = [s for s in segments
|
||||
if current_time <= s['start'] < chapter_end]
|
||||
|
||||
if chapter_segments:
|
||||
combined_text = ' '.join([s['text'] for s in chapter_segments])
|
||||
keywords = extract_keywords(combined_text)
|
||||
|
||||
# Get top 3 keywords for chapter title
|
||||
main_topics = [kw[0] for kw in keywords[:3]]
|
||||
|
||||
print(f"Chapter {chapter_num}: {seconds_to_timestamp(current_time)} - {seconds_to_timestamp(chapter_end)}")
|
||||
print(f"Main topics: {', '.join(main_topics)}")
|
||||
|
||||
# Show first 300 chars
|
||||
preview = combined_text[:300].replace('\n', ' ')
|
||||
print(f"Preview: {preview}...")
|
||||
print()
|
||||
print()
|
||||
|
||||
chapter_num += 1
|
||||
|
||||
current_time = chapter_end
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_video()
|
||||
167
analyze_transcript.py
Normal file
167
analyze_transcript.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze video transcript to identify topics and create chapter divisions.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from datetime import timedelta
|
||||
|
||||
def seconds_to_timestamp(seconds):
|
||||
"""Convert seconds to readable timestamp."""
|
||||
td = timedelta(seconds=float(seconds))
|
||||
hours, remainder = divmod(td.seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
|
||||
def load_transcript(file_path):
|
||||
"""Load JSON transcript file."""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
def extract_text_with_timestamps(data):
|
||||
"""Extract text segments with timestamps."""
|
||||
segments = []
|
||||
for entry in data:
|
||||
if 'events' in entry:
|
||||
for event in entry['events']:
|
||||
if 'segs' in event:
|
||||
for seg in event['segs']:
|
||||
if 'utf8' in seg:
|
||||
segments.append({
|
||||
'start': float(event.get('tStartMs', 0)) / 1000,
|
||||
'text': seg['utf8']
|
||||
})
|
||||
return segments
|
||||
|
||||
def clean_text(text):
|
||||
"""Clean transcript text."""
|
||||
# Remove extra whitespace
|
||||
text = ' '.join(text.split())
|
||||
return text
|
||||
|
||||
def identify_keywords(text):
|
||||
"""Identify important keywords in Indonesian business context."""
|
||||
keywords = {
|
||||
'market': ['market', 'pasar', 'grup', 'komunitas', 'community'],
|
||||
'problem': ['masalah', 'problem', 'kesulitan', 'error', 'gagal'],
|
||||
'branding': ['branding', 'personal branding', 'image', 'citra'],
|
||||
'funnel': ['funel', 'funnel', 'awareness', 'desire', 'action'],
|
||||
'client': ['klien', 'client', 'pelanggan', 'customer'],
|
||||
'price': ['harga', 'price', 'bayar', 'paid', 'invoice'],
|
||||
'negotiation': ['tawar', 'negosiasi', 'deal'],
|
||||
'service': ['jasa', 'service', 'website', 'plugin'],
|
||||
'exploration': ['explore', 'eksplor', 'coba', 'trial'],
|
||||
'network': ['network', 'jaringan', 'koneksi'],
|
||||
'sharing': ['sharing', 'share', 'bagi'],
|
||||
'product': ['produk', 'product', 'template', 'plugin'],
|
||||
'trust': ['trust', 'percaya', 'kepercayaan'],
|
||||
'sales': ['jual', 'sales', 'closing'],
|
||||
}
|
||||
return keywords
|
||||
|
||||
def analyze_structure():
|
||||
"""Analyze the transcript structure."""
|
||||
file_path = "/Users/dwindown/CascadeProjects/MeetDwindiCom/access-hub/Live Zoom - Diskusi Cara Jual Jasa via Online.json"
|
||||
|
||||
print("Loading transcript...")
|
||||
data = load_transcript(file_path)
|
||||
|
||||
print("Extracting segments...")
|
||||
segments = extract_text_with_timestamps(data)
|
||||
|
||||
print(f"\nTotal segments: {len(segments)}")
|
||||
|
||||
# Get total duration
|
||||
if segments:
|
||||
total_duration = segments[-1]['start']
|
||||
print(f"Total duration: {seconds_to_timestamp(total_duration)} ({total_duration/60:.1f} minutes)")
|
||||
|
||||
# Sample segments at different intervals
|
||||
print("\n=== SAMPLING SEGMENTS AT KEY INTERVALS ===\n")
|
||||
|
||||
sample_points = [0, 300, 600, 900, 1200, 1500, 1800, 2100, 2400, 2700, 3000,
|
||||
3300, 3600, 3900, 4200, 4500, 4800, 5100, 5400, 5700, 6000,
|
||||
6300, 6600, 6900, 7200, 7500, 7800, 8100, 8400, 8700, 9000]
|
||||
|
||||
for i, target_time in enumerate(sample_points):
|
||||
# Find closest segment
|
||||
closest = None
|
||||
min_diff = float('inf')
|
||||
|
||||
for seg in segments:
|
||||
diff = abs(seg['start'] - target_time)
|
||||
if diff < min_diff:
|
||||
min_diff = diff
|
||||
closest = seg
|
||||
|
||||
if closest and min_diff < 60: # Within 1 minute
|
||||
text = clean_text(closest['text'])
|
||||
if len(text) > 50: # Only meaningful segments
|
||||
print(f"[{seconds_to_timestamp(closest['start'])}]")
|
||||
print(f"{text[:200]}...")
|
||||
print()
|
||||
|
||||
# Look for transition phrases
|
||||
print("\n=== LOOKING FOR TRANSITION PHRASES ===\n")
|
||||
|
||||
transition_phrases = [
|
||||
'oke',
|
||||
'jadi',
|
||||
'nah',
|
||||
'kemudian',
|
||||
'selanjutnya',
|
||||
'setelah itu',
|
||||
'sekarang',
|
||||
'selanjutnya',
|
||||
'lanjut',
|
||||
'terus',
|
||||
'setelah',
|
||||
'nah sekarang',
|
||||
'oke jadi',
|
||||
'jadi sekarang',
|
||||
'nah kalau',
|
||||
]
|
||||
|
||||
# Look for sections mentioning main topics
|
||||
print("\n=== SEARCHING FOR TOPIC MENTIONS ===\n")
|
||||
|
||||
topic_keywords = {
|
||||
'Market/Community': ['market', 'pasar', 'grup', 'komunitas', 'community', 'telegram', 'facebook'],
|
||||
'Problem Finding': ['masalah', 'problem', 'kesulitan', 'permasalahan'],
|
||||
'Personal Branding': ['branding', 'personal branding', 'show off'],
|
||||
'AIDA Funnel': ['aida', 'awareness', 'interest', 'desire', 'action', 'funel', 'funnel'],
|
||||
'Getting Clients': ['klien', 'client', 'calon klien'],
|
||||
'Pricing/Payment': ['harga', 'bayar', 'budget', 'invoice', 'price'],
|
||||
'Negotiation': ['tawar', 'negosiasi', 'deal'],
|
||||
'Trust Building': ['trust', 'percaya', 'kepercayaan'],
|
||||
'Services/Products': ['jasa', 'service', 'produk', 'elementor', 'plugin', 'website'],
|
||||
'Cold/Warm/Hot Market': ['cold market', 'warm market', 'hot market'],
|
||||
}
|
||||
|
||||
# Find segments grouped by time periods
|
||||
print("\n=== CONTENT BY TIME PERIODS (every 10 minutes) ===\n")
|
||||
|
||||
period = 600 # 10 minutes in seconds
|
||||
current_period = 0
|
||||
|
||||
while current_period < total_duration:
|
||||
period_end = current_period + period
|
||||
period_segments = [s for s in segments if current_period <= s['start'] < period_end]
|
||||
|
||||
if period_segments:
|
||||
# Combine text from this period
|
||||
period_text = ' '.join([clean_text(s['text']) for s in period_segments[:20]]) # First 20 segments
|
||||
period_text = period_text[:500] # First 500 chars
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"PERIOD: {seconds_to_timestamp(current_period)} - {seconds_to_timestamp(period_end)}")
|
||||
print(f"{'='*70}")
|
||||
print(period_text)
|
||||
print()
|
||||
|
||||
current_period = period_end
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_structure()
|
||||
162
analyze_transcript2.py
Normal file
162
analyze_transcript2.py
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze video transcript to identify topics and create chapter divisions.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from datetime import timedelta
|
||||
|
||||
def seconds_to_timestamp(seconds):
|
||||
"""Convert seconds to readable timestamp."""
|
||||
td = timedelta(seconds=float(seconds))
|
||||
total_seconds = int(td.total_seconds())
|
||||
hours, remainder = divmod(total_seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
|
||||
def load_transcript(file_path):
|
||||
"""Load JSON transcript file."""
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
|
||||
def extract_transcript_segments(data):
|
||||
"""Extract all transcript segments with timestamps."""
|
||||
segments = []
|
||||
|
||||
# The structure has a 'tracks' key
|
||||
if 'tracks' in data[0]:
|
||||
for track in data[0]['tracks']:
|
||||
if track['kind'] == 'asr': # Automatic Speech Recognition
|
||||
for event in track['events']:
|
||||
start_time = event.get('tStartMs', 0) / 1000
|
||||
duration = event.get('dDurationMs', 0) / 1000
|
||||
|
||||
# Extract text from segments
|
||||
text_parts = []
|
||||
if 'segs' in event:
|
||||
for seg in event['segs']:
|
||||
if 'utf8' in seg:
|
||||
text_parts.append(seg['utf8'])
|
||||
|
||||
text = ' '.join(text_parts)
|
||||
if text.strip():
|
||||
segments.append({
|
||||
'start': start_time,
|
||||
'end': start_time + duration,
|
||||
'text': text
|
||||
})
|
||||
|
||||
return segments
|
||||
|
||||
def group_by_time_window(segments, window_seconds=600):
|
||||
"""Group segments into time windows for analysis."""
|
||||
groups = []
|
||||
current_time = 0
|
||||
|
||||
while current_time < segments[-1]['end']:
|
||||
window_end = current_time + window_seconds
|
||||
window_segments = [s for s in segments
|
||||
if current_time <= s['start'] < window_end]
|
||||
|
||||
if window_segments:
|
||||
combined_text = ' '.join([s['text'] for s in window_segments])
|
||||
groups.append({
|
||||
'start': current_time,
|
||||
'end': window_end,
|
||||
'segments': window_segments,
|
||||
'text': combined_text
|
||||
})
|
||||
|
||||
current_time = window_end
|
||||
|
||||
return groups
|
||||
|
||||
def extract_keywords(text):
|
||||
"""Extract key topics from text."""
|
||||
keywords = {
|
||||
'Market & Community': ['market', 'pasar', 'grup', 'komunitas', 'telegram', 'facebook', 'forum'],
|
||||
'Problem Finding': ['masalah', 'problem', 'kesulitan', 'permasalahan', 'error'],
|
||||
'Exploration': ['explor', 'coba', 'trial', 'nyoba', 'eksplor'],
|
||||
'Personal Branding': ['branding', 'personal branding', 'show off', 'image'],
|
||||
'AIDA/Funnel': ['aida', 'awareness', 'interest', 'desire', 'action', 'funel', 'funnel'],
|
||||
'Trust': ['trust', 'percaya', 'kepercayaan'],
|
||||
'Clients': ['klien', 'client', 'pelanggan', 'customer'],
|
||||
'Pricing': ['harga', 'price', 'bayar', 'budget', 'rp', 'juta', 'ribu'],
|
||||
'Negotiation': ['tawar', 'negosiasi', 'deal'],
|
||||
'Services': ['jasa', 'service', 'website', 'plugin', 'elementor', 'instal'],
|
||||
'Cold/Warm/Hot Market': ['cold', 'warm', 'hot', 'dingin', 'hangat'],
|
||||
'Network': ['network', 'jaringan', 'koneksi', 'hubungan'],
|
||||
'Sharing': ['sharing', 'share', 'bagi'],
|
||||
'Products': ['produk', 'product', 'template'],
|
||||
}
|
||||
|
||||
found = []
|
||||
text_lower = text.lower()
|
||||
|
||||
for topic, kw_list in keywords.items():
|
||||
count = sum(1 for kw in kw_list if kw.lower() in text_lower)
|
||||
if count > 0:
|
||||
found.append((topic, count))
|
||||
|
||||
return sorted(found, key=lambda x: x[1], reverse=True)
|
||||
|
||||
def identify_main_topics():
|
||||
"""Identify main topics throughout the video."""
|
||||
file_path = "/Users/dwindown/CascadeProjects/MeetDwindiCom/access-hub/Live Zoom - Diskusi Cara Jual Jasa via Online.json"
|
||||
|
||||
print("Loading transcript...")
|
||||
data = load_transcript(file_path)
|
||||
|
||||
print("Extracting segments...")
|
||||
segments = extract_transcript_segments(data)
|
||||
|
||||
print(f"Total segments: {len(segments)}")
|
||||
|
||||
if not segments:
|
||||
print("No segments found!")
|
||||
return
|
||||
|
||||
total_duration = segments[-1]['end']
|
||||
print(f"Total duration: {seconds_to_timestamp(total_duration)} ({total_duration/60:.1f} minutes)")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("ANALYZING CONTENT IN 10-MINUTE INTERVALS")
|
||||
print("="*80 + "\n")
|
||||
|
||||
# Group by 10-minute windows
|
||||
groups = group_by_time_window(segments, window_seconds=600)
|
||||
|
||||
for i, group in enumerate(groups, 1):
|
||||
print(f"\n{'='*80}")
|
||||
print(f"SECTION {i}: {seconds_to_timestamp(group['start'])} - {seconds_to_timestamp(group['end'])}")
|
||||
print(f"{'='*80}")
|
||||
|
||||
# Get first 500 chars for preview
|
||||
preview = group['text'][:500]
|
||||
print(f"\nContent Preview:\n{preview}...")
|
||||
|
||||
# Extract keywords
|
||||
keywords = extract_keywords(group['text'])
|
||||
if keywords:
|
||||
print(f"\nMain Topics:")
|
||||
for topic, count in keywords[:5]:
|
||||
print(f" - {topic}: {count} mentions")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("DETAILED BREAKDOWN (5-minute intervals for first hour)")
|
||||
print("="*80 + "\n")
|
||||
|
||||
# More detailed for first hour
|
||||
detailed_groups = group_by_time_window(segments[:int(len(segments)*0.4)], window_seconds=300)
|
||||
|
||||
for i, group in enumerate(detailed_groups, 1):
|
||||
print(f"\n--- {seconds_to_timestamp(group['start'])} - {seconds_to_timestamp(group['end'])} ---")
|
||||
|
||||
# Get text summary
|
||||
text_summary = group['text'][:300]
|
||||
print(f"{text_summary}...")
|
||||
|
||||
if __name__ == "__main__":
|
||||
identify_main_topics()
|
||||
@@ -247,13 +247,13 @@ export default function Bootcamp() {
|
||||
// Get video based on product's active source
|
||||
const getVideoSource = () => {
|
||||
if (activeSource === 'youtube') {
|
||||
if (lesson.youtube_url) {
|
||||
if (lesson.youtube_url && lesson.youtube_url.trim()) {
|
||||
return {
|
||||
type: 'youtube',
|
||||
url: lesson.youtube_url,
|
||||
embedUrl: getYouTubeEmbedUrl(lesson.youtube_url)
|
||||
};
|
||||
} else if (lesson.video_url) {
|
||||
} else if (lesson.video_url && lesson.video_url.trim()) {
|
||||
// Fallback to old video_url for backward compatibility
|
||||
return {
|
||||
type: 'youtube',
|
||||
@@ -262,24 +262,24 @@ export default function Bootcamp() {
|
||||
};
|
||||
} else {
|
||||
// Fallback to embed if YouTube not available
|
||||
return lesson.embed_code ? {
|
||||
return lesson.embed_code && lesson.embed_code.trim() ? {
|
||||
type: 'embed',
|
||||
html: lesson.embed_code
|
||||
} : null;
|
||||
}
|
||||
} else {
|
||||
if (lesson.embed_code) {
|
||||
if (lesson.embed_code && lesson.embed_code.trim()) {
|
||||
return {
|
||||
type: 'embed',
|
||||
html: lesson.embed_code
|
||||
};
|
||||
} else {
|
||||
// Fallback to YouTube if embed not available
|
||||
return lesson.youtube_url ? {
|
||||
return lesson.youtube_url && lesson.youtube_url.trim() ? {
|
||||
type: 'youtube',
|
||||
url: lesson.youtube_url,
|
||||
embedUrl: getYouTubeEmbedUrl(lesson.youtube_url)
|
||||
} : lesson.video_url ? {
|
||||
} : lesson.video_url && lesson.video_url.trim() ? {
|
||||
type: 'youtube',
|
||||
url: lesson.video_url,
|
||||
embedUrl: getYouTubeEmbedUrl(lesson.video_url)
|
||||
@@ -360,7 +360,7 @@ export default function Bootcamp() {
|
||||
>
|
||||
{isCompleted ? (
|
||||
<Check className="w-4 h-4 shrink-0 text-accent" />
|
||||
) : lesson.video_url || lesson.youtube_url || lesson.embed_code ? (
|
||||
) : (lesson.video_url?.trim() || lesson.youtube_url?.trim() || lesson.embed_code?.trim()) ? (
|
||||
<Play className="w-4 h-4 shrink-0" />
|
||||
) : (
|
||||
<BookOpen className="w-4 h-4 shrink-0" />
|
||||
|
||||
Reference in New Issue
Block a user