feat: v3.0 엔진 추상화 + 소설 파이프라인 추가

[1순위] 엔진 추상화 리팩토링
- config/engine.json: 단일 설정 파일로 writing/tts/image/video/publishing 엔진 제어
- bots/engine_loader.py: EngineLoader 팩토리 클래스 (Claude/OpenClaw/Gemini Writer, gTTS/GoogleCloud/OpenAI/ElevenLabs TTS, DALL-E/External 이미지)

[2순위] VideoEngine 추상화
- bots/converters/video_engine.py: VideoEngine ABC + FFmpegSlidesEngine/SeedanceEngine/SoraEngine/RunwayEngine/VeoEngine 구현
- Seedance 2.0 API 연동 + 실패 시 ffmpeg_slides 자동 fallback

[3순위] 소설 연재 파이프라인
- bots/novel/novel_writer.py: AI 에피소드 자동 생성 (Claude/엔진 추상화)
- bots/novel/novel_blog_converter.py: 에피소드 → 장르별 테마 Blogger HTML
- bots/novel/novel_shorts_converter.py: key_scenes → TTS + Pillow + VideoEngine → MP4
- bots/novel/novel_manager.py: 전체 파이프라인 조율 + Telegram 명령 처리
- config/novels/shadow-protocol.json: 예시 소설 설정 (2040 서울 SF 스릴러)

[스케줄러] 소설 파이프라인 통합
- 매주 월/목 09:00 자동 실행 (job_novel_pipeline)
- Telegram 명령: /novel_list, /novel_gen, /novel_status

[기타 수정]
- collector_bot.py: 한국어 유니코드 감지 + RSS 신뢰도 override 버그 수정
- quality_rules.json: min_score 70→60
- scripts/get_token.py: YouTube OAuth scope 추가
- .env.example: SEEDANCE/ELEVENLABS/GEMINI/RUNWAY API 키 항목 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sinmb79
2026-03-26 09:33:04 +09:00
parent b54f8e198e
commit 8a7a122bb3
16 changed files with 3487 additions and 8 deletions
+24 -5
View File
@@ -97,10 +97,24 @@ def calc_freshness_score(published_at: datetime | None, max_score: int = 20) ->
def calc_korean_relevance(text: str, rules: dict) -> int:
"""한국 독자 관련성 점수"""
max_score = rules['scoring']['korean_relevance']['max']
keywords = rules['scoring']['korean_relevance']['keywords']
# 한국어 문자(가-힣) 비율 체크 — 한국어 콘텐츠 자체에 기본점수 부여
korean_chars = sum(1 for c in text if '\uac00' <= c <= '\ud7a3')
korean_ratio = korean_chars / max(len(text), 1)
if korean_ratio >= 0.15:
base = 15 # 한국어 텍스트면 기본 15점
elif korean_ratio >= 0.05:
base = 8
else:
base = 0
# 브랜드/지역 키워드 보너스
matched = sum(1 for kw in keywords if kw in text)
score = min(matched * 6, rules['scoring']['korean_relevance']['max'])
return score
bonus = min(matched * 5, max_score - base)
return min(base + bonus, max_score)
def calc_source_trust(source_url: str, rules: dict) -> tuple[int, str]:
@@ -215,9 +229,14 @@ def calculate_quality_score(item: dict, rules: dict) -> int:
kr_score = calc_korean_relevance(text, rules)
fresh_score = calc_freshness_score(pub_at)
# search_demand: pytrends 연동 후 실제값 사용 (현재 기본값 10)
search_score = item.get('search_demand_score', 10)
trust_score, trust_level = calc_source_trust(source_url, rules)
# search_demand: pytrends 연동 후 실제값 사용 (RSS 기본값 12)
search_score = item.get('search_demand_score', 12)
# 신뢰도: _trust_override 이미 설정된 경우 우선 사용
if '_trust_score' in item:
trust_score = item['_trust_score']
trust_level = item.get('source_trust_level', 'medium')
else:
trust_score, trust_level = calc_source_trust(source_url, rules)
mono_score = calc_monetization(text, rules)
item['korean_relevance_score'] = kr_score