feat: YouTube Shorts 파이프라인 완성 및 HJW TV 업로드 연동
- youtube_uploader.py: YOUTUBE_REFRESH_TOKEN/CLIENT_ID/CLIENT_SECRET 환경변수 폴백 추가 (token.json 없는 Docker 환경에서 브랜드 계정 인증 가능) - shorts_config.json: corners_eligible를 실제 블로그 코너명으로 수정 - caption_renderer.py: render_captions() 반환값 누락 수정 - get_token.py: web→installed 타입 변환, port 8080 고정, prompt=consent 추가 - get_youtube_token.py: YouTube 전용 OAuth 토큰 발급 스크립트 (별도 클라이언트) - CLAUDE.md: 프로젝트 개요, 배포 방법, 핵심 파일, YouTube 채널 정보 추가 - publisher_bot.py: 이미지 분산 배치, SEO 검증, 버그 수정 - scheduler.py: 알림 강화, atomic write, 중복 방지, hot reload 개선 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -375,6 +375,7 @@ def render_captions(
|
||||
ass_content = header + '\n'.join(events) + '\n'
|
||||
ass_path.write_text(ass_content, encoding='utf-8-sig') # BOM for Windows compatibility
|
||||
logger.info(f'ASS 자막 생성: {ass_path.name} ({len(timestamps)}단어, {len(lines)}라인)')
|
||||
return ass_path
|
||||
|
||||
|
||||
# ── Standalone test ──────────────────────────────────────────────
|
||||
@@ -429,4 +430,3 @@ if __name__ == '__main__':
|
||||
assert exists and size > 0, "ASS 파일 생성 실패"
|
||||
|
||||
print("\n✅ 모든 테스트 통과")
|
||||
return ass_path
|
||||
|
||||
@@ -40,31 +40,56 @@ def _load_config() -> dict:
|
||||
|
||||
|
||||
def _get_youtube_service():
|
||||
"""YouTube Data API v3 서비스 객체 생성 (기존 OAuth token.json 재사용)."""
|
||||
"""YouTube Data API v3 서비스 객체 생성 (token.json 우선, env fallback)."""
|
||||
from google.oauth2.credentials import Credentials
|
||||
from google.auth.transport.requests import Request
|
||||
from googleapiclient.discovery import build
|
||||
|
||||
if not TOKEN_PATH.exists():
|
||||
raise RuntimeError(f'OAuth 토큰 없음: {TOKEN_PATH} — scripts/get_token.py 실행 필요')
|
||||
creds = None
|
||||
|
||||
creds_data = json.loads(TOKEN_PATH.read_text(encoding='utf-8'))
|
||||
client_id = os.environ.get('GOOGLE_CLIENT_ID', creds_data.get('client_id', ''))
|
||||
client_secret = os.environ.get('GOOGLE_CLIENT_SECRET', creds_data.get('client_secret', ''))
|
||||
# 1) token.json 파일 우선
|
||||
if TOKEN_PATH.exists():
|
||||
creds_data = json.loads(TOKEN_PATH.read_text(encoding='utf-8'))
|
||||
client_id = os.environ.get('GOOGLE_CLIENT_ID', creds_data.get('client_id', ''))
|
||||
client_secret = os.environ.get('GOOGLE_CLIENT_SECRET', creds_data.get('client_secret', ''))
|
||||
creds = Credentials(
|
||||
token=creds_data.get('token'),
|
||||
refresh_token=creds_data.get('refresh_token') or os.environ.get('GOOGLE_REFRESH_TOKEN'),
|
||||
token_uri='https://oauth2.googleapis.com/token',
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
)
|
||||
|
||||
creds = Credentials(
|
||||
token=creds_data.get('token'),
|
||||
refresh_token=creds_data.get('refresh_token') or os.environ.get('GOOGLE_REFRESH_TOKEN'),
|
||||
token_uri='https://oauth2.googleapis.com/token',
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
scopes=YOUTUBE_SCOPES,
|
||||
)
|
||||
if creds.expired and creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
# 갱신된 토큰 저장
|
||||
creds_data['token'] = creds.token
|
||||
TOKEN_PATH.write_text(json.dumps(creds_data, indent=2), encoding='utf-8')
|
||||
# 2) .env의 YOUTUBE_REFRESH_TOKEN으로 직접 생성 (Docker 환경 대응)
|
||||
if not creds:
|
||||
refresh_token = os.environ.get('YOUTUBE_REFRESH_TOKEN', '') or os.environ.get('GOOGLE_REFRESH_TOKEN', '')
|
||||
client_id = os.environ.get('YOUTUBE_CLIENT_ID', '') or os.environ.get('GOOGLE_CLIENT_ID', '')
|
||||
client_secret = os.environ.get('YOUTUBE_CLIENT_SECRET', '') or os.environ.get('GOOGLE_CLIENT_SECRET', '')
|
||||
if not all([refresh_token, client_id, client_secret]):
|
||||
raise RuntimeError(
|
||||
'OAuth 인증 정보 없음: token.json 또는 '
|
||||
'YOUTUBE_REFRESH_TOKEN/YOUTUBE_CLIENT_ID/YOUTUBE_CLIENT_SECRET 환경변수 필요'
|
||||
)
|
||||
creds = Credentials(
|
||||
token=None,
|
||||
refresh_token=refresh_token,
|
||||
token_uri='https://oauth2.googleapis.com/token',
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
)
|
||||
logger.info('token.json 없음 — YOUTUBE_REFRESH_TOKEN 환경변수로 인증')
|
||||
|
||||
# 토큰 갱신
|
||||
if creds.expired or not creds.token:
|
||||
if creds.refresh_token:
|
||||
creds.refresh(Request())
|
||||
# token.json이 있으면 갱신된 토큰 저장
|
||||
if TOKEN_PATH.exists():
|
||||
creds_data = json.loads(TOKEN_PATH.read_text(encoding='utf-8'))
|
||||
creds_data['token'] = creds.token
|
||||
TOKEN_PATH.write_text(json.dumps(creds_data, indent=2), encoding='utf-8')
|
||||
else:
|
||||
raise RuntimeError('refresh_token 없음 — 재인증 필요')
|
||||
|
||||
return build('youtube', 'v3', credentials=creds)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user