feat: 쇼츠 품질 모듈 4종 파이프라인 연결
- MotionEngine: stock_fetcher에서 kenburns 대신 7패턴 모션 적용 - HookOptimizer: 스크립트 추출 후 훅 점수 평가 및 최적화 - CaptionTemplates: 코너별 자막 템플릿 매핑 (AI인사이트→brand_4thpath 등) - ResilientAssembler: 클립별 개별 인코딩 + GPU 자동 감지 - video_assembler work_dir mkdir 누락 버그 수정 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+16
-5
@@ -160,7 +160,8 @@ def produce(article: dict, dry_run: bool = False, cfg: Optional[dict] = None) ->
|
||||
from shorts.stock_fetcher import fetch_clips
|
||||
from shorts.tts_engine import generate_tts
|
||||
from shorts.caption_renderer import render_captions
|
||||
from shorts.video_assembler import assemble
|
||||
from shorts.video_assembler import ResilientAssembler
|
||||
from shorts.hook_optimizer import HookOptimizer
|
||||
|
||||
if cfg is None:
|
||||
cfg = _load_config()
|
||||
@@ -192,6 +193,14 @@ def produce(article: dict, dry_run: bool = False, cfg: Optional[dict] = None) ->
|
||||
manifest = resolve(article, script=script, cfg=cfg)
|
||||
result.steps_completed.append('script_extract')
|
||||
|
||||
# ── STEP 1.5: Hook Optimization ─────────────────────────
|
||||
hook_optimizer = HookOptimizer(threshold=70)
|
||||
original_hook = script.get('hook', '')
|
||||
optimized_hook = hook_optimizer.optimize(original_hook, article)
|
||||
if optimized_hook != original_hook:
|
||||
script['hook'] = optimized_hook
|
||||
logger.info(f'[{article_id}] 훅 최적화: "{original_hook[:20]}" → "{optimized_hook[:20]}"')
|
||||
|
||||
# ── STEP 2: Visual Sourcing ──────────────────────────────
|
||||
logger.info(f'[{article_id}] STEP 2: Visual Sourcing')
|
||||
clips = fetch_clips(script, manifest, clips_dir, ts, cfg=cfg)
|
||||
@@ -227,12 +236,14 @@ def produce(article: dict, dry_run: bool = False, cfg: Optional[dict] = None) ->
|
||||
logger.info(f'[{article_id}] STEP 4: Caption Rendering')
|
||||
from shorts.tts_engine import _get_wav_duration
|
||||
wav_dur = _get_wav_duration(tts_wav)
|
||||
ass_path = render_captions(script, timestamps, captions_dir, ts, wav_dur, cfg=cfg)
|
||||
corner = article.get('corner', '')
|
||||
ass_path = render_captions(script, timestamps, captions_dir, ts, wav_dur, cfg=cfg, corner=corner)
|
||||
result.steps_completed.append('caption_render')
|
||||
|
||||
# ── STEP 5: Video Assembly ───────────────────────────────
|
||||
logger.info(f'[{article_id}] STEP 5: Video Assembly')
|
||||
video_path = assemble(clips, tts_wav, ass_path, rendered_dir, ts, cfg=cfg)
|
||||
# ── STEP 5: Video Assembly (ResilientAssembler + GPU 자동 감지) ──
|
||||
logger.info(f'[{article_id}] STEP 5: Video Assembly (Resilient)')
|
||||
assembler = ResilientAssembler(cfg=cfg)
|
||||
video_path = assembler.assemble_resilient(clips, tts_wav, ass_path, rendered_dir, ts)
|
||||
result.video_path = str(video_path)
|
||||
result.steps_completed.append('video_assemble')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user