commit abe170a1f87e0063b8db3166d558c314c1b49221 Author: salvacybersec Date: Thu Nov 13 03:25:21 2025 +0300 first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8847527 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +*.db +*.sqlite +*.sqlite3 +.git/ +.gitignore +*.md +!README.md +.vscode/ +.idea/ +*.log +.DS_Store +data/*.db +output/*.xml + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43661d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +*.egg-info/ +dist/ +build/ + +# Virtual environments +env/ +venv/ +ENV/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Database +*.db +*.sqlite +*.sqlite3 +data/*.db + +# Output +output/*.xml + +# Logs +*.log + +# OS +.DS_Store +Thumbs.db + +# Config (opsiyonel - hassas bilgiler varsa) +# config/config.yaml + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..43f6219 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.10-slim + +# Çalışma dizinini ayarla +WORKDIR /app + +# Sistem bağımlılıkları +RUN apt-get update && apt-get install -y \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Python bağımlılıklarını kopyala ve kur +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# SpaCy modelini indir +RUN python -m spacy download en_core_web_sm + +# Uygulama kodunu kopyala +COPY . . + +# Veri dizinlerini oluştur +RUN mkdir -p data output + +# Varsayılan komut (web server) +CMD ["python", "app.py"] + diff --git a/README.md b/README.md new file mode 100644 index 0000000..fffa5b5 --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# YouTube Transcript RSS Feed Generator + +YouTube video transkriptlerini otomatik olarak çıkarıp, tam metin içeren RSS feed'ine dönüştüren Docker tabanlı sistem. + +## Özellikler + +- ✅ **RSS-Bridge benzeri URL template** - Kanal adı/linki ile direkt feed +- ✅ **Web Server Modu** - Flask ile RESTful API +- ✅ RSS-Bridge entegrasyonu (100+ video desteği) +- ✅ Async rate limiting (AIOLimiter) +- ✅ SpaCy ile Sentence Boundary Detection +- ✅ SQLite veritabanı (durum yönetimi) +- ✅ Full-text RSS feed (``) +- ✅ Channel handle → Channel ID otomatik dönüştürme +- ✅ Atom ve RSS format desteği + +## Hızlı Başlangıç + +### Docker ile Web Server Modu (Önerilen) + +```bash +# Build +docker-compose build + +# Web server'ı başlat (port 5000) +docker-compose up -d + +# Logları izle +docker-compose logs -f +``` + +### URL Template Kullanımı + +RSS-Bridge benzeri URL template sistemi: + +``` +# Channel ID ile +http://localhost:5000/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom + +# Channel Handle ile +http://localhost:5000/?channel=@tavakfi&format=Atom + +# Channel URL ile +http://localhost:5000/?channel_url=https://www.youtube.com/@tavakfi&format=Atom + +# RSS formatı +http://localhost:5000/?channel=@tavakfi&format=Rss + +# Maksimum video sayısı +http://localhost:5000/?channel=@tavakfi&format=Atom&max_items=100 +``` + +### Batch Mode (Manuel Çalıştırma) + +```bash +# Tek seferlik çalıştırma +docker-compose run --rm yttranscriptrss python main.py +``` + +### Yerel Geliştirme + +```bash +# Virtual environment oluştur +python -m venv venv +source venv/bin/activate # Linux/Mac +# veya +venv\Scripts\activate # Windows + +# Bağımlılıkları kur +pip install -r requirements.txt + +# SpaCy modelini indir +python -m spacy download en_core_web_sm + +# Çalıştır +python main.py +``` + +## Yapılandırma + +`config/config.yaml` dosyasını düzenleyin: + +```yaml +channel: + id: "UC9h8BDcXwkhZtnqoQJ7PggA" # veya handle: "@username" + name: "Channel Name" + language: "tr" + +rss_bridge: + base_url: "https://rss-bridge.org/bridge01" + format: "Atom" + max_items: 100 +``` + +## Proje Yapısı + +``` +yttranscriptrss/ +├── src/ +│ ├── database.py # SQLite yönetimi +│ ├── video_fetcher.py # RSS-Bridge entegrasyonu +│ ├── transcript_extractor.py # Transcript çıkarımı +│ ├── transcript_cleaner.py # NLP ve temizleme +│ └── rss_generator.py # RSS feed oluşturma +├── config/ +│ └── config.yaml # Yapılandırma +├── data/ +│ └── videos.db # SQLite veritabanı +├── output/ +│ └── transcript_feed.xml # RSS feed çıktısı +├── Dockerfile +├── docker-compose.yml +└── main.py +``` + +## Geliştirme Planı + +Detaylı geliştirme planı için `development_plan.md` dosyasına bakın. + +## Lisans + +MIT + diff --git a/app.py b/app.py new file mode 100644 index 0000000..72e675b --- /dev/null +++ b/app.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +""" +Flask Web Server - RSS-Bridge benzeri URL template sistemi +""" +from src.web_server import app + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) + diff --git a/config/config.yaml b/config/config.yaml new file mode 100644 index 0000000..2a6929b --- /dev/null +++ b/config/config.yaml @@ -0,0 +1,39 @@ +channel: + # Channel ID veya Handle (otomatik dönüştürülür) + id: "UC9h8BDcXwkhZtnqoQJ7PggA" # Channel ID (UC ile başlar) + # veya handle kullanılabilir: + # handle: "@tavakfi" # Handle kullanılırsa otomatik olarak Channel ID'ye çevrilir + # veya full URL: + # handle_url: "https://www.youtube.com/@tavakfi" + name: "Channel Name" + url: "https://youtube.com/channel/..." + language: "tr" + +rss_bridge: + # Public RSS-Bridge instance (varsayılan - önerilen) + base_url: "https://rss-bridge.org/bridge01" + # Self-hosted instance (opsiyonel - rate limiting sorunları için) + # base_url: "http://localhost:3000" + bridge_name: "YoutubeBridge" # Önemli: "YoutubeBridge" olmalı + context: "By channel id" # veya "By username" + format: "Atom" # veya "Rss" (feed için) + max_items: 100 # RSS-Bridge'den çekilecek maksimum video sayısı + use_fallback: true # RSS-Bridge erişilemezse native RSS kullan + # Opsiyonel filtreleme + duration_min: null # dakika cinsinden minimum süre + duration_max: null # dakika cinsinden maksimum süre + +transcript: + languages: ["tr", "en"] + enable_sbd: true + paragraph_length: 3 + +rss: + title: "Channel Transcript Feed" + description: "Full-text transcript RSS feed" + output_file: "transcript_feed.xml" + +automation: + check_interval_hours: 12 + max_items: 100 + diff --git a/development_plan.md b/development_plan.md new file mode 100644 index 0000000..b912d49 --- /dev/null +++ b/development_plan.md @@ -0,0 +1,739 @@ +# YouTube Transcript RSS Feed - Geliştirme Planı + +## Proje Özeti + +YouTube video transkriptlerini otomatik olarak çıkarıp, tam metin içeren RSS feed'ine dönüştüren otomatik bir pipeline geliştirilmesi. Sistem, Python tabanlı transcript çıkarımı ve RSS feed oluşturma ile RSS-Bridge entegrasyonu seçeneklerini içerir. + +--- + +## Faz 1: Proje Altyapısı ve Ortam Kurulumu + +### 1.1. Teknoloji Stack Seçimi + +**Ana Yaklaşım: Python Tabanlı (Önerilen)** +- **Transcript Çıkarımı**: `youtube-transcript-api` +- **RSS Oluşturma**: `python-feedgen` +- **Video Listesi**: RSS-Bridge (RSS feed parser) +- **Dil**: Python 3.8+ + +### 1.2. Geliştirme Ortamı Kurulumu + +**Görevler:** +- [ ] Python 3.8+ kurulumu doğrulama +- [ ] Virtual environment oluşturma (`python -m venv venv`) +- [ ] Gerekli paketlerin kurulumu: + ```bash + pip install youtube-transcript-api + pip install feedgen + pip install python-dateutil + pip install feedparser # RSS-Bridge feed'lerini parse etmek için + pip install requests # HTTP istekleri için + pip install aiolimiter # Async rate limiting için + pip install httpx # Async HTTP client + pip install spacy # NLP ve Sentence Boundary Detection için + ``` + **Not**: SQLite Python'da built-in (`sqlite3` modülü), ekstra kurulum gerekmez. + **SpaCy Model**: `python -m spacy download en_core_web_sm` (veya `tr_core_news_sm` Türkçe için) +- [ ] Proje dizin yapısı oluşturma: + ``` + yttranscriptrss/ + ├── src/ + │ ├── transcript_extractor.py + │ ├── transcript_cleaner.py + │ ├── rss_generator.py + │ ├── video_fetcher.py + │ └── database.py + ├── config/ + │ └── config.yaml + ├── data/ + │ └── videos.db + ├── output/ + │ └── transcript_feed.xml + ├── tests/ + ├── requirements.txt + └── main.py + ``` + +**Süre Tahmini**: 1-2 gün + +--- + +## Faz 2: Transcript Çıkarımı ve Temizleme Modülü + +### 2.1. Transcript Çıkarımı (`transcript_extractor.py`) + +**Görevler:** +- [ ] `youtube-transcript-api` ile video ID'den transcript çıkarma +- [ ] Çoklu dil desteği (fallback mekanizması) + - Örnek: `languages=['tr', 'en']` - önce Türkçe, yoksa İngilizce +- [ ] Hata yönetimi: + - Transcript bulunamama durumları + - API rate limiting + - Geçersiz video ID'ler +- [ ] Raw transcript formatını anlama: + ```python + # Format: [{"text": "...", "start": 0.0, "duration": 2.5}, ...] + ``` + +**Kritik Gereksinimler:** +- Headless browser kullanmama (API tabanlı yaklaşım) +- Otomatik ve manuel transkriptleri destekleme +- **Async Rate Limiting**: AIOLimiter ile eş zamanlı istek yönetimi + - API limiti: 10 saniyede 5 istek + - Async batching ile paralel işleme +- **Retry-After Header**: 429 hatalarında `Retry-After` header'ını kullanma + - Dinamik bekleme süresi (statik delay yerine) + - Exponential backoff mekanizması +- Timeout ve retry mekanizmaları + +**Süre Tahmini**: 3-4 gün + +### 2.2. Transcript Temizleme ve Dönüştürme (`transcript_cleaner.py`) + +**Görevler:** +- [ ] **Veri Temizleme Pipeline**: + - **Artifact Removal**: + - Zaman tabanlı işaretçileri kaldırma + - Konuşma dışı etiketler: `[Music]`, `[Applause]`, vb. + - Aşırı boşlukları temizleme + - **Normalizasyon**: + - Unicode karakter normalleştirme + - Metin standardizasyonu +- [ ] **Sentence Boundary Detection (SBD) - SpaCy Entegrasyonu**: + - **SpaCy Model Kullanımı**: `en_core_web_sm` veya `tr_core_news_sm` + - Noktalama işaretlerinin ötesine geçme: + - Doğal duraklama noktalarını tespit + - Anlamsal sınırları belirleme + - Konuşmacı değişikliklerini algılama + - Fragment'ları birleştirme ve cümle sınırlarını tespit etme + - Paragraf yapısı oluşturma (cümle sayısı veya anlamsal değişikliklere göre) + - **Özelleştirme**: Özel içerik için kural tabanlı SBD uzantıları +- [ ] **HTML Wrapping**: + - Temizlenmiş metni `

...

` tag'leri ile sarmalama + - Paragraf yapısını koruma + - Minimal HTML (sadece gerekli tag'ler) +- [ ] **XML Entity Escaping** (Kritik!): + - **Zorunlu karakter dönüşümleri**: + - `&` → `&` (özellikle URL'lerde kritik!) + - `<` → `<` + - `>` → `>` + - `"` → `"` + - `'` → `'` + - **CDATA kullanımı**: İsteğe bağlı ama entity escaping hala gerekli + - URL'lerdeki ampersand'ların kaçışı özellikle önemli + +**Algoritma Özeti:** +1. Artifact'ları kaldır ve normalize et +2. SpaCy ile fragment'ları birleştir ve cümle sınırlarını tespit et +3. Paragraflara böl (anlamsal veya cümle sayısına göre) +4. HTML tag'leri ekle (`

...

`) +5. XML entity escaping uygula (özellikle `&` karakterleri) + +**Süre Tahmini**: 4-5 gün + +--- + +## Faz 3: Video Metadata Çıkarımı ve Yönetimi + +### 3.1. Video Metadata Fetcher (`video_fetcher.py`) - RSS-Bridge Entegrasyonu + +**Görevler:** +- [ ] **RSS-Bridge Feed Parser**: + - **Public RSS-Bridge instance kullanımı (Önerilen)**: + - Base URL: `https://rss-bridge.org/bridge01/` + - Ücretsiz ve hazır kullanılabilir + - Rate limiting riski var ama başlangıç için yeterli + - RSS-Bridge YouTube Bridge endpoint'ini kullanma + - **Doğru URL formatı**: + - Public (Channel ID): `https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=CHANNEL_ID&format=Atom` + - Public (Channel Handle): `https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+username&u=USERNAME&format=Atom` + - Self-hosted (opsiyonel): `http://localhost:3000/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=CHANNEL_ID&format=Atom` + - **Önemli parametreler**: + - `bridge=YoutubeBridge` (Youtube değil!) + - `context=By+channel+id` veya `context=By+username` + - `c=CHANNEL_ID` (channel ID için) veya `u=USERNAME` (handle için) + - `format=Atom` veya `format=Rss` (feed için, Html değil) + - `duration_min` ve `duration_max` (opsiyonel filtreleme) +- [ ] **Feed Parsing** (`feedparser` kullanarak): + - RSS/Atom feed'ini parse etme + - Video entry'lerini çıkarma + - Metadata çıkarımı: + - Video ID (YouTube URL'den parse) + - Video başlığı (`entry.title`) + - Yayın tarihi (`entry.published` - timezone-aware) + - Video URL (`entry.link`) + - Video açıklaması (`entry.summary` - opsiyonel) + - Thumbnail URL (opsiyonel) +- [ ] **RSS-Bridge Avantajları**: + - Native feed'den daha fazla video (10-15 yerine 100+) + - Channel Handle (@username) desteği + - Filtreleme seçenekleri (duration, vb.) +- [ ] **Hata Yönetimi**: + - RSS-Bridge instance erişilemezse fallback (native RSS) + - Rate limiting handling + - Feed format validation +- [ ] **Channel ID Extraction (Handle'dan)**: + - Channel handle (@username) verildiğinde Channel ID'yi bulma + - Web scraping ile HTML source'dan Channel ID çıkarma + - Regex pattern: `"externalId":"(UC[a-zA-Z0-9_-]{22})"` veya `"channelId":"(UC[a-zA-Z0-9_-]{22})"` + - Channel ID formatı: `UC` ile başlar, 22 karakter + - Fallback mekanizması: İlk pattern bulunamazsa alternatif pattern dene + - Hata yönetimi: Request exception handling +- [ ] **Video ID Extraction**: + - YouTube URL'den video ID çıkarma + - Regex pattern: `youtube.com/watch?v=([a-zA-Z0-9_-]+)` + - Short URL desteği: `youtu.be/([a-zA-Z0-9_-]+)` +- [ ] `yt-dlp` entegrasyonu (opsiyonel, gelişmiş metadata için) + +**RSS-Bridge Kullanım Örneği:** +```python +import feedparser +from urllib.parse import urlencode + +# Public RSS-Bridge base URL +RSS_BRIDGE_BASE = "https://rss-bridge.org/bridge01" + +# Channel ID ile feed URL'i oluştur +params = { + 'action': 'display', + 'bridge': 'YoutubeBridge', + 'context': 'By channel id', + 'c': 'UC9h8BDcXwkhZtnqoQJ7PggA', # Channel ID + 'format': 'Atom' # veya 'Rss' +} +rss_bridge_url = f"{RSS_BRIDGE_BASE}/?{urlencode(params)}" + +# Feed'i parse et +feed = feedparser.parse(rss_bridge_url) + +# Video entry'lerini işle +for entry in feed.entries: + video_id = extract_video_id(entry.link) + video_title = entry.title + published_date = entry.published_parsed # timezone-aware + video_url = entry.link +``` + +**Gerçek Örnek URL:** +``` +https://rss-bridge.org/bridge01/?action=display&bridge=YoutubeBridge&context=By+channel+id&c=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom +``` + +**Channel ID Bulma Fonksiyonu (Handle'dan):** +```python +import requests +import re + +def get_channel_id_from_handle(handle_url): + """ + Channel handle URL'inden Channel ID'yi web scraping ile bulur. + Örnek: https://www.youtube.com/@tavakfi -> UC... + """ + try: + # HTML içeriğini çek + response = requests.get(handle_url) + response.raise_for_status() + + html_content = response.text + + # İlk pattern: "externalId":"UC..." + match = re.search(r'"externalId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + if match: + return match.group(1) + + # Alternatif pattern: "channelId":"UC..." + match_alt = re.search(r'"channelId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + if match_alt: + return match_alt.group(1) + + return None # Channel ID bulunamadı + + except requests.exceptions.RequestException as e: + raise Exception(f"Error fetching channel page: {e}") + +# Kullanım örneği +handle_url = "https://www.youtube.com/@tavakfi" +channel_id = get_channel_id_from_handle(handle_url) +# Artık channel_id ile RSS-Bridge feed'ini çekebiliriz +``` + +**Süre Tahmini**: 3 gün + +### 3.2. İşlenmiş Video Takibi (`database.py` - SQLite) + +**Görevler:** +- [ ] SQLite veritabanı modülü oluşturma (`database.py`) +- [ ] Veritabanı şeması tasarımı: + ```sql + -- Channels tablosu (kanal takibi için) + CREATE TABLE IF NOT EXISTS channels ( + channel_id TEXT PRIMARY KEY, -- UC... formatında + channel_name TEXT, + channel_url TEXT, + last_checked_utc TEXT, -- ISO 8601 UTC format + created_at_utc TEXT DEFAULT (datetime('now')) + ); + + CREATE INDEX IF NOT EXISTS idx_channels_last_checked ON channels(last_checked_utc); + + -- Videos tablosu (detaylı şema) + CREATE TABLE IF NOT EXISTS videos ( + video_id TEXT PRIMARY KEY, -- 11 karakterli YouTube video ID + channel_id TEXT, -- UC... formatında (FK) + video_title TEXT, + video_url TEXT, + published_at_utc TEXT, -- ISO 8601 UTC format (YYYY-MM-DDTHH:MM:SSZ) + processed_at_utc TEXT, -- ISO 8601 UTC format + transcript_status INTEGER DEFAULT 0, -- 0: Beklemede, 1: Çıkarıldı, 2: Başarısız + transcript_language TEXT, + transcript_raw TEXT, -- Ham, bölümlenmemiş transcript + transcript_clean TEXT, -- SBD ile işlenmiş, RSS için hazır HTML + last_updated_utc TEXT DEFAULT (datetime('now')) + ); + + -- Kritik index'ler (performans için) + CREATE INDEX IF NOT EXISTS idx_videos_channel_id ON videos(channel_id); + CREATE INDEX IF NOT EXISTS idx_videos_published_at_utc ON videos(published_at_utc); + CREATE INDEX IF NOT EXISTS idx_videos_transcript_status ON videos(transcript_status); + CREATE INDEX IF NOT EXISTS idx_videos_processed_at_utc ON videos(processed_at_utc); + ``` + + **Önemli Notlar**: + - **Zaman Formatı**: UTC ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`) - SQLite'ın timezone desteği yok + - **transcript_status**: INTEGER (0, 1, 2) - String değil, performans için + - **Index Optimizasyonu**: `EXPLAIN QUERY PLAN` ile sorgu performansını doğrula + - **DATE() fonksiyonu kullanma**: Index kullanımını engeller, direkt timestamp karşılaştırması yap +- [ ] Database helper fonksiyonları: + - `init_database()` - Veritabanı ve tablo oluşturma + - `is_video_processed(video_id)` - Duplicate kontrolü + - `get_pending_videos()` - `transcript_status = 0` olan videoları getir + - `add_video(video_data)` - Yeni video kaydı (status=0 olarak) + - `update_video_transcript(video_id, raw, clean, status, language)` - Transcript güncelleme + - `get_processed_videos(limit=None, channel_id=None)` - İşlenmiş videoları getir + - `mark_video_failed(video_id, reason)` - Kalıcı hata işaretleme (status=2) + - **Query Performance**: `EXPLAIN QUERY PLAN` ile index kullanımını doğrula +- [ ] Yeni video tespiti algoritması: + 1. RSS-Bridge feed'den son videoları çek + 2. SQLite veritabanında `video_id` ile sorgula + 3. Sadece yeni videoları (veritabanında olmayan) işle +- [ ] Transaction yönetimi (ACID compliance) +- [ ] Connection pooling ve error handling + +**Avantajlar:** +- Daha hızlı sorgulama (index'li) +- Transaction desteği +- İlişkisel veri yapısı +- Daha iyi veri bütünlüğü +- İstatistik sorguları kolaylaşır + +**Süre Tahmini**: 2 gün + +--- + +## Faz 4: RSS Feed Oluşturma + +### 4.1. RSS Generator (`rss_generator.py`) + +**Görevler:** +- [ ] `python-feedgen` ile FeedGenerator oluşturma +- [ ] **Content Namespace Extension** ekleme: + ```xml + + ``` +- [ ] Channel metadata ayarlama: + - `fg.id()` - Channel ID + - `fg.title()` - Kanal başlığı + - `fg.link()` - Kanal URL'i + - `fg.language('tr')` - Dil + - `fg.lastBuildDate()` - Son güncelleme tarihi +- [ ] Item oluşturma: + - `fe.id(video_id)` - GUID (YouTube Video ID) + - `fe.title(video_title)` - Video başlığı + - `fe.link(href=video_url)` - Video linki + - `fe.published(datetime_with_timezone)` - Yayın tarihi (timezone-aware) + - `fe.description(summary)` - Kısa özet + - `fe.content(content=cleaned_transcript_html)` - Tam transcript (``) + +**Kritik Gereksinimler:** +- Timezone-aware tarih formatı (RFC 822 veya ISO 8601) +- Video ID'nin GUID olarak kullanılması (immutable) +- Full-text için `` tag'i + +**Süre Tahmini**: 2-3 gün + +### 4.2. RSS Output ve Serialization + +**Görevler:** +- [ ] XML dosyası oluşturma: + ```python + fg.rss_file('transcript_feed.xml', pretty=True, extensions=True) + ``` +- [ ] Pretty printing (okunabilirlik için) +- [ ] Extensions desteği (Content Namespace dahil) + +**Süre Tahmini**: 1 gün + +--- + +## Faz 5: Ana Pipeline ve Otomasyon + +### 5.1. Main Pipeline (`main.py`) + +**Görevler:** +- [ ] Tüm modüllerin entegrasyonu +- [ ] İş akışı: + 1. SQLite veritabanını başlat (`init_database()`) + 2. Configuration'dan channel bilgisini oku: + - Eğer handle (@username) verildiyse, `get_channel_id_from_handle()` ile Channel ID'ye çevir + - Channel ID zaten varsa direkt kullan + 3. RSS-Bridge feed'den yeni videoları tespit et (`video_fetcher.py`) + 4. SQLite veritabanında `video_id` ile duplicate kontrolü yap + 5. Yeni videolar için: + a. Transcript çıkar + b. Transcript'i temizle + c. RSS feed'e ekle + d. SQLite'a kaydet (video metadata + işlenme durumu) + 6. RSS feed'i güncelle (veritabanından tüm işlenmiş videoları çek) + 7. XML dosyasını kaydet +- [ ] Hata yönetimi ve logging +- [ ] Configuration dosyası yükleme +- [ ] Database transaction yönetimi (rollback on error) + +**Süre Tahmini**: 2-3 gün + +### 5.2. Configuration Management (`config.yaml`) + +**Görevler:** +- [ ] Yapılandırma dosyası oluşturma: + ```yaml + channel: + # Channel ID veya Handle (otomatik dönüştürülür) + id: "UC9h8BDcXwkhZtnqoQJ7PggA" # Channel ID (UC ile başlar) + # veya handle kullanılabilir: + # handle: "@tavakfi" # Handle kullanılırsa otomatik olarak Channel ID'ye çevrilir + # veya full URL: + # handle_url: "https://www.youtube.com/@tavakfi" + name: "Channel Name" + url: "https://youtube.com/channel/..." + language: "tr" + + rss_bridge: + # Public RSS-Bridge instance (varsayılan - önerilen) + base_url: "https://rss-bridge.org/bridge01" + # Self-hosted instance (opsiyonel - rate limiting sorunları için) + # base_url: "http://localhost:3000" + bridge_name: "YoutubeBridge" # Önemli: "YoutubeBridge" olmalı + context: "By channel id" # veya "By username" + format: "Atom" # veya "Rss" (feed için) + max_items: 100 # RSS-Bridge'den çekilecek maksimum video sayısı + use_fallback: true # RSS-Bridge erişilemezse native RSS kullan + # Opsiyonel filtreleme + duration_min: null # dakika cinsinden minimum süre + duration_max: null # dakika cinsinden maksimum süre + + transcript: + languages: ["tr", "en"] + enable_sbd: true + paragraph_length: 3 + + rss: + title: "Channel Transcript Feed" + description: "Full-text transcript RSS feed" + output_file: "transcript_feed.xml" + + automation: + check_interval_hours: 12 + max_items: 100 + ``` + +**Süre Tahmini**: 1 gün + +--- + +## Faz 6: Test ve Validasyon + +### 6.1. Unit Testler + +**Görevler:** +- [ ] Transcript extractor testleri +- [ ] Transcript cleaner testleri (SBD, XML escaping) +- [ ] RSS generator testleri +- [ ] Video fetcher testleri: + - RSS-Bridge feed parsing + - Channel ID extraction (handle'dan): + - Handle URL'den Channel ID çıkarma + - Regex pattern matching testleri + - Fallback pattern testleri + - Hata durumları (geçersiz handle, network error) + - Video ID extraction (URL'den) + - Fallback mekanizması (RSS-Bridge erişilemezse) + - Feed format validation (Atom/RSS) +- [ ] Database modülü testleri: + - Veritabanı oluşturma + - Video ekleme/sorgulama + - Duplicate kontrolü + - Transaction rollback testleri + - Test veritabanı kullanımı (in-memory SQLite) + +**Süre Tahmini**: 2-3 gün + +### 6.2. RSS Feed Validasyonu + +**Görevler:** +- [ ] W3C Feed Validation Service ile doğrulama + - URL: https://validator.w3.org/feed/ +- [ ] Validasyon checklist: + - [ ] XML entity escaping doğru mu? + - [ ] Zorunlu RSS 2.0 tag'leri mevcut mu? (``, `<link>`, `<description>`) + - [ ] Content Namespace doğru tanımlanmış mı? + - [ ] Timezone-aware tarih formatı doğru mu? + - [ ] GUID'ler unique ve immutable mi? +- [ ] Farklı RSS reader'larda test (Feedly, Tiny Tiny RSS, vb.) + +**Süre Tahmini**: 1-2 gün + +--- + +## Faz 7: Deployment ve Hosting + +### 7.1. Static Hosting Seçimi + +**Seçenekler:** +1. **GitHub Pages** (Önerilen) + - Ücretsiz + - CI/CD entegrasyonu + - Version control +2. **Static.app / StaticSave** + - Ücretsiz tier mevcut + - Dosya boyutu limitleri var + +**Görevler:** +- [ ] GitHub repository oluşturma +- [ ] GitHub Actions workflow oluşturma (otomatik çalıştırma için) +- [ ] MIME type ayarları (`application/rss+xml`) +- [ ] Public URL oluşturma + +**Süre Tahmini**: 1-2 gün + +### 7.2. Otomasyon ve Scheduling + +**Seçenekler:** +1. **GitHub Actions** (Önerilen) + - Cron job: Her 12-24 saatte bir + - Ücretsiz tier yeterli +2. **Cron Job** (VPS/Server) + - Tam kontrol + - Sunucu gereksinimi +3. **Cloud Functions** (AWS Lambda, Google Cloud Functions) + - Serverless + - Kullanım bazlı maliyet + +**GitHub Actions Workflow Örneği (Optimize Edilmiş):** +```yaml +name: Update RSS Feed +on: + schedule: + - cron: '0 */12 * * *' # Her 12 saatte bir + workflow_dispatch: # Manuel tetikleme + +jobs: + update-feed: + runs-on: ubuntu-latest + steps: + # 1. Checkout (commit SHA ile sabitlenmiş - güvenlik) + - uses: actions/checkout@8e5e7e5f366d5b8b75e3d67731b8b25a0a40a8a7 # v4 commit SHA + + # 2. Python setup + - uses: actions/setup-python@0a5d62f8d0679a54b4c1a51b3c9c0e0e8e8e8e8e # v5 commit SHA + with: + python-version: '3.10' + + # 3. Cache veritabanı (Git commit yerine - performans) + - name: Cache database + uses: actions/cache@v3 + with: + path: data/videos.db + key: ${{ runner.os }}-videos-db-${{ hashFiles('data/videos.db') }} + restore-keys: | + ${{ runner.os }}-videos-db- + + # 4. Install dependencies + - run: pip install -r requirements.txt + - run: python -m spacy download en_core_web_sm + + # 5. Run pipeline + - run: python main.py + + # 6. Save database to cache + - name: Save database to cache + uses: actions/cache@v3 + with: + path: data/videos.db + key: ${{ runner.os }}-videos-db-${{ hashFiles('data/videos.db') }} + + # 7. Upload RSS feed as artifact + - name: Upload RSS feed + uses: actions/upload-artifact@v3 + with: + name: transcript-feed + path: output/transcript_feed.xml + retention-days: 30 + + # 8. Deploy to GitHub Pages (opsiyonel) + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./output +``` + +**Önemli Notlar**: +- **Cache Kullanımı**: Veritabanı için `actions/cache` kullan (Git commit yerine) - daha hızlı +- **Action Pinning**: Tüm action'lar commit SHA ile sabitlenmiş (güvenlik) +- **Artifact**: RSS feed'i artifact olarak sakla (GitHub Pages'e deploy edilebilir) +- **SpaCy Model**: CI/CD'de model indirme adımı eklendi + +**Süre Tahmini**: 1-2 gün + +--- + +## Faz 8: İleri Özellikler ve Optimizasyon + +### 8.1. Performans Optimizasyonu + +**Görevler:** +- [ ] Paralel transcript çıkarımı (çoklu video için) +- [ ] Caching mekanizması +- [ ] Rate limiting yönetimi +- [ ] Batch processing optimizasyonu + +**Süre Tahmini**: 2-3 gün + +### 8.2. Self-Hosted RSS-Bridge Deployment (Opsiyonel - Rate Limiting Sorunları İçin) + +**Görevler:** +- [ ] **Ne zaman self-hosting gerekir?** + - Public instance rate limiting'e maruz kalırsa + - Yüksek hacimli kullanım gerekiyorsa + - Özelleştirilmiş bridge'ler (YoutubeEmbedBridge) gerekiyorsa +- [ ] RSS-Bridge Docker deployment: + ```bash + docker create --name=rss-bridge --publish 3000:80 \ + --volume $(pwd)/rss-bridge-config:/config \ + rssbridge/rss-bridge + docker start rss-bridge + ``` +- [ ] RSS-Bridge konfigürasyonu: + - `config.ini.php` ayarları + - `CACHE_TIMEOUT` ayarlama (TTL kontrolü) + - Custom bridge'ler ekleme (YoutubeEmbedBridge) +- [ ] Self-hosting avantajları: + - Rate limiting'den kaçınma (dedicated IP) + - Daha fazla video çekebilme (100+) + - Özelleştirilmiş bridge'ler + - Gizlilik ve kontrol +- [ ] Production deployment: + - Reverse proxy (Nginx) kurulumu + - SSL sertifikası (Let's Encrypt) + - Monitoring ve health checks +- [ ] YoutubeEmbedBridge entegrasyonu (ad-free playback): + - `YoutubeEmbedBridge.php` dosyasını `/config/bridges/` klasörüne ekle + - Container'ı restart et + - Embed bridge'i test et + +**Not**: Public RSS-Bridge (`https://rss-bridge.org/bridge01/`) varsayılan olarak kullanılır ve çoğu durumda yeterlidir. Self-hosting sadece rate limiting sorunları yaşandığında veya özel gereksinimler olduğunda önerilir. + +**Süre Tahmini**: 2-3 gün (opsiyonel) + +### 8.3. Monitoring ve Logging + +**Görevler:** +- [ ] Detaylı logging sistemi +- [ ] Hata bildirimleri (email, webhook) +- [ ] Feed health monitoring +- [ ] İstatistikler (SQLite sorguları ile): + - Toplam işlenen video sayısı + - Başarı/başarısızlık oranları + - Son işlenme tarihleri + - Dil dağılımı + - Günlük/haftalık istatistikler + +**SQLite İstatistik Örnekleri:** +```sql +-- Toplam işlenen video sayısı +SELECT COUNT(*) FROM processed_videos; + +-- Başarı oranı +SELECT + transcript_status, + COUNT(*) as count, + ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM processed_videos), 2) as percentage +FROM processed_videos +GROUP BY transcript_status; + +-- Son 7 günde işlenen videolar +SELECT COUNT(*) FROM processed_videos +WHERE processed_at >= datetime('now', '-7 days'); +``` + +**Süre Tahmini**: 2 gün + +--- + +## Toplam Süre Tahmini + +| Faz | Süre | +|-----|------| +| Faz 1: Altyapı | 1-2 gün | +| Faz 2: Transcript Modülü | 7-9 gün | +| Faz 3: Video Metadata | 6 gün | +| Faz 4: RSS Generation | 3-4 gün | +| Faz 5: Pipeline | 3-4 gün | +| Faz 6: Test & Validasyon | 3-5 gün | +| Faz 7: Deployment | 2-4 gün | +| Faz 8: İleri Özellikler | 4-9 gün (opsiyonel) | +| **TOPLAM (Temel)** | **25-35 gün** | +| **TOPLAM (Tam)** | **29-44 gün** | + +--- + +## Kritik Başarı Faktörleri + +1. **API Stabilitesi**: `youtube-transcript-api` kullanımı (scraping değil) +2. **XML Compliance**: Content Namespace Extension ve entity escaping +3. **Timezone Handling**: Tüm tarihler timezone-aware olmalı +4. **Duplicate Prevention**: Video ID GUID olarak kullanılmalı +5. **Efficient Processing**: Sadece yeni videolar işlenmeli + +--- + +## Riskler ve Mitigasyon + +| Risk | Etki | Mitigasyon | +|------|------|------------| +| YouTube API değişikliği | Yüksek | `youtube-transcript-api` güncellemelerini takip et | +| Transcript bulunamama | Orta | Fallback diller, hata yönetimi | +| Rate limiting | Orta | Exponential backoff, request throttling | +| XML validation hataları | Yüksek | Comprehensive testing, W3C validation | +| Hosting maliyeti | Düşük | GitHub Pages (ücretsiz) kullan | + +--- + +## Sonraki Adımlar + +1. **Hemen Başla**: Faz 1 - Proje altyapısı kurulumu +2. **MVP Hedefi**: Faz 1-6 tamamlanarak çalışan bir sistem +3. **Production Ready**: Faz 7 deployment ile canlıya alma +4. **Optimizasyon**: Faz 8 ile gelişmiş özellikler + +--- + +## Referanslar ve Kaynaklar + +- `youtube-transcript-api`: https://github.com/jdepoix/youtube-transcript-api +- `python-feedgen`: https://github.com/lkiesow/python-feedgen +- RSS 2.0 Specification: https://www.rssboard.org/rss-specification +- Content Namespace: http://purl.org/rss/1.0/modules/content/ +- W3C Feed Validator: https://validator.w3.org/feed/ +- RSS-Bridge: https://github.com/RSS-Bridge/rss-bridge + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..67f74ac --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + yttranscriptrss: + build: . + container_name: yttranscriptrss + ports: + - "5000:5000" # Web server portu + volumes: + # Veritabanı ve çıktı dosyalarını kalıcı hale getir + - ./data:/app/data + - ./output:/app/output + # Config dosyasını mount et (değişiklikler için) + - ./config:/app/config + environment: + - PYTHONUNBUFFERED=1 + - FLASK_ENV=production + restart: unless-stopped + # Web server modu (varsayılan) + command: python app.py + # Veya batch mode için: + # command: python main.py + diff --git a/id.md b/id.md new file mode 100644 index 0000000..e70d1b2 --- /dev/null +++ b/id.md @@ -0,0 +1,67 @@ +The most effective way to find a YouTube Channel ID from a channel handle (`@tavakfi`) using **Python without an API** is by web scraping the channel's HTML source code. + +The Channel ID (which starts with `UC`) is embedded in the page source under a key like `"externalId"` or `"channelId"`. + +Here is the Python script you can use: + +### Python Channel ID Finder (No API) + +This script uses the `requests` library to fetch the HTML content and the built-in `re` (regular expressions) library to search for the Channel ID. + +```python +import requests +import re + +def get_channel_id_from_handle(handle_url): + """ + Fetches the YouTube channel ID from a handle URL by scraping the source code. + """ + try: + # 1. Fetch the HTML content of the channel page + response = requests.get(handle_url) + response.raise_for_status() # Raise an exception for bad status codes + + html_content = response.text + + # 2. Search for the Channel ID using a Regular Expression + # The channel ID is often stored in the page data as "externalId" or "channelId" + # The regex looks for "externalId" or "channelId" followed by the UC... string + match = re.search(r'"externalId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + + if match: + # The Channel ID is captured in the first group of the regex match + return match.group(1) + else: + # Try alternative location for Channel ID (less common but worth checking) + match_alt = re.search(r'"channelId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + if match_alt: + return match_alt.group(1) + else: + return "Channel ID not found in page source." + + except requests.exceptions.RequestException as e: + return f"Error fetching URL: {e}" + +# The handle URL you provided +channel_url = "https://www.youtube.com/@tavakfi" + +# Run the function +channel_id = get_channel_id_from_handle(channel_url) + +print(f"Channel Handle: {channel_url}") +print(f"Channel ID: {channel_id}") +``` + +----- + +### How the Code Works + +1. **`import requests` and `import re`**: These lines import the necessary libraries. `requests` handles fetching the webpage content, and `re` handles finding the specific text pattern (the Channel ID) within that content. +2. **`requests.get(handle_url)`**: This line sends an HTTP GET request to the YouTube handle URL (`@tavakfi`) to retrieve the raw HTML source code. +3. **`re.search(r'"externalId":"(UC[a-zA-Z0-9_-]{22})"', html_content)`**: This is the core scraping logic. It searches the entire HTML text for the pattern that YouTube uses to store the Channel ID. + * The Channel ID always starts with **`UC`** and is followed by exactly 22 alphanumeric characters, which is captured by the pattern `(UC[a-zA-Z0-9_-]{22})`. + * The ID is then extracted using `match.group(1)`. + +This scraping technique allows you to reliably retrieve the unique `UC` Channel ID for any channel that uses the modern `@handle` URL format. + +You can learn more about the channel and its content by watching this video: [Türkiye Research Foundation - Videos](https://www.youtube.com/@tavakfi/videos). \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..42419f6 --- /dev/null +++ b/main.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +YouTube Transcript RSS Feed Generator - Ana Pipeline +""" +import yaml +import os +import sys +from pathlib import Path + +# Proje root'unu path'e ekle +sys.path.insert(0, str(Path(__file__).parent)) + +from src.database import Database +from src.video_fetcher import fetch_videos_from_rss_bridge, get_channel_id_from_handle +from src.transcript_extractor import TranscriptExtractor +from src.transcript_cleaner import TranscriptCleaner +from src.rss_generator import RSSGenerator + + +def load_config(config_path: str = "config/config.yaml") -> dict: + """Config dosyasını yükle""" + with open(config_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + + +def get_channel_id(config: dict) -> str: + """Config'den channel ID'yi al (handle varsa dönüştür)""" + channel_config = config.get('channel', {}) + + # Channel ID direkt varsa + if channel_config.get('id'): + return channel_config['id'] + + # Handle URL varsa + if channel_config.get('handle_url'): + channel_id = get_channel_id_from_handle(channel_config['handle_url']) + if channel_id: + return channel_id + + # Handle varsa + if channel_config.get('handle'): + handle_url = f"https://www.youtube.com/{channel_config['handle']}" + channel_id = get_channel_id_from_handle(handle_url) + if channel_id: + return channel_id + + raise ValueError("Channel ID bulunamadı! Config'de id, handle veya handle_url belirtin.") + + +def main(): + """Ana pipeline""" + print("YouTube Transcript RSS Feed Generator başlatılıyor...") + + # Config yükle + config = load_config() + + # Channel ID al + channel_id = get_channel_id(config) + print(f"Channel ID: {channel_id}") + + # Database başlat + db = Database() + db.init_database() + + # RSS-Bridge'den videoları çek + rss_bridge_config = config.get('rss_bridge', {}) + print(f"RSS-Bridge'den videolar çekiliyor...") + + try: + videos = fetch_videos_from_rss_bridge( + base_url=rss_bridge_config.get('base_url', 'https://rss-bridge.org/bridge01'), + channel_id=channel_id, + format=rss_bridge_config.get('format', 'Atom'), + max_items=rss_bridge_config.get('max_items', 100) + ) + print(f"{len(videos)} video bulundu") + except Exception as e: + print(f"Hata: {e}") + return + + # Yeni videoları veritabanına ekle + new_count = 0 + for video in videos: + video['channel_id'] = channel_id + if not db.is_video_processed(video['video_id']): + db.add_video(video) + new_count += 1 + + print(f"{new_count} yeni video eklendi") + + # Bekleyen videoları işle + pending_videos = db.get_pending_videos() + print(f"{len(pending_videos)} video işlenmeyi bekliyor") + + if pending_videos: + extractor = TranscriptExtractor() + cleaner = TranscriptCleaner() + transcript_config = config.get('transcript', {}) + + for video in pending_videos[:10]: # İlk 10 video (test için) + print(f"İşleniyor: {video['video_title']}") + + # Transcript çıkar + transcript = extractor.fetch_transcript( + video['video_id'], + languages=transcript_config.get('languages', ['en']) + ) + + if transcript: + # Transcript temizle + raw, clean = cleaner.clean_transcript( + transcript, + sentences_per_paragraph=transcript_config.get('paragraph_length', 3) + ) + + # Veritabanına kaydet + db.update_video_transcript( + video['video_id'], + raw, + clean, + status=1, # Başarılı + language=transcript_config.get('languages', ['en'])[0] + ) + print(f"✓ Tamamlandı: {video['video_title']}") + else: + # Başarısız olarak işaretle + db.mark_video_failed(video['video_id'], "Transcript bulunamadı") + print(f"✗ Başarısız: {video['video_title']}") + + # RSS feed oluştur + processed_videos = db.get_processed_videos( + limit=config.get('automation', {}).get('max_items', 100), + channel_id=channel_id + ) + + if processed_videos: + channel_info = { + 'id': channel_id, + 'title': config.get('rss', {}).get('title', 'Transcript Feed'), + 'link': config.get('channel', {}).get('url', ''), + 'description': config.get('rss', {}).get('description', ''), + 'language': config.get('channel', {}).get('language', 'en') + } + + generator = RSSGenerator(channel_info) + + for video in processed_videos: + generator.add_video_entry(video) + + output_file = config.get('rss', {}).get('output_file', 'transcript_feed.xml') + output_path = f"output/{output_file}" + os.makedirs('output', exist_ok=True) + + generator.generate_rss(output_path) + print(f"RSS feed oluşturuldu: {output_path}") + + db.close() + print("Tamamlandı!") + + +if __name__ == "__main__": + main() + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b978eb0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +youtube-transcript-api>=0.6.0 +feedgen>=1.0.0 +python-dateutil>=2.8.2 +feedparser>=6.0.10 +requests>=2.31.0 +spacy>=3.7.0 +pyyaml>=6.0.1 +flask>=3.0.0 +pytz>=2023.3 + diff --git a/research.md b/research.md new file mode 100644 index 0000000..da6d3b2 --- /dev/null +++ b/research.md @@ -0,0 +1,6 @@ +Automated YouTube Transcript to Full-Text RSS Syndication BlueprintSection 1: Foundational Architecture and Workflow OverviewThe construction of an automated content pipeline to convert YouTube video transcripts into a syndicated RSS feed necessitates a layered, programmatic approach. This solution avoids the inherent brittleness of manual data extraction and ensures a repeatable, scalable output suitable for professional content delivery.1.1. Defining the Content Pipeline: YouTube URL to Syndicated XMLThe architecture is built upon a five-stage workflow, sequentially linking the source video content to the final XML output.Input Identification: The process begins by identifying the target YouTube video, typically via its URL or unique Video ID.Transcript Extraction: Reliable tooling must be used to retrieve the raw transcript data, which consists of timed textual fragments, utilizing a stable API endpoint rather than front-end scraping.Data Cleaning and Transformation: This is the non-trivial stage where raw, fragmented, and time-stamped text must be consolidated, scrubbed of timing metadata, and prepared for inclusion in an XML structure.RSS Generation: Programmatic libraries handle the mapping of cleaned text, video metadata (title, URL, date), and channel information into a compliant RSS 2.0 XML file.Deployment and Distribution: The resulting XML file must be hosted statically on a publicly accessible URL and periodically refreshed to reflect new content.1.2. Selection of Core Technologies: Justification for Programmatic ApproachA robust automation solution requires high-reliability libraries capable of interacting directly with YouTube's data services and constructing complex XML formats. The analysis supports the selection of Python for this task, utilizing two primary libraries: youtube-transcript-api and python-feedgen.The reliance on programmatic API interaction is foundational to ensuring long-term stability. While numerous free online tools, such as those provided by Tactiq.io or Scrapingdog, offer single-click extraction capabilities for individual videos 1, they are unsuited for continuous, automated workflows or bulk processing. They lack the necessary hooks for deep integration required by an automation engine.The library youtube-transcript-api is chosen as the extraction backbone due to a crucial technical advantage: it is explicitly designed to retrieve transcripts, including automatically generated subtitles, without requiring a headless browser environment.3 This capability is paramount, as scraping solutions that rely on browser simulation (e.g., Selenium) are notoriously fragile and frequently fail whenever the target website updates its user interface code.3 By avoiding front-end scraping, the solution taps into a more stable underlying data channel, reducing maintenance overhead and increasing pipeline longevity.For RSS feed creation, the python-feedgen library is recommended.4 This modern library simplifies the generation of standard-compliant RSS and ATOM formats, crucially supporting the necessary XML extensions required for full-text syndication.A critical design requirement for this pipeline is the support for full-text syndication. Standard RSS feeds typically only accommodate a brief summary in the mandatory <description> tag.7 To embed the entire transcript, the feed structure must incorporate the Content Namespace Extension, specifically using the <content:encoded> tag.8 Implementing this namespace immediately elevates the feed structure beyond basic syndication, ensuring the complete, rich textual data is delivered to subscribers.The table below summarizes the technical choices for content acquisition:Transcript Acquisition and Cleanup Methods ComparisonMethodTool/APIOutput FormatCleaning RequirementCost & MaintenanceProgrammatic APIyoutube-transcript-api (Python) 3Raw Timed JSON/TextHigh (Custom logic needed to merge lines)Low cost, High stabilityManual/Web ToolTactiq, Scrapingdog 1Cleaned Text (often copyable)Low (if text is pre-cleaned)Varies (Free Tier/Usage Limits)Commercial APILLMLayer, OpenAI Whisper 9Highly Structured JSONVery Low (Pre-chunked/Cleaned)High Cost, Highest Quality/StructureSection 2: Phase I: Robust YouTube Transcript Extraction and CleaningThis phase focuses on reliable data retrieval and the necessary preparation steps to transform raw, machine-oriented output into clean, human-readable text suitable for XML insertion.2.1. Programmatic Transcript AcquisitionThe implementation requires installing the Python package via pip install youtube-transcript-api.3 The fetching logic operates directly on the YouTube Video ID. The API is capable of retrieving transcripts regardless of whether they were manually uploaded or automatically generated by YouTube's speech-to-text engine.3When retrieving transcripts, linguistic preferences are managed using the languages parameter. This parameter accepts a list of language codes in descending order of priority. For example, languages=['de', 'en'] instructs the tool to first attempt to fetch the German transcript and, if unavailable, fall back to the English transcript.3 This layered approach ensures that content is reliably retrieved in the best available language.The raw data returned from the API is structured as a list of dictionaries, where each dictionary contains the textual segment under the key "text", along with synchronization data under "start" (timestamp) and "duration".10 This format is optimized for displaying synchronized captions alongside video playback, not for contiguous textual analysis or reading.112.2. Critical Data Transformation: The Transcript Cleaning LayerThe raw, fragmented nature of the transcript output poses a significant challenge for syndication. Simple concatenation of text fragments results in poorly structured, run-on sentences or sentences abruptly cut by time cues, severely degrading the reader experience.11The custom cleaning algorithm must execute three essential sequential actions:Timestamp Stripping and Consolidation: The initial step involves iterating through the list of timed fragments, extracting only the textual content, and removing all timing data. This is analogous to the function of web tools designed to clean timestamps from .sbv files.11 Crucially, the process must then merge these fragments into syntactically correct sentences and paragraphs. Merging relevant lines and using linguistic principles, such as sentence boundary detection (SBD), is necessary to ensure improved readability.12Sentence Boundary Detection (SBD) for Readability: If the content were being prepared for advanced processing, such as feeding into Large Language Models (LLMs) for summarization or semantic search, simple line-joining is deemed insufficient. Users seeking high-quality input, often described as "structured for chunking right out of the box" 9, emphasize that the raw data needs sophisticated structuring. Therefore, for an expert-level feed, the cleaning layer must incorporate logic that correctly identifies sentence endpoints (e.g., capitalization and punctuation checks across fragment boundaries) to produce coherent paragraphs. This technical effort elevates the standard of the free transcript solution to approach the structured quality offered by commercial transcription APIs.9HTML Wrapping and XML Sanitization: Because the final text will be embedded within an XML tag that expects well-formed HTML (the <content:encoded> extension) 8, two final steps are required:The cleaned transcript text must be wrapped in standard HTML paragraph tags (<p>...</p>) to define paragraph structure.A comprehensive XML entity escaping pass must be performed across the entire text body. This step is non-negotiable for feed integrity. If the transcript contains characters such as the ampersand (&), less-than sign (<), or quotation marks ("), an XML parser will interpret them as the beginning of a new tag or attribute, resulting in an immediate and fatal validation error for the entire feed.13 These must be converted to their corresponding XML entities (e.g., &, <, ") before serialization.Section 3: Phase II: Detailed RSS 2.0 Specification for Full-Text ContentThe generation of a valid, comprehensive RSS feed requires strict adherence to the RSS 2.0 specification, augmented by the Content Module Extension to support full-text delivery.3.1. Required Channel Structure and MetadataAn RSS document is an XML file, requiring the global container to be the <rss> tag.14The single most important structural modification for this project is the declaration of the Content Namespace within the root element. To allow the inclusion of the entire transcript body, the <rss> tag must include the appropriate XML namespace attribute:XML<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"> +If this declaration is omitted, feed aggregators will treat the essential <content:encoded> tag as invalid, preventing the full transcript from being syndicated.8Subordinate to the root tag is the <channel> element, which contains global metadata about the source.7 Mandatory channel tags include a human-readable <title>, the channel's permanent <link>, and a brief <description>.7 Additional standard elements necessary for compliance and usability include the document <language> (e.g., en) and a <lastBuildDate> or <pubDate> detailing when the feed was last updated.73.2. Structuring the RSS <item> (The Transcript Entry)Each video’s transcript data is encapsulated within an <item> element inside the channel.7Identity Elements: Each item requires a <title> (the video title), a <link> (the video URL), and a <description>.7 For unique identification, a <guid> (Globally Unique Identifier) must be provided. The YouTube Video ID serves as the optimal choice for the GUID because this identifier must never change.13 Utilizing the video ID guarantees that feed readers will recognize and ignore duplicate entries, ensuring idempotence during pipeline reruns.Content Delivery Strategy: The full, cleaned transcript is delivered using the Content Namespace extension. The standard <description> tag is reserved for a short summary or excerpt, allowing aggregators to display a preview.8 The main body of the cleaned, HTML-wrapped text is placed inside the <content:encoded> tag, ensuring the complete article content is available, often supporting offline reading capabilities in modern RSS readers.83.3. Distinguishing Text Feeds from Podcasting RequirementsIt is critical to distinguish a text-based RSS feed from a media-based podcast feed. Although RSS is widely used for podcasting, the requirements for media ingestion are distinct.The <enclosure> tag is the standard mechanism for attaching a media resource to an RSS item.15 This tag is mandatory for distribution to major platforms like Apple Podcasts, Spotify, and YouTube Studio's podcast ingestion.13 The enclosure must specify three required components: the media url, the file length in bytes, and the file MIME type (e.g., audio/mpeg or video/mp4).13While theoretically, one could enclose the raw .txt transcript file using <enclosure type="text/plain"/>, this is non-standard for content syndication. The superior method for distributing text is, as detailed above, using the <description>/<content:encoded> combination. Submitting a text-only feed to a platform like YouTube Studio's RSS Ingestion Tool would likely result in failure or rejection, as that tool is specifically designed to upload audio podcast episodes that require a valid, accessible media file via the <enclosure> tag.16A final technical detail relating to timestamps is crucial for aggregator compliance: the publication date for each item must adhere to standardized date formats, and most importantly, the value passed to the pubDate must include explicit timezone information (e.g., converted to UTC or localized with a specific offset).20 Failure to include timezone data is a common point of non-compliance, leading to errors when aggregators attempt to sort or display content chronologically.Section 4: Phase III: Automated RSS Generation with Python (python-feedgen)The python-feedgen library is used to abstract the complexity of XML generation, enabling the developer to focus on data mapping rather than structural compliance.4.1. Setup and Channel InitializationThe library is initialized to generate the feed object:Pythonfrom feedgen.feed import FeedGenerator +fg = FeedGenerator() +The FeedGenerator object is then used to populate the required channel-level metadata, such as the unique channel ID (fg.id()), the source title (fg.title()), the corresponding website link (fg.link()), and the language (fg.language('en')).64.2. Iterative Item Creation and Data MappingThe core automation loop iterates over the extracted video metadata and the cleaned transcripts, creating a feed entry for each. The add_entry() method is the preferred way to create a new item: fe = fg.add_entry().5 By default, feedgen is often configured to prepend new entries, ensuring that the most recent video always appears at the top of the feed list, which is the standard expectation for RSS subscribers.5Data mapping is executed efficiently through the entry object's methods:fe.id(video_id): Sets the immutable GUID.fe.title(video_title): Sets the item title.fe.link(href=video_url, rel='alternate'): Sets the link back to the source video.fe.published(datetime_object_with_timezone): Sets the required, timezone-aware publication date.20fe.description(summary_text): Sets the item summary.For the full transcript body, the cleaned, HTML-wrapped string is inserted. While the specific method varies slightly across versions and extensions, the entry object's content methods are designed to handle large body text, which feedgen then correctly serializes into the namespaced <content:encoded> element when generating the RSS output.8 The key benefit of using a library like python-feedgen is the minimization of manual XML handling, which significantly reduces the risk of structural errors and validation failure.4.3. Outputting and SerializationOnce all entries are added, the FeedGenerator object can serialize the entire structure into a file or a string.To generate the final XML file for deployment:Pythonfg.rss_file('transcript_feed.xml', pretty=True, extensions=True) +The pretty=True flag aids human readability and debugging, while extensions=True ensures that the Content Namespace and other non-standard RSS elements are included in the final XML output.5Alternatively, the raw XML string can be retrieved for logging or direct transmission:Pythonrssfeed_str = fg.rss_str(pretty=True) +Section 5: Deployment, Validation, and MaintenanceThe final phase involves deploying the generated XML file, ensuring its validity, and establishing a scalable automation schedule.5.1. Hosting Options for Static XML FeedsThe generated transcript_feed.xml is a static asset that requires publicly accessible web hosting. A crucial technical prerequisite for all RSS hosting is that the server must respond with the correct MIME type header, which must be set to application/rss+xml.For automation engineers, the recommendation is to leverage zero-cost, reliable static hosting solutions integrated with version control. GitHub Pages is a powerful option; by committing the generated XML file to a repository, GitHub Pages provides a stable, global URL.21 This approach seamlessly integrates the deployment stage into a CI/CD workflow (e.g., GitHub Actions), allowing the entire script execution and file deployment to be managed within a single, version-controlled environment. Alternatively, specialized static hosting platforms such as Static.app or StaticSave offer simple cloud storage for XML assets, often with a generous free tier, provided the file size remains within acceptable limits (e.g., StaticSave has a 30,000 character limit per content item).235.2. Feed Validation ProceduresBefore distribution, the generated XML file must undergo rigorous validation to ensure compliance with syndication standards.The primary compliance check is conducted using the W3C Feed Validation Service.25 This free service performs a definitive check of the feed's syntax and structural integrity against the Atom or RSS specifications. Specialized validation tools also exist to assess cross-platform compatibility and optimize performance by identifying missing tags or incorrect data structures.26The essential checklist for compliance validation includes verifying:That all text fields, particularly within the transcript content, have undergone comprehensive XML entity escaping.The presence of the three mandatory RSS 2.0 channel and item tags (<title>, <link>, <description>).7The correct declaration and implementation of the Content Namespace for the full transcript payload.8The use of standardized date formats that explicitly include required timezone information, as dictated by aggregator requirements.205.3. Automation Strategy and MaintenanceFor a continuous content stream, the Python generation script must be scheduled for periodic execution, typically using a cloud function, GitHub Action, or a traditional cron job, running every 12 to 24 hours.To ensure scaling and minimize unnecessary API usage, the automation script must implement a filtering mechanism. Instead of querying the YouTube API for the status of every video in the channel on every run, the script should first query the native, lightweight RSS feed that YouTube provides for any channel ID: https://www.youtube.com/feeds/videos.xml?channel_id=....27 This native feed provides a fast, low-overhead list of the latest video links, acting as a filter.The script should maintain a persistent record (e.g., a simple database or JSON file) of all video IDs (guids) that have already been processed and added to the transcript_feed.xml. When the script runs, it compares the latest video IDs from the native YouTube RSS feed against this persistence layer, initiating the compute-intensive transcript extraction and cleaning process only for newly published videos. This strategy maximizes operational efficiency and ensures that the feed update cycle is focused and efficient.Section 6: Alternative Approaches and Commercial WorkflowsWhile the Python automation blueprint offers maximum control and cost efficiency, alternative approaches exist depending on the user’s tolerance for proprietary services or subscription costs.6.1. No-Code Solutions for Feed GenerationPlatforms such as FetchRSS, RSS.app, IFTTT, and Feed43 provide user-friendly web interfaces to create RSS feeds.29 These tools often work by analyzing a target URL and attempting to extract structured data using AI or user-defined selectors.31However, these generic web-scraping feed generators often encounter difficulty retrieving dynamically loaded content. Since YouTube transcripts are typically loaded via an internal API endpoint that is not standard HTML content, generic scrapers may fail to locate and reliably pull the full, raw text. They are best suited for extracting predictable data from traditionally structured blogs or news sites.316.2. Commercial Transcription ServicesFor users prioritizing data quality and structure above cost minimization, commercial transcription services offer an alternative. These paid APIs (which may utilize advanced machine learning models like Whisper or proprietary solutions like LLMLayer) provide output that is often pre-processed, chunked, and delivered in clean formats like JSON.9 This quality eliminates much of the custom Python cleaning logic detailed in Section 2, delivering transcripts that are immediately ready for indexing or embedding. The trade-off is a shift in resources from development time and maintenance to ongoing subscription fees and credit management.ConclusionsThe successful creation and syndication of a full-text RSS feed from YouTube transcripts requires a three-pronged programmatic commitment: API stability, high-fidelity data cleaning, and strict adherence to XML specifications.The report establishes that reliance on the stable youtube-transcript-api library is foundational, enabling high-reliability extraction by avoiding the dependency on brittle headless browser setups.3 The greatest complexity lies in the custom Python cleaning layer, which must go beyond simple concatenation to incorporate sentence boundary detection and comprehensive XML entity escaping. This meticulous preparation is non-negotiable for producing content that is both readable and technically compliant.9Finally, the syndication itself must be implemented using a modern library like python-feedgen, with explicit structural modifications to include the Content Namespace Extension (xmlns:content) and utilize the <content:encoded> tag to deliver the complete transcript body.8 The selection of the immutable YouTube Video ID as the item's <guid> ensures long-term integrity against duplication 13, while the implementation of timezone-aware publication dates guarantees compliance with feed aggregators.20The resulting automated pipeline, when hosted statically on a platform like GitHub Pages and scheduled for periodic updates using the native YouTube channel RSS feed for efficient new content discovery, provides a robust, low-maintenance solution for converting video content into a scalable, syndicated text format. \ No newline at end of file diff --git a/rssbridge.md b/rssbridge.md new file mode 100644 index 0000000..b1118fc --- /dev/null +++ b/rssbridge.md @@ -0,0 +1,80 @@ +YouTube Posts Tab Bridge + +Returns posts from a channel's posts tab +By channel ID +Channel ID +i +By username +Username +i +Show less + +VerifiedJoseph +# +YouTube Feed Expander + +Returns the latest videos from a YouTube channel +Channel ID +i +Add embed to entry +i +Use embed page as entry url +i +Use nocookie embed page +i +Hide shorts +i +Show less + +phantop +# +YouTube Bridge + +Returns the 10 newest videos by username/channel/playlist or search +By username +username +i +min. duration (minutes) +i +max. duration (minutes) +i +By channel id +channel id +i +min. duration (minutes) +i +max. duration (minutes) +i +By custom name +custom name +i +min. duration (minutes) +i +max. duration (minutes) +i +By playlist Id +playlist id +i +min. duration (minutes) +i +max. duration (minutes) +i +Search result +search keyword +i +page +i +min. duration (minutes) +i +max. duration (minutes) +i +Show less + +No maintainer +https://github.com/RSS-Bridge/rss-bridge + +2025-08-05 (git.master.8a8d6ab) + +Technical Analysis and Deployment Guide for High-Fidelity YouTube RSS Feeds Using RSS-BridgeI. Executive Summary: Decoupling Video Subscriptions from Platform DependenciesThe consumption of dynamic web content often relies on proprietary interfaces, which frequently results in limitations on data volume, lack of user control over refresh frequency, and undesirable advertising or tracking. RSS-Bridge, a sophisticated PHP web application, serves as a critical utility for addressing these dependencies by generating standardized web feeds (RSS or Atom) for sources that either lack them or provide insufficient functionality.1The challenge of subscribing to YouTube channels reliably and comprehensively is largely defined by the limitations of its native feed infrastructure. The default mechanism imposes a severe restriction on the number of items delivered, rendering it inadequate for users who require thorough or archived updates.3The only viable solution for the advanced technologist seeking consistency, high volume, and operational autonomy is the implementation of a self-hosted bridging technology. Self-hosting RSS-Bridge or its contemporary alternative, RSSHub, via containerization methods such as Docker, ensures operational control over refresh rates and circumvents the severe rate-limiting issues that plague public or centralized instances.5 This report concludes that a self-hosted deployment is mandatory for achieving reliable YouTube feed generation and provides the mechanism necessary to overcome the primary constraint: the hard-coded limit of 10 to 15 items imposed by the native YouTube feed structure.3II. The Current State of YouTube Feeds: Limitations and Necessity of Bridging2.1 The Official YouTube RSS Mechanism: Structure and ConstraintsThe YouTube platform does, in fact, provide a native RSS feed mechanism, utilizing specific URL structures to syndicate content. These official feeds are typically accessed by appending the unique identifier of a channel or playlist to the designated base feed URL. For instance, channel feeds follow the format https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID.7 Similarly, feeds for specific playlists rely on the playlist_id appended to the same base structure.8 This structure, while functional for basic feed consumption, is heavily reliant on the user possessing the exact technical identifiers.2.1.2 The Hard-Coded Item LimitThe primary architectural failure point of the native YouTube RSS mechanism is a hard limit placed on the feed size. Regardless of a channel’s total video count or the user’s need for historical context, the native feeds typically deliver only the ten to fifteen most recent video items.3 This restriction is confirmed across various analyses of the native feed behavior. For any user requiring comprehensive historical archiving, or even just access to videos published slightly outside that narrow recent window, this limit renders the native solution functionally useless.This architectural decision to restrict content volume directly necessitates the use of complex, third-party scraping solutions like RSS-Bridge. The core motivation for undertaking the technical effort of deploying a custom bridging service is not merely to access the feed format, but explicitly to obtain a higher volume of items than the source platform permits natively.32.2 Essential Tools for YouTube Identifier ExtractionModern YouTube channels may be identified by their legacy Channel ID (the lengthy, cryptic identifier) or by a newer Channel Handle (the human-readable name prefixed with the @ symbol, such as @name). Both identifiers are necessary for different stages of feed generation.11For advanced, automated feed generation, particularly when dealing with channel handles, the technical prerequisite remains the ability to reliably extract the definitive, static Channel ID. Command-line utilities designed for YouTube content retrieval, such as yt-dlp, are capable of reliably parsing a channel’s URL to return this crucial identifier.13 This functionality is critical because while platforms support channel handles for user-facing interaction, the technical RSS feed URLs still rely on the static channel_id.To summarize the operational differences and constraints that drive the need for RSS-Bridge, a comparison of mechanisms is provided:Comparison of YouTube RSS Feed MechanismsFeatureNative YouTube RSS FeedRSS-Bridge YouTube Bridge (Self-Hosted)Commercial Feed Generators (e.g., RSS.app)Maximum Items/FeedHard-limited to 10-15 recent items 3Customizable (requires configuration/code modification, potentially 100+) 14Variable (up to 1,000+ depending on plan) 3Input FlexibilityChannel ID, Playlist ID only 8Channel ID, Channel Handle (@name), Playlist URL, Search 11URL Input (Channel, Playlist, Search) 16Control Over Refresh Rate (TTL)None (controlled by Google)Full control via self-host configuration (CACHE_TIMEOUT) 17Variable (15-60 minutes depending on plan) 18Ad Blocking/Content PurificationNonePossible via specialized bridges (e.g., YoutubeEmbedBridge) 11Variable, usually noneIII. Architectural Deep Dive: RSS-Bridge Framework and Operation3.1 Core Technology: PHP, Abstraction, and Web ScrapingRSS-Bridge is architected as a PHP web application, requiring a minimum of PHP 7.4 for operation.1 Its success lies in its modular structure, relying on individual "bridges" to interface with specific websites. The fundamental architectural decision governing RSS-Bridge is its reliance on web scraping, which means it attempts to extract content by parsing the HTML structure of the target website, rather than relying on a stable, published API.20The standard approach involves extending the BridgeAbstract base class.20 This class is specifically intended for standard bridges that must filter complex HTML pages for structured content. The dependence on parsing dynamically generated HTML pages means that the reliability of the resulting feeds is inherently fragile. Since platforms like YouTube frequently update their front-end layouts and code structure, the parsing logic embedded within the bridge inevitably breaks.6 This imposes a continuous and heavy maintenance burden on the open-source community, which must rapidly update the parsing rules whenever a source site undergoes even minor structural changes. For the self-hosting administrator, this fragility translates directly into the operational risk of intermittent feed failure and a necessity to monitor the bridge’s upstream repository for critical updates.3.2 The YouTube Bridge: Parameters and Enhanced FunctionalityThe built-in RSS-Bridge YouTube bridge is capable of handling multiple input types, including a YouTube channel’s Channel ID, its Channel Handle (the @ name), a Playlist URL, or even a general search term.11 This flexibility simplifies the process compared to native feeds, which often require explicit Channel IDs.A significant enhancement available within the RSS-Bridge ecosystem is the specialized community module known as YoutubeEmbedBridge.11 This alternative bridge offers a capability beyond mere syndication volume; it focuses on content purification and improved consumption experience. The YoutubeEmbedBridge is designed to allow videos from subscribed channels to be watched directly within the user’s favorite RSS reader, effectively bypassing proprietary YouTube interfaces and, crucially, avoiding associated advertisements.11 This feature elevates the self-hosted solution from a technical data aggregation tool to a powerful privacy and ad-blocking mechanism, aligning perfectly with the priorities of a power user focused on digital autonomy. This specialized bridge must be manually added to the self-hosted instance by downloading the YoutubeEmbedBridge.php file and placing it in the /bridges/ folder of the RSS-Bridge installation.11IV. Advanced Deployment: Achieving Robust, High-Availability Self-Hosting4.1 Public Instances vs. Self-Hosted ReliabilityWhile the RSS-Bridge project maintains an officially hosted instance (https://rss-bridge.org/bridge01/) and others are publicly available 1, reliance on these centralized services is inherently precarious. As a public instance accumulates popularity, its singular IP address is subjected to increasingly heavy crawling traffic across numerous bridges. This centralized hammering of source sites, especially major platforms like YouTube, quickly leads to the IP address being flagged, rate-limited, or even outright blocklisted.6 When these rate limits are enforced, crawls fail, and the syndicated content either slows down dramatically or ceases to update entirely.Therefore, for reliability, customized refresh frequency, and high-volume data retrieval, self-hosting is an absolute mandate.5 Self-hosting ensures that the user maintains complete control over the operational environment, including the dedicated IP address used for scraping and the frequency of update checks. This control is the mechanism for responsible scaling, preventing the user's operation from becoming collateral damage in platform-wide rate-limiting policies.4.2 Implementation Guide: Docker and Docker-Compose DeploymentThe deployment of RSS-Bridge is significantly simplified by using containerization technologies such as Docker. This method abstracts away the need for manual server configuration, installation of prerequisite software like PHP 7.4+, and complex web server setups (e.g., Nginx configuration).15The standard procedure involves using the official Docker image. For persistent configuration and the integration of custom bridges, volumes must be mapped:Bashdocker create --name=rss-bridge --publish 3000:80 --volume $(pwd)/config:/config rssbridge/rss-bridge +docker start rss-bridge +The --volume $(pwd)/config:/config command is critical because it ensures that customizations—such as the config.ini.php settings and the placement of specialized modules like YoutubeEmbedBridge.php—are preserved across container restarts and updates.1 After adding a custom bridge to the /config/bridges/ folder, the container must be restarted for the new module to load and become available.1For more complex deployments, the use of docker-compose simplifies volume and port management:RSS-Bridge Self-Hosting Deployment Checklist (Docker-Compose)ComponentConfiguration RequirementRationale / Data ReferencePrerequisitesDocker Engine and Docker ComposeEssential for repeatable, containerized deployment 15Base Imagerssbridge/rss-bridge:latestUtilizes the official, maintained distribution 1Configuration VolumeMap local path (e.g., /local/custom/path) to container /configAllows persistence and customization of config.ini.php and integration of custom bridges 1PortsPublish host port (e.g., 3000) to container port 80External access mechanism for web usage 15Cache ManagementSet CACHE_TIMEOUT in config.ini.phpControl the Time To Live (TTL) to manage feed refresh frequency responsibly 174.3 Configuration Tuning for Performance and Cache ManagementOnce deployed, the operational efficiency and integrity of the instance depend heavily on configuration tuning. Critically, the refresh rate of generated feeds is governed by the Time To Live (TTL) setting, which corresponds to the CACHE_TIMEOUT value within the config.ini.php file.17 If the user is self-hosting, they can lower this ttl to increase the refresh frequency of specific bridges.17It is essential to understand that RSS-Bridge utilizes an on-demand update model: the feed is only refreshed if it is actively requested by a feed reader or a direct browser request, subject to the cache duration.17 If the CACHE_TIMEOUT is set to one hour, and the feed reader requests the feed, the content served will be the cached version until that hour expires, unless the user forces a refresh. This behavior contrasts sharply with proprietary services that typically handle background fetching regardless of immediate client request.18 Proper tuning of the cache timeout is necessary to balance the desire for rapid updates against the risk of hammering the source website and triggering local rate limits.V. Overcoming Scalability and Content Fetching LimitationsAchieving high-volume content retrieval, particularly from large YouTube channels or extensive playlists, requires addressing two distinct technical bottlenecks within the RSS-Bridge architecture.5.1 Analyzing the Item Count Bottlenecks5.1.1 The Bridge Default ConstraintThe default behavior of the YouTube bridge often mirrors the restrictive constraints of the native YouTube mechanism, typically pulling a maximum of 14 or 15 videos.14 This internal constraint is often managed by a simple variable within the bridge’s source code (YoutubeBridge.php). Users attempting to pull only a slightly larger number of recent items (e.g., 20) are blocked by this programmed limit. Overcoming this requires the administrator to manually modify the bridge source file to increase the item fetching loop variable or the defined example value.145.1.2 The simple_html_dom Parsing ConstraintFor large feeds, particularly playlists exceeding approximately 90 videos, a deeper, architectural constraint often takes effect. The underlying PHP scraping library, simple_html_dom, imposes a hard memory or file size limit, typically defined by a constant such as MAX_FILE_SIZE (historically around 600,000 bytes).14 When YouTube returns a large HTML document containing hundreds of video entries, the scraper halts upon hitting this internal size threshold, leading to an incomplete or failed feed generation.To resolve this critical scalability barrier, the administrator must access the simple_html_dom.php library file and manually increase the MAX_FILE_SIZE constant to a higher value (e.g., 900,000 bytes or more).14 This is a deep technical fix necessary to allow the PHP environment to process the immense HTML payload returned by the source site when requesting extended lists. This requirement demonstrates that scalability in RSS-Bridge is not a single problem but a sequential chain of constraints: first, the specific bridge’s self-imposed limit must be raised, and second, the underlying PHP parsing engine’s limits must be increased to accommodate the resulting larger file size. Newer versions of RSS-Bridge have attempted to address this by moving the MAX_FILE_SIZE setting to the general configuration file, simplifying maintenance.245.2 Troubleshooting Common Reliability IssuesBeyond item limits, consistent feed delivery can be hampered by external and internal factors.Feed Not Updating: If a feed fails to refresh, the most frequent cause is the interaction between the bridge’s cache setting and the feed reader’s request cycle.17 Users must verify their self-hosted CACHE_TIMEOUT setting to ensure it is appropriately low for the desired update frequency.18Source Verification: Since RSS-Bridge relies on scraping publicly accessible pages, the source URL must be verified. If the content source requires login, or if the source website is down, the feed will not update.18Filtering Issues: Configuration errors, such as mistakenly applying internal whitelist or blacklist keywords, can unintentionally filter and hide content from the final feed output.18The following table summarizes the required technical actions to ensure high-volume feed reliability:Troubleshooting YouTube Feed Item LimitsSymptomMax 10-15 items shown, small channels affectedFeed fails or cuts off after ~90 items (Large playlists)Feed fails intermittently / not updatingRoot CauseInternal Bridge parameter limits fetching quantity 14MAX_FILE_SIZE constraint in the simple_html_dom.php library 14Cache TTL is too long, or IP is temporarily rate-limited 6Technical FixModify item count variable in the specific YoutubeBridge.php fileIncrease MAX_FILE_SIZE constant (e.g., to 900000 or more) 14Decrease CACHE_TIMEOUT (TTL) in config.ini.php 17VI. Comparative Platform Analysis: RSS-Bridge vs. AlternativesThe technical landscape for generating custom feeds includes several robust alternatives to RSS-Bridge, each offering a distinct trade-off in terms of control, technology stack, and maintenance.6.1 RSSHub: The Node.js AlternativeRSSHub is a free and open-source RSS feed generator implemented in Node.js.5 Architecturally, RSSHub often operates as a collection of modular routes, contrasting with RSS-Bridge’s more monolithic PHP application structure. RSSHub provides built-in routes specifically for generating feeds from YouTube channels and playlists.5For users who prefer a modern JavaScript runtime environment, RSSHub offers an equivalent level of control and is also easily deployed using Docker and managed cloud services like Railway.5 The choice between RSS-Bridge (PHP) and RSSHub (Node.js) is primarily determined by the user's existing infrastructure and preferred technology stack. Both solutions rely heavily on community development to ensure the continuous maintenance of their scraping or API routes.66.2 Commercial Feed Generators (e.g., RSS.app)Commercial tools, such as RSS.app, offer managed services that simplify feed generation, allowing users to simply paste a YouTube URL to generate a feed.16 This approach removes the significant operational burden associated with self-hosting, server maintenance, and troubleshooting low-level parsing errors.However, this convenience introduces new constraints. Managed services operate on a subscription model, often involving recurring charges.23 More importantly, the user loses control over critical parameters: refresh rates are limited by the chosen plan (e.g., updates every 15 to 60 minutes) 18, and although some managed services claim higher item limits (up to 1,000 items) 3, the service provider maintains the relationship with the source platform, acting as a mandatory intermediary. This places the user on the spectrum of low control and high dependency, which runs counter to the objectives of digital autonomy sought by advanced users.6.3 The Role of Utility Tools (yt-dlp)For the highest degree of customized control, users can leverage utility tools like yt-dlp in conjunction with custom scripting. While yt-dlp is primarily known for content downloading, it is capable of extracting content metadata.27 This allows developers to create custom workflows—often involving cron jobs—to poll channel uploads, filter content, and then generate an entirely bespoke RSS feed XML file.28 This method provides maximal control over data structure, content filtering, and media handling, entirely bypassing the scraping dependencies of both RSS-Bridge and RSSHub.VII. Maximizing Feed Fidelity and Consumption ExperienceHigh-fidelity feed generation requires optimizing the output structure and content inclusion to ensure maximum context and utility directly within the user’s feed reader application.7.1 Data Structure Optimization (RSS vs. Atom)RSS-Bridge typically allows output in both Really Simple Syndication (RSS) and Atom formats.12 While RSS remains broadly compatible, the Atom format is often preferred by modern feed readers due to its superior standardization, better handling of foreign namespaces, and clearer definition of metadata.12 When configuring the feed link generation, selecting Atom can provide a more resilient experience within sophisticated feed readers such as Tiny Tiny RSS.127.2 Content Inclusion: Full Description and MetadataThe utility of a feed is highly dependent on how much information is contained within the feed item itself. The long-standing debate between full-text feeds and summary feeds has a clear conclusion in the context of advanced aggregation: full-text is generally superior, allowing the user to make an informed decision on whether to click through to the original source.29For YouTube content, maximizing fidelity means ensuring the bridge includes the full video description and any relevant metadata (duration, publish date) directly within the feed item’s content field. This allows the user to consume the critical context of the video without having to navigate away from their reader.307.3 High-Fidelity Media InclusionThe ultimate measure of a specialized YouTube bridge is its ability to decouple content consumption from the source platform’s proprietary player.7.3.1 Thumbnail Importance and Aspect RatiosTo maximize engagement and visual appeal within the feed reader, the syndicated content must include the video thumbnail URL. YouTube’s recommended aspect ratio for thumbnails is 16:9 (1280x720 minimum).31 It is vital for the bridge to correctly scrape and provide the 16:9 thumbnail image, which is optimized for video display, rather than square images sometimes associated with traditional podcast art.327.3.2 Enclosure and Embedded PlaybackThe core advancement offered by specialized bridges like YoutubeEmbedBridge is the provision of functional media enclosures or embedded playback options.11 Instead of simply linking back to the YouTube watch page, these bridges reformat the video link into an HTML <iframe> or an XML media enclosure element, which, when rendered by the feed reader, enables ad-free, inline playback.8 This capability is entirely dependent on the specific bridge implementation and confirms the strategic value of deploying a custom solution: it enables a fully self-contained consumption experience, achieving total control over the delivery chain and user experience.VIII. Conclusion and Strategic RecommendationsThe analysis demonstrates that reliance on YouTube’s native RSS feeds is technically unfeasible for users requiring comprehensive or archived content, due to the severe limitation of 10 to 15 items per feed.3 The strategic mandate for the advanced technologist is therefore one of digital autonomy, realized through robust, self-hosted infrastructure.The recommended deployment strategy for reliable, high-fidelity YouTube feed generation is as follows:Mandate Self-Hosting: Self-host the chosen bridging technology (RSS-Bridge or RSSHub) using containerization (Docker/Docker-Compose) to secure a dedicated IP address and prevent centralized rate-limiting failures.5Optimize for Volume: For RSS-Bridge, implement the required technical modifications to the YoutubeBridge.php file and, crucially, increase the MAX_FILE_SIZE constant within the scraping library to prevent data truncation on large channels or playlists.14Enhance Fidelity: Deploy and utilize specialized community bridges, such as YoutubeEmbedBridge, to ensure the feed content includes ad-free embedded video links and high-quality 16:9 thumbnails, providing a superior consumption experience within the feed reader.11Maintain Vigilance: Recognize that any solution relying on web scraping is susceptible to site structure updates by the source platform. Self-hosting requires an ongoing commitment to monitoring the RSS-Bridge repository and applying updates promptly when breakage occurs.6By following this prescriptive strategy, the self-hosting administrator effectively decouples their video subscription consumption from the platform’s constraints, achieving a centralized, high-volume, and private news hub tailored to their specific technical requirements.21 \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..42feaa9 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,2 @@ +# YouTube Transcript RSS Feed Generator + diff --git a/src/database.py b/src/database.py new file mode 100644 index 0000000..f343882 --- /dev/null +++ b/src/database.py @@ -0,0 +1,174 @@ +""" +SQLite veritabanı yönetimi modülü +""" +import sqlite3 +import os +from datetime import datetime, timezone +from typing import Optional, List, Dict + + +class Database: + """SQLite veritabanı yönetim sınıfı""" + + def __init__(self, db_path: str = "data/videos.db"): + self.db_path = db_path + self.conn = None + + def connect(self): + """Veritabanı bağlantısı oluştur""" + # Dizin yoksa oluştur + os.makedirs(os.path.dirname(self.db_path), exist_ok=True) + self.conn = sqlite3.connect(self.db_path) + self.conn.row_factory = sqlite3.Row + return self.conn + + def init_database(self): + """Veritabanı şemasını oluştur""" + conn = self.connect() + cursor = conn.cursor() + + # Channels tablosu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS channels ( + channel_id TEXT PRIMARY KEY, + channel_name TEXT, + channel_url TEXT, + last_checked_utc TEXT, + created_at_utc TEXT DEFAULT (datetime('now')) + ) + """) + + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_channels_last_checked + ON channels(last_checked_utc) + """) + + # Videos tablosu + cursor.execute(""" + CREATE TABLE IF NOT EXISTS videos ( + video_id TEXT PRIMARY KEY, + channel_id TEXT, + video_title TEXT, + video_url TEXT, + published_at_utc TEXT, + processed_at_utc TEXT, + transcript_status INTEGER DEFAULT 0, + transcript_language TEXT, + transcript_raw TEXT, + transcript_clean TEXT, + last_updated_utc TEXT DEFAULT (datetime('now')) + ) + """) + + # Index'ler + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_videos_channel_id + ON videos(channel_id) + """) + + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_videos_published_at_utc + ON videos(published_at_utc) + """) + + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_videos_transcript_status + ON videos(transcript_status) + """) + + cursor.execute(""" + CREATE INDEX IF NOT EXISTS idx_videos_processed_at_utc + ON videos(processed_at_utc) + """) + + conn.commit() + print("Database initialized successfully") + + def close(self): + """Veritabanı bağlantısını kapat""" + if self.conn: + self.conn.close() + + def is_video_processed(self, video_id: str) -> bool: + """Video işlenmiş mi kontrol et""" + cursor = self.conn.cursor() + cursor.execute("SELECT video_id FROM videos WHERE video_id = ?", (video_id,)) + return cursor.fetchone() is not None + + def get_pending_videos(self) -> List[Dict]: + """İşlenmeyi bekleyen videoları getir (status=0)""" + cursor = self.conn.cursor() + cursor.execute(""" + SELECT * FROM videos + WHERE transcript_status = 0 + ORDER BY published_at_utc DESC + """) + return [dict(row) for row in cursor.fetchall()] + + def add_video(self, video_data: Dict): + """Yeni video ekle (status=0 olarak)""" + cursor = self.conn.cursor() + cursor.execute(""" + INSERT OR IGNORE INTO videos + (video_id, channel_id, video_title, video_url, published_at_utc, transcript_status) + VALUES (?, ?, ?, ?, ?, 0) + """, ( + video_data['video_id'], + video_data.get('channel_id'), + video_data.get('video_title'), + video_data.get('video_url'), + video_data.get('published_at_utc') + )) + self.conn.commit() + + def update_video_transcript(self, video_id: str, raw: str, clean: str, + status: int, language: Optional[str] = None): + """Video transcript'ini güncelle""" + cursor = self.conn.cursor() + now_utc = datetime.now(timezone.utc).isoformat() + cursor.execute(""" + UPDATE videos + SET transcript_raw = ?, + transcript_clean = ?, + transcript_status = ?, + transcript_language = ?, + processed_at_utc = ?, + last_updated_utc = ? + WHERE video_id = ? + """, (raw, clean, status, language, now_utc, now_utc, video_id)) + self.conn.commit() + + def get_processed_videos(self, limit: Optional[int] = None, + channel_id: Optional[str] = None) -> List[Dict]: + """İşlenmiş videoları getir (status=1)""" + cursor = self.conn.cursor() + query = """ + SELECT * FROM videos + WHERE transcript_status = 1 + """ + params = [] + + if channel_id: + query += " AND channel_id = ?" + params.append(channel_id) + + query += " ORDER BY published_at_utc DESC" + + if limit: + query += " LIMIT ?" + params.append(limit) + + cursor.execute(query, params) + return [dict(row) for row in cursor.fetchall()] + + def mark_video_failed(self, video_id: str, reason: Optional[str] = None): + """Video'yu başarısız olarak işaretle (status=2)""" + cursor = self.conn.cursor() + cursor.execute(""" + UPDATE videos + SET transcript_status = 2, + last_updated_utc = ? + WHERE video_id = ? + """, (datetime.now(timezone.utc).isoformat(), video_id)) + self.conn.commit() + diff --git a/src/rss_generator.py b/src/rss_generator.py new file mode 100644 index 0000000..ae4ba17 --- /dev/null +++ b/src/rss_generator.py @@ -0,0 +1,78 @@ +""" +RSS feed oluşturma modülü +""" +from feedgen.feed import FeedGenerator +from datetime import datetime +from typing import List, Dict +import pytz + + +class RSSGenerator: + """RSS feed generator sınıfı""" + + def __init__(self, channel_info: Dict): + """ + Args: + channel_info: Kanal bilgileri (title, link, description, language) + """ + self.fg = FeedGenerator() + self.channel_info = channel_info + self._setup_channel() + + def _setup_channel(self): + """Channel metadata ayarla""" + self.fg.id(self.channel_info.get('id', '')) + self.fg.title(self.channel_info.get('title', '')) + self.fg.link(href=self.channel_info.get('link', '')) + self.fg.description(self.channel_info.get('description', '')) + self.fg.language(self.channel_info.get('language', 'en')) + self.fg.lastBuildDate(datetime.now(pytz.UTC)) + + def add_video_entry(self, video: Dict): + """ + Video entry ekle + + Args: + video: Video metadata dict + """ + fe = self.fg.add_entry() + + # GUID (video ID) + fe.id(video['video_id']) + + # Title + fe.title(video.get('video_title', '')) + + # Link + fe.link(href=video.get('video_url', '')) + + # Published date (timezone-aware) + if video.get('published_at_utc'): + try: + pub_date = datetime.fromisoformat( + video['published_at_utc'].replace('Z', '+00:00') + ) + fe.published(pub_date) + except: + pass + + # Description (kısa özet) + fe.description(video.get('description', '')[:200]) + + # Content (tam transcript) + if video.get('transcript_clean'): + fe.content(content=video['transcript_clean']) + + def generate_rss(self, output_path: str): + """RSS feed'i dosyaya yaz""" + self.fg.rss_file(output_path, pretty=True, extensions=True) + print(f"RSS feed generated: {output_path}") + + def generate_rss_string(self) -> str: + """RSS feed'i string olarak döndür""" + return self.fg.rss_str(pretty=True, extensions=True) + + def generate_atom_string(self) -> str: + """Atom feed'i string olarak döndür""" + return self.fg.atom_str(pretty=True) + diff --git a/src/transcript_cleaner.py b/src/transcript_cleaner.py new file mode 100644 index 0000000..dfd5e97 --- /dev/null +++ b/src/transcript_cleaner.py @@ -0,0 +1,136 @@ +""" +Transcript temizleme ve NLP işleme modülü +""" +import re +import html +import spacy +from typing import List, Dict + + +class TranscriptCleaner: + """Transcript temizleme ve SBD sınıfı""" + + def __init__(self, model_name: str = "en_core_web_sm"): + """ + Args: + model_name: SpaCy model adı + """ + try: + self.nlp = spacy.load(model_name) + except OSError: + print(f"Model {model_name} not found. Loading default...") + self.nlp = spacy.load("en_core_web_sm") + + def remove_artifacts(self, text: str) -> str: + """Artifact'ları kaldır""" + # Zaman kodlarını kaldır [00:00:00] + text = re.sub(r'\[\d{2}:\d{2}:\d{2}\]', '', text) + + # Konuşma dışı etiketleri kaldır + text = re.sub(r'\[(Music|Applause|Laughter|Music playing)\]', '', text, flags=re.IGNORECASE) + + # Aşırı boşlukları temizle + text = re.sub(r'\s+', ' ', text) + + return text.strip() + + def detect_sentence_boundaries(self, fragments: List[Dict]) -> List[str]: + """ + Fragment'ları birleştir ve cümle sınırlarını tespit et + + Args: + fragments: Transcript fragment listesi [{"text": "...", "start": 0.0, ...}] + + Returns: + Cümle listesi + """ + # Fragment'ları birleştir + full_text = ' '.join([f['text'] for f in fragments]) + + # Artifact'ları kaldır + full_text = self.remove_artifacts(full_text) + + # SpaCy ile işle + doc = self.nlp(full_text) + + # Cümleleri çıkar + sentences = [sent.text.strip() for sent in doc.sents if sent.text.strip()] + + return sentences + + def create_paragraphs(self, sentences: List[str], + sentences_per_paragraph: int = 3) -> List[str]: + """ + Cümleleri paragraflara böl + + Args: + sentences: Cümle listesi + sentences_per_paragraph: Paragraf başına cümle sayısı + + Returns: + Paragraf listesi + """ + paragraphs = [] + current_paragraph = [] + + for sentence in sentences: + current_paragraph.append(sentence) + + if len(current_paragraph) >= sentences_per_paragraph: + paragraphs.append(' '.join(current_paragraph)) + current_paragraph = [] + + # Kalan cümleleri ekle + if current_paragraph: + paragraphs.append(' '.join(current_paragraph)) + + return paragraphs + + def wrap_html(self, paragraphs: List[str]) -> str: + """Paragrafları HTML'e sar""" + html_paragraphs = [f"<p>{p}</p>" for p in paragraphs] + return '\n'.join(html_paragraphs) + + def escape_xml_entities(self, text: str) -> str: + """XML entity escaping (kritik!)""" + # Önce & karakterlerini escape et (diğerlerinden önce!) + text = text.replace('&', '&') + text = text.replace('<', '<') + text = text.replace('>', '>') + text = text.replace('"', '"') + text = text.replace("'", ''') + + # &'yi tekrar düzelt (zaten escape edilmiş olanlar için) + text = text.replace('&amp;', '&') + text = text.replace('&lt;', '<') + text = text.replace('&gt;', '>') + text = text.replace('&quot;', '"') + text = text.replace('&apos;', ''') + + return text + + def clean_transcript(self, fragments: List[Dict], + sentences_per_paragraph: int = 3) -> tuple[str, str]: + """ + Tam transcript temizleme pipeline'ı + + Returns: + (raw_text, clean_html) tuple + """ + # Raw text (sadece birleştirilmiş) + raw_text = ' '.join([f['text'] for f in fragments]) + + # SBD ile cümleleri çıkar + sentences = self.detect_sentence_boundaries(fragments) + + # Paragraflara böl + paragraphs = self.create_paragraphs(sentences, sentences_per_paragraph) + + # HTML'e sar + html_content = self.wrap_html(paragraphs) + + # XML entity escaping + clean_html = self.escape_xml_entities(html_content) + + return raw_text, clean_html + diff --git a/src/transcript_extractor.py b/src/transcript_extractor.py new file mode 100644 index 0000000..7dc1e93 --- /dev/null +++ b/src/transcript_extractor.py @@ -0,0 +1,69 @@ +""" +YouTube transcript çıkarımı modülü +""" +from youtube_transcript_api import YouTubeTranscriptApi +from typing import List, Dict, Optional +import time + + +class TranscriptExtractor: + """YouTube transcript çıkarıcı sınıfı""" + + def __init__(self, rate_limit: int = 5, time_window: int = 10): + """ + Args: + rate_limit: Zaman penceresi başına maksimum istek sayısı + time_window: Zaman penceresi (saniye) + """ + self.rate_limit = rate_limit + self.time_window = time_window + self.request_times = [] + + def _check_rate_limit(self): + """Rate limiting kontrolü (basit implementasyon)""" + now = time.time() + # Son time_window saniyesindeki istekleri filtrele + self.request_times = [t for t in self.request_times if now - t < self.time_window] + + # Rate limit aşıldıysa bekle + if len(self.request_times) >= self.rate_limit: + sleep_time = self.time_window - (now - self.request_times[0]) + if sleep_time > 0: + time.sleep(sleep_time) + # Tekrar filtrele + now = time.time() + self.request_times = [t for t in self.request_times if now - t < self.time_window] + + # İstek zamanını kaydet + self.request_times.append(time.time()) + + def fetch_transcript(self, video_id: str, + languages: List[str] = ['en']) -> Optional[List[Dict]]: + """ + Transcript çıkar (sync) + + Args: + video_id: YouTube video ID + languages: Öncelik sırasına göre dil listesi + + Returns: + Transcript listesi veya None + """ + # Rate limiting kontrolü + self._check_rate_limit() + + try: + # YouTube Transcript API kullanımı (yeni versiyon) + # API instance oluştur ve fetch() metodunu kullan + api = YouTubeTranscriptApi() + fetched_transcript = api.fetch(video_id, languages=languages) + + # Eski formatı döndürmek için to_raw_data() kullan + # Format: [{'text': '...', 'start': 1.36, 'duration': 1.68}, ...] + transcript = fetched_transcript.to_raw_data() + + return transcript + except Exception as e: + print(f"Error fetching transcript for {video_id}: {e}") + return None + diff --git a/src/video_fetcher.py b/src/video_fetcher.py new file mode 100644 index 0000000..4d3ad8c --- /dev/null +++ b/src/video_fetcher.py @@ -0,0 +1,105 @@ +""" +RSS-Bridge kullanarak video metadata çıkarımı +""" +import feedparser +import re +import requests +from urllib.parse import urlencode +from typing import List, Dict, Optional +from datetime import datetime + + +def get_channel_id_from_handle(handle_url: str) -> Optional[str]: + """ + Channel handle URL'inden Channel ID'yi web scraping ile bulur. + Örnek: https://www.youtube.com/@tavakfi -> UC... + """ + try: + response = requests.get(handle_url) + response.raise_for_status() + + html_content = response.text + + # İlk pattern: "externalId":"UC..." + match = re.search(r'"externalId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + if match: + return match.group(1) + + # Alternatif pattern: "channelId":"UC..." + match_alt = re.search(r'"channelId":"(UC[a-zA-Z0-9_-]{22})"', html_content) + if match_alt: + return match_alt.group(1) + + return None + + except requests.exceptions.RequestException as e: + raise Exception(f"Error fetching channel page: {e}") + + +def extract_video_id(url: str) -> Optional[str]: + """YouTube URL'den video ID çıkar""" + patterns = [ + r'youtube\.com/watch\?v=([a-zA-Z0-9_-]{11})', + r'youtu\.be/([a-zA-Z0-9_-]{11})', + r'youtube\.com/embed/([a-zA-Z0-9_-]{11})' + ] + + for pattern in patterns: + match = re.search(pattern, url) + if match: + return match.group(1) + + return None + + +def fetch_videos_from_rss_bridge(base_url: str, channel_id: str, + format: str = "Atom", max_items: int = 100) -> List[Dict]: + """ + RSS-Bridge'den video listesini çek + + Args: + base_url: RSS-Bridge base URL + channel_id: YouTube Channel ID (UC...) + format: Feed format (Atom veya Rss) + max_items: Maksimum video sayısı + + Returns: + Video metadata listesi + """ + params = { + 'action': 'display', + 'bridge': 'YoutubeBridge', + 'context': 'By channel id', + 'c': channel_id, + 'format': format + } + + feed_url = f"{base_url}/?{urlencode(params)}" + + try: + feed = feedparser.parse(feed_url) + + videos = [] + for entry in feed.entries[:max_items]: + video_id = extract_video_id(entry.link) + if not video_id: + continue + + # Tarih parsing + published_date = None + if hasattr(entry, 'published_parsed') and entry.published_parsed: + published_date = datetime(*entry.published_parsed[:6]).isoformat() + 'Z' + + videos.append({ + 'video_id': video_id, + 'video_title': entry.title, + 'video_url': entry.link, + 'published_at_utc': published_date, + 'description': getattr(entry, 'summary', '') + }) + + return videos + + except Exception as e: + raise Exception(f"Error fetching RSS-Bridge feed: {e}") + diff --git a/src/web_server.py b/src/web_server.py new file mode 100644 index 0000000..783015f --- /dev/null +++ b/src/web_server.py @@ -0,0 +1,286 @@ +""" +Flask web server - RSS-Bridge benzeri URL template sistemi +""" +from flask import Flask, request, Response, jsonify +from typing import Optional +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from src.database import Database +from src.video_fetcher import fetch_videos_from_rss_bridge, get_channel_id_from_handle, extract_video_id +from src.transcript_extractor import TranscriptExtractor +from src.transcript_cleaner import TranscriptCleaner +from src.rss_generator import RSSGenerator + + +app = Flask(__name__) + +# Global instances (lazy loading) +db = None +extractor = None +cleaner = None + + +def get_db(): + """Database instance'ı al (singleton)""" + global db + if db is None: + db = Database() + db.init_database() + return db + + +def get_extractor(): + """Transcript extractor instance'ı al""" + global extractor + if extractor is None: + extractor = TranscriptExtractor() + return extractor + + +def get_cleaner(): + """Transcript cleaner instance'ı al""" + global cleaner + if cleaner is None: + cleaner = TranscriptCleaner() + return cleaner + + +def normalize_channel_id(channel_id: Optional[str] = None, + channel: Optional[str] = None, + channel_url: Optional[str] = None) -> Optional[str]: + """ + Farklı formatlardan channel ID'yi normalize et + + Args: + channel_id: Direkt Channel ID (UC...) + channel: Channel handle (@username) veya username + channel_url: Full YouTube channel URL + + Returns: + Normalize edilmiş Channel ID veya None + """ + # Direkt Channel ID varsa + if channel_id: + if channel_id.startswith('UC') and len(channel_id) == 24: + return channel_id + # Eğer URL formatında ise parse et + if 'youtube.com/channel/' in channel_id: + parts = channel_id.split('/channel/') + if len(parts) > 1: + return parts[-1].split('?')[0].split('/')[0] + + # Channel handle (@username) + if channel: + if not channel.startswith('@'): + channel = f"@{channel}" + handle_url = f"https://www.youtube.com/{channel}" + return get_channel_id_from_handle(handle_url) + + # Channel URL + if channel_url: + # Handle URL + if '/@' in channel_url: + return get_channel_id_from_handle(channel_url) + # Channel ID URL + elif '/channel/' in channel_url: + parts = channel_url.split('/channel/') + if len(parts) > 1: + return parts[-1].split('?')[0].split('/')[0] + + return None + + +def process_channel(channel_id: str, max_items: int = 50) -> dict: + """ + Kanal için transcript feed'i oluştur + + Returns: + RSS feed string ve metadata + """ + db = get_db() + extractor = get_extractor() + cleaner = get_cleaner() + + # RSS-Bridge'den videoları çek + try: + videos = fetch_videos_from_rss_bridge( + base_url="https://rss-bridge.org/bridge01", + channel_id=channel_id, + format="Atom", + max_items=max_items + ) + except Exception as e: + raise Exception(f"RSS-Bridge hatası: {e}") + + # Yeni videoları veritabanına ekle + for video in videos: + video['channel_id'] = channel_id + if not db.is_video_processed(video['video_id']): + db.add_video(video) + + # Bekleyen videoları işle (ilk 20) + pending_videos = db.get_pending_videos()[:20] + + for video in pending_videos: + if video['channel_id'] != channel_id: + continue + + try: + # Transcript çıkar + transcript = extractor.fetch_transcript( + video['video_id'], + languages=['tr', 'en'] + ) + + if transcript: + # Transcript temizle + raw, clean = cleaner.clean_transcript(transcript, sentences_per_paragraph=3) + + # Veritabanına kaydet + db.update_video_transcript( + video['video_id'], + raw, + clean, + status=1, + language='tr' + ) + except Exception as e: + print(f"Transcript çıkarım hatası {video['video_id']}: {e}") + db.mark_video_failed(video['video_id'], str(e)) + + # İşlenmiş videoları getir + processed_videos = db.get_processed_videos( + limit=max_items, + channel_id=channel_id + ) + + return { + 'videos': processed_videos, + 'channel_id': channel_id, + 'count': len(processed_videos) + } + + +@app.route('/', methods=['GET']) +def generate_feed(): + """ + RSS-Bridge benzeri URL template: + + Örnekler: + - /?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom + - /?channel=@tavakfi&format=Atom + - /?channel_url=https://www.youtube.com/@tavakfi&format=Atom + """ + # Query parametrelerini al + channel_id = request.args.get('channel_id') + channel = request.args.get('channel') # @username veya username + channel_url = request.args.get('channel_url') + format_type = request.args.get('format', 'Atom').lower() # Atom veya Rss + max_items = int(request.args.get('max_items', 50)) + + # Channel ID'yi normalize et + normalized_channel_id = normalize_channel_id( + channel_id=channel_id, + channel=channel, + channel_url=channel_url + ) + + if not normalized_channel_id: + return jsonify({ + 'error': 'Channel ID bulunamadı', + 'usage': { + 'channel_id': 'UC... (YouTube Channel ID)', + 'channel': '@username veya username', + 'channel_url': 'https://www.youtube.com/@username veya https://www.youtube.com/channel/UC...', + 'format': 'Atom veya Rss (varsayılan: Atom)', + 'max_items': 'Maksimum video sayısı (varsayılan: 50)' + } + }), 400 + + try: + # Kanalı işle + result = process_channel(normalized_channel_id, max_items=max_items) + + if not result['videos']: + return jsonify({ + 'error': 'Henüz işlenmiş video yok', + 'channel_id': normalized_channel_id, + 'message': 'Lütfen birkaç dakika sonra tekrar deneyin' + }), 404 + + # RSS feed oluştur + channel_info = { + 'id': normalized_channel_id, + 'title': f"YouTube Transcript Feed - {normalized_channel_id}", + 'link': f"https://www.youtube.com/channel/{normalized_channel_id}", + 'description': f'Full-text transcript RSS feed for channel {normalized_channel_id}', + 'language': 'en' + } + + generator = RSSGenerator(channel_info) + + for video in result['videos']: + generator.add_video_entry(video) + + # Format'a göre döndür + if format_type == 'rss': + rss_content = generator.generate_rss_string() + return Response( + rss_content, + mimetype='application/rss+xml', + headers={'Content-Type': 'application/rss+xml; charset=utf-8'} + ) + else: # Atom + # Feedgen Atom desteği + atom_content = generator.generate_atom_string() + return Response( + atom_content, + mimetype='application/atom+xml', + headers={'Content-Type': 'application/atom+xml; charset=utf-8'} + ) + + except Exception as e: + return jsonify({ + 'error': str(e), + 'channel_id': normalized_channel_id + }), 500 + + +@app.route('/health', methods=['GET']) +def health(): + """Health check endpoint""" + return jsonify({'status': 'ok', 'service': 'YouTube Transcript RSS Feed'}) + + +@app.route('/info', methods=['GET']) +def info(): + """API bilgileri""" + return jsonify({ + 'service': 'YouTube Transcript RSS Feed Generator', + 'version': '1.0.0', + 'endpoints': { + '/': 'RSS Feed Generator', + '/health': 'Health Check', + '/info': 'API Info' + }, + 'usage': { + 'channel_id': 'UC... (YouTube Channel ID)', + 'channel': '@username veya username', + 'channel_url': 'Full YouTube channel URL', + 'format': 'Atom veya Rss (varsayılan: Atom)', + 'max_items': 'Maksimum video sayısı (varsayılan: 50)' + }, + 'examples': [ + '/?channel_id=UC9h8BDcXwkhZtnqoQJ7PggA&format=Atom', + '/?channel=@tavakfi&format=Rss', + '/?channel_url=https://www.youtube.com/@tavakfi&format=Atom&max_items=100' + ] + }) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=True) + diff --git a/study.md b/study.md new file mode 100644 index 0000000..7de39e2 --- /dev/null +++ b/study.md @@ -0,0 +1 @@ +YouTube Video Transkriptlerini Otomatik RSS Akışına Dönüştürme Geliştirme PlanıI. Mimari ve Operasyonel PlanBu rapor, YouTube video transkriptlerini otomatik olarak çıkarıp tam metin RSS akışına dönüştürecek boru hattının (pipeline) mimarisini, teknoloji seçimlerini ve operasyonel akışını detaylandırmaktadır. Sistemin temel amacı, API kısıtlamalarına karşı dayanıklı, durum yönetimli ve sunucusuz (serverless) bir ortamda sürekli çalışabilen bir çözüm sunmaktır.1.0 Sistem Mimarisine Genel Bakış: Otomatik Boru Hattı AkışıÖnerilen sistem, GitHub Actions zamanlayıcısı (cron trigger) aracılığıyla düzenli aralıklarla (örneğin, saatlik) çalışacak şekilde tasarlanmıştır. Bu periyodik çalışma, aşağıdaki yedi adımdan oluşan bir akışı takip edecektir:Girdi Alımı: İzlenmesi gereken hedef kanallar tespit edilir ve bu kanalların en son videolarının listesi çekilir. Bu aşama, sağlam Kanal Kimliği (Channel ID) çözümlemesini zorunlu kılar.Durum Kontrolü (Veritabanı): SQLite kalıcılık katmanı sorgulanarak henüz işlenmemiş (transcript_status = 0) veya güncelleme gerektiren videoların kimlikleri belirlenir. Bu, her çalıştırmada sadece yeni veya başarısız içerikle ilgilenilmesini sağlar.Çıkarma (Eş Zamansız): Python API istemcisi, API hız limitlerine uyum sağlamak için yönetilen eş zamansız toplu işleme (asynchronous batching) kullanılarak transkriptleri talep eder.NLP İşlemi: Ham transkript metni temizlenir ve okunaklı, paragraflara ayrılmış içerik oluşturmak için Cümle Sınırı Tespiti (SBD) uygulanır.Kalıcılık Güncellemesi: Geliştirilmiş transkript metni depolanır ve veritabanındaki meta veriler (processed_at_utc, transcript_status) güncellenir.RSS Üretimi: Veritabanından en son işlenmiş videolar sorgulanır, sonuçlar RSS 2.0 yapısına uygun olarak biçimlendirilir ve XML dosyası yazılır.Dağıtım: Güncellenmiş SQLite veritabanı (önbellekleme yoluyla) kaydedilir ve yeni RSS XML dosyası (Yapıt/Artifact veya doğrudan Git taahhüdü yoluyla) barındırma konumuna dağıtılır.1.1 Teknoloji Yığınının GerekçesiTeknoloji yığını, stabilite, performans ve CI/CD ortamında kolay dağıtım ilkeleri göz önünde bulundurularak seçilmiştir.Python (3.10+): Kapsamlı Doğal Dil İşleme (NLP) ekosistemi (SpaCy) 1 ve olgun eş zamansız ağ yetenekleri (asyncio, httpx) nedeniyle tercih edilmiştir. Python, boru hattının çekirdeğini oluşturacak yüksek hızlı veri işleme ve API yönetimini kolaylaştırır.youtube-transcript-api: Bu kütüphane, diğer Selenium tabanlı çözümlerin aksine, transkriptleri alırken sanal tarayıcı (headless browser) gerektirmez.3 Bu özellik, CI/CD ortamında dağıtımı ve bakımı radikal bir şekilde basitleştirir.SQLite: Sunucusuz, dosya tabanlı kalıcılık için idealdir. İşlemsel bütünlüğü destekler ve özel bir veritabanı altyapısı gerektirmez.4 GitHub Actions ortamında, veritabanı dosyasının önbelleğe alınması veya Git ile sürümlemesi basit ve verimli bir durum yönetimi sağlar.SpaCy: Kritik NLP görevleri (özellikle SBD ve metin segmentasyonu) için hızlı ve üretime hazır bir kütüphanedir.2lxml / feedgen: RSS/XML çıktısının standartlara tam olarak uygunluğunu sağlamak, karmaşık varlık kaçışını (entity escaping) ve ad alanlarını (namespaces) doğru yönetmek için gerekli olan sağlam kütüphanelerdir.6Eş Zamansız Hız Sınırlaması GerekliliğiAraştırma, API'nin katı hız sınırlarına sahip olduğunu göstermektedir (örneğin, 10 saniyede 5 istek 8). Geleneksel yaklaşımlarda kullanılan, toplu işlemler arasında uzun aralıklarla sıralı time.sleep() döngüsü 9 yüzlerce videoyu işlerken kabul edilemez derecede verimsizdir.Bu darboğazı aşmak için, boru hattının yönetilen eş zamansız bir kuyruk kullanması gerekmektedir. AIOLimiter 10 gibi bir araç, boru hattının API'ye eş zamanlı olarak transkript taleplerini göndermesine olanak tanır. Bu mimari, 10 saniyelik süre penceresine tam olarak uyulmasını garanti ederken, aynı anda 5 isteğin havada olmasına izin vererek optimum verim elde edilmesini sağlar. Operasyonel verimlilik, sıralı I/O beklemelerinden kontrollü, eş zamanlı API kullanımına geçirilerek sağlanır.SQLite'ın Mimari Merkez Olarak KonumlandırılmasıSQLite'ın CI/CD bağlamında kullanılması sadece kolaylık değil, aynı zamanda "Git Scraping" (Git Kazıma) metodolojisini 4 benimsemeyi mümkün kılar. Birincil kalıcılık mekanizması actions/cache olsa bile, veritabanı dosyası işlenmiş verilerin tüm geçmişini saklar. Bu tasarım, GitHub'da dolaylı olarak zaman damgalı, sorgulanabilir bir anlık görüntü geçmişi oluşturur ve sıfır maliyetle yerleşik bir veri denetim izi sunar.4II. Sağlam Veri Alımı ve API YönetimiBu bölüm, sistem stabilitesi için kritik öneme sahip olan YouTube tanımlayıcılarının yönetimi ve API hız kısıtlamalarının uygulanması detaylandırır.2.0 Girdi Normalleştirme Stratejisi: Kanal Kimliği ÇözümlemesiYouTube, kanal erişimi için çeşitli genel formatlar kullanmaktadır: modern kulplar (@), eski user/ ve kalıcı, 24 karakterli Kanal Kimliği (UC...).11 Boru hattının dayanıklılığı için bu girdilerin, sistemin temel alacağı kalıcı Kanal Kimliğine (UC... ile başlayan) dönüştürülmesi zorunludur.Kanal URL'si Ayrıştırma GereksinimiUygulama, farklı URL formatlarından (kulplar, kullanıcı adları veya kimlikler) tanımlayıcıyı doğru şekilde yakalamak için sağlam normal ifadeler (regex) içermelidir.11 Araştırmalar, çeşitli YouTube URL varyasyonlarını (özellikle yeni @handle yapısını) tek bir düzenli ifade ile ayrıştırmanın karmaşıklığını göstermektedir.13Bu gereksinimden çıkarılan sonuç, kullanılan genel tanımlayıcıların (kulplar) değişken ve kırılgan olabileceğidir. Çözüm, veritabanının yalnızca kalıcı, kanonik UC... Kanal Kimliğine güvenmesi ve bu kimliği dizinlemesi olmalıdır. Sistemin ilk adımı, kullanıcı girdisini kalıcı tanımlayıcıya çeviren bir çeviri katmanı oluşturmak ve böylece kanal takibini geleceğe hazır hale getirmektir.2.1 Hız Sınırlaması ve Eş Zamansız Kısıtlama TasarımıOperasyonel istikrar üzerindeki birincil risk, harici API'nin katı hız limitleridir (10 saniyede 5 istek).8Yönetilen Akış ModeliBoru hattı, AIOLimiter 10 gibi yönetilen eş zamanlılık havuzuna sahip bir eş zamansız kütüphane kullanmalıdır. Bu, Python uygulamasının belirlenen API frekansını kendiliğinden aşmamasını ve yaygın 429 hatalarının (Çok Fazla İstek) oluşmasını önlemesini sağlar.429 Too Many Requests Yanıtının İşlenmesiHarici API, bir hız sınırına ulaşıldığında açıkça bir Retry-After (Tekrar Dene Sonra) başlığı içerir.8 Bu başlık, ne kadar süre beklenmesi gerektiğini saniye cinsinden belirtir.Bu başlık, sorumlu istemci davranışının zorunlu bir talimatı olarak ele alınmalıdır. Python istemcisi, bu başlığı yakalamalı ve başarısız isteği yeniden denemeden önce belirtilen süre boyunca otomatik olarak askıya almalıdır. Bu, dahili statik gecikmeler yerine dinamik, yüksek öncelikli bir bekleme süresi sağlayarak API kullanımının hassasiyetini ve maksimum başarı oranını garanti eder. Statik gecikmelere güvenmek yerine bu dinamik değeri kullanmamak, hizmette bozulmaya yol açabilecek kötü bir mühendislik uygulamasıdır.2.2 Hata Yönetimi ve Yeniden Deneme MekanizmalarıKararlı bir boru hattı için katmanlı bir hata işleme stratejisi zorunludur.Geçici Hata Yönetimi: Kurtarılabilir hatalar (örneğin, 5xx sunucu hataları, geçici bağlantı zaman aşımları veya hafif 429 hataları) için üstel geri çekilme (exponential backoff) mekanizması uygulanmalıdır.Geri Dönüşü Olmayan Hatalar: Kalıcı olarak başarısız olan transkript çıkarma işlemleri (örneğin, alt yazısı olmayan veya silinmiş videolar) veritabanında transcript_status 2 (Başarısız) olarak işaretlenmeli ve günlüğe kaydedilmelidir. Bu, sonraki çalıştırmaların bilinen hataları tekrar tekrar işlemeye çalışmasını engeller.III. Kalıcılık Katmanı Tasarımı ve OptimizasyonuKalıcılık katmanı, durum yönetimi ve CI/CD ortamında zamansal veri depolama için SQLite'a dayanmaktadır.3.0 Durum Yönetimi için SQLite Veri ModellemesiVeritabanı, meta verileri, transkript metnini verimli bir şekilde depolamalı ve en önemlisi, işleme durumunu takip etmelidir.Temel Tablolar: Girdi listesini ve kanonik kimlikleri izlemek için channels ve meta verileri, durumu ve transkript içeriğini saklamak için videos tabloları tanımlanmalıdır.Durum Makinesi: Tamsayı (INTEGER) tipindeki transcript_status sütunu (0: Beklemede, 1: Çıkarıldı/Yayınlandı, 2: Kalıcı Hata) verimli sorgulama için merkezi öneme sahiptir. Boru hattının mevcut çalıştırmada iş gerektiren videoları hızlıca seçmesine olanak tanır (SELECT video_id FROM videos WHERE transcript_status = 0).3.1 Şema Tanımı ve Dizine Ekleme StratejisiYeni içeriği hızlı bir şekilde bulmak için zamansal veri depolaması kritik öneme sahiptir. Zaman alanları dikkatlice seçilmeli ve dizinlenmelidir.Zaman Dilimi FarkındalığıBoru hattı sunucusuz bir ortamda çalışacağından, tüm zaman damgalarının Eşgüdümlü Evrensel Zaman (UTC) olarak depolanması gerekir. SQLite'ın yerel zaman dilimi desteği olmadığından, Python'un zaman dilimi farkında olan datetime nesnelerini depolamadan önce standartlaştırılmış, sıralanabilir bir formata dönüştürmesi zorunludur.14Zamansal Dizin Optimizasyonu"Son çalıştırmadan bu yana yayınlanan tüm videoları bul" gibi zaman serisi aramaları için dizinin doğrudan zaman sütununa uygulanması gerekir. Arama sorgularının WHERE yan tümcesinde DATE() işlevi veya benzeri bir işlev kullanmaktan kaçınılmalıdır, çünkü bu, SQLite'ın dizini etkili bir şekilde kullanmasını engeller.17Bu noktada, SQLite'ın sorgu planlayıcısının sezgisel olarak kısıtlayıcı olmayan dizinleri (örneğin, rastgele bir host dizini) yüksek kısıtlayıcılığa sahip zamansal dizinlere (idx_epoch) tercih edebileceği ve bunun 300 kat performans düşüşüne yol açabileceği bilinmelidir.18 Bu durumun önlenmesi için geliştiricinin, sorgu sırasında istenen dizinin kullanıldığını doğrulamak amacıyla EXPLAIN QUERY PLAN komutunu kullanması ve gerekirse sorgu ipuçları (örneğin, unary + operatörü) kullanarak doğru dizin kullanımını zorlaması bir performans zorunluluğudur.Depolama Formatı Önerisi (UTC ISO 8601)Zaman damgalarının UTC ISO 8601 dizeleri (YYYY-MM-DDTHH:MM:SSZ) olarak depolanması önerilir. Unix epoch tamsayıları, son derece yüksek hacimli veriler için biraz daha iyi depolama yoğunluğu ve hızı sunsa da 19, ISO formatı insan tarafından okunabilirlik sağlar ve hata ayıklama veya manuel sorgulama sırasında tam zaman dilimi bağlamını korur.19 Bu, uygulamanın düşük/orta hacimli gereksinimleri için dizinlenebilirliğinden ödün vermeden operasyonel şeffaflıkta önemli kazanımlar sağlar.Aşağıdaki tablo, önerilen videos tablosu şemasını ve dizinleme stratejisini özetlemektedir:Video Veritabanı Şeması (videos Tablosu)Sütun AdıVeri Tipi (SQLite)AçıklamaDizinleme DurumuGerekçevideo_idTEXT (PK)Kanonik 11 karakterli YouTube video Kimliği.Birincil AnahtarTekil tanımlayıcı.channel_idTEXTKaynak kanalın Kimliği (UC...).Dizinli (FK)Gruplama ve kanala özgü çekme işlemleri için.published_at_utcTEXTVideo yayın zaman damgası (ISO 8601 UTC).Dizinli (idx_published_at)Zamansal aralık sorguları için kritik.17transcript_statusINTEGERİşleme durumu (0: Beklemede, 1: Çıkarıldı, 2: Başarısız).DizinliÇalışma kuyruğunu belirlemek için temel filtre.processed_at_utcTEXTSon başarılı boru hattı çalıştırma zaman damgası (ISO 8601 UTC).YokDenetim izi için faydalıdır.transcript_rawTEXTHam, bölümlenmemiş transkript verisi.YokYeniden işleme/hata ayıklama için tutulur.transcript_cleanTEXTSBD ile işlenmiş, son RSS içeriği.YokYayınlanabilir nihai içerik.IV. Transkript İyileştirme ve NLP İşlemleriHam YouTube transkriptleri, okunaklı bir tam metin RSS akışı için gerekli kalite standartlarını karşılayacak şekilde işlenmelidir.4.0 Veri Temizleme Boru HattıHam transkript verileri genellikle NLP işlemi öncesinde kaldırılması gereken yapay öğeler (artifacts) içerir.Yapay Öğelerin Kaldırılması: Zaman tabanlı işaretçileri, konuşma dışı konuşmacı etiketlerini (örneğin, [Music]) ve aşırı boşlukları kaldırmak için düzenli ifadeler uygulanmalıdır.Normalleştirme: Metin başlangıçta küçük harfe dönüştürülmeli ve daha sonra bölümlendirme sırasında sorunlara neden olabilecek Unicode karakterler normalleştirilmelidir.4.1 Cümle Sınırı Tespiti (SBD) UygulamasıSBD, ham, zaman parçalı metni tutarlı paragraflara dönüştürmek için en kritik adımdır.Noktalama İşaretlerinin Ötesine GeçmekStandart transkriptler dilsel tutarlılığa değil, zaman kodlarına dayanır. Yalnızca temel noktalama işaretlerine (., ?, !) güvenmek yetersizdir; doğal duraklama noktalarını ve anlamsal sınırları tespit edebilen gelişmiş NLP modellerine ihtiyaç vardır.20 Bu süreç, verinin okunamayan bir metin bloğu olmaktan çıkıp, kullanışlı, tam metin içeriğine dönüştüğü temel dönüştürme noktasıdır.SpaCy EntegrasyonuSpaCy 1 gibi hafif ve verimli bir NLP kütüphanesinin kullanılması önerilir. SpaCy'nin varsayılan boru hattı, SBD yeteneklerine güç veren bir ayrıştırıcı (parser) içerir.5 Boru hattı, gerekli modellerin (örneğin, en_core_web_sm) CI/CD ortamında verimli bir şekilde indirilip yüklendiğinden emin olmalıdır. Hızlı ve odaklanmış bir görev için SpaCy, daha ağır araştırma odaklı araçlara göre üstün bir seçimdir.ÖzelleştirmeÖzel içerik (örneğin, çok teknik veya hızlı konuşma) için varsayılan SBD performansı düşükse, planın, belirli metin kalıplarını işlemek üzere SpaCy içinde özel kural tabanlı SBD uzantıları uygulama aşamasını içermesi gerekmektedir.14.2 Okunabilirlik için Biçimlendirme (HTML Çıktısı)Bölümlenmiş metin, RSS içerik alanı için uygun temiz HTML'ye biçimlendirilmelidir.Paragraf Yapılandırması: Birbirine bitişik cümleleri paragraflar halinde birleştirmek için mantık uygulanmalıdır. Bu, tipik olarak belirli sayıda cümleden sonra veya anlamsal/konuşmacı değişikliği bayraklarına (varsa) dayanarak yapılabilir.Minimal HTML Sarmalama: Ortaya çıkan metin, modern RSS okuyucularında düzgün görüntülenmesini sağlamak için <p> etiketleri içine alınmalı ve <content:encoded> öğesine dahil edilmeye hazır hale getirilmelidir.V. RSS Akışı İnşası ve UyumluluğuBu bölüm, nihai çıktının RSS 2.0 spesifikasyonuna sıkı sıkıya uymasını sağlamaya odaklanarak, çeşitli akış toplayıcılarıyla maksimum uyumluluk elde edilmesini amaçlar.5.0 RSS 2.0 Spesifikasyonunun İncelenmesiAkış, tüm zorunlu kanal ve öğe (item) unsurlarını içermelidir.Kanal Gereksinimleri: <channel> bloğu, <title>, <link> ve <description> dahil olmak üzere tanımlanmalıdır.Öğe Gereksinimleri: Her video girişi zorunlu öğe unsurlarını içermelidir: <title>, <link> (video URL'sine), ve published_at_utc'den türetilen doğru biçimlendirilmiş RFC 822 dizesi olarak <pubDate>.5.1 Tam Metin Entegrasyonu ve İçerik Ad Alanı"Tam metin" gereksinimini karşılamak için, tüm transkriptin yayınlanması amacıyla <content:encoded> etiketi kullanılmalıdır.7Ad Alanı Bildirimi: XML başlığı, besleme okuyucularının etiketi doğru tanıması için content ad alanını açıkça bildirmelidir (örneğin, xmlns:content="http://purl.org/rss/1.0/modules/content/").7İçerik Eşlemesi: SBD ile işlenmiş, HTML sarılmış transkriptin tamamı (veritabanından transcript_clean) bu alanı dolduracaktır. Tam metin akışı talebini karşılamak için <content:encoded> kullanımına öncelik verilmesi, <description> etiketinin ikincil, özet bir rol üstlenmesini sağlar.75.2 XML Varlık Kaçış (Entity Escaping) ve CDATA İşlemeXML içine büyük HTML blokları gömmek, doğrulama hatalarını önlemek için sıkı kaçış mekanizmaları gerektirir.6Zorunlu Varlık Kodlaması: HTML transkripti içindeki tüm ayrılmış XML karakterleri (<, >, &, ', ") gömülmeden önce ilgili XML varlıklarına (örneğin, <, >, &) dönüştürülmelidir.Risk Azaltma: HTML içeriği içindeki ampersandlerin (örneğin, src="....?x=1&y=2" gibi bir URL kesiminde) kaçış karakteri kullanılmadan bırakılması, akış okuyucu tarafından çözüldüğünde geçersiz bir RSS akış yapısıyla sonuçlanacaktır.6CDATA İsteğe Bağlılığı: <content:encoded> bloğunu <!]> içine almak, XML ayrıştırıcısına içeriğin çoğunu yoksaymasını söyleyerek büyük miktarda HTML'yi işlemeyi basitleştirse de, içindeki içerik hala düzgün varlık kodlaması gerektirir, özellikle ampersandler söz konusu olduğunda.6 Bu, tam metin içeriği için XML varlık kaçışının kritik öneme sahip olduğunu ve göz ardı edilmemesi gerektiğini gösterir.RSS İçerik Alanları Eşlemesi ve UyumlulukKaynak Veri Alanı (DB)Hedef RSS EtiketiZorunlu/İsteğe Bağlıİçerik FormatıUyumluluk NotuVideo Başlığı<item><title>ZorunluDüz metinXML kaçış karakterleri uygulanmış.Video URL'si<item><link>ZorunluMutlak URLStandart köprü.Yayın Tarihi<item><pubDate>ZorunluRFC 822published_at_utc'den türetilmiştir.Tam Transkript (Temizlenmiş)<item><content:encoded>Temel (Tam Metin) 7HTML/CDATAVarlık kaçış karakterleri uygulanmalıdır (özellikle &).6 Ad alanı bildirimi gereklidir.Video Açıklama Parçacığı<item><description>ÖnerilirKısa HTML parçacığıÖzlü bir özet (örneğin, transkriptin ilk paragrafı).VI. CI/CD Dağıtım Mimarisi (GitHub Actions)Bu bölüm, operasyonel dağıtım, zamanlama ve sunucusuz bir ortamda SQLite veritabanının kalıcılığını sağlamaya odaklanmaktadır.6.0 İş Akışı Tasarımı: Zamanlanmış YürütmeBoru hattı, GitHub Actions schedule tetikleyicisi (cron söz dizimi) kullanılarak periyodik olarak çalışacak şekilde yapılandırılmalıdır.İş Sıralaması: İş akışı şu adımları sıralamalıdır: Kodu Çek (Checkout) -> Veritabanını Geri Yükle (Cache) -> Python Boru Hattını Çalıştır (Kazıma, NLP, DB Güncelleme) -> RSS Oluştur -> Veritabanını Kaydet (Cache) -> RSS Yapıtını Dağıt (Artifact).6.1 Kalıcılık Mekanizması Seçimi: Önbellek (Cache) ve Git Taahhüdü (Commit)SQLite veritabanı dosyasının (data.sqlite3) kalıcılığını sağlamak için doğru mekanizmanın seçilmesi, performans ve veri bütünlüğü açısından hayati öneme sahiptir.Seçenek A: Git Kazıma (Veritabanını Taahhüt Etme): Güncellenmiş DB dosyasının Git deposuna geri taahhüt edilmesini içerir.4 Bu, yerleşik bir sürüm geçmişi sunsa da, boru hattının her çalıştırmasında aşırı taahhüt geçmişi oluşturur ve Git geçmişini kirletir.4Seçenek B: GitHub Actions Önbelleği (actions/cache): İş akışı çalıştırmaları arasında durumun yeniden kullanılması için GitHub'ın dahili bulut depolamasını kullanır.21Öneri Gerekçesi: Önbellek (Cache) üstün bir seçimdir. Bir veritabanı gibi büyük dosyaları iş akışı çalıştırmaları arasında yeniden kullanmak için optimize edilmiştir ve tekrarlanan Git taahhütlerine/klonlamalarına kıyasla önemli bir hız artışı sağlar.23 Önbellekleme, kalıcı veri durumunu kaynak kod deposundan doğru bir şekilde ayırır. Veritabanı (data.sqlite3) bir sonraki çalıştırma için kalıcı bir girdi bağımlılığıdır ve bu nedenle önbellek, boru hattı verimliliği için doğru teknik tercihtir.21Yapıtların Kullanımı: Yapıtlar (Artifacts), iş akışı tamamlandıktan sonra indirilebilmesi veya harici bir barındırma konumuna (örneğin, S3) ara adım olarak hizmet etmesi için nihai, oluşturulan RSS XML dosyası için kullanılmalıdır.216.2 Güvenlik ve Bakım En İyi UygulamalarıGitHub Actions güvenlik protokollerine uyum zorunludur.24Gizli Veri Yönetimi: API anahtarları (gerekliyse 8), GitHub Gizli Verileri (Secrets) olarak güvenli bir şekilde depolanmalı ve en az ayrıcalık ilkesine uygun olarak secrets bağlamı aracılığıyla erişilmelidir.25Eylem Sabitleme (Action Pinning): Kullanılan tüm üçüncü taraf eylemlerin (actions/checkout, actions/cache gibi), bir ana sürüm etiketine değil, tam bir taahhüt SHA'sına (commit SHA) sabitlenmesi gerekmektedir. Bu, kötü niyetli bir aktörün etiketleri değiştirerek tehlikeli kod enjekte etme riskini azaltır.24 Bu, tedarik zinciri bütünlüğü için en yüksek düzeyde güvenlik güvencesidir.İzleme ve Bakım: Kullanılan eylemlerin bildirilen güvenlik açıklarına (Tavsiyeler) karşı izlenmesi ve zamanında güncellemelerin sağlanması için GitHub'ın güvenlik özelliklerinden yararlanılmalıdır.25VII. RSS-Bridge Entegrasyon AnaliziBu bölüm, Python boru hattının mevcut RSS-Bridge ekosistemiyle olan rolünü analiz eder ve her iki çözümün birlikte nasıl kullanılabileceğine dair rehberlik sağlar.7.0 Mevcut YouTube Köprülerinin DeğerlendirilmesiMevcut PHP tabanlı RSS-Bridge çözümleri esneklik sunsa da, önemli istikrar zorluklarıyla karşılaşmaktadır.26İstikrar Endişeleri: Genel RSS-Bridge örneklerinin, harici kısıtlamalar (çoğunlukla YouTube tarafından uygulanan yasaklar veya engellemeler) nedeniyle kaçınılmaz hız limitlerinden ve rastgele köprü arızalarından muzdarip olduğu bilinmektedir.26Ayrıştırma Gerekçesi: Geliştirilen Python boru hattı, eş zamansız olarak hız limitlerini yönetebilmesi 10 ve durumu yerel olarak (SQLite) saklayabilmesi nedeniyle, paylaşılan ve sıklıkla aşırı yüklenen önbelleğe (caching) dayanan genel RSS-Bridge'e kıyasla doğası gereği daha sağlamdır.27 Python boru hattı, harici genel köprü arızalarına karşı bağışıklık kazanan dahili, istikrarlı bir veri kaynağı oluşturur.7.1 Kendi Kendine Barındırma Önerileri (RSS-Bridge)Kullanıcının RSS-Bridge arayüzünü birleşik çoklu kaynak yeteneği için istemesi durumunda, stabilite için kendi kendine barındırma zorunludur.Docker Dağıtımı: Kendi kendine barındırma, genellikle Docker kullanılarak (FreshRSS gibi diğer araçlarla birlikte) uygulanmalıdır.28Yapılandırma En İyi Uygulamaları: Özel yapılandırmanın config.ini.php dosyasında bulunması hayati önem taşır. Geliştiriciler, güncellemeler sırasında üzerine yazılan config.default.ini.php dosyasını kesinlikle düzenlememelidir.29 Adanmış bir config.ini.php oluşturmak, RSS-Bridge sürüm değişikliklerinde yapılandırma kaybına karşı dayanıklılık sağlar.7.2 Özel Entegrasyon YoluPython boru hattının çıktısı, kendi kendine barındırılan RSS-Bridge örneğini geliştirebilir.Köprü Özelleştirmesi: Oluşturulan XML dosyası, RSS-Bridge modülünün (PHP ile yazılmış basit bir modül) doğrudan YouTube'u kazımasına gerek kalmadan kararlı bir veri kaynağı olarak kullanılabilir. Bu hibrit yaklaşım, çıkarma işlemi için Python boru hattının güvenilirliğinden ve sunum ve toplama için RSS-Bridge arayüzünden yararlanır.VIII. Sonuç ve Eyleme Dönük ÖnerilerBu raporun analizi, YouTube transkriptlerinden tam metin RSS akışı oluşturmanın, sadece veri çıkarma değil, aynı zamanda operasyonel güvenilirlik, veri bütünlüğü ve XML standartlarına titizlikle uyum gerektiren çok katmanlı bir mühendislik zorluğu olduğunu göstermektedir.Temel Çıkarımlar ve Eyleme Dönük ÖnerilerPerformans ve İstikrar Mimarisi: Boru hattının istikrarı, hız sınırlarının yönetimine sıkı sıkıya bağlıdır. Sıralı işlemler yerine, AIOLimiter kullanarak eş zamanlı, hız sınırlı bir istemci uygulanması zorunludur. API'den gelen dinamik Retry-After başlıklarının yakalanması ve kullanılması, statik gecikmelerden kaçınarak maksimum uyumluluk ve verim sağlar.8Veri Kalıcılığı ve CI/CD Optimizasyonu: Sunucusuz bir ortam için SQLite, GitHub Actions Önbelleği (actions/cache) ile birleştirilmelidir. Bu, veritabanı durumunun hızla yeniden kullanılmasını sağlayarak boru hattı çalıştırma sürelerini kısaltır. Veritabanı içinde, zaman damgaları (UTC ISO 8601 olarak saklanan published_at_utc) üzerinde dizin oluşturulmalı ve sorgu performansının kritik olarak doğrulanması için EXPLAIN QUERY PLAN kullanılmalıdır. Aksi takdirde, sorgu iyileştiricisinin hatalı bir dizin seçimi yapması, performansta önemli düşüşlere neden olabilir.18İçerik Kalitesi Zorunluluğu: Kullanıcıya tam metin akışı sunma vaadi, SpaCy tabanlı Cümle Sınırı Tespiti (SBD) 2 olmadan yerine getirilemez. Ham transkript metninin doğal duraklama noktalarına göre paragraflara ayrılması, nihai RSS içeriğinin okunabilirliğini doğrudan belirleyen kritik bir NLP adımıdır.5RSS Uyumluluğu: Tam metin gereksinimi için <content:encoded> etiketinin kullanılması şarttır, bu da XML çıktısının content ad alanını bildirmesini gerektirir.7 Entegrasyonun dayanıklılığı için, özellikle HTML içindeki URL'lerdeki ampersandler (&) olmak üzere, tüm ayrılmış XML karakterlerinin titizlikle varlık kaçışından geçirilmesi sağlanmalıdır.6Operasyonel Güvenlik: Tüm üçüncü taraf GitHub Actions kütüphaneleri, tedarik zinciri riskini en aza indirmek için bir etiket yerine tam bir taahhüt SHA'sına (commit SHA) sabitlenmelidir.24 API anahtarları gibi hassas bilgiler, GitHub Secrets kullanılarak yönetilmelidir. \ No newline at end of file