<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>부록. 블로그 메이킹 on Physical AI 드림팀</title><link>https://ditis3g.github.io/categories/%EB%B6%80%EB%A1%9D.-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A9%94%EC%9D%B4%ED%82%B9/</link><description>Recent content in 부록. 블로그 메이킹 on Physical AI 드림팀</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Thu, 02 Jul 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ditis3g.github.io/categories/%EB%B6%80%EB%A1%9D.-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A9%94%EC%9D%B4%ED%82%B9/index.xml" rel="self" type="application/rss+xml"/><item><title>대본만 쓰면 강의 영상이 나온다 — 버튜버 자동 제작 파이프라인 구축기</title><link>https://ditis3g.github.io/posts/2026-07-02-vtuber-lecture-automation/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/2026-07-02-vtuber-lecture-automation/</guid><description>&lt;h2 id="무엇을-만들었나"&gt;무엇을 만들었나
&lt;/h2&gt;&lt;p&gt;블로그 1장 &amp;ldquo;AI가 화면 밖으로 나왔다 — 피지컬 AI 입문 가이드&amp;quot;를 10분짜리 유튜브 강의 영상으로 만들었다. 목표는 분명했다. &lt;strong&gt;사람이 카메라 앞에서 연기하지도, 녹화 버튼을 누르지도 않는 것.&lt;/strong&gt; 대본을 쓰면 AI 음성 합성 → 아바타 자동 립싱크 → 자동 녹화까지 한 번에 이어지도록 배선했고, 사용한 도구가 전부 무료라 총비용은 0원이었다.&lt;/p&gt;
&lt;p&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;블로그 글 → 구어체 대본 (Claude 작성)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Edge TTS로 mp3 생성 (tts.py)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 음성 내장 HTML 슬라이드, 타임스탬프 자동 전환 (slides.html)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → OBS 재생 음성을 가상 오디오 케이블로 VMagicMirror에 전달 → 아바타 립싱크
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → OBS WebSocket API로 녹화 시작/종료 자동화 (record.py)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="무료-도구로-짠-파이프라인"&gt;무료 도구로 짠 파이프라인
&lt;/h2&gt;&lt;p&gt;파이프라인을 구성하는 도구는 모두 무료이거나 오픈소스다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VRoid Studio&lt;/strong&gt; (Pixiv): 슬라이더 조작만으로 3D 아바타를 만들어 VRM 파일로 내보낸다. 샘플 모델로 시작해도 충분하며, 내보낼 때는 호환성이 가장 좋은 VRM 0.0 형식을 쓴다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VMagicMirror&lt;/strong&gt; (MIT 라이선스): VRM 아바타를 띄우고 마이크 입력만으로 립싱크한다. 웹캠 없이 쓰는 것을 전제로 만들어져 숨쉬기·눈 깜빡임 같은 대기 모션이 자연스럽고, 그래서 &amp;ldquo;AITuber&amp;rdquo; 용도에 잘 맞는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VB-Audio Cable&lt;/strong&gt; (도네이션웨어): 가상 오디오 케이블. 재생 소리를 가상 마이크(CABLE Output)로 흘려보내, mp3를 마이크 입력인 것처럼 넣어 준다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;edge-tts&lt;/strong&gt; (파이썬 패키지): 마이크로소프트 Edge의 한국어 TTS를 무료로 쓴다. &lt;code&gt;ko-KR-SunHiNeural&lt;/code&gt; 음성으로, 10분 분량 대본이 몇십 초 만에 mp3가 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OBS Studio&lt;/strong&gt;: 배경·슬라이드·아바타를 합성하고 녹화한다. WebSocket 서버를 켜면 파이썬으로 원격 제어할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 구조에서 가장 중요한 건 &lt;strong&gt;소리 배선&lt;/strong&gt;이다. mp3 재생음을 아바타의 &amp;ldquo;마이크 입력&amp;quot;으로 착각하게 만들어야 립싱크가 걸리기 때문이다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OBS &lt;code&gt;설정 → 오디오 → 고급 → 모니터링 장치&lt;/code&gt;를 &lt;strong&gt;CABLE Input&lt;/strong&gt;으로.&lt;/li&gt;
&lt;li&gt;음성 소스(슬라이드 브라우저 소스)의 &lt;code&gt;고급 오디오 속성 → 모니터링&lt;/code&gt;을 **&amp;ldquo;모니터링과 출력&amp;rdquo;**으로.&lt;/li&gt;
&lt;li&gt;VMagicMirror &lt;code&gt;스트리밍 탭 → 립싱크&lt;/code&gt; 마이크를 &lt;strong&gt;CABLE Output&lt;/strong&gt;으로.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;정리하면 소리는 이렇게 흐른다. OBS가 mp3를 재생 → CABLE Input → CABLE Output → VMagicMirror가 마이크 입력으로 인식 → 립싱크. 같은 소리가 녹화 트랙에도 그대로 들어간다.&lt;/p&gt;
&lt;h2 id="슬라이드-자동-전환과-자동-녹화"&gt;슬라이드 자동 전환과 자동 녹화
&lt;/h2&gt;&lt;p&gt;슬라이드는 PPT가 아니라 &lt;strong&gt;HTML 한 파일&lt;/strong&gt;로 만들었다. 1920×1080 고정, 12장짜리이고 OBS 브라우저 소스로 바로 로드한다. HTML 안에 &lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt;로 강의 mp3를 내장하고, JS가 &lt;code&gt;audio.currentTime&lt;/code&gt;을 지켜보다가 미리 계산해 둔 타임스탬프에 슬라이드를 자동으로 넘긴다. 사람이 큐시트를 보며 넘길 필요가 없다.&lt;/p&gt;
&lt;p&gt;전환 시각은 섹션별 mp3 실측 길이에 대본 글자 수 비율을 얹어 계산했다. TTS 발화 속도가 균일해서 글자 비율만으로도 추정이 꽤 정확했다. 아바타가 우측 하단에 들어가므로 슬라이드 레이아웃에서는 그 자리를 비워 뒀다(padding-right 460px).&lt;/p&gt;
&lt;p&gt;녹화는 &lt;code&gt;record.py&lt;/code&gt;가 맡는다. OBS 28버전 이상은 WebSocket 서버가 내장돼 있어(&lt;code&gt;도구 → WebSocket 서버 설정 → 활성화&lt;/code&gt;), 파이썬 &lt;code&gt;obsws-python&lt;/code&gt; 패키지로 붙어 &lt;strong&gt;녹화 시작 → 브라우저 소스 새로고침(&lt;code&gt;refreshnocache&lt;/code&gt;로 페이지를 리로드해 음성을 처음부터 재생) → 재생 길이만큼 대기 → 녹화 종료&lt;/strong&gt;를 순서대로 실행한다. 결과적으로 명령 한 줄이면 10분 45초짜리 1080p60 영상이 나온다.&lt;/p&gt;
&lt;h2 id="막혔던-지점들"&gt;막혔던 지점들
&lt;/h2&gt;&lt;p&gt;자동화 과정에서 걸렸던 문제와 해결책을 남겨 둔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;아바타 배경이 검게 찍힘&lt;/strong&gt;: VMagicMirror 배경 투명화를 켰을 때 OBS 게임 캡처의 &amp;ldquo;투명도 허용&amp;quot;을 체크하지 않으면 투명 영역이 검정으로 나온다. 게임 캡처 속성에서 체크 하나로 해결.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;얼굴이 잘림&lt;/strong&gt;: VMM 카메라를 &amp;ldquo;프리 카메라 모드&amp;quot;로 두고 아바타 창에서 휠(줌)·휠 드래그(이동)·우클릭 드래그(회전)로 구도를 잡는다. 잡은 구도는 퀵 세이브 슬롯에 저장할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10분 맞추기&lt;/strong&gt;: 첫 대본이 11분 10초로 나와, 중복 문장을 덜어 10분 39초로 맞췄다. 한국어 TTS 기본 속도 기준 10분은 공백 포함 4,000자 안팎이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TTS 오독&lt;/strong&gt;: &amp;ldquo;유니트리 G1&amp;quot;을 &amp;ldquo;지원&amp;quot;으로 읽는 식의 오독이 있어, 외래어·기호는 대본 단계에서 미리 한글로 풀어썼다(챗지피티, 러로봇, 아이작 랩 등).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;조작용 UI가 화면에 찍힘&lt;/strong&gt;: 슬라이드 카운터(HUD)가 녹화에 들어가서, 자동 재생 중에는 숨기도록 고쳤다. 녹화 화면에 들어가는 소스에 &amp;ldquo;조작용 표시&amp;quot;가 없는지 확인할 것.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;창을 가려도 되나&lt;/strong&gt;: OBS 게임 캡처는 GPU 렌더링을 직접 후킹하므로 다른 창에 가려져도 캡처된다. 다만 &lt;strong&gt;최소화는 금지&lt;/strong&gt;(렌더링이 멈춘다). OBS 자체는 최소화해도 녹화가 계속된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한 가지 한계도 분명하다. 립싱크·표정·대기 모션까지는 자동이지만, 손짓이나 판서 같은 큰 제스처는 안 된다. 다만 강의 영상은 슬라이드 하이라이트로 대체할 수 있어 실용상 문제는 없었다. 이제 다음 장부터는 &amp;ldquo;대본 작성 → TTS → 슬라이드 생성 → 자동 녹화&amp;quot;가 명령 몇 번으로 끝난다. 유튜브 강의 채널을 글 쓰는 속도로 운영할 수 있는 구조가 된 셈이다.&lt;/p&gt;
&lt;h2 id="한-줄-정리"&gt;한 줄 정리
&lt;/h2&gt;&lt;p&gt;대본만 쓰면 TTS·아바타 립싱크·슬라이드 전환·녹화까지 이어지는 무료 파이프라인을 짜서, 유튜브 강의 영상을 글 쓰는 속도로 찍어낼 수 있게 됐다.&lt;/p&gt;</description></item><item><title>블로그 글을 숏폼 영상으로 자동 변환해서 유튜브에 올리기까지</title><link>https://ditis3g.github.io/posts/2026-07-02-blog-to-shorts/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/2026-07-02-blog-to-shorts/</guid><description>&lt;p&gt;블로그에 정리해 둔 글을 그냥 두기 아까워서, &lt;strong&gt;글 하나를 60초짜리 세로형 교육용 숏폼 영상으로 만들고 유튜브에 자동으로 올리는 파이프라인&lt;/strong&gt;을 만들었다. 목표는 세 가지였다. (1) 글을 어떻게 영상 구성으로 바꿀 것인가, (2) 그걸 자동으로 할 수 있는가, (3) 실제 영상 파일로 인코딩해 저장·업로드까지 되는가. 결론부터 말하면 셋 다 된다. 비전공자도 따라올 수 있게, 개념과 내가 헤맨 지점 위주로 정리한다.&lt;/p&gt;
&lt;h2 id="전체-그림-먼저"&gt;전체 그림 먼저
&lt;/h2&gt;&lt;p&gt;파이프라인은 이렇게 흐른다. 각 단계는 뒤에서 하나씩 풀어 쓴다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;블로그 글(.md)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ① 글을 &amp;#34;장면 대본&amp;#34;으로 압축 (AI가 요약)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;장면 JSON
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ② 나레이션 음성 만들기 (TTS, 무료 한국어)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ③ 장면 화면(이미지) 만들기 (HTML 또는 Pillow)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ④ 이미지 + 음성 → 영상으로 합치기 (ffmpeg)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;숏폼 mp4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ ⑤ 유튜브 업로드 (YouTube API)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;유튜브 Shorts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이 전체를 매일 일정 시간에 자동으로 도는 스케줄로 걸어두는 게 마지막 단계다.&lt;/p&gt;
&lt;h2 id="준비물-무료-도구-네-가지"&gt;준비물: 무료 도구 네 가지
&lt;/h2&gt;&lt;p&gt;돈 드는 건 없다. 전부 무료다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python&lt;/strong&gt; — 파이프라인을 묶는 접착제 역할.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ffmpeg&lt;/strong&gt; — 이미지와 음성을 합쳐 mp4로 &lt;strong&gt;인코딩&lt;/strong&gt;(영상 파일로 만드는 것)하는 도구. 영상 자동화의 핵심.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;edge-tts&lt;/strong&gt; — 글자를 사람 목소리로 읽어주는 &lt;strong&gt;TTS&lt;/strong&gt;(Text To Speech). Microsoft의 무료 한국어 음성을 API 키 없이 쓸 수 있다. &lt;code&gt;ko-KR-SunHiNeural&lt;/code&gt;(여성) 같은 목소리가 꽤 자연스럽다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화면 만드는 도구&lt;/strong&gt; — 두 가지를 다 만들어 비교했다. 뒤에서 설명한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Windows라면 ffmpeg는 &lt;code&gt;winget install Gyan.FFmpeg&lt;/code&gt;, 나머지는 &lt;code&gt;pip install&lt;/code&gt;로 끝난다.&lt;/p&gt;
&lt;h2 id="-글을-장면-대본으로-쪼개기"&gt;① 글을 &amp;ldquo;장면 대본&amp;quot;으로 쪼개기
&lt;/h2&gt;&lt;p&gt;블로그 글은 길다. 60초 숏폼에는 한국어로 대략 320~380자밖에 못 담는다. 그래서 글을 통째로 넣을 수 없고 &lt;strong&gt;핵심만 압축&lt;/strong&gt;해야 하는데, 이 요약·재구성이야말로 AI(LLM)가 제일 잘하는 일이다.&lt;/p&gt;
&lt;p&gt;글을 다음처럼 &lt;strong&gt;5개 장면&lt;/strong&gt;으로 나눈 대본(JSON 형식)으로 바꾼다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;후킹&lt;/strong&gt; (0&lt;del&gt;3초): 강한 한 줄로 시선 잡기 (&amp;ldquo;최신 AI 모델, 외우지 마세요&amp;rdquo;)
2&lt;/del&gt;4. &lt;strong&gt;핵심 카드&lt;/strong&gt;: 한 장에 한 개념. 짧은 문장 3개 + 나레이션&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마무리&lt;/strong&gt;: 결론 한 줄 + &amp;ldquo;전체 글은 블로그에서&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;각 장면에는 화면에 띄울 글자와, 성우가 읽을 나레이션이 따로 들어간다. 블로그 글의 제목·소제목·표가 자연스럽게 후킹·핵심 카드·계보 도표로 대응된다는 점이 좋았다.&lt;/p&gt;
&lt;p&gt;한 가지 팁: 나레이션에서 영어 약어는 한글 발음으로 적어야 TTS가 제대로 읽는다. &lt;code&gt;VLA&lt;/code&gt;는 &amp;ldquo;브이엘에이&amp;rdquo;, &lt;code&gt;AI&lt;/code&gt;는 &amp;ldquo;에이아이&amp;quot;처럼. 숫자도 &amp;ldquo;96%&amp;ldquo;보다 &amp;ldquo;구십육 퍼센트&amp;quot;로 적으면 자연스럽다.&lt;/p&gt;
&lt;h2 id="-나레이션-음성-만들기"&gt;② 나레이션 음성 만들기
&lt;/h2&gt;&lt;p&gt;장면별 나레이션 문장을 edge-tts에 넣으면 mp3가 나온다. 명령 한 줄이면 된다. 이 음성의 &lt;strong&gt;길이&lt;/strong&gt;를 재서, 나중에 그 장면 화면을 그 시간만큼 보여주면 화면과 목소리가 자동으로 맞는다.&lt;/p&gt;
&lt;h2 id="-화면-만들기-두-가지-방법"&gt;③ 화면 만들기: 두 가지 방법
&lt;/h2&gt;&lt;p&gt;세로형(9:16, 유튜브 Shorts 규격)의 카드 이미지를 장면마다 만든다. 두 방식을 다 만들어 비교했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A안 — HTML/CSS로 만들고 캡처&lt;/strong&gt;: 웹페이지처럼 예쁘게 꾸민 뒤 브라우저로 스크린샷을 찍는다. 한글 타이포와 표·도표가 훨씬 깔끔하다. 교육용에 유리해서 이걸 채택했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B안 — Pillow(파이썬 그림 라이브러리)로 직접 그리기&lt;/strong&gt;: 브라우저가 필요 없고 가볍다. 담백한 스타일.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 초보가 꼭 만나는 문제 하나. &lt;strong&gt;자막이 화면 폭을 넘어 줄이 깨진다.&lt;/strong&gt; 글자 길이가 글마다 다르니 고정 폰트 크기로는 안 된다. 그래서 &amp;ldquo;한 줄에 들어갈 때까지 폰트 크기를 자동으로 줄이는&amp;rdquo; 로직을 넣었다. 이러면 어떤 글이 와도 자막이 깔끔하게 한 줄로 맞는다.&lt;/p&gt;
&lt;h2 id="-영상으로-합치기-ffmpeg"&gt;④ 영상으로 합치기 (ffmpeg)
&lt;/h2&gt;&lt;p&gt;장면마다 &lt;code&gt;이미지 + 그 장면 음성&lt;/code&gt;을 붙여 짧은 클립을 만들고, 클립들을 순서대로 이어 붙이면 하나의 mp4가 된다. 페이드 인 같은 효과도 ffmpeg 옵션으로 넣을 수 있다. 60초 영상이 1~2MB로 가볍게 나온다.&lt;/p&gt;
&lt;h2 id="-유튜브-자동-업로드-여기가-진짜-삽질-구간"&gt;⑤ 유튜브 자동 업로드: 여기가 진짜 삽질 구간
&lt;/h2&gt;&lt;p&gt;영상 만드는 것보다 &lt;strong&gt;유튜브에 자동으로 올리는 인증 과정&lt;/strong&gt;이 훨씬 까다로웠다. 공식 방법은 &lt;strong&gt;YouTube Data API + OAuth 인증&lt;/strong&gt;이다. 초보가 순서대로 만나는 함정을 그대로 적어둔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud에서 API 사용 설정 + OAuth 클라이언트(데스크톱 앱) 만들기.&lt;/strong&gt; 다운로드한 인증 파일(&lt;code&gt;client_secret.json&lt;/code&gt;)이 &lt;code&gt;&amp;quot;installed&amp;quot;&lt;/code&gt;로 시작하면 데스크톱 앱(정상), &lt;code&gt;&amp;quot;web&amp;quot;&lt;/code&gt;이면 잘못 만든 것.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;앱 이름에 &amp;ldquo;Google&amp;rdquo;·&amp;ldquo;YouTube&amp;rdquo; 같은 상표명을 넣으면 안 된다.&lt;/strong&gt; 정책상 거부된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;프로덕션&amp;quot;으로 올리면 오히려 막힌다.&lt;/strong&gt; 유튜브 업로드는 민감한 권한이라 프로덕션 상태에선 구글 정식 심사를 통과해야 한다. 개인용이면 &lt;strong&gt;&amp;ldquo;테스트&amp;rdquo; 상태로 두고 내 계정을 테스트 사용자로 추가&lt;/strong&gt;하는 게 답이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;확인하지 않은 앱&amp;rdquo; 경고는 차단이 아니다.&lt;/strong&gt; 테스트 모드에선 이 경고가 정상이다. &lt;code&gt;고급 → (앱이름)(으)로 이동&lt;/code&gt;을 눌러 통과하면 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;youtubeSignupRequired&lt;/code&gt; 오류 = 유튜브 채널이 없다는 뜻.&lt;/strong&gt; 인증은 됐는데 그 구글 계정에 채널이 없으면 올릴 곳이 없다. youtube.com에서 채널을 먼저 만들면 해결된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 모드는 토큰이 약 7일마다 만료된다.&lt;/strong&gt; 자동화가 일주일에 한 번쯤 끊기므로, 주 1회 인증 명령을 한 번 다시 실행해 토큰을 갱신해 줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고로 무료 사용량은 하루 10,000 유닛이고 업로드 1건이 1,600 유닛이라, &lt;strong&gt;하루 약 6개까지&lt;/strong&gt; 올릴 수 있다. 매일 몇 개면 충분하다.&lt;/p&gt;
&lt;h2 id="매일-자동-실행-규칙을-정하다"&gt;매일 자동 실행: 규칙을 정하다
&lt;/h2&gt;&lt;p&gt;마지막으로 위 전체를 &lt;strong&gt;Windows 작업 스케줄러&lt;/strong&gt;에 매일 오후 3시로 걸었다. 규칙은 이렇게 정했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;게시된 글만&lt;/strong&gt; 대상 (초안 제외).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;블로그 메이킹&amp;rdquo; 카테고리는 제외&lt;/strong&gt; (지금 이 글처럼 블로그 만들기 자체를 다룬 글은 영상으로 안 만든다).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;오래된 글부터 순서대로 하루 2개&lt;/strong&gt;씩 처리. 이미 처리한 글은 기록해 두고 다시 만들지 않는다.&lt;/li&gt;
&lt;li&gt;밀린 글이 다 소진되면, 그다음부터는 새로 올라온 글을 자동으로 집어 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="한-줄-정리"&gt;한 줄 정리
&lt;/h2&gt;&lt;p&gt;블로그 글을 숏폼으로 바꾸는 핵심은 &lt;strong&gt;&amp;ldquo;긴 글을 5개 장면으로 압축하는 AI 단계&amp;rdquo;&lt;/strong&gt; 와 &lt;strong&gt;&amp;ldquo;이미지+음성을 ffmpeg로 합치는 인코딩&amp;rdquo;&lt;/strong&gt; 이고, 진짜 난관은 영상 제작이 아니라 &lt;strong&gt;유튜브 OAuth 인증(테스트 모드·채널 생성·토큰 만료)&lt;/strong&gt; 이다. 이 셋만 넘으면 매일 자동으로 도는 숏폼 공장이 완성된다.&lt;/p&gt;
&lt;h2 id="실제-결과물--이-글이-이렇게-숏폼이-됐다"&gt;실제 결과물 — 이 글이 이렇게 숏폼이 됐다
&lt;/h2&gt;&lt;p&gt;아래 원본 글 하나가, 위에서 설명한 파이프라인을 거쳐 60초짜리 세로 영상으로 자동 변환됐다. 긴 글이 어떻게 5개 장면으로 압축됐는지 원본과 나란히 보면 감이 온다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;원본 글&lt;/strong&gt;: &lt;a class="link" href="https://ditis3g.github.io/posts/2026-07-01-study/" &gt;피지컬 AI, 최신 모델을 외우지 말고 &amp;lsquo;흐름&amp;rsquo;을 읽어라&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과 숏폼&lt;/strong&gt;: 이 파이프라인으로 처음 업로드한 영상 (아래)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="video-wrapper"&gt;
 &lt;iframe loading="lazy" 
 src="https://www.youtube.com/embed/LQcLLUg-Jew" 
 allowfullscreen 
 title="YouTube Video"
 &gt;
 &lt;/iframe&gt;
&lt;/div&gt;
</description></item><item><title>GitHub Pages 블로그를 구글 검색에 노출시키기</title><link>https://ditis3g.github.io/posts/2026-07-01-study-2/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/2026-07-01-study-2/</guid><description>&lt;p&gt;블로그를 만들어 배포하는 것으로 끝이 아니다. 사이트가 존재해도 구글이 그 존재를 모르면 검색으로는 아무도 찾아올 수 없다. GitHub Pages에 Hugo 사이트를 올린 상태에서 실제로 검색 노출까지 진행하며 한 일과, 그 과정에서 만난 함정을 정리한다.&lt;/p&gt;
&lt;h2 id="검색에-뜨려면-무엇이-필요한가"&gt;검색에 뜨려면 무엇이 필요한가
&lt;/h2&gt;&lt;p&gt;블로그가 구글 검색 결과에 나타나려면 네 가지가 모두 갖춰져야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;구글이 사이트의 존재를 알아야 한다 → &lt;strong&gt;Google Search Console에 등록&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;구글이 사이트를 크롤링할 수 있어야 한다 → &lt;strong&gt;robots.txt에 sitemap 링크&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;구글이 안에 무엇이 있는지 알아야 한다 → &lt;strong&gt;sitemap.xml 제출&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;구글이 페이지를 색인에 넣어야 한다 → &lt;strong&gt;크롤링 대기 (3일~2주)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다행히 Hugo는 sitemap.xml을 기본으로 자동 생성하고, &lt;code&gt;enableRobotsTXT = true&lt;/code&gt;만 켜 두면 robots.txt도 만들어 준다. 결국 실제로 손봐야 할 것은 &lt;strong&gt;몇 가지 SEO 보완과 Search Console 등록&lt;/strong&gt; 정도다.&lt;/p&gt;
&lt;h2 id="현재-상태-진단과-seo-보완"&gt;현재 상태 진단과 SEO 보완
&lt;/h2&gt;&lt;p&gt;먼저 배포된 사이트에서 세 가지가 정상인지 &lt;code&gt;curl&lt;/code&gt;로 확인한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sI &lt;span class="s2"&gt;&amp;#34;https://&amp;lt;도메인&amp;gt;/sitemap.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → HTTP 200, content-type: application/xml 이면 정상&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s &lt;span class="s2"&gt;&amp;#34;https://&amp;lt;도메인&amp;gt;/robots.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → User-agent: * 는 기본. Sitemap: 링크가 있는지 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s &lt;span class="s2"&gt;&amp;#34;https://&amp;lt;도메인&amp;gt;/&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -oE &lt;span class="s1"&gt;&amp;#39;&amp;lt;meta [^&amp;gt;]*(description|og:|canonical)[^&amp;gt;]*&amp;gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# → og:title, og:description, canonical 있는지 확인&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hugo + Stack 기본 구성이면 sitemap과 meta 태그는 이미 있지만, 두 가지가 빠져 있었다. &lt;strong&gt;robots.txt에 sitemap 링크가 없어서&lt;/strong&gt; 구글이 사이트맵을 자동으로 발견하지 못하고, &lt;strong&gt;og:image가 없어서&lt;/strong&gt; 검색 결과나 메신저 공유에서 썸네일이 뜨지 않는 문제다.&lt;/p&gt;
&lt;p&gt;첫 번째는 &lt;code&gt;layouts/robots.txt&lt;/code&gt; 파일을 만들어 해결한다. Hugo는 이 파일이 있으면 우선 사용한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User-agent: *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allow: /
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sitemap: {{ &amp;#34;sitemap.xml&amp;#34; | absURL }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;absURL&lt;/code&gt;이 baseURL을 붙여 절대 경로로 렌더하므로 다중 도메인이나 리버스 프록시 환경에서도 안전하다.&lt;/p&gt;
&lt;p&gt;두 번째는 &lt;code&gt;hugo.toml&lt;/code&gt;의 defaultImage 블록을 활성화한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opengraph&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;local&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;img/avatar.png&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;local = true&lt;/code&gt;면 &lt;code&gt;static/&lt;/code&gt; 아래 경로를 그대로 쓴다. 개별 글에 cover 이미지가 있으면 그쪽이 우선하고, 없으면 이 기본값이 쓰인다.&lt;/p&gt;
&lt;h2 id="search-console-등록과-sitemap-제출"&gt;Search Console 등록과 Sitemap 제출
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://search.google.com/search-console" target="_blank" rel="noopener"
 &gt;Google Search Console&lt;/a&gt;에 로그인한 뒤 진행한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;속성 추가 → URL 접두어&lt;/strong&gt;를 선택하고 사이트 URL(예: &lt;code&gt;https://&amp;lt;user&amp;gt;.github.io/&lt;/code&gt;)을 입력한다.&lt;/li&gt;
&lt;li&gt;소유권 확인에서 &lt;strong&gt;HTML 파일&lt;/strong&gt; 방식을 고르면 구글이 &lt;code&gt;googleXXXX...html&lt;/code&gt; 파일을 준다.&lt;/li&gt;
&lt;li&gt;이 파일을 블로그 프로젝트의 &lt;code&gt;static/&lt;/code&gt; 폴더에 그대로 두고 commit + push 한다.&lt;/li&gt;
&lt;li&gt;GitHub Pages 배포가 끝나면 &lt;code&gt;https://&amp;lt;도메인&amp;gt;/googleXXXX...html&lt;/code&gt;로 접근되는지 &lt;code&gt;curl&lt;/code&gt;로 확인한다.&lt;/li&gt;
&lt;li&gt;Search Console로 돌아와 &lt;strong&gt;확인&lt;/strong&gt; 버튼을 누르면 &amp;ldquo;소유권이 확인됨&amp;quot;이 뜬다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;파일 내용은 &lt;code&gt;google-site-verification: ...&lt;/code&gt; 한 줄뿐이다. 이 토큰 자체가 인증 역할을 하지만 저장소에 push해도 위험하지 않다. 다만 구글이 준 파일명과 내용은 &lt;strong&gt;이 사이트·이 계정 조합에서만 유효&lt;/strong&gt;하다는 점만 기억하면 된다.&lt;/p&gt;
&lt;p&gt;인증이 끝나면 왼쪽 메뉴 &lt;strong&gt;Sitemaps&lt;/strong&gt;에서 입력란에 &lt;code&gt;sitemap.xml&lt;/code&gt;만 넣고 제출한다. 처음에는 &amp;ldquo;가져올 수 없음&amp;quot;이 자주 뜨는데, 대부분 구글이 방금 등록된 속성을 캐시하는 중이거나 첫 fetch가 일시적으로 실패한 것이다. &lt;strong&gt;12~48시간 기다리면&lt;/strong&gt; 자동으로 재시도되어 &amp;ldquo;성공&amp;quot;으로 바뀐다. 급하면 사이트맵을 삭제 후 재제출해 강제로 다시 트리거할 수 있다.&lt;/p&gt;
&lt;p&gt;우선 노출을 원하는 페이지가 있다면 상단 &lt;strong&gt;URL 검사 도구&lt;/strong&gt;로 특정 URL을 넣고 &lt;strong&gt;색인 생성 요청&lt;/strong&gt;을 할 수 있다. 홈(&lt;code&gt;/&lt;/code&gt;), 대표·최신 글 몇 개, 핵심 카테고리 페이지 순으로 요청하면 몇 시간&lt;del&gt;하루 안에 처리된다. 하루 요청 수 제한(10&lt;/del&gt;15개)이 있으니 중요한 URL부터, 한 URL당 한 번만 누른다.&lt;/p&gt;
&lt;h2 id="노출까지의-시간차-그리고-함정"&gt;노출까지의 시간차, 그리고 함정
&lt;/h2&gt;&lt;p&gt;세팅은 하루면 끝나지만, 실제 노출까지는 시간이 걸린다.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;시점&lt;/th&gt;
					&lt;th&gt;상태&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;지금&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;site:&amp;lt;도메인&amp;gt;&lt;/code&gt; 검색 결과 0 (정상)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3~7일&lt;/td&gt;
					&lt;td&gt;홈과 대표 글 색인 시작 → 결과 1~5개&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;1~2주&lt;/td&gt;
					&lt;td&gt;대부분 페이지 색인 완료&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;이후&lt;/td&gt;
					&lt;td&gt;고유 이름(블로그 제목 등)부터 상위 노출 시작&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;진행 상황은 두 곳에서 볼 수 있다. 구글에서 &lt;code&gt;site:&amp;lt;도메인&amp;gt;&lt;/code&gt;을 검색하면 색인된 페이지 수를 대략 파악할 수 있고, Search Console의 &lt;strong&gt;색인생성 → 페이지&lt;/strong&gt;에서 &amp;ldquo;색인 생성됨/미생성&amp;rdquo; 개수를 정확히 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;겪은 함정은 모두 &lt;strong&gt;시간차&lt;/strong&gt;에서 비롯된 것이었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인증 직후 URL 검사 색인 요청 실패&lt;/strong&gt;: 시스템 반영에 시간이 필요하다. 30분~1시간 뒤 재시도하면 대부분 성공한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sitemap 제출 직후 &amp;ldquo;가져올 수 없음&amp;rdquo;&lt;/strong&gt;: 흔한 초기 현상. 기다리면 자동 재시도되어 &amp;ldquo;성공&amp;quot;으로 바뀐다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;site:&amp;lt;도메인&amp;gt;&lt;/code&gt; 결과 0&lt;/strong&gt;: 세팅 문제가 아니라 아직 색인되지 않은 것뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 시간차를 미리 알고 있으면 조급해할 일이 없다. 파일 배치와 제출은 하루 안에 끝나고, 나머지는 구글의 스케줄대로 진행된다. 한 번 해 두면 이후로는 계속 자동으로 크롤링·색인되므로, 여유가 될 때 각 글에 개별 cover 이미지를 붙여 썸네일을 다양화하거나 Article 구조화 데이터(Structured Data)를 추가해 검색 스니펫을 더 다듬을 수 있다.&lt;/p&gt;
&lt;h2 id="한-줄-정리"&gt;한 줄 정리
&lt;/h2&gt;&lt;p&gt;검색 노출 세팅 자체는 robots.txt·og:image 보완과 Search Console 등록·sitemap 제출로 하루면 끝나고, 나머지는 구글이 3일~2주에 걸쳐 크롤링·색인하기를 기다리는 시간차의 문제다.&lt;/p&gt;</description></item><item><title>PaperMod에서 Stack 테마로 갈아타며 배운 것들</title><link>https://ditis3g.github.io/posts/2026-06-30-study-2/</link><pubDate>Tue, 30 Jun 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/2026-06-30-study-2/</guid><description>&lt;p&gt;블로그 테마를 PaperMod에서 Stack(&lt;code&gt;CaiJimmy/hugo-theme-stack&lt;/code&gt;)으로 교체했다. 색은 무채색(zinc) 톤으로, 폰트는 Pretendard로 커스텀했다. 단순히 &amp;ldquo;옷 갈아입기&amp;quot;로 끝나는 작업이 아니라 글 메타데이터와 자동화 prompt까지 한 사이클로 묶어야 일이 닫힌다는 걸 새삼 느꼈다. 헤맸던 지점 위주로 정리한다.&lt;/p&gt;
&lt;h2 id="테마-붙이고-config에서-처음에-헤매는-것들"&gt;테마 붙이고 config에서 처음에 헤매는 것들
&lt;/h2&gt;&lt;p&gt;Stack은 submodule로 붙인다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule add https://github.com/CaiJimmy/hugo-theme-stack themes/hugo-theme-stack
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;GitHub Actions로 빌드한다면 &lt;code&gt;checkout&lt;/code&gt; 단계에 &lt;code&gt;submodules: recursive&lt;/code&gt; 옵션이 있어야 테마가 같이 받아진다. 이게 없으면 로컬에선 멀쩡한데 배포 빌드만 깨진다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hugo.toml&lt;/code&gt;은 길지만, 처음에 발목 잡히는 건 세 가지뿐이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mainSections = [&amp;quot;posts&amp;quot;]&lt;/code&gt;&lt;/strong&gt; — Stack 기본값은 단수형 &lt;code&gt;&amp;quot;post&amp;quot;&lt;/code&gt;다. 우리 폴더가 &lt;code&gt;posts/&lt;/code&gt;라면 이걸 명시하지 않으면 홈에 글이 한 개도 안 뜬다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sidebar.avatar&lt;/code&gt;는 문자열 한 줄로.&lt;/strong&gt; &lt;code&gt;{enabled=..., src=...}&lt;/code&gt; 같은 중첩 테이블을 쓰면 빌드 에러가 난다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메뉴 아이콘은 실재하는 파일명으로.&lt;/strong&gt; &lt;code&gt;themes/.../assets/icons/&lt;/code&gt;에 실제로 있는 이름이어야 한다. &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;처럼 없는 이름을 쓰면 빌드가 fail한다. &lt;code&gt;archives&lt;/code&gt;, &lt;code&gt;categories&lt;/code&gt;, &lt;code&gt;tag&lt;/code&gt;, &lt;code&gt;hash&lt;/code&gt;, &lt;code&gt;home&lt;/code&gt; 정도가 안전하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="커스텀의-90는-customscss-한-파일에서-끝난다"&gt;커스텀의 90%는 custom.scss 한 파일에서 끝난다
&lt;/h2&gt;&lt;p&gt;Stack은 자기 &lt;code&gt;style.scss&lt;/code&gt; 마지막에 &lt;code&gt;@import &amp;quot;custom.scss&amp;quot;;&lt;/code&gt;를 박아둔다. 그래서 우리 사이트의 &lt;code&gt;assets/scss/custom.scss&lt;/code&gt;만 만들면 별도 설정 없이 자동으로 끼워져 컴파일된다. 이게 Stack 커스텀의 핵심이다.&lt;/p&gt;
&lt;p&gt;이 파일에서 한 일은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;색 변수(&lt;code&gt;--body-background&lt;/code&gt;, &lt;code&gt;--accent-color&lt;/code&gt;, &lt;code&gt;--card-background&lt;/code&gt; 등)를 라이트/다크 모두 zinc 톤으로 override. 다크 모드는 &lt;code&gt;:root[data-scheme=&amp;quot;dark&amp;quot;]&lt;/code&gt; 블록에서 따로 잡는다.&lt;/li&gt;
&lt;li&gt;Pretendard를 &lt;code&gt;@import url(...)&lt;/code&gt; CDN으로 불러오고 &lt;code&gt;--base-font-family&lt;/code&gt;를 교체. Stack 기본 폰트인 Lato는 한글 글리프가 없어서 시스템 fallback으로 떨어지면 글자가 둔탁해진다.&lt;/li&gt;
&lt;li&gt;카드 타이틀을 2.2rem → 1.9rem으로 줄이고 &lt;code&gt;letter-spacing: -0.01em&lt;/code&gt;. 한글은 영문보다 자간을 살짝 좁히는 쪽이 균형이 맞는다.&lt;/li&gt;
&lt;li&gt;본문에 &lt;code&gt;word-break: keep-all; overflow-wrap: anywhere;&lt;/code&gt; — 한국어가 어절 단위로 줄바꿈되어 훨씬 깔끔해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="가장-큰-함정-카테고리-배지의-inline-style"&gt;가장 큰 함정: 카테고리 배지의 inline style
&lt;/h2&gt;&lt;p&gt;제일 오래 잡아먹은 건 카테고리/태그 배지였다. Stack은 카테고리·태그마다 hash로 색을 자동 생성해서 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; 태그에 &lt;strong&gt;inline style&lt;/strong&gt;로 직접 박는다. inline style은 외부 CSS를 이기기 때문에, SCSS로 그냥 덮어쓰면 먹지 않는다.&lt;/p&gt;
&lt;p&gt;해결은 &lt;code&gt;!important&lt;/code&gt;로 강제하는 것이다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-scss" data-lang="scss"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nc"&gt;.article-category&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.article-tags&lt;/span&gt; &lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;background-color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;card-background-selected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;card-text-color-secondary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이걸 안 하면 사이트 전체는 무채색인데 카테고리 배지만 알록달록하게 튄다.&lt;/p&gt;
&lt;h2 id="글-메타데이터와-자동화-prompt까지-함께-손보기"&gt;글 메타데이터와 자동화 prompt까지 함께 손보기
&lt;/h2&gt;&lt;p&gt;Stack 사이드바의 카테고리 위젯이 살아나려면 각 글의 frontmatter에 &lt;code&gt;categories:&lt;/code&gt;가 있어야 한다. 기존 글들은 PaperMod 시절이라 &lt;code&gt;tags:&lt;/code&gt;만 있어서 위젯이 비어 있었다. 그래서 모든 글에 카테고리를 VLA / 피지컬 AI / 메타 3개 분류로 채워 넣었다.&lt;/p&gt;
&lt;p&gt;여기서 멈추면 다음 날 새로 생기는 글에서 같은 문제가 반복된다. 그래서 매일 밤 자동 초안을 만드는 &lt;code&gt;prompt-instructions.txt&lt;/code&gt;에도 categories 지시를 추가했다. 이제 다음 날부터 새 글은 자동으로 올바른 분류에 들어간다. 테마 교체를 글 메타데이터·자동화 prompt까지 한 사이클로 묶어야 일이 닫힌다는 게 이 작업의 교훈이다.&lt;/p&gt;
&lt;p&gt;작업 흐름에서 챙긴 팁도 적어둔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;별도 브랜치에서 작업.&lt;/strong&gt; main은 머지 시점까지 건드리지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;로컬에서 &lt;code&gt;hugo server&lt;/code&gt;를 띄워두고&lt;/strong&gt; SCSS를 고치면 hot reload된다. 확인할 때마다 GitHub Pages에 push하지 말 것.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배포 후 옛 모양으로 보이면 십중팔구 브라우저 캐시다.&lt;/strong&gt; &lt;code&gt;Ctrl+Shift+R&lt;/code&gt;이나 시크릿 창으로 먼저 확인하자. 빌드 자체를 의심하기 전에 &lt;code&gt;curl&lt;/code&gt;로 라이브 CSS에 새 규칙이 들어갔는지 보는 게 더 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="한-줄-정리"&gt;한 줄 정리
&lt;/h2&gt;&lt;p&gt;Stack 테마 교체의 핵심은 &lt;code&gt;custom.scss&lt;/code&gt; 한 파일과 inline style을 이기는 &lt;code&gt;!important&lt;/code&gt;이며, 테마 변경은 글 메타데이터와 자동화 prompt까지 한 사이클로 묶어야 비로소 끝난다.&lt;/p&gt;</description></item><item><title>블로그를 열며 — Physical AI 드림팀</title><link>https://ditis3g.github.io/posts/hello-world/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/hello-world/</guid><description>&lt;p&gt;안녕하세요. &lt;strong&gt;Physical AI 드림팀&lt;/strong&gt;의 첫 글입니다.&lt;/p&gt;
&lt;p&gt;이곳은 Physical AI를 비롯해 돈 되는 모든 걸 다 하는 팀의 기록을 남기는 공간입니다.
연구 노트, 실험 결과, 그리고 떠오르는 아이디어를 차곡차곡 쌓아가려 합니다.&lt;/p&gt;
&lt;h2 id="앞으로-다룰-것"&gt;앞으로 다룰 것
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Physical AI / 로보틱스 연구 노트&lt;/li&gt;
&lt;li&gt;실험과 삽질의 기록&lt;/li&gt;
&lt;li&gt;돈 되는 아이디어와 그 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;차근차근 채워가겠습니다. 잘 부탁드립니다. 🚀&lt;/p&gt;</description></item><item><title>코드 한 줄 없이, 0원으로 내 블로그 만들기 (GitHub Pages + Hugo)</title><link>https://ditis3g.github.io/posts/2026-06-26-study/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0900</pubDate><guid>https://ditis3g.github.io/posts/2026-06-26-study/</guid><description>&lt;p&gt;&amp;ldquo;블로그 하나 갖고 싶은데, 서버비도 부담되고 코드는 무섭고…&amp;rdquo;&lt;/p&gt;
&lt;p&gt;저도 그랬어요. 그런데 오늘 직접 해보니, &lt;strong&gt;코드를 거의 짜지 않고도 단돈 0원으로&lt;/strong&gt; 인터넷에 내 블로그를 띄울 수 있더라고요. 신기했던 마음 그대로, 누구나 따라 할 수 있게 차근차근 정리해 봤습니다. 천천히 같이 가봐요. 🙂&lt;/p&gt;
&lt;h2 id="1-github에-가입하고-내-땅-만들기"&gt;1. GitHub에 가입하고 &amp;lsquo;내 땅&amp;rsquo; 만들기
&lt;/h2&gt;&lt;p&gt;먼저 &lt;a class="link" href="https://github.com" target="_blank" rel="noopener"
 &gt;GitHub&lt;/a&gt;에 가입했어요. 그리고 블로그 파일이 들어갈 저장소(폴더 같은 것)를 하나 만들었습니다.&lt;/p&gt;
&lt;p&gt;여기서 &lt;strong&gt;딱 하나만 틀리지 않으면 돼요.&lt;/strong&gt; 저장소 이름을 반드시 이렇게 지어야 합니다.&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;내사용자이름.github.io&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;이 형식을 지켜야만 무료 주소가 자동으로 켜지거든요. 점 하나, 철자 하나라도 다르면 안 돼요. 이게 가장 중요한 약속입니다.&lt;/p&gt;
&lt;h2 id="2-무료-발행-스위치-켜기-github-pages"&gt;2. 무료 발행 스위치 켜기 (GitHub Pages)
&lt;/h2&gt;&lt;p&gt;GitHub에는 &lt;strong&gt;Pages&lt;/strong&gt;라는 고마운 기능이 있어요. 내 저장소에 올린 파일을 &lt;strong&gt;무료 웹사이트로 띄워 주는&lt;/strong&gt; 서비스죠. 서버를 따로 빌릴 필요가 없으니, 그 유명한 &amp;ldquo;서버비 0원&amp;quot;이 여기서 나옵니다.&lt;/p&gt;
&lt;p&gt;켜는 법도 간단했어요.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;저장소 상단의 &lt;strong&gt;Settings(설정)&lt;/strong&gt; 로 들어가서&lt;/li&gt;
&lt;li&gt;왼쪽 메뉴에서 &lt;strong&gt;Pages&lt;/strong&gt; 를 누르고&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt; 를 &lt;strong&gt;GitHub Actions&lt;/strong&gt; 로 바꿔 주기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;마지막의 &amp;lsquo;GitHub Actions&amp;rsquo;가 핵심이에요. 이걸로 바꿔야 다음 단계의 Hugo가 자동으로 블로그를 지어 줍니다.&lt;/p&gt;
&lt;h2 id="3-진짜-블로그-엔진-얹기-hugo--테마"&gt;3. 진짜 블로그 엔진 얹기 (Hugo + 테마)
&lt;/h2&gt;&lt;p&gt;이제 글을 쓰면 예쁜 페이지로 만들어 줄 엔진을 올릴 차례예요. 저는 &lt;strong&gt;Hugo&lt;/strong&gt;를 골랐습니다.&lt;/p&gt;
&lt;p&gt;Hugo는 쉽게 말해, &lt;strong&gt;글만 쓰면 블로그 페이지를 알아서 만들어 주는 도구&lt;/strong&gt;예요. 저는 그냥 글을 적기만 하고, 보기 좋은 HTML 페이지로 바꾸는 건 Hugo가 다 해줍니다.&lt;/p&gt;
&lt;p&gt;디자인은 직접 만들 필요도 없었어요. 이미 잘 만들어진 &lt;strong&gt;PaperMod&lt;/strong&gt;라는 테마를 가져다 붙이니, 단숨에 깔끔한 블로그 모양이 갖춰지더라고요.&lt;/p&gt;
&lt;h2 id="4-깃허브에-올리기--그리고-만난-작은-함정"&gt;4. 깃허브에 올리기 — 그리고 만난 작은 함정
&lt;/h2&gt;&lt;p&gt;작업한 내용을 깃허브에 올리는 흐름은 두 단어로 정리돼요.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;commit(커밋)&lt;/strong&gt; — 변경한 내용을 저장하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;push(푸시)&lt;/strong&gt; — 그 내용을 깃허브로 올려보내기&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다만 여기서 한 번 막혔어요. 올리려는데 &lt;strong&gt;403 에러&lt;/strong&gt;가 뜨더라고요. 알고 보니 컴퓨터에 &lt;strong&gt;다른 계정 정보가 저장돼 있어서&lt;/strong&gt; 생긴 일이었어요. 저장된 정보를 제 계정으로 정리해 주니 깔끔하게 해결됐습니다. 혹시 같은 에러를 만나도 당황하지 마세요. 실력 문제가 아니라 그냥 계정 설정이 엇갈린 것뿐이에요.&lt;/p&gt;
&lt;h2 id="5-한-줄-정리"&gt;5. 한 줄 정리
&lt;/h2&gt;&lt;p&gt;코드를 거의 짜지 않고도, &lt;strong&gt;GitHub Pages + Hugo + PaperMod&lt;/strong&gt; 조합이면 0원으로 진짜 내 블로그를 띄울 수 있어요. 막히는 순간이 와도 그건 끝이 아니라 그냥 &amp;ldquo;다음에 물어볼 차례&amp;quot;일 뿐이고요. 오늘 첫 발을 뗀 당신, 이미 절반은 온 거예요. 👏&lt;/p&gt;</description></item></channel></rss>