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

View File

@@ -75,6 +75,9 @@ CLAUDE_SYSTEM_PROMPT = """당신은 The 4th Path 블로그 자동 수익 엔진
/report — 주간 리포트
/images — 이미지 제작 현황
/convert — 수동 변환 실행
/novel_list — 연재 소설 목록
/novel_gen [novel_id] — 에피소드 즉시 생성
/novel_status — 소설 파이프라인 진행 현황
사용자의 자연어 요청을 이해하고 적절히 안내하거나 답변해주세요.
한국어로 간결하게 답변하세요."""
@@ -403,6 +406,30 @@ def job_image_prompt_batch():
logger.error(f"이미지 배치 오류: {e}")
def job_novel_pipeline():
"""소설 파이프라인 — 월/목 09:00 활성 소설 에피소드 자동 생성"""
logger.info("[스케줄] 소설 파이프라인 시작")
try:
sys.path.insert(0, str(BASE_DIR / 'bots'))
from novel.novel_manager import NovelManager
manager = NovelManager()
results = manager.run_all()
if results:
for r in results:
if r.get('error'):
logger.error(f"소설 파이프라인 오류 [{r['novel_id']}]: {r['error']}")
else:
logger.info(
f"소설 에피소드 완료 [{r['novel_id']}] "
f"{r['episode_num']}화 blog={bool(r['blog_path'])} "
f"shorts={bool(r['shorts_path'])}"
)
else:
logger.info("[소설] 오늘 발행 예정 소설 없음")
except Exception as e:
logger.error(f"소설 파이프라인 오류: {e}")
# ─── Telegram 명령 핸들러 ────────────────────────────
async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -590,6 +617,27 @@ async def cmd_imgbatch(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("📤 프롬프트 배치 전송 완료.")
async def cmd_novel_list(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""소설 목록 조회"""
sys.path.insert(0, str(BASE_DIR / 'bots'))
from novel.novel_manager import handle_novel_command
await handle_novel_command(update, context, 'list', [])
async def cmd_novel_gen(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""소설 에피소드 즉시 생성: /novel_gen [novel_id]"""
sys.path.insert(0, str(BASE_DIR / 'bots'))
from novel.novel_manager import handle_novel_command
await handle_novel_command(update, context, 'gen', context.args or [])
async def cmd_novel_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""소설 파이프라인 진행 현황"""
sys.path.insert(0, str(BASE_DIR / 'bots'))
from novel.novel_manager import handle_novel_command
await handle_novel_command(update, context, 'status', [])
async def cmd_imgcancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""이미지 대기 상태 취소"""
chat_id = update.message.chat_id
@@ -780,7 +828,12 @@ def setup_scheduler() -> AsyncIOScheduler:
day_of_week='mon', hour=10, minute=0, id='image_batch')
logger.info("이미지 request 모드: 매주 월요일 10:00 배치 전송 등록")
logger.info("스케줄러 설정 완료 (v3 시차 배포 포함)")
# 소설 파이프라인: 매주 월/목 09:00
scheduler.add_job(job_novel_pipeline, 'cron',
day_of_week='mon,thu', hour=9, minute=0, id='novel_pipeline')
logger.info("소설 파이프라인: 매주 월/목 09:00 등록")
logger.info("스케줄러 설정 완료 (v3 시차 배포 + 소설 파이프라인)")
return scheduler
@@ -807,6 +860,11 @@ async def main():
app.add_handler(CommandHandler('imgbatch', cmd_imgbatch))
app.add_handler(CommandHandler('imgcancel', cmd_imgcancel))
# 소설 파이프라인
app.add_handler(CommandHandler('novel_list', cmd_novel_list))
app.add_handler(CommandHandler('novel_gen', cmd_novel_gen))
app.add_handler(CommandHandler('novel_status', cmd_novel_status))
# 이미지 파일 수신
app.add_handler(MessageHandler(filters.PHOTO, handle_photo))
app.add_handler(MessageHandler(filters.Document.IMAGE, handle_document))