<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Fl!k LAB</title>
    <link>https://flik.tistory.com/</link>
    <description>All about Programming &amp;amp; Creative things.</description>
    <language>ko</language>
    <pubDate>Fri, 14 Aug 2026 16:54:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Fl!k</managingEditor>
    <item>
      <title>WebP가 정말로 필요할까?</title>
      <link>https://flik.tistory.com/140</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;WebP를 쓰면 뭐가 좋아요? 개선 효과가 있나요?&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt;WebP를 쓰는 이유가 뭐에요?&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #333333; text-align: center;&quot;&gt;WebP가 뭐에요???&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebP는 웹 최적화에 초점을 맞춘 포맷으로 &lt;b&gt;효율성, 성능 개선, 호환성&lt;/b&gt;에서 강점이 있는데, 특정 사용 환경이나 요구 사항에 따라 실효성을 판단해야 한다. 아래는 WebP의 주요 실효성에 해단 분석이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;b&gt;성능 및 데이터 절감 효과&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;효율성&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구글의 연구&lt;/b&gt;에 따르면, WebP는 JPEG 대비 약 25~34% 더 작은 파일 크기로, 동일한 화질을 제공한다.&lt;/li&gt;
&lt;li&gt;무손실 WebP는 PNG 대비 약 26% 더 작은 파일 크기를 제공하며, 투명 이미지에서도 효율적.&lt;/li&gt;
&lt;li&gt;파일 크기를 줄이면 네트워크 트래픽과 데이터 사용량이 감소하며, 모바일 환경에서 유리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;실효성 사례&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유튜브: WebP를 도입해 썸네일 파일 크기를 줄였으며, 페이지 로딩 시간을 약 10% 단축.&lt;/li&gt;
&lt;li&gt;넷플릭스: WebP 사용으로 이미지 데이터 트래픽 약 50% 절감.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실효성 판단&lt;/b&gt;:&lt;br /&gt;웹사이트 로딩 속도와 데이터 전송량이 중요한 환경에서는 매우 실효성이 높다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;b&gt;브라우저 및 플랫폼 호환성&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;지원 환경&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;웹 브라우저&lt;/b&gt;: 크롬, 사파리, 파이어폭스, 엣지 등 주요 브라우저에서 WebP를 지한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024년 기준 WebP는 &lt;b&gt;97% 이상의 브라우저에서 호환&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영체제&lt;/b&gt;: 대부분의 최신 OS에서 WebP를 기본 지원하며, Windows 및 macOS에서도 파일 뷰어와 편집 프로그램을 통해 활용 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;제한 사항&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구형 브라우저(Internet Explorer) 및 일부 오래된 소프트웨어에서 지원하지 않을 수 있음.&lt;/li&gt;
&lt;li&gt;브라우저 호환성 문제가 발생할 경우 &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; 태그로 Fallback 이미지 제공이 필요하다:
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;picture&amp;gt;
&amp;lt;source srcset=&quot;image.webp&quot; type=&quot;image/webp&quot;&amp;gt;
&amp;lt;img src=&quot;image.jpg&quot; alt=&quot;Example Image&quot;&amp;gt;
&amp;lt;/picture&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실효성 판단&lt;/b&gt;:&lt;br /&gt;현대적인 웹 환경에서는 호환성 문제가 거의 없지만, 구형 시스템에서는 추가 작업이 필요할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. &lt;b&gt;비교 대상 포맷과의 효율성&lt;/b&gt;&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;포맷&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;WebP&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;JPEG&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;PNG&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;GIF&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;압축 효율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;JPEG 대비 25~34% 더 작은 크기&lt;/td&gt;
&lt;td&gt;손실 압축 (효율적)&lt;/td&gt;
&lt;td&gt;무손실 압축 (효율 낮음)&lt;/td&gt;
&lt;td&gt;무손실 압축 (효율 낮음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;투명도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 지원&lt;/td&gt;
&lt;td&gt;❌ 지원하지 않음&lt;/td&gt;
&lt;td&gt;✅ 지원&lt;/td&gt;
&lt;td&gt;✅ 제한적 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;애니메이션&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 지원&lt;/td&gt;
&lt;td&gt;❌ 지원하지 않음&lt;/td&gt;
&lt;td&gt;❌ 지원하지 않음&lt;/td&gt;
&lt;td&gt;✅ 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;색상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1,670만 색상&lt;/td&gt;
&lt;td&gt;1,670만 색상&lt;/td&gt;
&lt;td&gt;1,670만 색상&lt;/td&gt;
&lt;td&gt;256색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실효성 판단&lt;/b&gt;:&lt;br /&gt;WebP는 JPEG, PNG, GIF의 주요 기능을 대체하면서 파일 크기를 줄이고 효율성을 극대화하므로 종합적으로 우수하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. &lt;b&gt;사용 사례별 실효성&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;WebP가 실효성이 높은 경우&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;웹사이트 최적화&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지가 많은 전자상거래 사이트, 블로그, 뉴스 포털 등.&lt;/li&gt;
&lt;li&gt;빠른 로딩이 중요한 사용자 경험(UX)을 요구하는 환경.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모바일 환경&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 절감과 속도가 중요한 모바일 사용자 대상 서비스.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;투명 이미지와 애니메이션 사용&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PNG나 GIF를 대체하면서 용량을 줄이고 고화질을 유지하고자 할 때.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;WebP의 실효성이 제한적인 경우&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;고급 색상 표현(HDR)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebP는 24비트 색상까지만 지원하므로, HDR 이미지가 필요하다면 HEIF가 더 적합.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레거시 시스템과의 호환성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구형 브라우저나 시스템에서는 JPEG/PNG로 대체해야 할 수도 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사진 전문 편집&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebP는 사진의 원본 데이터를 보존하지 않으므로, 사진 보관이나 전문 편집 용도로는 RAW, TIFF 등이 더 적합.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. &lt;b&gt;SEO와 Lighthouse 점수 향상&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Lighthouse는 &quot;Serve images in next-gen formats&quot;를 권장하며, WebP 사용 시 성능 점수가 상승하며, 이는 SEO 순위에도 긍정적인 영향을 미친다. 실제로&amp;nbsp;WebP 적용 시 페이지 로딩 속도 약 10~20% 개선 효과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실효성 판단&lt;/b&gt;:&lt;br /&gt;SEO와 웹 퍼포먼스 최적화 측면에서 WebP는 탁월한 선택입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebP의 실효성은 사용 환경과 요구 사항에 따라 달라질 수 있지만, &lt;b&gt;대부분의 웹 환경에서 최적화된 선택&lt;/b&gt;이다. 특히 &lt;b&gt;빠른 로딩 속도, 작은 파일 크기, 투명도/애니메이션 지원&lt;/b&gt; 등의 장점은 전자상거래, 콘텐츠 중심 사이트, 모바일 앱 등에서 실효성이 매우 높다고 할 수 있다.&amp;nbsp; 다만, 구형 시스템 또는 고품질 HDR 작업에는 한계가 있을 수 있으니, &lt;b&gt;필요에 따라 보완 포맷을 사용하는 전략&lt;/b&gt;이 좋다.&lt;/p&gt;</description>
      <category>Diary/Tip, Review</category>
      <category>SEO</category>
      <category>WebP</category>
      <category>오블완</category>
      <category>이미지 포맷</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/140</guid>
      <comments>https://flik.tistory.com/140#entry140comment</comments>
      <pubDate>Mon, 25 Nov 2024 00:00:09 +0900</pubDate>
    </item>
    <item>
      <title>웹용 이미지 파일에 대한 a to z</title>
      <link>https://flik.tistory.com/139</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이미지 파일에 대한 모든 것을 정리하며 기록해 본다. &lt;br&gt;&lt;br&gt;이미지 파일이란 디지털 장치에서 사진, 그림, 그래픽을 표현하기 위해 사용되는 데이터 파일이다. 이미지 파일은 다양한 포맷으로 존재하며, 각각의 특성과 목적에 따라 사용된다. 파일 크기, 품질, 지원 기능 등이 포맷마다 다르기 때문에 목적에 맞는 포맷을 선택하는 것이 중요하다.&lt;br&gt;&lt;br&gt;이미지 파일의 대표적인 포맷으로는 JPEG, PNG, GIF, TIFF, WebP, BMP, HEIC, 그리고 SVG가 있다. 이들 포맷은 용도와 특성이 다 다르며, SEO나 웹 최적화, 디자인 작업, 또는 고품질 인쇄 등의 작업에서 각각의 역할을 한다.&lt;br&gt;&lt;br&gt;JPEG는 가장 많이 사용되는 이미지 포맷 중 하나다. 손실 압축을 통해 파일 크기를 줄이며, 사진과 같은 복잡한 이미지를 저장하기에 적합하다. 하지만 여러 번 저장하거나 압축하면 품질이 저하될 수 있다는 단점이 있다.&lt;br&gt;&lt;br&gt;PNG는 무손실 압축을 지원하며, 투명도를 처리할 수 있다. 주로 로고나 그래픽 디자인 파일에 많이 사용된다. 파일 크기는 JPEG보다 크지만, 품질 유지가 중요할 때 적합하다.&lt;br&gt;&lt;br&gt;GIF는 256색 제한이 있는 포맷으로, 간단한 애니메이션을 지원한다. 요즘은 WebP와 같은 최신 포맷이 등장하면서 GIF의 사용 빈도가 줄어들고 있다.&lt;br&gt;&lt;br&gt;TIFF는 고품질 이미지 저장을 위해 사용된다. 주로 인쇄나 스캔 작업에서 활용되며, 무손실 데이터를 포함할 수 있어 전문가용 작업에 적합하다.&lt;br&gt;&lt;br&gt;WebP는 구글에서 개발한 최신 포맷으로, 손실 및 무손실 압축 모두를 지원한다. 파일 크기가 작으면서도 품질을 유지해, 웹 최적화에 자주 사용된다.&lt;br&gt;&lt;br&gt;BMP는 원시 비트맵 데이터로, 무손실로 이미지를 저장한다. 하지만 파일 크기가 매우 크기 때문에 웹보다는 주로 시스템 내부 작업에서 사용된다.&lt;br&gt;&lt;br&gt;HEIC는 iPhone에서 기본으로 사용하는 포맷이다. 효율적인 압축과 고품질 이미지를 제공하며, 현대적인 디바이스에서 점차 사용이 늘어나고 있다.&lt;br&gt;&lt;br&gt;SVG는 벡터 기반의 이미지 포맷으로, 무한히 확대해도 품질 저하가 없다. 주로 아이콘, 로고, 일러스트 작업에서 사용되며, 코드로 작성되는 파일이다.&lt;br&gt;&lt;br&gt;이미지 파일에는 메타데이터라는 부가 정보가 포함될 수 있다. 메타데이터는 이미지의 속성과 정보를 저장하는 데이터로, EXIF, IPTC, XMP, ICC 프로파일 등이 있다. EXIF는 카메라 정보와 촬영 데이터를, IPTC는 제목, 키워드, 저작권 정보를 저장한다. XMP는 Adobe가 개발한 포맷으로, 확장성과 호환성이 뛰어나다. ICC 프로파일은 색상 정보를 저장해 디바이스 간의 색상 일치를 돕는다.&lt;br&gt;&lt;br&gt;SEO를 위해 이미지 파일에서 중요한 데이터는 파일 이름, Alt 텍스트, 캡션, 파일 크기, 이미지 포맷 등이다. 파일 이름은 이미지의 내용을 설명해야 하며, Alt 텍스트는 이미지 내용을 대체하는 문구로 검색 엔진 최적화와 웹 접근성에 중요한 역할을 한다. 캡션은 이미지의 맥락을 추가로 설명하는 역할을 하며, 파일 크기와 포맷은 페이지 로딩 속도와 사용자 경험에 영향을 준다.&lt;br&gt;&lt;br&gt;이미지 파일은 단순히 시각적 정보를 저장하는 것을 넘어, 정보를 전달하고 SEO를 강화하며 사용자 경험을 높이는 중요한 요소다. 포맷 선택과 메타데이터 관리, 그리고 최적화를 신경 쓴다면 이미지 파일을 효과적으로 활용할 수 있다. 이 내용을 바탕으로 이미지 파일과 관련된 도구를 설계하거나 더 깊이 있는 연구를 진행할 수 있을 것이다.&lt;/p&gt;</description>
      <category>Programming/All</category>
      <category>exif</category>
      <category>gif</category>
      <category>png</category>
      <category>오블완</category>
      <category>이미지 메타 데이터</category>
      <category>이미지 웹 최적화</category>
      <category>이미지 파일</category>
      <category>이미지 포맷</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/139</guid>
      <comments>https://flik.tistory.com/139#entry139comment</comments>
      <pubDate>Fri, 22 Nov 2024 23:53:27 +0900</pubDate>
    </item>
    <item>
      <title>Vercel에서 간단히 서버리스 함수 사용하기(Next.js 없이)</title>
      <link>https://flik.tistory.com/138</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서론&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프론트엔드 개발을 하다 보면 서버리스 함수(Serverless Functions)를 사용하는 일이 잦다. 특히 Next.js에서는&amp;nbsp;pages/api&amp;nbsp;폴더에 함수만 작성하면 자동으로 서버리스 함수로 동작하는 것은 익숙한 사실이다. Vercel에 프로젝트를 배포할 때 Functions라는 기능으로 서비리스 함수를 구현하는 방식을 제공하는 것이 생각나서 한번 테스트 겸 작성을 해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;꼭 Next.js 프로젝트가 아니어도 프로젝트의&amp;nbsp;api&amp;nbsp;폴더 안에 작성된 함수 파일을 Vercel에서는 서버리스 함수로 인식하고 동작한다는 것이다. 단순한 HTML 파일만 있는 프로젝트에서도 서버리스 함수를 간단하게 구현해서 api를 만들 수 있다는 것이 가능하다. 간단한 사실이지만, 누군가에는 새로운 사실이 될 수 있을 것 같아서 좀 더 정리를 해 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프론트엔드 개발자로서 서버 코드를 작성하는 게 익숙하지 않은 경우, 이 방법으로 간단한 API를 테스트하거나 빠르게 프로토타입을 만들 때 꽤 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_DALL&amp;amp;middot;E 2024-11-21 23.51.15 - A highly realistic 3D rendered thumbnail for a blog post titled 'Using Serverless Functions in Vercel Without Next.js'. The image features a sleek, fu.webp&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSM8YF/btsKSPd9L5z/vl7XoY8EKR5V6rOJITSuKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSM8YF/btsKSPd9L5z/vl7XoY8EKR5V6rOJITSuKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSM8YF/btsKSPd9L5z/vl7XoY8EKR5V6rOJITSuKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSM8YF%2FbtsKSPd9L5z%2Fvl7XoY8EKR5V6rOJITSuKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;290&quot; height=&quot;290&quot; data-filename=&quot;edited_DALL&amp;middot;E 2024-11-21 23.51.15 - A highly realistic 3D rendered thumbnail for a blog post titled 'Using Serverless Functions in Vercel Without Next.js'. The image features a sleek, fu.webp&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 기본 프로젝트 구조&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아주 간단하게 시작했다. 아래처럼 폴더를 구성했다&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;axapta&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;project-root/
├── api/
│   └── hello.js
├── index.html&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;api/hello.js&lt;/b&gt;: 서버리스 함수 역할을 할 파일.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;index.html&lt;/b&gt;: 브라우저에서 함수를 호출할 간단한 페이지.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. 서버리스 함수 작성&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;api/hello.js&amp;nbsp;파일을 만들어 다음 코드를 작성했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;export default function handler(req, res) {
    res.status(200).json({ message: 'Hello from Vercel Serverless Function!' });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 함수는&amp;nbsp;GET&amp;nbsp;요청을 처리하며, 클라이언트가&amp;nbsp;/api/hello&amp;nbsp;경로로 호출하면 &quot;Hello from Vercel Serverless Function!&quot;이라는 메시지를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 알게 된 점&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;req&amp;nbsp;객체로 요청 데이터를 읽을 수 있고,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;res&amp;nbsp;객체로 클라이언트에 응답을 보낼 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. Vercel에 배포&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트 준비가 끝나면 Vercel에 배포했다. 방법은 간단했다&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://vercel.com/&quot;&gt;Vercel&lt;/a&gt;에 로그인.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로젝트를 업로드하거나,&amp;nbsp;vercel&amp;nbsp;CLI를 사용해 배포.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;cmake&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;npm install -g vercel
vercel&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배포가 완료되면&amp;nbsp;/api/hello&amp;nbsp;경로에서 서버리스 함수가 잘 동작하는지 확인할 수 있다. 나의 경우, 배포된 URL에서 응답 메시지를 바로 확인할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. 클라이언트에서 함수 호출&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;index.html에서 이 서버리스 함수를 호출해 봤다. 간단한 버튼을 추가해, 클릭하면 API 호출 결과를 화면에 표시하도록 만들었다&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;title&amp;gt;Vercel Serverless Example&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Vercel Serverless Functions&amp;lt;/h1&amp;gt;
    &amp;lt;button id=&quot;callApi&quot;&amp;gt;Call API&amp;lt;/button&amp;gt;
    &amp;lt;div id=&quot;response&quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
        document.getElementById('callApi').addEventListener('click', async () =&amp;gt; {
            const response = await fetch('/api/hello');
            const data = await response.json();
            document.getElementById('response').innerText = data.message;
        });
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결과: 버튼을 누르자 서버리스 함수가 반환한 메시지가 화면에 출력되었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;마무리하며&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;먼저 알게된 점, 느낀점을 정리해본다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Next.js 없이도 서버리스 함수를 쓸 수 있다는 점이 정말 편리하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단순한 정적 사이트에서도 API 호출을 테스트하거나 간단한 서버 로직을 구현하는 데 활용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프론트엔드 중심의 프로젝트에서 별도의 백엔드 환경 없이도 빠르게 데이터를 처리할 수 있다는 점이 강점이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;외부 API 호출 연동하기,&amp;nbsp;기본 인증이나 API 키로 보안 강화하기,&amp;nbsp;간단한 CRUD API 예제 만들어 보기 등&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;서버리스 함수를 활용해서 만들어볼 거리가 많다.&amp;nbsp;Vercel로 인해 작은 아이디어를 실험하는 문턱이 많이 낮아지는 것 같다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Programming/All</category>
      <category>vercel</category>
      <category>vercel functions</category>
      <category>서버리스 함수</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/138</guid>
      <comments>https://flik.tistory.com/138#entry138comment</comments>
      <pubDate>Fri, 22 Nov 2024 00:00:51 +0900</pubDate>
    </item>
    <item>
      <title>Working Copy 소개(ios용 깃 클라이언트 및 코드 편집 앱 )</title>
      <link>https://flik.tistory.com/136</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt;Working Copy는 iOS용 Git 클라이언트입니다. iPhone과 iPad에서 Git 리포지토리를 클론, 편집, 커밋, 푸시할 수 있는 앱을 찾던 중에 알게 된 앱입니다. &lt;br&gt;&lt;br&gt;코드 편집기로 활용도 가능하기 때문에 repository를 클론 해둔 상태에서 간단한 코드 수정을 하는 용도로도 종종 활용하게 됩니다.&lt;br&gt;&lt;br&gt;코드를 읽거나 편집하는 기능만으로도 다른 앱에 비해 좋다보니 단순히 코드를 보는 용도로도 많이 사용합니다.&lt;br&gt;&lt;br&gt;요즘들어서는 git을 활용한 자동화를 실험중인데 ios shortcut을 지원해주는게 너무 좋습니다.&lt;br&gt;&lt;br&gt;전 무료 기능을 1년이상 사용하다가 유료 결제를 했는데 한번 결제하면 평생 사용 가능하기 때문에 큰 부담은 되지 않았던 것 같습니다. 학생들을 위해 GitHub Student Developer Pack의 일환으로 Pro 기능을 무료로 제공하고 있습니다.&lt;br&gt;&lt;br&gt;자세한 정보는 아래에 더 남기겠습니다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;상세정보&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: justify;&quot;&gt; • Git 클라이언트: iOS에서 동작하는 Git 클라이언트로 GitHub, GitLab, Bitbucket 등 다양한 플랫폼과 호환됨.&lt;br&gt; • 코드 편집 기능:&lt;br&gt; • 50개 이상의 프로그래밍 언어에 대해 구문 강조 기능 제공.&lt;br&gt; • 내장된 코드 편집기 사용 가능.&lt;br&gt; • 오프라인 지원: 네트워크가 없어도 커밋 가능하며, 나중에 푸시할 수 있음.&lt;br&gt; • 파일 미리보기:&lt;br&gt; • 이미지, Markdown, HTML 파일 지원.&lt;br&gt; • JavaScript 콘솔 포함, 코드 오류 확인 가능.&lt;br&gt; • 자동화 기능:&lt;br&gt; • Shortcuts 앱과 연동 가능.&lt;br&gt; • 특정 작업을 단축어로 실행 가능.&lt;br&gt; • 검색 기능:&lt;br&gt; • 심볼, 파일명, 텍스트 등 리포지토리 내에서 빠르게 검색.&lt;br&gt; • 프로(Pro) 버전:&lt;br&gt; • 무료로 다운로드 가능하지만, 커밋 푸시나 5개 이상의 리포지토리 관리 등은 Pro 기능 활성화 필요.&lt;br&gt; • Pro 버전은 일회성 구매 방식으로 제공되며, 이후 1년간 새로운 기능 포함.&lt;br&gt; • GitHub Student Developer Pack 사용자에게 Pro 무료 제공.&lt;br&gt; • 특이점:&lt;br&gt; • iPhone과 iPad에서 모두 사용 가능.&lt;br&gt; • 모바일 환경에서도 Git 작업을 효율적으로 처리할 수 있는 강력한 도구임.&lt;br&gt;&lt;br&gt;&lt;/p&gt;</description>
      <category>Diary/Tip, Review</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/136</guid>
      <comments>https://flik.tistory.com/136#entry136comment</comments>
      <pubDate>Sat, 16 Nov 2024 23:58:03 +0900</pubDate>
    </item>
    <item>
      <title>텍스트가 포함된 이미지 생성하는 파이썬 코드</title>
      <link>https://flik.tistory.com/135</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flik.tistory.com/134&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 포스팅&lt;/a&gt;에서 소개했던 파이썬으로 이미지를 생성하는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 현재 디렉토리의 .txt 파일 이름을 이용해 최대 3줄로 줄바꿈한 텍스트를 포함하는 128x128 크기의 검은색 썸네일 이미지를 생성하고, 이를 thumbnails 폴더에 저장합니다. 텍스트는 흰색으로 중앙에 배치되며, 텍스트 크기는 이미지에 맞도록 조정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 생성에는 PIL이라는 라이브러리를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 실행 시 폴더에 이미 해당 이름의 이미지 파일이 있다면 새로 생성하지 않고 넘어갑니다. 최종적으로, .txt 파일마다 해당 텍스트가 표시된 썸네일이 저장되는 구조로 작동합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;import os
from PIL import Image, ImageDraw, ImageFont

# Define the path for the thumbnails folder
thumbnails_path = './thumbnails'
os.makedirs(thumbnails_path, exist_ok=True)

# Loop through each text file in the current directory
for file in os.listdir('.'):
    if file.endswith('.txt'):
        # Get the filename without the '.txt' extension
        filename = file.replace('.txt', '')
        thumbnail_file = f'{thumbnails_path}/{filename}.png'

        # Check if the thumbnail already exists
        if os.path.exists(thumbnail_file):
            print(f&quot;'{thumbnail_file}' 파일이 이미 존재하여 건너뜁니다.&quot;)
            continue  # 파일이 이미 존재하면 다음 파일로 넘어갑니다.

        # Create a 128x128 black image
        img = Image.new('RGB', (128, 128), color='black')
        d = ImageDraw.Draw(img)

        # Define the initial font and maximum font size
        font_size = 24  # 초기 글꼴 크기 설정
        font = ImageFont.load_default()

        # Try to fit the text within the image, wrapping text if necessary
        max_width, max_height = 128, 128  # 이미지 크기
        text = filename  # 텍스트 파일 이름을 표시할 텍스트로 사용

        # 줄바꿈된 텍스트 계산 함수
        def wrap_text(text, font, max_width):
            words = text.split()
            lines = []
            current_line = &quot;&quot;
            for word in words:
                test_line = f&quot;{current_line} {word}&quot;.strip()
                bbox = d.textbbox((0, 0), test_line, font=font)
                width = bbox[2] - bbox[0]  # 텍스트의 너비 계산
                if width &amp;lt;= max_width:
                    current_line = test_line
                else:
                    lines.append(current_line)
                    current_line = word
            lines.append(current_line)
            return lines[:3]  # 최대 3줄까지만 반환

        # Adjust font size to fit within the image
        while font_size &amp;gt; 8:  # 최소 글꼴 크기 8로 제한
            font = ImageFont.truetype(&quot;fonts/BMDOHYEON_ttf.ttf&quot;, font_size)  # 시스템에 따라 폰트 경로 조정 필요
            wrapped_text = wrap_text(text, font, max_width)
            text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
            if text_height &amp;lt;= max_height:
                break
            font_size -= 1

        # Calculate position to center the wrapped text vertically
        total_text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
        y_offset = (max_height - total_text_height) // 2

        # Draw each line of wrapped text
        for line in wrapped_text:
            bbox = d.textbbox((0, 0), line, font=font)
            text_width = bbox[2] - bbox[0]
            text_height = bbox[3] - bbox[1]
            position = ((max_width - text_width) // 2, y_offset)
            d.text(position, line, fill=(255, 255, 255), font=font)
            y_offset += text_height

        # Save thumbnail image in thumbnails folder
        img.save(thumbnail_file)
        print(f&quot;'{thumbnail_file}' 파일을 새로 생성했습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Programming/All</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/135</guid>
      <comments>https://flik.tistory.com/135#entry135comment</comments>
      <pubDate>Fri, 15 Nov 2024 23:59:00 +0900</pubDate>
    </item>
    <item>
      <title>Github Actions로 이미지 생성 자동화 하기</title>
      <link>https://flik.tistory.com/134</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Intro&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 Github Actions로 여러가지 자동화를 할 수 있는 것을 이야기 했는데 구체적인 예시를 소개하고자 합니다.&lt;br /&gt;Text 파일 업로드를 하면 해당 텍스트 파일을 기준으로 이미지를 생성해서 업로드 하는 Workflow입니다. 이번 포스팅에서는 각 줄을 좀 더 자세히 하나하나 설명을 적어보았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;작동방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;텍스트 파일을 깃허브 레포에 커밋하면 파이썬 코드가, 해당 텍스트파일을 가지고 이미지를 만들도록 할 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;어떻게 되는지 한번 보여드리겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 처럼 텍스트 파일을 만들어서 Push 하면..&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6sRdK/btsKJ8Znly2/jEn2Lt2HvxZ7hIcxwgBLyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6sRdK/btsKJ8Znly2/jEn2Lt2HvxZ7hIcxwgBLyk/img.png&quot; data-alt=&quot;Push Text Files&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6sRdK/btsKJ8Znly2/jEn2Lt2HvxZ7hIcxwgBLyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6sRdK%2FbtsKJ8Znly2%2FjEn2Lt2HvxZ7hIcxwgBLyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;181&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Push Text Files&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Action이 돌아갑니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br5wBo/btsKJnbZbSy/YkS2Iad8z7ad3yYs3by2R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br5wBo/btsKJnbZbSy/YkS2Iad8z7ad3yYs3by2R1/img.png&quot; data-alt=&quot;Github Action&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br5wBo/btsKJnbZbSy/YkS2Iad8z7ad3yYs3by2R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr5wBo%2FbtsKJnbZbSy%2FYkS2Iad8z7ad3yYs3by2R1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;349&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github Action&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이미지 파일이 만들어집니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2pqbc/btsKICgl3ta/eVIlgvUQCKX0yV3es89Lw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2pqbc/btsKICgl3ta/eVIlgvUQCKX0yV3es89Lw0/img.png&quot; data-alt=&quot;와우.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2pqbc/btsKICgl3ta/eVIlgvUQCKX0yV3es89Lw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2pqbc%2FbtsKICgl3ta%2FeVIlgvUQCKX0yV3es89Lw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;237&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;와우.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 긴 텍스트는 잘리네요. 하지만 파이썬 코드를 수정해야 할 것 같은데 이번에는 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRRcDd/btsKIetiWYc/0AMtiFX9rpkWmtDtekYFak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRRcDd/btsKIetiWYc/0AMtiFX9rpkWmtDtekYFak/img.png&quot; data-alt=&quot;깃허브 액션으로 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRRcDd/btsKIetiWYc/0AMtiFX9rpkWmtDtekYFak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRRcDd%2FbtsKIetiWYc%2F0AMtiFX9rpkWmtDtekYFak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;241&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;깃허브 액션으로 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이썬 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 현재 디렉토리의 .txt 파일 이름을 이용해 최대 3줄로 줄바꿈한 텍스트를 포함하는 128x128 크기의 검은색 썸네일 이미지를 생성하고, 이를 thumbnails 폴더에 저장합니다. 텍스트는 흰색으로 중앙에 배치되며, 텍스트 크기는 이미지에 맞도록 조정됩니다. 코드 실행 시 폴더에 이미 해당 이름의 이미지 파일이 있다면 새로 생성하지 않고 넘어갑니다. 최종적으로, .txt 파일마다 해당 텍스트가 표시된 썸네일이 저장되는 구조로 작동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄씩 보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;elm&quot;&gt;&lt;code&gt;import os
from PIL import Image, ImageDraw, ImageFont&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;os&lt;/code&gt;: 파일과 디렉토리 작업을 위한 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PIL.Image&lt;/code&gt;, &lt;code&gt;PIL.ImageDraw&lt;/code&gt;, &lt;code&gt;PIL.ImageFont&lt;/code&gt;: 이미지를 생성하고 글자를 그리기 위한 Pillow 라이브러리 모듈들입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;thumbnails_path = './thumbnails'
os.makedirs(thumbnails_path, exist_ok=True)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;thumbnails_path&lt;/code&gt; 변수에 썸네일 저장 경로를 지정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os.makedirs&lt;/code&gt;는 &lt;code&gt;thumbnails&lt;/code&gt; 폴더를 생성하는데, &lt;code&gt;exist_ok=True&lt;/code&gt; 옵션 덕분에 폴더가 이미 존재해도 오류 없이 넘어갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;for file in os.listdir('.'):
    if file.endswith('.txt'):&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 디렉토리에서 &lt;code&gt;.txt&lt;/code&gt; 확장자로 끝나는 파일을 하나씩 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;filename = file.replace('.txt', '')
thumbnail_file = f'{thumbnails_path}/{filename}.png'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 이름에서 &lt;code&gt;.txt&lt;/code&gt; 확장자를 제거하여 &lt;code&gt;filename&lt;/code&gt; 변수에 저장하고, 썸네일 파일명을 &lt;code&gt;thumbnail_file&lt;/code&gt;에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;if os.path.exists(thumbnail_file):
    print(f&quot;'{thumbnail_file}' 파일이 이미 존재하여 건너뜁니다.&quot;)
    continue&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;thumbnail_file&lt;/code&gt; 경로에 파일이 이미 존재하면 메시지를 출력하고, 다음 파일로 넘어갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;img = Image.new('RGB', (128, 128), color='black')
d = ImageDraw.Draw(img)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;img&lt;/code&gt;라는 128x128 크기의 검은색 이미지를 생성하고, &lt;code&gt;d&lt;/code&gt; 객체로 이미지 위에 텍스트를 그릴 준비를 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;font_size = 24
font = ImageFont.load_default()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;font_size&lt;/code&gt;를 24로 초기화하고, 기본 시스템 폰트를 &lt;code&gt;font&lt;/code&gt; 변수에 할당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;max_width, max_height = 128, 128
text = filename&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 크기(&lt;code&gt;max_width&lt;/code&gt;, &lt;code&gt;max_height&lt;/code&gt;)를 설정하고, 텍스트에 파일 이름을 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;def wrap_text(text, font, max_width):&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트를 줄바꿈하여 이미지에 맞추기 위한 &lt;code&gt;wrap_text&lt;/code&gt; 함수를 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;words = text.split()
lines = []
current_line = &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 이름 텍스트를 단어별로 나누고, 줄을 저장할 리스트 &lt;code&gt;lines&lt;/code&gt;와 현재 줄을 나타낼 &lt;code&gt;current_line&lt;/code&gt;을 초기화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;for word in words:
    test_line = f&quot;{current_line} {word}&quot;.strip()&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 단어를 추가하며 줄이 최대 너비를 넘지 않는지 확인하기 위해 &lt;code&gt;test_line&lt;/code&gt; 변수에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bbox = d.textbbox((0, 0), test_line, font=font)
width = bbox[2] - bbox[0]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;textbbox&lt;/code&gt; 메서드로 텍스트의 너비를 계산합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;if width &amp;lt;= max_width:
    current_line = test_line
else:
    lines.append(current_line)
    current_line = word
lines.append(current_line)
return lines[:3]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;줄의 너비가 &lt;code&gt;max_width&lt;/code&gt;보다 작으면 현재 줄에 단어를 추가하고, 초과하면 &lt;code&gt;lines&lt;/code&gt; 리스트에 저장하고 새로운 줄을 시작합니다. 최대 3줄까지만 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;maxima&quot;&gt;&lt;code&gt;while font_size &amp;gt; 8:
    font = ImageFont.truetype(&quot;fonts/BMDOHYEON_ttf.ttf&quot;, font_size)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;font_size&lt;/code&gt;를 줄여가며 텍스트가 이미지에 맞을 때까지 폰트를 설정합니다. (최소 8 포인트로 제한)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;wrapped_text = wrap_text(text, font, max_width)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;wrap_text&lt;/code&gt; 함수를 호출하여 줄바꿈한 텍스트를 &lt;code&gt;wrapped_text&lt;/code&gt; 변수에 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
if text_height &amp;lt;= max_height:
    break
font_size -= 1&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;텍스트의 총 높이를 계산하여 이미지 높이보다 작아질 때까지 폰트 크기를 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;hsp&quot;&gt;&lt;code&gt;total_text_height = sum(d.textbbox((0, 0), line, font=font)[3] - d.textbbox((0, 0), line, font=font)[1] for line in wrapped_text)
y_offset = (max_height - total_text_height) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;줄바꿈한 텍스트의 총 높이를 기준으로 수직 중앙에 배치할 &lt;code&gt;y_offset&lt;/code&gt;을 계산합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;for line in wrapped_text:
    bbox = d.textbbox((0, 0), line, font=font)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]
    position = ((max_width - text_width) // 2, y_offset)
    d.text(position, line, fill=(255, 255, 255), font=font)
    y_offset += text_height&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 줄을 이미지 중앙에 맞추어 그린 후, &lt;code&gt;y_offset&lt;/code&gt;을 업데이트하여 다음 줄의 위치를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;img.save(thumbnail_file)
print(f&quot;'{thumbnail_file}' 파일을 새로 생성했습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 이미지를 &lt;code&gt;thumbnail_file&lt;/code&gt; 경로에 저장하고, 생성 메시지를 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;WorkFlow&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 GitHub Actions 워크플로우는 저장소에 &lt;code&gt;.txt&lt;/code&gt; 파일이 추가되거나 변경될 때마다 트리거되어, 해당 파일들을 기반으로 썸네일 이미지를 생성한 뒤 &lt;code&gt;thumbnails&lt;/code&gt; 폴더에 저장하고, 생성된 썸네일 이미지를 저장소에 커밋하여 푸시합니다. 워크플로우는 우분투 환경에서 실행되며, Python을 사용하여 이미지 생성 스크립트를 수행합니다. 이후 생성된 썸네일 이미지들을 저장소에 자동으로 커밋하고 푸시하여 저장소에 반영되도록 합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;on:
  push:
    paths:
      - '**/*.txt'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;.txt&lt;/code&gt; 파일이 저장소에 추가되거나 수정될 때마다 이 워크플로우가 자동으로 실행되도록 트리거를 설정합니다. 모든 디렉토리와 서브 디렉토리에서 &lt;code&gt;.txt&lt;/code&gt; 파일의 변경 사항을 감지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;jobs:
  generate_thumbnail:
    runs-on: ubuntu-latest&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;generate_thumbnail&lt;/code&gt;이라는 작업을 정의하고, 우분투 최신 버전 환경에서 실행되도록 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;permissions:
  contents: write&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 작업에서는 저장소의 파일을 수정하고 커밋할 권한이 필요하므로 &lt;code&gt;write&lt;/code&gt; 권한을 부여합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;steps:
  - name: Checkout repository
    uses: actions/checkout@v2&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 단계로, &lt;code&gt;actions/checkout&lt;/code&gt; 액션을 사용하여 저장소의 코드를 현재 워크플로우 실행 환경에 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;  - name: Set up Python
    uses: actions/setup-python@v2
    with:
      python-version: '3.x'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Python 3.x 버전을 설정하여 이후 파이썬 스크립트를 실행할 준비를 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;  - name: Install dependencies
    run: |
      pip install pillow&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pillow 라이브러리를 설치합니다. Pillow는 Python에서 이미지 처리를 할 수 있게 도와주는 라이브러리로, 이 워크플로우에서 썸네일 이미지를 생성하는 데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;  - name: Generate Thumbnail
    run: |
      python scripts/generate_thumbnail.py&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;generate_thumbnail.py&lt;/code&gt; 스크립트를 실행하여 &lt;code&gt;.txt&lt;/code&gt; 파일을 기반으로 썸네일 이미지를 생성합니다. 해당 스크립트는 &lt;code&gt;scripts&lt;/code&gt; 폴더에 위치해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;  - name: Commit and push thumbnails
    run: |
      git config --local user.name &quot;github-actions[bot]&quot;
      git config --local user.email &quot;github-actions[bot]@users.noreply.github.com&quot;
      git add thumbnails/
      git commit -m &quot;Add thumbnails for text files&quot;
      git push&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;썸네일 생성 후, Git 설정을 통해 &lt;code&gt;github-actions[bot]&lt;/code&gt; 사용자로 커밋 작성자를 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git add thumbnails/&lt;/code&gt;를 통해 &lt;code&gt;thumbnails&lt;/code&gt; 폴더의 변경 사항을 추가하고, &lt;code&gt;git commit&lt;/code&gt;으로 커밋을 생성한 뒤, &lt;code&gt;git push&lt;/code&gt;로 저장소에 푸시하여 생성된 썸네일이 저장소에 반영되도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 간단해 보이지만 만드는과정에서 많은 실패가 일어납니다. Workflow에 권한이 없다느니, 파이썬 파일 실행과정에서의 문제 등으로 인해 수많은 fail을 거쳐 성공을 할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nhiKr/btsKI3YVdHz/dGkpkRAov339SHkPmN3dkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nhiKr/btsKI3YVdHz/dGkpkRAov339SHkPmN3dkk/img.png&quot; data-alt=&quot;Pass &amp;amp;amp; Fails...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nhiKr/btsKI3YVdHz/dGkpkRAov339SHkPmN3dkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnhiKr%2FbtsKI3YVdHz%2FdGkpkRAov339SHkPmN3dkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;342&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Pass &amp;amp; Fails...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상으로 포스팅을 마칩니다.&lt;/p&gt;</description>
      <category>Programming/All</category>
      <category>github actions</category>
      <category>PIL</category>
      <category>Python</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/134</guid>
      <comments>https://flik.tistory.com/134#entry134comment</comments>
      <pubDate>Thu, 14 Nov 2024 19:06:48 +0900</pubDate>
    </item>
    <item>
      <title>클로드로 UI 코드를 뽑아보며 든 생각</title>
      <link>https://flik.tistory.com/133</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;프론트엔드 개발을 하면서 가장 귀찮은 일 중 하나가 UI 코드를 짜는 것이다. 기능만 동작하게끔 화면에 아무렇게나 구현하라고 하면 쉽겠지만 대부분 실제 사용자의 경험과 디자인 컨셉 등을 반영하다 보면 만들기 까다로운 화면은 존재한다.&lt;br&gt;사실 꼭 고난도의 화면이 아니라도 같은 디자인 컨셉으로 매일 만드는 화면인데도 교묘하게 다른 구성요소 여백차이를 일일히 보면서 만드는게 지겨운 일이다.&lt;br&gt;AI를 코딩에 활용하는 것은 이제 새롭지 않을 정도로 널리 활용되고 있다. Cursor 라는 IDE를 메인으로 쓰고 있고 gpt, claud도 유료 플랜을 구독하며 활용중이다.&lt;br&gt;사실 나는 Claud보다는 GPT를 주로 사용해왔는데 이번에 클로드를 써보고 나서 깜짝 놀랐다. 개발중인 앱 스크린샷을 클로드에 그냥 넣었더니 그대로 구현이 되어서 나왔다. 완전히 동일하지는 않지만 80%정도 똑같이 나온것 같다.&amp;nbsp;&amp;nbsp;그다음 다른 앱 컨셉을 설명하고 Apple의 디자인 컨셉으로 만들어 달라고 하거나 다른 디자인 철학이나 컨셉을 이야기해보니 곧잘 만들어준다. 코드를 구현하고 그걸 실행해서 화면으로 보여주는게 놀라웠다.&lt;br&gt;&lt;br&gt;이런식의 AI코딩이 인간을 아직 대체할 정도는 아니다. 완성도가 높고 장기적인 유지보수가 필요한 앱들은 아직 AI가 대신해주지는 못한다. 아직까지는 완전히 대체보다는 AI를 적극적으로 활용하면 동일한 일을 좀더 빠르게 할수 있는 수준.&lt;br&gt;&lt;br&gt;이번에 클로드가 만들어준 코드가 매우 놀라운 수준이긴 하지만 우리 앱에서 쓰는 라이브러리와 컨벤션 등 특수한 조건에 맞춰서 만들어 주지는 못하는 수준.&lt;br&gt;그래도 요즘 날이 가면 갈수록 AI의 코딩 수준이 놀랍게 변하고 있어서 개발자란 직업을 계속 할수 있을까 의문이 들 정도.&lt;br&gt;앞으로 내가 예상하는 미래의 코딩은 현재의 영상 편집 정도로 비유할 수 있을 것 같다. 아무나 하지는 못하지만 조금만 배우면 누구나 할수 있다. 흔한 유튜브 채널들처럼 코딩 자체를 잘하는 능력보다 AI를 활용해서 아이디어를 구현하는 기획력과 실행력이 아마 더 중요해질 것 같다. 남들이 이것저것 만든다면 어나더 레벨을 만들어보자.&lt;/p&gt;</description>
      <category>Diary/Notes</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/133</guid>
      <comments>https://flik.tistory.com/133#entry133comment</comments>
      <pubDate>Mon, 11 Nov 2024 23:31:01 +0900</pubDate>
    </item>
    <item>
      <title>Github Actions 의 workflow 주기적으로 실행하기(crontab 활용하기)</title>
      <link>https://flik.tistory.com/132</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Intro&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 액션으로 특정 작업을 등록해두면, 원하는 순간에 작업을 실행하도록 할 수 있습니다.&lt;br /&gt;보통은 push가 발생할때, pr이 등록될 때, tag가 올라올 때 등 이벤트를 기준으로 작업을 실행하는 것을 많이 활용하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 특별한 이벤트가 없어도 주기적으로 작업을 실행하는 방법을 소개해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법으로 주기적으로 실행이 필요한 작업을 github actions를 활용해서 자동화하는데에 활용할 수 있습니다.&lt;br /&gt;주기적으로 업데이트 확인하기, 크롤링이나 데이터 수집, 로그 정리 등에 활용할 수 있습니다.&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;crontab이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;crontab은 특정 시간에 특정 명령어를 실행하는 규칙을 설정합니다. crontab 을 사용하면 원하는 시간에 작업을 실행하게 할 수 있습니다.&lt;br /&gt;주로 시스템 관리나 백업, 로그 정리, 데이터 수집 등을 자동으로 처리하는 데 사용됩니다.&lt;br /&gt;별도로 설치하거나 할 필요는 없고, 깃업 액션의 workflow 파일에 규칙에 맞게 써주면됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;code&gt;crontab&lt;/code&gt;의 구성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;crontab&lt;/code&gt;은 특정 시간에 특정 명령어를 실행하는 규칙을 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;code&gt;crontab&lt;/code&gt; 설정 파일에는 다음과 같은 형식으로 규칙이 작성됩니다.&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;* * * * * 명령어&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 별표(&lt;code&gt;*&lt;/code&gt;)는 시간과 관련된 요소를 나타내며, 순서대로 다음을 의미합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 &lt;code&gt;*&lt;/code&gt;: &lt;b&gt;분&lt;/b&gt; (0 - 59)&lt;/li&gt;
&lt;li&gt;두 번째 &lt;code&gt;*&lt;/code&gt;: &lt;b&gt;시&lt;/b&gt; (0 - 23)&lt;/li&gt;
&lt;li&gt;세 번째 &lt;code&gt;*&lt;/code&gt;: &lt;b&gt;일&lt;/b&gt; (1 - 31)&lt;/li&gt;
&lt;li&gt;네 번째 &lt;code&gt;*&lt;/code&gt;: &lt;b&gt;월&lt;/b&gt; (1 - 12)&lt;/li&gt;
&lt;li&gt;다섯 번째 &lt;code&gt;*&lt;/code&gt;: &lt;b&gt;요일&lt;/b&gt; (0 - 6, 0 = 일요일)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서, &lt;code&gt;crontab&lt;/code&gt; 규칙을 &lt;code&gt;0 * * * *&lt;/code&gt;로 설정하면 매시간 정각에 실행되도록 설정하는 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;매일 자정에 실행하기&lt;/b&gt;: &lt;code&gt;0 0 * * *&lt;/code&gt; - 매일 00:00에 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매시간 실행하기&lt;/b&gt;: &lt;code&gt;0 * * * *&lt;/code&gt; - 매시 정각마다 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매 5분마다 실행하기&lt;/b&gt;: &lt;code&gt;*/5 * * * *&lt;/code&gt; - 5분 간격으로 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Github Actions에서의 &lt;code&gt;crontab&lt;/code&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions에서는 &lt;code&gt;crontab&lt;/code&gt; 형식을 사용하여 &lt;b&gt;주기적으로 실행하는 스케줄링 작업&lt;/b&gt;을 설정할 수 있다. 이를 위해 &lt;code&gt;schedule&lt;/code&gt; 이벤트와 &lt;code&gt;cron&lt;/code&gt; 속성을 사용하면 되죠! 간단하게 설정 방법을 보여드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;schedule&lt;/code&gt; 이벤트에서 &lt;code&gt;cron&lt;/code&gt; 속성을 사용해 원하는 주기에 맞춰 워크플로우가 자동 실행되도록 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;code&gt;0 * * * *&lt;/code&gt;로 설정하면 &lt;b&gt;1시간마다 워크플로우가 자동 실행&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의할 점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cron&lt;/code&gt; 설정은 &lt;b&gt;UTC&lt;/b&gt; 기준으로 작동하므로, 한국 시간으로 설정하고 싶다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;UTC 시간에 +9시간을 더해서 설정&lt;/b&gt;&lt;/span&gt;해야 합니다.&lt;br /&gt;예를 들어, &lt;code&gt;0 0 * * *&lt;/code&gt; 으로 등록하면 한국시간 기준 9시가 됩니다.&lt;br /&gt;한국시간 기준 0시는 UTC 기준 15시가 되므로 &lt;code&gt;0 15 * * *&lt;/code&gt;으로 설정하면 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github Actions에서 crontab 형식 사용하기&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Github Action 워크플로우 파일 생성&lt;/b&gt;: &lt;code&gt;.github/workflows&lt;/code&gt; 디렉토리에 YAML 파일을 생성해요. 예를 들어, &lt;code&gt;schedule.yml&lt;/code&gt; 같은 파일을 만들면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스케줄 설정&lt;/b&gt;: 워크플로우 파일에서 &lt;code&gt;schedule&lt;/code&gt; 이벤트와 &lt;code&gt;cron&lt;/code&gt;을 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# .github/workflows/schedule.yml
name: Scheduled Job

on:
  schedule:
    - cron: &quot;0 * * * *&quot;  # 이 부분이 crontab 형식 (매시 정각마다 실행)

jobs:
  scheduled-task:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Run Script
        run: |
          echo &quot;스케줄에 따라 작업을 실행합니다.&quot;
          # 원하는 명령어나 스크립트 실행&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;cron&lt;/code&gt; 구문 예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;매시간 정각마다 실행&lt;/b&gt;: &lt;code&gt;0 * * * *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매일 자정에 실행&lt;/b&gt;: &lt;code&gt;0 0 * * *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매주 월요일 자정에 실행&lt;/b&gt;: &lt;code&gt;0 0 * * 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매 10분마다 실행&lt;/b&gt;: &lt;code&gt;*/10 * * * *&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Actions에서 주기적으로 작업을 실행하는 방법을 소개해봤습니다. 워크플로우 파일에 규칙에 맞게 써주면 원하는 시간에 작업을 실행할 수 있습니다. 주로 시스템 관리나 백업, 로그 정리, 데이터 수집 등에 활용할 수 있을 것 같습니다. 도움이 되었길 바랍니다.&lt;/p&gt;</description>
      <category>Programming/All</category>
      <category>crontab</category>
      <category>github actions</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/132</guid>
      <comments>https://flik.tistory.com/132#entry132comment</comments>
      <pubDate>Sun, 10 Nov 2024 23:16:47 +0900</pubDate>
    </item>
    <item>
      <title>유사 이미지 검색의 개념과 원리, 구현 방법</title>
      <link>https://flik.tistory.com/131</link>
      <description>&lt;p&gt;유사이미지 검색에 대해서 한번에 다 다룰 자신은 없지만 조금씩 정리해보려고 합니다.&lt;/p&gt;
&lt;h2&gt;유사 이미지 검색의 개념&lt;/h2&gt;
&lt;p&gt;유사 이미지 검색은 사용자가 입력한 이미지와 시각적으로 유사한 이미지를 데이터베이스에서 찾아주는 기술입니다. 이는 전통적인 텍스트 기반 검색과 달리, 이미지의 색상, 형태, 질감 등 시각적 특징을 분석하여 유사도를 판단합니다. 예를 들어, 사용자가 특정 상품의 이미지를 입력하면, 해당 상품과 유사한 디자인이나 색상의 다른 상품 이미지를 추천해주는 방식입니다.&lt;/p&gt;
&lt;h2&gt;유사 이미지 검색의 원리와 작동 방식&lt;/h2&gt;
&lt;p&gt;유사 이미지 검색은 주로 다음과 같은 단계로 이루어집니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징 추출:&lt;/strong&gt; 입력된 이미지에서 색상, 형태, 질감 등의 시각적 특징을 추출합니다. 이를 위해 딥러닝 기반의 신경망 모델이 주로 사용됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;임베딩 생성:&lt;/strong&gt; 추출된 특징을 고차원 벡터 공간의 임베딩으로 변환합니다. 이러한 임베딩은 이미지 간의 유사도를 수치적으로 비교할 수 있게 해줍니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터베이스 저장:&lt;/strong&gt; 생성된 임베딩을 데이터베이스에 저장합니다. 이때, 효율적인 검색을 위해 벡터 검색을 지원하는 데이터베이스를 사용합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;유사도 계산 및 검색:&lt;/strong&gt; 사용자가 새로운 이미지를 입력하면, 해당 이미지의 임베딩을 생성하고, 데이터베이스 내의 임베딩들과의 유사도를 계산하여 가장 유사한 이미지를 반환합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;일반적인 인프라 구성&lt;/h2&gt;
&lt;p&gt;유사 이미지 검색 시스템의 일반적인 인프라 구성은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 저장소:&lt;/strong&gt; 원본 이미지와 해당 임베딩을 저장하는 스토리지 시스템이 필요합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징 추출 및 임베딩 생성 모듈:&lt;/strong&gt; 이미지에서 특징을 추출하고 임베딩을 생성하는 딥러닝 모델이 포함됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;벡터 검색 엔진:&lt;/strong&gt; 임베딩 간의 유사도를 효율적으로 계산하고 검색할 수 있는 엔진이 필요합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API 레이어:&lt;/strong&gt; 사용자 요청을 처리하고 검색 결과를 반환하는 인터페이스를 제공합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AWS를 활용한 구체적인 구현 방법&lt;/h2&gt;
&lt;p&gt;AWS는 유사 이미지 검색 시스템을 구축하기 위한 다양한 서비스를 제공합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;데이터 저장소:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon S3:&lt;/strong&gt; 원본 이미지를 안전하게 저장하고 관리할 수 있는 객체 스토리지 서비스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;특징 추출 및 임베딩 생성:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon SageMaker:&lt;/strong&gt; 딥러닝 모델을 학습하고 배포할 수 있는 완전관리형 서비스로, 이미지 특징 추출 및 임베딩 생성을 위한 모델을 구축하고 호스팅할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;벡터 검색 엔진:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Amazon OpenSearch Service:&lt;/strong&gt; k-NN(k-Nearest Neighbors) 기능을 통해 벡터 기반의 유사도 검색을 지원합니다. 이미지 임베딩을 저장하고, 유사도 계산을 통해 유사 이미지를 효율적으로 검색할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;API 레이어:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Lambda:&lt;/strong&gt; 서버리스 컴퓨팅 서비스로, 이미지 업로드 시 자동으로 트리거되어 임베딩 생성 및 OpenSearch에 저장하는 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Amazon API Gateway:&lt;/strong&gt; 사용자 요청을 처리하고, Lambda 함수를 호출하여 OpenSearch에서 유사 이미지 검색 결과를 반환하는 API를 구축할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;구현 흐름:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;이미지 업로드:&lt;/strong&gt; 사용자가 이미지를 업로드하면, 해당 이미지는 Amazon S3에 저장됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;임베딩 생성:&lt;/strong&gt; S3에 이미지가 저장되면, AWS Lambda가 트리거되어 SageMaker에서 호스팅 중인 딥러닝 모델을 호출하여 이미지의 임베딩을 생성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;임베딩 저장:&lt;/strong&gt; 생성된 임베딩은 Amazon OpenSearch Service에 저장됩니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;유사 이미지 검색:&lt;/strong&gt; 사용자가 새로운 이미지를 업로드하여 검색을 요청하면, 동일한 과정을 통해 임베딩을 생성하고, OpenSearch의 k-NN 기능을 활용하여 가장 유사한 이미지를 검색하여 반환합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이러한 AWS 서비스를 활용하면 확장성과 효율성을 갖춘 유사 이미지 검색 시스템을 구축할 수 있습니다. 또한, 서버리스 아키텍처를 도입함으로써 운영 부담을 줄이고, 비용 효율적인 시스템을 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;참고 자료:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.awsstartup.io/analytics/analytics-services/amazon-opensearch-knn-image-search&quot;&gt;Amazon OpenSearch Service의 k-NN 기능을 사용한 유사 이미지 검색 구현하기&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/ko/blogs/tech/ai-application-ecs-sagemaker/&quot;&gt;Amazon SageMaker를 활용한 이미지 분류 모델 구축&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 자료를 참고하여 AWS를 활용한 유사 이미지 검색 시스템 구축에 대한 이해를 높일 수 있습니다. &lt;/p&gt;</description>
      <category>API Gateway</category>
      <category>OpenSearch</category>
      <category>Sagemaker</category>
      <category>오블완</category>
      <category>유사이미지 검색</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/131</guid>
      <comments>https://flik.tistory.com/131#entry131comment</comments>
      <pubDate>Sat, 9 Nov 2024 23:58:58 +0900</pubDate>
    </item>
    <item>
      <title>Streamlit과 Cursor를 활용하여 빠르게 웹 어플리케이션 만들기</title>
      <link>https://flik.tistory.com/130</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Streamlit이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마전 AWS의 워크샵에 참여하면서 Streamlit을 처음 알게되었다. Streamlit은 Python 기반의 웹 애플리케이션 프레임워크로, 데이터를 시각화하거나, 머신러닝 모델을 공유하거나, 데이터 분석용 대시보드를 쉽게 만들 수 있게 도와주는 도구이다. 특히, Python 코드로만 웹 애플리케이션을 개발할 수 있는게 특징이다. html, javascript 를 전혀 작성하지 않아도 웹 어플리케이션이 뚝딱 만들어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Streamlit의 가장 큰 장점은 &lt;b&gt;간결하고 직관적인 문법&lt;/b&gt;인데 Python으로 간단한 코드만 작성하면 대화형 웹 애플리케이션이 만들어진다. 코드를 수정할 때마다 실시간으로 애플리케이션이 업데이트되기 때문에, 개발 중간에도 결과를 바로바로 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 요소(버튼, 입력 상자, 차트 등)을 추가하는것이 매우 단순하고, 디자인 같은걸 신경쓰지 않아도 알아서 그려주는것은 장점이다. 때문에 세세하게 디자인 하고 싶다면 불가능하다는게 단점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 주로 쓰는 사람들은 실행이 가능한 시스템을 만들고 이것을 웹페이지에서 실행 가능한 어플리케이션으로 만들어서 바로 확인하려는 사람이 될 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://streamlit.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;streamlit.io&lt;/a&gt;에서 좀 더 자세한 정보를 확인할 수 잇다.&lt;/p&gt;
&lt;figure id=&quot;og_1731072890580&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Streamlit &amp;bull; A faster way to build and share data apps&quot; data-og-description=&quot;Streamlit is an open-source Python framework for data scientists and AI/ML engineers to deliver interactive data apps &amp;ndash; in only a few lines of code.&quot; data-og-host=&quot;streamlit.io&quot; data-og-source-url=&quot;https://streamlit.io/&quot; data-og-url=&quot;https://streamlit.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3yMBe/hyXs0ozAs6/GiUlZW3HoW1wjWnh9B0ySk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/POdX8/hyXs5wDv9Y/rSTBkr1uJLX5YGr493Pqc1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://streamlit.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://streamlit.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3yMBe/hyXs0ozAs6/GiUlZW3HoW1wjWnh9B0ySk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/POdX8/hyXs5wDv9Y/rSTBkr1uJLX5YGr493Pqc1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Streamlit &amp;bull; A faster way to build and share data apps&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Streamlit is an open-source Python framework for data scientists and AI/ML engineers to deliver interactive data apps &amp;ndash; in only a few lines of code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;streamlit.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AI를 활용하여 빠르게 실습하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Streamlit에는 그렇게 많은 기능이 들어가 있지는 않다. 그래서 직접 학습하면서 진행할 수도 있지만, Cursor를 활용하여 빠르게 만들었을 때에도 거의 그럴듯한 앱이 뚝딱 만들어지는것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소에 API 테스트를 포스트맨으로 하고 있지만 업무흐름에 맞지 않아 조금 아쉬운 부분들이 있었는데 현재 개발중인 도메인에 좀더 적절하게 테스트가 가능한 웹페이지를 빠르게 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GPT 활용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cursor를 사용하지 않는다면 GPT로도 충분히 빠르게 앱을 만들 수 있다. 다음은 내가 만들어 본 예시이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;1594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbHKZ3/btsKCPT2jv0/ClrmPyiBOcqpO3NYVruFeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbHKZ3/btsKCPT2jv0/ClrmPyiBOcqpO3NYVruFeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbHKZ3/btsKCPT2jv0/ClrmPyiBOcqpO3NYVruFeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbHKZ3%2FbtsKCPT2jv0%2FClrmPyiBOcqpO3NYVruFeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;514&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;1594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;a style=&quot;color: #666666;&quot; title=&quot;전체 대화 보기&quot; href=&quot;https://chatgpt.com/share/672e1966-0cdc-8002-ae81-eef846ae3c61&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;전체 대화 보기&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇번의 대화 끝에 이렇게 앱이 나왔다. 와우...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6hyLG/btsKDfrahcb/SZhR4w6xZyDYDYmVW7nHJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6hyLG/btsKDfrahcb/SZhR4w6xZyDYDYmVW7nHJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6hyLG/btsKDfrahcb/SZhR4w6xZyDYDYmVW7nHJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6hyLG%2FbtsKDfrahcb%2FSZhR4w6xZyDYDYmVW7nHJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;790&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brRhbJ/btsKClrXMLZ/r42EKqxB3c68STlKgwJTj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brRhbJ/btsKClrXMLZ/r42EKqxB3c68STlKgwJTj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brRhbJ/btsKClrXMLZ/r42EKqxB3c68STlKgwJTj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrRhbJ%2FbtsKClrXMLZ%2Fr42EKqxB3c68STlKgwJTj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;790&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 Streamlit의 코딩 경험이 너무 좋아서, 주변의 개발자들과 파이썬을 사용하는 초보자들에게 적극 추천할 것 같다.&lt;/p&gt;</description>
      <category>Programming/All</category>
      <category>CURSOR</category>
      <category>GPT</category>
      <category>Streamlit</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>Fl!k</author>
      <guid isPermaLink="true">https://flik.tistory.com/130</guid>
      <comments>https://flik.tistory.com/130#entry130comment</comments>
      <pubDate>Fri, 8 Nov 2024 23:39:50 +0900</pubDate>
    </item>
  </channel>
</rss>