import gradio as gr import random import os import re from huggingface_hub import InferenceClient from youtube_transcript_api import YouTubeTranscriptApi, NoTranscriptFound from fpdf import FPDF from fpdf.enums import XPos, YPos from datetime import datetime # 클라이언트 생성 함수 def create_client(model_name): return InferenceClient(model_name, token=os.getenv("HF_TOKEN")) client = create_client("CohereForAI/c4ai-command-r-plus") # API 호출 함수 def call_api(content, system_message, max_tokens, temperature, top_p): messages = [{"role": "system", "content": system_message}, {"role": "user", "content": content}] random_seed = random.randint(0, 1000000) response = client.chat_completion(messages=messages, max_tokens=max_tokens, temperature=temperature, top_p=top_p, seed=random_seed) return response.choices[0].message.content # 정보 분석 함수 def analyze_info(category, style, transcripts): transcript_list = transcripts.split("\n\n---\n\n") analyzed_content = f"선택한 카테고리: {category}\n선택한 포스팅 스타일: {style}\n\n" for i, transcript in enumerate(transcript_list, 1): analyzed_content += f"유튜브 트랜스크립트 {i}:\n{transcript}\n\n" return analyzed_content # 블로그 포스트 생성 함수 def generate_blog_post(category, style, transcripts, category_prompt, style_prompt, max_tokens, temperature, top_p): full_content = analyze_info(category, style, transcripts) combined_prompt = f"{category_prompt}\n\n{style_prompt}\n\n{full_content}" modified_text = call_api(combined_prompt, "", max_tokens, temperature, top_p) return modified_text.replace('\n', '\n\n') # 유튜브 대본 요약 함수 def summarize_transcript(transcripts, system_message, max_tokens, temperature, top_p): summary = call_api(transcripts, system_message, max_tokens, temperature, top_p) return summary # 유튜브 비디오 ID 추출 함수 def get_video_id(youtube_url): video_id_match = re.search(r"(?<=v=)[^#&?]*", youtube_url) or re.search(r"(?<=youtu.be/)[^#&?]*", youtube_url) return video_id_match.group(0) if video_id_match else None # 유튜브 트랜스크립트 추출 함수 def get_transcript(youtube_url): video_id = get_video_id(youtube_url) if not video_id: return "Invalid YouTube URL. Please enter a valid URL." language_order = ['ko', 'en', 'ja', 'zh-Hans', 'pt', 'es', 'it', 'fr', 'de', 'ru'] for lang in language_order: try: transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=[lang]) return f"Transcript in {lang}:\n\n{' '.join([entry['text'] for entry in transcript])}" except NoTranscriptFound: continue except Exception as e: return f"Error: {str(e)}" return "No transcript available in the specified languages." # 카테고리별 프롬프트 함수 def get_blog_post_prompt(category): if category == "일반형": return """ #유튜브 대본을 블로그 포스팅으로 변환하는 규칙(일반형_v4) ##[기본 규칙] 1.한국어로 작성하되, 쉽고 자연스러운 표현 사용 2.여러 유튜브 대본의 내용을 종합하여 하나의 일관된 블로그 글로 재구성 3. 각 섹션은 최소 400자 이상 작성하고 최종 글의 길이는 3000자 이상으로 작성 4. 추천 제목 3가지, 도입부, 본론(소주제 포함), 결론의 구조로 작성할 것(다른 구조 출력금지) 5. 주제에 맞는 독창적인 가상 필자 닉네임을 사용하여 작성할 것(필명 별도 출력 금지) 6. 포스팅 스타일에 맞게 말투, 어투, 어휘를 조정할 것 ##[포스팅 구조] ###[추천 제목] 1. 통합된 주제를 반영하고 독자의 흥미를 유발하는 제목 3가지 제시 ###[도입부] 1. 여러 영상의 공통 주제나 핵심 메시지를 소개 2. 독자의 관심을 끄는 요소(질문, 흥미로운 사실, 통계, 공감대 형성 등)로 시작 3. 글에서 다룰 주요 내용을 간략히 소개 ###[본론] 1. 주제별 통합 분석 - 공통 주제나 연관성 있는 내용을 중심으로 섹션 구성 - 각 영상의 관점이나 정보를 비교/대조하며 종합 - 섹션 간 자연스러운 연결과 흐름 유지 2. 독특한 인사이트 강조 - 각 영상에서 제시된 고유한 관점이나 정보 강조 - 필요시 배경 정보, 예시, 관련 통계 등을 추가하여 내용 보강 3. 데이터 및 예시 통합 - 여러 대본에서 제시된 데이터나 예시를 종합하여 제시 - 독자가 공감할 수 있는 상황이나 사례 포함 4. 시간적 흐름 또는 발전 과정 반영 - 주제에 관한 시간적 변화나 발전 과정이 있다면 이를 설명 ###[결론] 1. 통합된 내용의 핵심 포인트 요약 2. 독자에게 관련 행동이나 생각의 변화 유도 3. 추가 탐구가 필요한 주제나 향후 전망 제시 ##[최적화 전략] 1. SEO 최적화 - 통합된 주제와 관련된 주요 키워드를 제목, 소제목, 본문에 자연스럽게 포함 - 검색 의도에 부합하는 포괄적인 내용 구성 2. 가독성 향상 - 간결하고 명확한 문장 사용 - 적절한 단락 구분과 여백 활용 3. 독자 참여 유도 - 독자의 경험이나 의견을 묻는 질문 포함 - 관련 정보나 추가 자료에 대한 안내 제공 ##[교정 및 편집] 1. 맞춤법, 문법, 어휘 사용의 정확성 확인 2. 문체와 톤의 일관성 유지 3. 전체 글의 논리적 흐름과 구조 점검 4. '필자'라는 표현 제외 5. 유튜브 관련 표현 제거 및 블로그 형식에 맞게 조정 6. 유튜브 특정 용어(예: '구독', '좋아요', '알림', '다음 영상' 등) 사용 금지 7. '유튜버', 'YouTuber' 등의 표현을 '제작자' 또는 '진행자' 같은 다른 표현으로 대체 8. 채널명, 제작자 실명 등 개인 정보 제외 ##[주의사항] 1. 대본의 직접적인 인용 금지, 내용을 재구성하여 작성 2. 상충되는 정보가 있을 경우 공정하게 다루고 가능한 설명 제시 """ elif category == "정보성": return """ #유튜브 대본을 블로그 포스팅으로 변환하는 규칙(정보성_v4) ##[기본 원칙] 1. 반드시 한국어(한글)로 작성하라 2. 여러 유튜브 대본의 내용을 종합하여 하나의 주제에 대한 정보를 제공하는 블로그 글로 재구성하라 3. 최종 글의 길이는 3000-4000자 사이로 작성 4. 추천제목3가지, 도입부, 본론, 결론의 구조로 작성 5. 객관적이고 신뢰할 수 있는 정보를 제공하는 톤으로 작성 6. 여러 유튜브 대본의 내용을 종합하여 하나의 포괄적이고 독창적인 블로그 글 작성 ##[포스팅 구조] ###[추천제목] 1. 주제를 명확히 나타내고 독자의 관심을 끌 수 있는 제목 3가지를 가장 먼저 출력하라 2. 독자의 관심을 끌 수 있는 형태로 작성하라(예: "알아두면 유용한 ~", "전문가가 알려주는 ~") ##[도입부] 1. 주제의 중요성과 일상생활에서의 관련성 설명 2. 다룰 주요 내용 간략히 소개 3. 독자가 얻을 수 있는 실용적인 정보나 이점 제시 4. 독자의 관심을 끄는 요소(질문, 흥미로운 사실, 통계, 공감대 형성 등)로 시작 ###[본론] 1. 주제에 따라 4~6개의 주요 섹션으로 유연하게 구성하라. 2. 작성된 글이 2500자 이상 되도록 작성하라 3. 각 섹션은 명확한 소제목을 사용하여 구분하라 4. 정보의 중요도에 따라 내용의 깊이와 순서 조정하라 5. 각 섹션은 주제의 핵심 측면을 다루도록한다. ###[결론] 1. 주요 포인트 요약 2. 독자에게 실용적인 조언이나 행동 지침 제공 3. 독자와의 상호작용을 유도하는 질문이나 call-to-action 포함 4. 주제와 관련된 향후 전망이나 추세 간단히 언급 ##[최적화 전략] ###[SEO 최적화] 1. 주제와 관련된 키워드를 자연스럽게 포함 (키워드 밀도 2-3% 유지) 2. 검색 의도에 부합하는 포괄적인 내용 구성 ###[가독성 향상] 1. 명확한 소제목과 단락 구분 사용 2. 필요시 번호 매기기나 글머리 기호 사용 ##[주의사항] 1. 여러개의 유튜브 대본중 하나에 치우친 내용 금지, 객관적이고 공정한 정보 제공 2. 최신 정보 사용, 작성 시점 명시 3. 복잡한 전문 용어나 개념은 일반 독자도 이해할 수 있게 쉽게 설명 4. 유튜브 관련 표현 제거 및 블로그 형식에 맞게 조정 5. 유튜브 특정 용어(예: '구독', '좋아요', '알림', '다음 영상' 등) 사용 금지 6. '유튜버', 'YouTuber' 등의 표현을 '제작자' 또는 '진행자' 같은 다른 표현으로 대체 7. 채널명, 제작자 실명 등 개인 정보 제외 """ elif category == "1개 상품 추천형": return """ #유튜브 대본을 블로그 포스팅으로 변환하는 규칙(추천형_v4) ##[기본 규칙] 1. 한국어로 작성하되, 쉽고 자연스러운 표현 사용 2. 여러 유튜브 대본의 내용을 종합하여 하나의 상품에 집중된 추천 블로그 글로 재구성 3. 최종 글의 길이는 3000자 이상으로 작성 4. 추천 제목, 도입부, 본론, 결론의 구조로 작성 5. 주제에 맞는 독창적인 가상 필자 닉네임을 사용하여 작성 6. 전문성과 객관성을 갖춘 톤으로 작성 ##[포스팅 구조] ###[추천 제목] 1. 제품의 핵심 장점이나 특징을 강조하는 제목 2. 독자의 호기심을 자극하는 질문형 제목 3. 제품의 혁신성이나 차별점을 부각시키는 제목 4. 추천제목 3가지만 출력하라(다른구조, 설명 출력금지) ###[도입부] 1. 제품의 주요 특징과 시장에서의 위치 소개 2. 독자의 관심을 끄는 요소(문제 제기, 흥미로운 사실 등) 포함 3. 리뷰의 주요 내용 간략히 예고 ###[본론] - 다음 요소들을 주제에 맞게 선택적으로 포함하고 맞는 주제가 없다면 적합한 주제를 생성하라 - 단, 충분한 양(3000-4000자)의 글이 작성될 수 있도록 섹션을 설정하라. - 각 섹션은 명확한 소제목을 사용하여 구분하고, 정보의 중요도에 따라 내용의 깊이와 순서, 소제목 조정하라 1. 제품 개요 - 기본 스펙 및 주요 기능 설명 - 경쟁 제품과의 차별점 강조 2. 디자인 및 build quality - 외관 디자인 및 마감 품질 평가 - 무게, 크기 등 휴대성 관련 정보 3. 성능 분석 - 벤치마크 테스트 결과 상세 설명 (필요시 그래프나 표 활용) - 실제 사용 경험 기반의 성능 평가 - 발열 및 소음 관련 정보 4. 배터리 성능 - 배터리 지속 시간 테스트 결과 - 충전 속도 및 배터리 관리 기능 설명 5. 특수 기능 및 소프트웨어 - AI 기능 등 제품만의 특별한 기능 상세 설명 - 제조사 전용 소프트웨어의 유용성 평가 6. 사용 경험 - 키보드, 트랙패드, 디스플레이 등 사용감 평가 - 일상적인 작업 및 특수한 상황에서의 사용 경험 공유 7. 호환성 및 확장성 - 소프트웨어 호환성 이슈 설명 - 포트 및 연결성 관련 정보 8. 가격 및 경쟁력 - 가격대 비 성능 평가 - 유사 제품과의 비교 분석 ###[결론] 1. 제품의 핵심 장단점 요약 2. 적합한 사용자 유형 제시 3. 최종 추천 의견 및 구매 조언 ##[최적화 전략] 1. SEO 최적화 - 제품명, 주요 기능, 성능 관련 키워드 자연스럽게 포함 - 검색 의도에 부합하는 포괄적인 내용 구성 2. 가독성 향상 - 명확한 소제목 사용으로 정보 구분 - 중요 정보는 굵은 글씨나 기울임꼴로 강조 - 필요시 표나 차트 활용하여 정보 시각화 3. 독자 참여 유도 - 제품 사용 경험이나 의견을 묻는 질문 포함 - 추가 정보나 리소스에 대한 안내 제공 ##[주의사항] 1. 객관적이고 공정한 리뷰 제공, 과도한 홍보성 표현 자제 2. 테스트 환경 및 조건을 명확히 명시 3. 가격 정보 등은 작성 시점 기준임을 명시 4. 유튜브 관련 표현 제거 및 블로그 형식에 맞게 조정 5. 전문 용어나 기술적 특징 설명 시 일반 독자도 이해할 수 있도록 쉽게 풀어서 설명 6. 제품의 한계점이나 개선이 필요한 부분도 균형있게 다룸 """ elif category == "큐레이션형": return """ #유튜브 대본을 블로그 포스팅으로 변환하는 규칙(큐레이션형_v3) ##[기본 규칙] 1. 한국어로 작성하되, 쉽고 자연스러운 표현 사용하라 2. 여러 유튜브 대본의 내용을 종합하여 하나의 주제에 대한 다양한 상품 추천 블로그 글로 재구성 3. 반드시 각 섹션은 최소 400자 이상 작성하고 최종 글의 길이는 3000자 이상으로 작성 4. 추천 제목, 도입부, 본론(제품 유형 비교, 선정 기준, 추천 상품 리스트), 결론의 구조로 작성 5. 주제에 맞는 독창적인 가상 필자 닉네임을 사용하여 작성 6. 객관적이고 정보 제공에 중점을 둔 톤으로 작성 ##[포스팅 구조] ###[추천 제목] 1. 추천제목 3가지만 출력하라(다른구조, 설명 출력금지) 2. 독자의 호기심을 자극하는 흥미로운 제목 사용 3. 제품의 핵심 가치나 문제 해결 능력을 강조하는 제목 4. 비교나 대조를 활용한 제목 5. 질문 형식이나 도전적인 문구를 사용한 제목 6. 숫자나 순위를 활용한 제목 (필요시) 7. 독자층을 명확히 하는 제목 8. "Best X" 또는 "Top Y" 형식의 제목도 적절히 사용하라 9. 독자의 관심을 끌 수 있는 흥미로운 제목 ###[도입부] 1. 추천 주제의 필요성과 선택의 어려움 소개 2. 글의 목적 설명 (제품 유형 비교 및 추천 제품 소개) 3. 독자의 관심을 끄는 요소 포함 (예: 계절적 요인, 생활 개선 효과 등) ###[본론] - 다음 요소들을 주제에 맞게 선택적으로 포함하고 맞는 주제가 없다면 적합한 주제를 생성하라 - 단, 충분한 양(3000-4000자)의 글이 작성될 수 있도록 섹션을 설정하라. - 각 섹션은 명확한 소제목을 사용하여 구분하고, 정보의 중요도에 따라 내용의 깊이와 순서 조정 1. 제품 유형별 비교 - 각 유형의 장단점 분석 - 사용 환경에 따른 적합성 설명 2. 제품 선정 기준 상세 설명 - 주요 기능 및 성능 설명 - 사용 편의성 고려 사항 - 가격 및 유지 비용 분석 3. 추천 제품 리스트 (3-5개) - 제품명과 핵심 특징 소개 - 각 제품의 장단점 상세 설명 - 적합한 사용자 유형 제시 4. 브랜드 및 모델 라인업 설명 (필요시) - 주요 브랜드의 제품 라인 소개 - 모델별 차이점 및 특징 설명 ###[결론] 1. 추천 제품 요약 및 최종 선택 가이드 2. 구매 시 고려해야 할 추가 사항 안내 3. 향후 기술 발전 방향이나 트렌드 제시 (있을 경우) ##[최적화 전략] 1. SEO 최적화 - 추천 주제와 관련된 주요 키워드를 자연스럽게 포함 - 검색 의도에 부합하는 포괄적인 내용 구성 2. 가독성 향상 - 간결하고 명확한 문장 사용 - 적절한 단락 구분과 번호 매기기 활용 - 필요시 표나 차트 활용하여 정보 시각화 3. 독자 참여 유도 - 독자의 경험이나 의견을 묻는 질문 포함 - 관련 정보나 추가 리소스에 대한 안내 제공 ##[주의사항] 1. 객관적이고 공정한 비교 분석 제공 2. 과도한 홍보성 표현 자제 3. 가격 정보 등은 작성 시점 기준임을 명시 4. 유튜브 관련 표현 제거 및 블로그 형식에 맞게 조정 5. 개인정보 보호를 위해 특정 개인의 실명이나 채널명 언급 금지 6. 다양한 브랜드와 가격대의 제품을 포함하여 편향성 방지 7. 전문 용어나 기술적 특징 설명 시 일반 독자도 이해할 수 있도록 쉽게 풀어서 설명 """ # 포스팅 스타일 프롬프트 함수 def get_style_prompt(style): prompts = { "친근한": """ #친근한 블로그 포스팅 스타일 가이드 1. 톤과 어조 - 대화하듯 편안하고 친근한 말투 사용 - 독자를 "여러분" 또는 "독자님들"로 지칭 - 적절한 이모지를 sparse하게 사용하여 친근감 표현 2. 문장 및 내용 구성 - 짧고 간결한 문장 위주로 작성 - 구어체 표현 사용 (예: "~했어요", "~인 것 같아요") - '~니다', '~했죠'는 사용하지 말 것. - 개인적인 경험이나 일화로 시작 - 실생활에서 쉽게 적용할 수 있는 팁 제공 - 독자의 공감을 얻을 수 있는 사례 포함 3. 용어 및 설명 방식 - 전문 용어 대신 쉬운 단어로 풀어서 설명 - 비유나 은유를 활용하여 복잡한 개념 설명 - 수사의문문 활용하여 독자와 소통하는 느낌 주기 4. 독자와의 상호작용 - 독자의 의견을 물어보는 질문 포함 - 댓글 달기를 독려하는 문구 사용 5. 이모지 활용 - 주요 포인트나 새로운 섹션을 시작할 때만 관련 이모지 사용 - 전체 글에서 3-5개 정도의 이모지만 사용하여 과도하지 않게 유지 6. 마무리 - 친근하고 격려하는 톤으로 마무리 - 다음 포스팅에 대한 기대감 유발 주의사항: 너무 가벼운 톤은 지양하고, 주제의 중요성을 해치지 않는 선에서 친근함 유지 예시: "여러분, 오늘 하루는 어떠셨나요? 😊 저는 오늘 재미있는 경험을 했어요. 바로 '미니멀 라이프'에 도전해본 건데요. 처음에는 좀 막막했지만, 생각보다 즐거운 경험이었어요! 여러분도 한번 따라해보시겠어요? 제가 알게 된 꿀팁들을 하나하나 알려드릴게요. """, "일반":"""#일반적인 블로그 포스팅 스타일 가이드 1. 톤과 어조 - 중립적이고 객관적인 톤 유지 - 적절한 존댓말 사용 (예: "~합니다", "~입니다") 2. 내용 구조 및 전개 - 명확한 주제 제시로 시작 - 논리적인 순서로 정보 전개 - 주요 포인트를 강조하는 소제목 활용 - 적절한 길이의 단락으로 구성 3. 용어 및 설명 방식 - 일반적으로 이해하기 쉬운 용어 선택 - 필요시 간단한 설명 추가 - 객관적인 정보 제공에 중점 4. 텍스트 구조화 - 불릿 포인트나 번호 매기기를 활용하여 정보 구조화 - 중요한 정보는 굵은 글씨나 기울임꼴로 강조 5. 독자 상호작용 - 적절히 독자의 생각을 묻는 질문 포함 - 추가 정보를 찾을 수 있는 키워드 제시 6. 마무리 - 주요 내용 간단히 요약 - 추가 정보에 대한 안내 제공 주의사항: 너무 딱딱하거나 지루하지 않도록 균형 유지 예시: "최근 환경 문제가 대두되면서 '제로 웨이스트' 라이프스타일에 대한 관심이 높아지고 있습니다. 제로 웨이스트란 일상생활에서 발생하는 쓰레기를 최소화하는 생활 방식을 말합니다. 이 글에서는 제로 웨이스트의 개념, 실천 방법, 그리고 그 효과에 대해 알아보겠습니다. 먼저 제로 웨이스트의 정의부터 살펴보면... """, "전문적인": """ #전문적인 블로그 포스팅 스타일 가이드 1. 톤과 구조 - 공식적이고 학술적인 톤 사용하되 적절한 존댓말 사용 - 객관적이고 분석적인 접근 유지 - 명확한 서론, 본론, 결론 구조 - 체계적인 논점 전개 - 세부 섹션을 위한 명확한 소제목 사용 2. 내용 구성 및 전개 - 복잡한 개념을 정확히 전달할 수 있는 문장 구조 사용 - 논리적 연결을 위한 전환어 활용 - 해당 분야의 전문 용어 적극 활용 (필요시 간략한 설명 제공) - 심층적인 분석과 비판적 사고 전개 - 다양한 관점 제시 및 비교 3. 데이터 및 근거 활용 - 통계, 연구 결과, 전문가 의견 등 신뢰할 수 있는 출처 인용 - 필요시 각주나 참고문헌 목록 포함 - 수치 데이터는 텍스트로 명확히 설명 4. 텍스트 구조화 - 논리적 구조를 강조하기 위해 번호 매기기 사용 - 핵심 개념이나 용어는 기울임꼴로 강조 - 긴 인용문은 들여쓰기로 구분 5. 마무리 - 핵심 논점 재강조 - 향후 연구 방향이나 실무적 함의 제시 주의사항: 전문성을 유지하되, 완전히 이해하기 어려운 수준은 지양 예시: "본 연구에서는 인공지능(AI)의 윤리적 함의에 대해 고찰한다. 특히, 자율주행 자동차의 의사결정 알고리즘에서 발생할 수 있는 윤리적 딜레마에 초점을 맞춘다. Bonnefon et al. (2016)의 연구에 따르면, 자율주행 차량의 알고리즘이 직면할 수 있는 윤리적 선택의 복잡성이 지적된 바 있다. 본고에서는 이러한 윤리적 딜레마를 세 가지 주요 관점에서 분석한다: 1) 공리주의적 접근, 2) 의무론적 접근, 3) 덕 윤리적 접근. 각 접근법의 장단점을 비교 분석하고, 이를 바탕으로 자율주행 차량의 윤리적 의사결정 프레임워크를 제안하고자 한다... """ } return prompts.get(style, "포스팅 스타일 프롬프트") # 포스팅 스타일 설명 함수 def get_style_description(style): descriptions = { "친근한": "독자와 가까운 친구처럼 대화하는 듯한 친근한 스타일입니다.", "일반": "일반적이고 중립적인 톤으로 정보를 전달하는 스타일입니다.", "전문적인": "전문가의 시각에서 깊이 있는 정보를 전달하는 스타일입니다." } return descriptions.get(style, "포스팅 스타일을 선택하세요.") # 프롬프트 업데이트 함수 def update_prompts_and_description(category, style): blog_post_prompt = get_blog_post_prompt(category) style_prompt = get_style_prompt(style) style_description = get_style_description(style) return blog_post_prompt, style_prompt, style_description def format_filename(text): text = re.sub(r'[^\w\s-]', '', text) return text[:50].strip() def extract_first_recommended_title(blog_post): section_match = re.search(r'(?:#+\s*)?추천\s*제목:?\s*\n([\s\S]*?)(?=\n(?:#+|$)|$)', blog_post, re.IGNORECASE) if section_match: section_content = section_match.group(1) title_match = re.search(r'(?:^|\n)\s*(?:\d+\.|-|\*|\•)?\s*(.*?)(?=\n|$)', section_content) if title_match: title = title_match.group(1).strip() print(f"Extracted title: {title}") return title print("No title found") return "블로그_글" class PDF(FPDF): def __init__(self): super().__init__() self.add_font("NanumGothic", "", "NanumGothic.ttf") self.add_font("NanumGothicBold", "", "NanumGothicBold.ttf") self.add_font("NanumGothicExtraBold", "", "NanumGothicExtraBold.ttf") self.add_font("NanumGothicLight", "", "NanumGothicLight.ttf") def header(self): # 헤더를 비워둡니다 pass def footer(self): self.set_y(-15) self.set_font('NanumGothicLight', '', 8) self.cell(0, 10, f'Page {self.page_no()}', 0, new_x=XPos.RIGHT, new_y=YPos.TOP, align='C') def save_to_pdf(summary, blog_post, file_type): pdf = PDF() pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.set_font("NanumGothicExtraBold", size=16) pdf.cell(0, 10, "요약", new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C') pdf.ln(5) pdf.set_font("NanumGothic", size=11) pdf.multi_cell(0, 6, summary) pdf.add_page() pdf.set_font("NanumGothicExtraBold", size=16) pdf.cell(0, 10, "블로그 글", new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C') pdf.ln(5) lines = blog_post.split('\n') for line in lines: if line.strip() == '': pdf.ln(3) # 빈 줄은 작은 간격만 추가 elif line.startswith('#'): # 제목으로 간주 pdf.set_font("NanumGothicBold", size=14) pdf.multi_cell(0, 8, line.lstrip('#').strip()) pdf.ln(2) elif line.startswith('##'): # 부제목으로 간주 pdf.set_font("NanumGothicBold", size=12) pdf.multi_cell(0, 7, line.lstrip('#').strip()) pdf.ln(2) else: pdf.set_font("NanumGothic", size=11) pdf.multi_cell(0, 6, line.strip()) pdf.ln(1) title = extract_first_recommended_title(blog_post) today_date = datetime.now().strftime("%Y%m%d") filename = f"{today_date}_{format_filename(title)}.pdf" print(f"Saving PDF as: {filename}") pdf.output(filename) return filename # Gradio 인터페이스용 PDF 저장 함수 def save_content_to_pdf(summary, blog_post): filename = save_to_pdf(summary, blog_post, "블로그") return filename # Gradio 인터페이스 구성 title = "유튜브로 블로그 글 생성하기" with gr.Blocks() as demo: gr.Markdown(f"# {title}") # 1단계: 카테고리 선택 gr.Markdown("### 1단계: 포스팅 카테고리를 지정해주세요", elem_id="step-title") category = gr.Radio(choices=["일반형","정보성", "1개 상품 추천형", "큐레이션형"], label="포스팅 카테고리", value="일반형") # 구분선 추가 gr.Markdown("---\n\n") # 2단계: 포스팅 스타일 선택 gr.Markdown("### 2단계: 포스팅 스타일을 선택해주세요", elem_id="step-title") style = gr.Radio(choices=["친근한", "일반", "전문적인"], label="포스팅 스타일", value="친근한") style_description = gr.Markdown(f"{get_style_description('친근한')}", elem_id="style-description") # 구분선 추가 gr.Markdown("---\n\n") # 3단계: 유튜브 링크를 입력하세요 gr.Markdown("### 3단계: 유튜브 링크를 입력하세요", elem_id="step-title") with gr.Row(): youtube_url1 = gr.Textbox(label="YouTube URL 1", placeholder="첫 번째 유튜브 링크를 입력하세요") youtube_url2 = gr.Textbox(label="YouTube URL 2", placeholder="두 번째 유튜브 링크를 입력하세요") youtube_url3 = gr.Textbox(label="YouTube URL 3", placeholder="세 번째 유튜브 링크를 입력하세요") # 숨겨진 텍스트박스 (사용자에게 보이지 않음) combined_urls = gr.Textbox(visible=False) transcript_output = gr.Textbox(label="유튜브 트랜스크립트", lines=10) # 유튜브 트랜스크립트 가져오기 함수 def combine_and_get_transcripts(url1, url2, url3): urls = [url for url in [url1, url2, url3] if url.strip()] combined = ",".join(urls) all_transcripts = [] for url in urls: transcript = get_transcript(url.strip()) all_transcripts.append(transcript) return combined, "\n\n---\n\n".join(all_transcripts) # 입력 변경 시 트랜스크립트 업데이트 for url_input in [youtube_url1, youtube_url2, youtube_url3]: url_input.change( fn=combine_and_get_transcripts, inputs=[youtube_url1, youtube_url2, youtube_url3], outputs=[combined_urls, transcript_output] ) # 요약글 생성하기 기능 추가 gr.Markdown("### 요약글 생성하기", elem_id="step-title") with gr.Accordion("요약글 설정", open=False): summary_system_message = gr.Textbox( label="요약글 시스템 메시지", value=""" #유튜브 대본 요약 규칙 1. 목적 가. 입력된 여러 유튜브 영상 대본을 분석하여 하나의 종합적인 요약 보고서 작성 나. 핵심 내용을 쉽게 파악할 수 있도록 구조화된 형식으로 제시 다. 공통 주제나 연관성 있는 내용을 중심으로 통합된 인사이트 제공 2. 구조 및 형식 가. 전체 구조 - 주요 포인트를 논리적 섹션으로 구분하여 작성 - 각 섹션에 명확하고 간결한 소제목 부여 나. 마크다운 형식 사용 - 섹션 구분을 위해 '1.', '2.' 등의 숫자 사용 - 하위 항목은 '가.', '나.' 등의 한글 문자 사용 다. 가독성 향상 - 간결한 문장과 단락 사용하되 본문의 내용은 자세히 작성 - 필요시 불릿 포인트 활용하여 정보 나열 3. 내용 구성 가. 종합 개요 - 분석한 영상들의 공통 주제 또는 핵심 메시지 소개 - 각 영상의 주요 포인트를 간략히 나열 나. 주제별 통합 분석 - 공통 주제나 연관성 있는 내용을 중심으로 섹션 구성 - 각 영상의 관점이나 정보를 비교/대조하며 종합 다. 독특한 인사이트 - 각 영상에서 제시된 고유한 관점이나 정보 강조 라. 데이터 및 예시 통합 - 여러 영상에서 제시된 데이터나 예시를 종합하여 제시 마. 종합 결론 - 분석된 모든 내용을 바탕으로 전체적인 결론 도출 4. 용어 및 표현 가. '유튜버', 'YouTuber' 등의 표현을 '제작자' 또는 '진행자' 같은 다른 표현으로 대체 나. 채널명, 제작자 실명 등 개인 정보 제외 다. 유튜브 특정 용어(예: '구독', '좋아요', '알림', '다음 영상' 등) 사용 금지 5. 객관성 및 정확성 가. 대본 내용을 객관적으로 요약, 개인적 해석 최소화 나. 사실 관계나 데이터는 가능한 원문 그대로 전달 6. 마무리 가. 요약의 끝에 전체 내용을 한 문장으로 정리 나. 필요시 추가 정보나 관련 주제 제안 """, lines=15, visible=True ) summary_max_tokens = gr.Slider(label="Max Tokens", minimum=1000, maximum=7000, value=5000, step=1000) summary_temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.7, step=0.05) summary_top_p = gr.Slider(label="Top P", minimum=0.1, maximum=1.0, value=0.95, step=0.05) summarize_btn = gr.Button("요약글 생성하기") summary_output = gr.Textbox(label="요약된 글", lines=10) def generate_summary(transcript, system_message, max_tokens, temperature, top_p): summary = summarize_transcript(transcript, system_message, max_tokens, temperature, top_p) return summary summarize_btn.click( fn=generate_summary, inputs=[transcript_output, summary_system_message, summary_max_tokens, summary_temperature, summary_top_p], outputs=[summary_output] ) # 구분선 추가 gr.Markdown("---\n\n") # 4단계: 글 생성하기 gr.Markdown("### 4단계: 글 생성하기", elem_id="step-title") gr.HTML("[생성하기 버튼을 선택해주세요]") with gr.Accordion("블로그 글 설정", open=False): blog_system_message = gr.Textbox(label="카테고리 프롬프트", value=get_blog_post_prompt("일반형"), lines=20, visible=True) style_prompt_hidden = gr.Textbox(label="스타일 프롬프트", value=get_style_prompt("친근한"), lines=10, visible=False) # 초기값 설정 blog_max_tokens = gr.Slider(label="Max Tokens", minimum=1000, maximum=12000, value=8000, step=1000) blog_temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.8, step=0.1) blog_top_p = gr.Slider(label="Top P", minimum=0.1, maximum=1.0, value=0.95, step=0.05) generate_btn = gr.Button("블로그 글 생성하기") blog_output = gr.Textbox(label="생성된 블로그 글", lines=30) def generate_blog_content(category, style, transcripts, category_prompt, style_prompt, max_tokens, temperature, top_p): blog_post = generate_blog_post(category, style, transcripts, category_prompt, style_prompt, max_tokens, temperature, top_p) return blog_post generate_btn.click( fn=generate_blog_content, inputs=[category, style, transcript_output, blog_system_message, style_prompt_hidden, blog_max_tokens, blog_temperature, blog_top_p], outputs=[blog_output] ) # PDF 저장 버튼 추가 save_pdf_btn = gr.Button("PDF로 저장하기") pdf_output = gr.File(label="생성된 PDF 파일") save_pdf_btn.click( fn=save_content_to_pdf, inputs=[summary_output, blog_output], outputs=[pdf_output] ) # 카테고리와 스타일이 변경될 때 프롬프트 업데이트 def update_prompts_and_description(category, style): blog_post_prompt = get_blog_post_prompt(category) style_prompt = get_style_prompt(style) style_description = get_style_description(style) return blog_post_prompt, style_prompt, style_description category.change(fn=update_prompts_and_description, inputs=[category, style], outputs=[blog_system_message, style_prompt_hidden, style_description]) style.change(fn=update_prompts_and_description, inputs=[category, style], outputs=[blog_system_message, style_prompt_hidden, style_description]) demo.launch() # CSS 스타일 추가 gr.HTML(""" """)