## 변환 엔진 (bots/converters/) - blog_converter: HTML 자동감지 + Schema.org JSON-LD + AdSense 플레이스홀더 - card_converter: Pillow 1080×1080 인스타그램 카드 이미지 - thread_converter: X 스레드 280자 자동 분할 - newsletter_converter: 주간 HTML 뉴스레터 - shorts_converter: TTS + ffmpeg 뉴스앵커 쇼츠 영상 (1080×1920) ## 배포 엔진 (bots/distributors/) - image_host: ImgBB 업로드 / 로컬 HTTP 서버 - instagram_bot: Instagram Graph API (컨테이너 → 폴링 → 발행) - x_bot: X API v2 OAuth1 스레드 게시 - tiktok_bot: TikTok Content Posting API v2 청크 업로드 - youtube_bot: YouTube Data API v3 재개가능 업로드 ## 기타 - article_parser: KEY_POINTS 파싱 추가 (SNS/TTS용 핵심 3줄) - publisher_bot: HTML 본문 직접 발행 지원 - scheduler: 시차 배포 스케줄 + Telegram 변환/배포 명령 추가 - remote_claude: Claude Agent SDK Telegram 연동 - templates/shorts_template.json: 코너별 색상/TTS/트랜지션 설정 - scripts/download_fonts.py: NotoSansKR / 맑은고딕 자동 설치 - .gitignore: .claude/, 기획문서, 생성 미디어 파일 추가 - .env.example: 플레이스홀더 텍스트 (실제 값 없음) - README: v3 아키텍처 전체 문서화 (설치/API키/상세설명/FAQ) - requirements.txt: openai, pydub 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
140 lines
4.3 KiB
Python
140 lines
4.3 KiB
Python
"""
|
|
폰트 다운로드 스크립트
|
|
Noto Sans KR 폰트를 assets/fonts/에 다운로드.
|
|
카드 변환봇(card_converter.py)에서 사용.
|
|
실행: python scripts/download_fonts.py
|
|
"""
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
BASE_DIR = Path(__file__).parent.parent
|
|
FONTS_DIR = BASE_DIR / 'assets' / 'fonts'
|
|
FONTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Google Fonts에서 직접 다운로드 (Noto Sans KR)
|
|
FONTS = {
|
|
'NotoSansKR-Regular.ttf': (
|
|
'https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/'
|
|
'NotoSansCJKkr-Regular.otf'
|
|
),
|
|
'NotoSansKR-Bold.ttf': (
|
|
'https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/'
|
|
'NotoSansCJKkr-Bold.otf'
|
|
),
|
|
'NotoSansKR-Medium.ttf': (
|
|
'https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/Korean/'
|
|
'NotoSansCJKkr-Medium.otf'
|
|
),
|
|
}
|
|
|
|
# Windows에 이미 설치된 한글 폰트를 복사하는 대안
|
|
WINDOWS_FONT_CANDIDATES = [
|
|
('malgunbd.ttf', 'NotoSansKR-Bold.ttf'), # 맑은고딕 Bold
|
|
('malgun.ttf', 'NotoSansKR-Regular.ttf'), # 맑은고딕 Regular
|
|
('malgun.ttf', 'NotoSansKR-Medium.ttf'),
|
|
]
|
|
|
|
|
|
def copy_windows_fonts() -> list[str]:
|
|
"""Windows 기본 한글 폰트를 assets/fonts/에 복사"""
|
|
import shutil
|
|
win_fonts = Path('C:/Windows/Fonts')
|
|
copied = []
|
|
for src_name, dst_name in WINDOWS_FONT_CANDIDATES:
|
|
src = win_fonts / src_name
|
|
dst = FONTS_DIR / dst_name
|
|
if dst.exists():
|
|
print(f" 이미 존재: {dst_name}")
|
|
copied.append(dst_name)
|
|
continue
|
|
if src.exists():
|
|
shutil.copy2(src, dst)
|
|
print(f" 복사: {src_name} → {dst_name}")
|
|
copied.append(dst_name)
|
|
else:
|
|
print(f" 없음: {src_name}")
|
|
return copied
|
|
|
|
|
|
def download_from_url(url: str, dst_path: Path) -> bool:
|
|
"""URL에서 폰트 파일 다운로드"""
|
|
try:
|
|
print(f" 다운로드 중: {dst_path.name} ...")
|
|
headers = {'User-Agent': 'Mozilla/5.0'}
|
|
req = urllib.request.Request(url, headers=headers)
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
dst_path.write_bytes(resp.read())
|
|
print(f" 완료: {dst_path.name} ({dst_path.stat().st_size // 1024} KB)")
|
|
return True
|
|
except Exception as e:
|
|
print(f" 실패: {e}")
|
|
return False
|
|
|
|
|
|
def verify_font(font_path: Path) -> bool:
|
|
"""PIL로 폰트 로드 테스트"""
|
|
try:
|
|
from PIL import ImageFont
|
|
ImageFont.truetype(str(font_path), 30)
|
|
return True
|
|
except Exception as e:
|
|
print(f" 폰트 검증 실패: {e}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
print("=== Noto Sans KR 폰트 설치 ===\n")
|
|
print(f"대상 폴더: {FONTS_DIR}\n")
|
|
|
|
# 1단계: Windows 기본 폰트 복사 시도 (가장 빠름)
|
|
print("[1단계] Windows 기본 한글 폰트 복사...")
|
|
copied = copy_windows_fonts()
|
|
|
|
if len(copied) >= 2:
|
|
print(f"\n✅ Windows 폰트 복사 완료 ({len(copied)}개)")
|
|
_verify_all()
|
|
return
|
|
|
|
# 2단계: GitHub에서 직접 다운로드
|
|
print("\n[2단계] GitHub Noto Sans CJK KR 다운로드...")
|
|
downloaded = 0
|
|
for filename, url in FONTS.items():
|
|
dst = FONTS_DIR / filename
|
|
if dst.exists() and dst.stat().st_size > 1000:
|
|
print(f" 이미 존재: {filename}")
|
|
downloaded += 1
|
|
continue
|
|
if download_from_url(url, dst):
|
|
downloaded += 1
|
|
|
|
if downloaded > 0:
|
|
print(f"\n✅ 다운로드 완료 ({downloaded}개)")
|
|
_verify_all()
|
|
else:
|
|
print("\n❌ 폰트 설치 실패. 수동 설치 방법:")
|
|
print(" 1. https://fonts.google.com/noto/specimen/Noto+Sans+KR 에서 다운로드")
|
|
print(f" 2. TTF 파일들을 {FONTS_DIR} 에 복사")
|
|
print(" 3. NotoSansKR-Regular.ttf, NotoSansKR-Bold.ttf 로 이름 변경")
|
|
sys.exit(1)
|
|
|
|
|
|
def _verify_all():
|
|
print("\n[검증] 폰트 로드 테스트...")
|
|
ok = True
|
|
for f in FONTS_DIR.glob('*.ttf'):
|
|
if verify_font(f):
|
|
print(f" ✅ {f.name}")
|
|
else:
|
|
print(f" ❌ {f.name}")
|
|
ok = False
|
|
if ok:
|
|
print("\n카드 변환봇 준비 완료!")
|
|
else:
|
|
print("\n일부 폰트 오류. card_converter.py는 대체 폰트로 동작합니다.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|