<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>tts 보관 - 하우인포-IT·테크</title>
	<atom:link href="https://howinfo.kr/tag/tts/feed/" rel="self" type="application/rss+xml" />
	<link>https://howinfo.kr/tag/tts/</link>
	<description>IT·AI 자동화 &#38; 인프라 전문 블로그 (하우인포)</description>
	<lastBuildDate>Thu, 12 Feb 2026 02:26:05 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://howinfo.kr/wp-content/uploads/2026/02/cropped-ChatGPT-Image-2026년-2월-12일-오후-05_39_40-32x32.png</url>
	<title>tts 보관 - 하우인포-IT·테크</title>
	<link>https://howinfo.kr/tag/tts/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>파이썬으로 음성 알람 만들기: EdgeTTS 캐시 + 중복방지 + systemd 자동실행</title>
		<link>https://howinfo.kr/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9c%bc%eb%a1%9c-%ec%9d%8c%ec%84%b1-%ec%95%8c%eb%9e%8c-%eb%a7%8c%eb%93%a4%ea%b8%b0-edgetts-%ec%ba%90%ec%8b%9c-%ec%a4%91%eb%b3%b5%eb%b0%a9%ec%a7%80-systemd-%ec%9e%90/</link>
					<comments>https://howinfo.kr/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9c%bc%eb%a1%9c-%ec%9d%8c%ec%84%b1-%ec%95%8c%eb%9e%8c-%eb%a7%8c%eb%93%a4%ea%b8%b0-edgetts-%ec%ba%90%ec%8b%9c-%ec%a4%91%eb%b3%b5%eb%b0%a9%ec%a7%80-systemd-%ec%9e%90/#respond</comments>
		
		<dc:creator><![CDATA[hong]]></dc:creator>
		<pubDate>Thu, 12 Feb 2026 02:25:07 +0000</pubDate>
				<category><![CDATA[개발·코딩]]></category>
		<category><![CDATA[EdgeTTS]]></category>
		<category><![CDATA[sysemd]]></category>
		<category><![CDATA[tts]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[스마트홈]]></category>
		<category><![CDATA[알람시계]]></category>
		<category><![CDATA[오렌지파이5]]></category>
		<category><![CDATA[자동화]]></category>
		<category><![CDATA[파이썬]]></category>
		<guid isPermaLink="false">https://howinfo.kr/?p=1594</guid>

					<description><![CDATA[<p>아침에 알람이 울리긴 하는데…“몇 시인지 말로 알려주면 진짜 바로 일어나겠는데?” 싶을 때가 있죠. 이번 글에서는 오렌지파이5 + Ubuntu 환경에서, 파이썬으로...</p>
<p>게시물 <a href="https://howinfo.kr/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9c%bc%eb%a1%9c-%ec%9d%8c%ec%84%b1-%ec%95%8c%eb%9e%8c-%eb%a7%8c%eb%93%a4%ea%b8%b0-edgetts-%ec%ba%90%ec%8b%9c-%ec%a4%91%eb%b3%b5%eb%b0%a9%ec%a7%80-systemd-%ec%9e%90/">파이썬으로 음성 알람 만들기: EdgeTTS 캐시 + 중복방지 + systemd 자동실행</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></description>
										<content:encoded><![CDATA[
<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading"></h1>



<p>아침에 알람이 울리긴 하는데…<br>“몇 시인지 말로 알려주면 진짜 바로 일어나겠는데?” 싶을 때가 있죠.</p>



<p>이번 글에서는 <strong>오렌지파이5 + Ubuntu</strong> 환경에서, 파이썬으로 <strong>말하는 음성 알람</strong>을 만드는 방법을 정리했습니다.</p>



<ul class="wp-block-list">
<li>06:00부터 10분 단위로 06:30까지</li>



<li>“주인님 일어나세요. 현재 시간 06시 10분입니다.” 같은 문장을 <strong>TTS로 말해주고</strong></li>



<li>설정파일 1개로 <strong>매일/평일/1회 + 공휴일 제외</strong>까지 제어하고</li>



<li><strong>EdgeTTS 캐시(재생 빠름)</strong> + <strong>중복 재생 방지(안전)</strong> + **systemd 자동 실행(운영 편함)**까지 묶었습니다.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">목표 동작 요약</h2>



<ul class="wp-block-list">
<li>알람 시간: <code>06:00</code>, <code>06:10</code>, <code>06:20</code>, <code>06:30</code></li>



<li>출력: 스피커로 음성 재생(mp3)</li>



<li>스케줄 방식: 설정파일(JSON) 기반</li>



<li>운영 안정성:
<ul class="wp-block-list">
<li>같은 분에 두 번 울리는 것 방지(상태파일 기록)</li>



<li>TTS는 캐시(mp3 재사용)로 속도/안정성 개선</li>



<li>systemd로 부팅 후 자동 실행</li>
</ul>
</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">준비물</h2>



<ul class="wp-block-list">
<li>Orange Pi 5 (또는 Ubuntu 머신)</li>



<li>Ubuntu 22.04/24.04 계열</li>



<li>스피커(3.5mm/USB/블루투스 등)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">설치(필수 패키지)</h2>



<pre class="wp-block-code"><code>sudo apt update
sudo apt install -y python3-pip mpg123
pip3 install edge-tts holidays
</code></pre>



<ul class="wp-block-list">
<li><code>edge-tts</code> : 텍스트 → 음성(mp3) 생성</li>



<li><code>mpg123</code> : mp3를 바로 재생(가볍고 안정적)</li>



<li><code>holidays</code> : 한국 공휴일 제외(선택처럼 보이지만 “휴일 제외”를 쓰려면 필요)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1) 설정파일 1개로 알람 규칙 관리하기</h2>



<p>프로젝트 폴더를 만들고, 설정파일을 준비합니다.</p>



<pre class="wp-block-code"><code>mkdir -p ~/edge_alarm
cd ~/edge_alarm
nano alarm_config.json
</code></pre>



<h3 class="wp-block-heading"><code>alarm_config.json</code></h3>



<pre class="wp-block-code"><code>{
  "mode": "weekdays",
  "times": &#91;"06:00", "06:10", "06:20", "06:30"],
  "message_template": "주인님 일어나세요. 현재 시간 {hh}시 {mm}분입니다.",
  "voice": "ko-KR-SunHiNeural",
  "rate": "+0%",
  "volume": 100,
  "exclude_public_holidays": true,
  "country_holidays": "KR",
  "once_date": "2026-02-12"
}
</code></pre>



<h3 class="wp-block-heading">핵심 옵션 설명</h3>



<ul class="wp-block-list">
<li><code>mode</code>
<ul class="wp-block-list">
<li><code>daily</code> : 매일</li>



<li><code>weekdays</code> : 평일만(토/일 제외)</li>



<li><code>once</code> : 특정 날짜 <code>once_date</code>에만 1회 실행</li>
</ul>
</li>



<li><code>times</code> : 울릴 시간을 배열로 관리</li>



<li><code>message_template</code> : <code>{hh}</code>, <code>{mm}</code>가 현재 시각으로 자동 치환</li>



<li><code>exclude_public_holidays</code> : 공휴일 제외 여부</li>



<li><code>country_holidays</code> : 한국은 <code>"KR"</code></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2) 파이썬 실행 코드(alarm_tts.py)</h2>



<p>이 코드는 아래 3가지를 “운영 가능한 수준”으로 묶는 게 포인트입니다.</p>



<ol class="wp-block-list">
<li><strong>EdgeTTS 캐시</strong>: 같은 문장은 mp3를 저장해 재사용</li>



<li><strong>중복방지</strong>: <code>YYYY-MM-DD_HH:MM</code> 키로 “이미 울림” 기록</li>



<li><strong>자동실행</strong>: systemd로 부팅 시 자동 기동</li>
</ol>



<p>아래 파일을 저장하세요.</p>



<pre class="wp-block-code"><code>nano alarm_tts.py
chmod +x alarm_tts.py
</code></pre>



<h3 class="wp-block-heading"><code>alarm_tts.py</code> (한글 상세 주석 포함)</h3>



<pre class="wp-block-code"><code>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
&#91;EdgeTTS 음성 알람 스크립트]
- 설정 파일(alarm_config.json) 하나만 수정해서 운영 가능
- EdgeTTS로 MP3 생성 후 스피커로 재생(mpg123 사용)
- MP3 캐시 저장(같은 문장 재사용) -&gt; 빠르고 안정적
- 상태 파일 기록(같은 분 중복 재생 방지)
- systemd 서비스로 등록하면 부팅 후 자동 실행 가능
"""

import asyncio
import json
import os
import sys
import time
import hashlib
import subprocess
from datetime import datetime, date, timedelta

# EdgeTTS 라이브러리 import
try:
    import edge_tts
except ImportError:
    print("edge-tts가 설치되어 있지 않습니다. `pip3 install edge-tts`를 실행하세요.")
    sys.exit(1)

# 공휴일 제외 기능을 위한 라이브러리(없으면 공휴일 판단 기능이 비활성)
try:
    import holidays as holidays_lib
except ImportError:
    holidays_lib = None

# -----------------------------
# 파일 경로(환경변수로 오버라이드 가능)
# -----------------------------
CONFIG_PATH = os.environ.get("ALARM_CONFIG", "./alarm_config.json")  # 설정 파일
STATE_PATH  = os.environ.get("ALARM_STATE", "./alarm_state.json")    # 중복방지 상태 파일
CACHE_DIR   = os.environ.get("ALARM_CACHE", "./tts_cache")           # TTS mp3 캐시 폴더


def load_json(path: str, default):
    """JSON 파일 로딩. 파일이 없으면 default 반환"""
    if not os.path.exists(path):
        return default
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def save_json(path: str, obj):
    """
    JSON 저장을 안전하게 하기 위한 방식
    - 임시 파일(.tmp)에 먼저 저장한 뒤 os.replace로 교체
    - 저장 중 전원 문제 등으로 파일이 깨질 위험을 줄임
    """
    tmp = path + ".tmp"
    with open(tmp, "w", encoding="utf-8") as f:
        json.dump(obj, f, ensure_ascii=False, indent=2)
    os.replace(tmp, path)


def ensure_dir(p: str):
    """폴더가 없으면 생성"""
    os.makedirs(p, exist_ok=True)


def is_public_holiday(d: date, country_code: str) -&gt; bool:
    """
    특정 국가 공휴일 여부 판단
    - holidays 라이브러리가 없으면 False 처리(공휴일 제외 비활성)
    """
    if holidays_lib is None:
        return False

    try:
        h = holidays_lib.country_holidays(country_code)
        return d in h
    except Exception:
        return False


def should_run_today(cfg: dict, today: date) -&gt; bool:
    """
    오늘 알람을 동작시킬지 판단
    - mode(daily/weekdays/once)
    - 공휴일 제외 옵션
    """
    mode = cfg.get("mode", "daily").lower()

    # once 모드: 특정 날짜에만 동작
    if mode == "once":
        once_date = cfg.get("once_date")
        if not once_date:
            return False
        try:
            od = datetime.strptime(once_date, "%Y-%m-%d").date()
            return today == od
        except ValueError:
            return False

    # weekdays 모드: 토/일이면 동작 안 함
    if mode == "weekdays":
        if today.weekday() &gt;= 5:
            return False

    # 공휴일 제외 옵션
    if cfg.get("exclude_public_holidays", False):
        cc = cfg.get("country_holidays", "KR")
        if is_public_holiday(today, cc):
            return False

    return True


def parse_times(cfg: dict):
    """
    설정 times(&#91;"06:00","06:10"...])를 (hh,mm) 튜플 리스트로 변환
    - 잘못된 값은 무시
    - 중복 제거 + 정렬
    """
    times = cfg.get("times", &#91;])
    parsed = &#91;]
    for t in times:
        try:
            hh, mm = t.split(":")
            parsed.append((int(hh), int(mm)))
        except Exception:
            pass
    return sorted(set(parsed))


def make_message(cfg: dict, now: datetime) -&gt; str:
    """설정 템플릿에서 멘트 생성({hh},{mm} 치환)"""
    tpl = cfg.get("message_template", "주인님 일어나세요. 현재 시간 {hh}시 {mm}분입니다.")
    return tpl.format(hh=now.strftime("%H"), mm=now.strftime("%M"))


def tts_cache_path(text: str, voice: str, rate: str) -&gt; str:
    """
    같은 텍스트/목소리/속도 조합은 mp3를 재사용하기 위해 해시 파일명으로 캐시 저장
    """
    key = f"{voice}|{rate}|{text}".encode("utf-8")
    h = hashlib.sha256(key).hexdigest()&#91;:24]
    return os.path.join(CACHE_DIR, f"{h}.mp3")


async def synthesize_mp3(text: str, voice: str, rate: str, out_path: str):
    """EdgeTTS로 mp3 생성(비동기)"""
    communicate = edge_tts.Communicate(text=text, voice=voice, rate=rate)
    await communicate.save(out_path)


def play_mp3(path: str, volume: int = 100):
    """
    mpg123로 mp3 재생
    - volume(0~100)을 gain으로 완만하게 반영
    """
    gain = max(0, min(32768, int(volume) * 80))
    subprocess.run(&#91;"mpg123", "-q", "-f", str(gain), path], check=False)


def minute_key(d: date, hh: int, mm: int) -&gt; str:
    """중복방지 키: YYYY-MM-DD_HH:MM"""
    return f"{d.isoformat()}_{hh:02d}:{mm:02d}"


def next_trigger_datetime(now: datetime, times):
    """
    현재 시각 기준으로 다음 알람 시각 찾기
    - 오늘~모레까지 탐색(안전장치)
    """
    for day_offset in range(0, 3):
        base = now.date() + timedelta(days=day_offset)
        for hh, mm in times:
            dt = datetime.combine(base, datetime.min.time()).replace(hour=hh, minute=mm)
            if dt &gt; now:
                return dt
    return None


async def main():
    """메인 루프"""
    ensure_dir(CACHE_DIR)

    cfg = load_json(CONFIG_PATH, default={})
    state = load_json(STATE_PATH, default={"fired": {}})

    times = parse_times(cfg)
    if not times:
        print("times 설정이 비어 있거나 형식이 잘못되었습니다.")
        return

    voice = cfg.get("voice", "ko-KR-SunHiNeural")
    rate = cfg.get("rate", "+0%")
    volume = int(cfg.get("volume", 100))

    print(f"&#91;alarm] 시작 mode={cfg.get('mode')} times={cfg.get('times')} voice={voice}")

    while True:
        now = datetime.now()
        today = now.date()

        # 오늘 동작 조건이 아니면 내일 새벽까지 대기
        if not should_run_today(cfg, today):
            tomorrow = datetime.combine(today + timedelta(days=1), datetime.min.time()).replace(minute=1)
            sleep_sec = max(5, int((tomorrow - now).total_seconds()))
            print(f"&#91;alarm] 오늘({today}) 스킵. {sleep_sec}초 후 재확인")
            time.sleep(sleep_sec)
            continue

        # 다음 알람 시각 계산
        nxt = next_trigger_datetime(now, times)
        if not nxt:
            time.sleep(10)
            continue

        # 다음 알람까지 대기(너무 길게 한번에 sleep하지 않도록 최대 60초 단위로 쪼갬)
        sleep_sec = (nxt - now).total_seconds()
        if sleep_sec &gt; 1:
            time.sleep(min(60, sleep_sec))
            continue

        # 중복방지: 같은 분에 이미 울렸으면 스킵
        k = minute_key(nxt.date(), nxt.hour, nxt.minute)
        if state.get("fired", {}).get(k):
            time.sleep(1)
            continue

        # 현재 시간 안내가 정확하도록 "울리는 순간"의 시간을 기준으로 멘트 생성
        speak_time = datetime.now()
        text = make_message(cfg, speak_time)

        # 캐시 mp3가 있으면 재사용, 없으면 새로 생성
        cache_path = tts_cache_path(text, voice, rate)
        if not os.path.exists(cache_path):
            try:
                await synthesize_mp3(text, voice, rate, cache_path)
            except Exception as e:
                print("&#91;alarm] TTS 생성 실패:", e)
                time.sleep(2)
                continue

        print(f"&#91;alarm] 울림 {k} =&gt; {text}")
        play_mp3(cache_path, volume=volume)

        # 상태 기록(이 분에는 이미 울렸음)
        state.setdefault("fired", {})&#91;k] = True
        save_json(STATE_PATH, state)

        # once 모드면 오늘 남은 알람이 없을 때 종료
        if cfg.get("mode", "").lower() == "once":
            remaining = &#91;]
            for hh, mm in times:
                dt = datetime.combine(today, datetime.min.time()).replace(hour=hh, minute=mm)
                if dt &gt; speak_time:
                    remaining.append(dt)
            if not remaining:
                print("&#91;alarm] once 모드 완료. 종료")
                return

        time.sleep(1)


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n&#91;alarm] 사용자에 의해 종료됨")
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3) 실행 방법(수동 테스트)</h2>



<pre class="wp-block-code"><code>cd ~/edge_alarm
python3 alarm_tts.py
</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>당장 테스트하고 싶으면 <code>times</code>를 현재 시간 기준으로 1~2분 뒤로 잠깐 바꿔보면 바로 확인됩니다.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4) systemd 자동 실행(부팅 시 자동 시작)</h2>



<h3 class="wp-block-heading">1) 서비스 파일 생성</h3>



<pre class="wp-block-code"><code>sudo nano /etc/systemd/system/edge-alarm.service
</code></pre>



<h3 class="wp-block-heading">2) 아래 내용 입력(경로는 본인 계정에 맞게 수정)</h3>



<pre class="wp-block-code"><code>&#91;Unit]
Description=Edge TTS Alarm (EdgeTTS cache + dedup + systemd)
After=network.target sound.target

&#91;Service]
Type=simple
WorkingDirectory=/home/orangepi/edge_alarm
ExecStart=/usr/bin/python3 /home/orangepi/edge_alarm/alarm_tts.py
Restart=always
RestartSec=3

&#91;Install]
WantedBy=multi-user.target
</code></pre>



<h3 class="wp-block-heading">3) 적용 및 실행</h3>



<pre class="wp-block-code"><code>sudo systemctl daemon-reload
sudo systemctl enable --now edge-alarm.service
sudo systemctl status edge-alarm.service
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">운영 팁(실제로 써보면 도움이 되는 부분)</h2>



<ul class="wp-block-list">
<li><strong>네트워크가 잠깐 끊겨도</strong> 이미 만들어둔 mp3 캐시가 있으면 재생은 계속 됩니다.</li>



<li>“같은 시간에 두 번 울림”이 싫다면 <strong>상태파일(alarm_state.json)</strong> 방식이 꽤 든든합니다.</li>



<li>멘트/시간/평일여부는 코드가 아니라 <strong>설정파일 하나로</strong> 운영하면 나중에 유지보수가 편해요.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">FAQ</h2>



<p><strong>Q. 공휴일 제외는 어떻게 동작해요?</strong><br>A. <code>holidays</code> 라이브러리에서 KR 공휴일을 체크해서 해당 날짜면 스킵합니다.</p>



<p><strong>Q. 스피커가 USB/블루투스면 안 나올 때가 있어요.</strong><br>A. 대부분 “기본 출력 장치”가 다르게 잡혀서 생깁니다. 먼저 Ubuntu 사운드 출력 장치를 확인해 주세요.</p>



<p><strong>Q. 멘트를 바꾸려면 코드를 수정해야 하나요?</strong><br>A. 아니요. <code>message_template</code>만 바꾸면 됩니다.</p>



<p></p>
<p>게시물 <a href="https://howinfo.kr/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9c%bc%eb%a1%9c-%ec%9d%8c%ec%84%b1-%ec%95%8c%eb%9e%8c-%eb%a7%8c%eb%93%a4%ea%b8%b0-edgetts-%ec%ba%90%ec%8b%9c-%ec%a4%91%eb%b3%b5%eb%b0%a9%ec%a7%80-systemd-%ec%9e%90/">파이썬으로 음성 알람 만들기: EdgeTTS 캐시 + 중복방지 + systemd 자동실행</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://howinfo.kr/%ed%8c%8c%ec%9d%b4%ec%8d%ac%ec%9c%bc%eb%a1%9c-%ec%9d%8c%ec%84%b1-%ec%95%8c%eb%9e%8c-%eb%a7%8c%eb%93%a4%ea%b8%b0-edgetts-%ec%ba%90%ec%8b%9c-%ec%a4%91%eb%b3%b5%eb%b0%a9%ec%a7%80-systemd-%ec%9e%90/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Orange Pi로 만드는 실내·실외 환경 음성 안내 시스템</title>
		<link>https://howinfo.kr/orange-pi%eb%a1%9c-%eb%a7%8c%eb%93%9c%eb%8a%94-%ec%8b%a4%eb%82%b4%c2%b7%ec%8b%a4%ec%99%b8-%ed%99%98%ea%b2%bd-%ec%9d%8c%ec%84%b1-%ec%95%88%eb%82%b4-%ec%8b%9c%ec%8a%a4%ed%85%9c/</link>
					<comments>https://howinfo.kr/orange-pi%eb%a1%9c-%eb%a7%8c%eb%93%9c%eb%8a%94-%ec%8b%a4%eb%82%b4%c2%b7%ec%8b%a4%ec%99%b8-%ed%99%98%ea%b2%bd-%ec%9d%8c%ec%84%b1-%ec%95%88%eb%82%b4-%ec%8b%9c%ec%8a%a4%ed%85%9c/#respond</comments>
		
		<dc:creator><![CDATA[hong]]></dc:creator>
		<pubDate>Sun, 08 Feb 2026 11:50:34 +0000</pubDate>
				<category><![CDATA[개발·코딩]]></category>
		<category><![CDATA[arduino연동]]></category>
		<category><![CDATA[Orangepi]]></category>
		<category><![CDATA[python자동화]]></category>
		<category><![CDATA[tts]]></category>
		<category><![CDATA[리눅스오디오]]></category>
		<category><![CDATA[스마트홈]]></category>
		<category><![CDATA[음성안내시스템]]></category>
		<guid isPermaLink="false">https://howinfo.kr/?p=1437</guid>

					<description><![CDATA[<p>Arduino + Python + TTS 자동 음성 알림 프로그램 분석 집이나 사무실에서“지금 온도 몇 도지?”,“밖이 많이 추울까?”같은 정보를 말로 알려주는...</p>
<p>게시물 <a href="https://howinfo.kr/orange-pi%eb%a1%9c-%eb%a7%8c%eb%93%9c%eb%8a%94-%ec%8b%a4%eb%82%b4%c2%b7%ec%8b%a4%ec%99%b8-%ed%99%98%ea%b2%bd-%ec%9d%8c%ec%84%b1-%ec%95%88%eb%82%b4-%ec%8b%9c%ec%8a%a4%ed%85%9c/">Orange Pi로 만드는 실내·실외 환경 음성 안내 시스템</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">Arduino + Python + TTS 자동 음성 알림 프로그램 분석</h3>



<p>집이나 사무실에서<br>“지금 온도 몇 도지?”,<br>“밖이 많이 추울까?”<br>같은 정보를 <strong>말로 알려주는 시스템</strong>이 있으면 꽤 유용합니다.</p>



<p>이번 글에서는<br>Orange Pi와 Arduino를 연동해<br><strong>실내·실외 온도/습도 + 날씨 정보를 주기적으로 음성으로 안내하는<br>Python 프로그램</strong>을 분석해봅니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">프로그램 개요</h2>



<p>이 프로그램은 다음과 같은 역할을 합니다.</p>



<ul class="wp-block-list">
<li>Arduino에서 <strong>실내 온·습도 값 수신</strong></li>



<li>OpenWeather API를 이용해 <strong>실외 날씨 정보 조회</strong></li>



<li>현재 시각 + 실내·실외 환경 정보를 <strong>자연스러운 한국어 문장으로 구성</strong></li>



<li>TTS(Text-to-Speech)로 음성 파일 생성</li>



<li>Orange Pi 스피커로 <strong>30분마다 자동 음성 안내</strong></li>
</ul>



<p>즉,<br>👉 <strong>완전 자동 환경 음성 알림 시스템</strong>입니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">전체 동작 흐름</h2>



<p>프로그램의 동작 구조를 간단히 정리하면 아래 순서입니다.</p>



<p>1️⃣ 시리얼 포트(<code>/dev/ttyUSB0</code>)로 Arduino 센서 값 수신<br>2️⃣ 실외 위치(김포시 기준) 날씨 API 호출<br>3️⃣ 현재 시각 포함한 안내 멘트 생성<br>4️⃣ TTS로 음성 파일 생성<br>5️⃣ ALSA(<code>aplay</code>)를 이용해 스피커 출력<br>6️⃣ 30분 대기 후 반복</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">1. Arduino 연동 (실내 환경 수집)</h2>



<p>프로그램은 Arduino와 시리얼 통신을 통해<br><strong>실내 온도와 습도</strong>를 받아옵니다.</p>



<ul class="wp-block-list">
<li>통신 속도: <code>9600 bps</code></li>



<li>포트: <code>/dev/ttyUSB0</code></li>



<li>수신 값 예시:
<ul class="wp-block-list">
<li>온도: 22℃</li>



<li>습도: 38%</li>
</ul>
</li>
</ul>



<p>이 방식의 장점은:</p>



<ul class="wp-block-list">
<li>센서 교체가 쉬움</li>



<li>Orange Pi 부하 최소화</li>



<li>확장성 좋음 (조도, 미세먼지 등 추가 가능)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">2. 실외 날씨 정보 수집</h2>



<p>실외 정보는 <strong>OpenWeather API</strong>를 사용합니다.</p>



<p>수집 항목:</p>



<ul class="wp-block-list">
<li>현재 기온</li>



<li>습도</li>



<li>날씨 상태 (맑음, 흐림 등)</li>
</ul>



<p>프로그램 내부에서는<br>좌표 기반으로 김포시 날씨를 조회하고,<br>음성 안내 시에는 **“김포 바깥 기온”**처럼 사람이 듣기 쉬운 표현으로 변환합니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">3. 음성 멘트 생성 로직 (이 프로그램의 핵심)</h2>



<p>이 프로그램의 가장 좋은 점은<br><strong>단순한 값 나열이 아니라, 말하듯이 안내한다는 점</strong>입니다.</p>



<p>예시 멘트 구조:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>지금 시각은 20시 32분이에요.<br>김포 바깥 기온은 영하 8도, 습도는 49퍼센트 정도고 날씨는 맑아요.<br>실내는 온도 22도, 습도 38퍼센트 정도예요.<br>실내가 실외보다 약 30도 정도 더 따뜻해요.<br>갑자기 밖으로 나가실 땐 온도 차이에만 조금 주의해 주세요.</p>
</blockquote>



<p>단순 수치가 아니라<br>👉 <strong>상황에 맞는 안내 멘트</strong>가 자동으로 만들어집니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">4. TTS(Text-to-Speech) 처리 방식</h2>



<p>생성된 문장은 TTS 엔진을 통해 음성 파일로 변환됩니다.</p>



<p>특징:</p>



<ul class="wp-block-list">
<li>임시 파일 사용 (wav)</li>



<li>단일 채널(monoral) 출력</li>



<li>안정적인 샘플레이트로 변환 후 재생</li>
</ul>



<p>덕분에:</p>



<ul class="wp-block-list">
<li>음성 끊김 없음</li>



<li>SBC 환경에서도 안정적</li>



<li>다른 음성 엔진으로 교체도 쉬움</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">5. ALSA 기반 오디오 출력</h2>



<p>Orange Pi는 리눅스 기반이기 때문에<br>ALSA(<code>aplay</code>)를 이용해 음성을 재생합니다.</p>



<p>중요 포인트:</p>



<ul class="wp-block-list">
<li><strong>출력 가능한 오디오 장치 선택 필수</strong></li>



<li>마이크 전용 USB 오디오 사용 시 오류 발생 가능</li>



<li>실제 출력 장치(온보드 코덱)를 지정해야 정상 동작</li>
</ul>



<p>이 부분을 잘 설정하면<br>부팅 후 무인 상태에서도 안정적으로 음성 안내가 가능합니다.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">6. 주기적 실행 구조</h2>



<p>이 프로그램은 <strong>30분 간격</strong>으로 동작합니다.</p>



<ul class="wp-block-list">
<li>무한 루프 구조</li>



<li>음성 안내 후 sleep</li>



<li>별도 입력 없이 자동 반복</li>
</ul>



<p>그래서:</p>



<ul class="wp-block-list">
<li>시계처럼 사용 가능</li>



<li>아침/저녁 환경 체크용으로 적합</li>



<li>systemd 서비스로 등록하면 상시 운영 가능</li>
</ul>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="664" height="188" src="https://howinfo.kr/wp-content/uploads/2026/02/image-1.png" alt="" class="wp-image-1438" srcset="https://howinfo.kr/wp-content/uploads/2026/02/image-1.png 664w, https://howinfo.kr/wp-content/uploads/2026/02/image-1-300x85.png 300w" sizes="(max-width: 664px) 100vw, 664px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">활용 아이디어</h2>



<p>이 프로그램은 기본 구조가 탄탄해서 확장이 쉽습니다.</p>



<p>활용 예:</p>



<ul class="wp-block-list">
<li>❄️ 겨울철 외출 전 체감 온도 안내</li>



<li>🏠 집안 환경 자동 브리핑</li>



<li>👵 부모님 댁 음성 안내 시스템</li>



<li>🏢 사무실 환경 알림</li>



<li>🔔 특정 조건(급격한 온도 차)일 때만 안내</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">마무리</h2>



<p><code>indoor_outdoor_tts_plughw_adv_v1.py</code>는<br>단순한 TTS 예제가 아니라,</p>



<p>👉 <strong>센서 + API + 음성 안내를 결합한 실사용형 자동화 프로그램</strong>입니다.</p>



<p>Orange Pi, Arduino, 리눅스 환경에서<br>음성 기반 안내 시스템을 만들고 싶다면<br>아주 좋은 출발점이 되는 구조라고 볼 수 있습니다.</p>



<pre class="wp-block-code"><code>파일이 첨부가 안되어 아래 full 소스 제공한다 . 

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import asyncio
import os
import re
import time
import tempfile
import threading
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional, Tuple

import requests
import serial
import edge_tts

# =========================
# 사용자 설정
# =========================
CITY_NAME = "Gimpo"
COUNTRY_CODE = "KR"
TIMEZONE_NAME = "Asia/Seoul"

# 화면에/멘트에 쓸 도시 이름(한국어 표기)
DISPLAY_CITY_NAME = "김포"

# Arduino (실내 DHT11) 시리얼
SERIAL_PORT = "/dev/ttyUSB0"
SERIAL_BAUD = 9600

# 오디오 출력 장치: plughw 사용(자동 변환) -> aplay 에러 방지
AUDIO_DEVICE = "plughw:4,0"

# 30분마다 말하기
SPEAK_EVERY_MIN = 30
# 실내값 오래됨 처리(초)
INDOOR_STALE_SEC = 5 * 60
# 시작 직후 실내값 기다리기(초)
INDOOR_WAIT_ON_START_SEC = 6

# Edge TTS 보이스
VOICE = "ko-KR-SunHiNeural"
VOLUME = "+0%"
# 말 속도를 살짝 느리게 해서 자연스럽게
RATE = "-15%"

# =========================
# 권고 기준(원하면 숫자만 바꾸면 됨)
# =========================
# 실내 습도 기준(환기 권고)
VENTILATE_HUMIDITY_HIGH = 60   # % 이상
VERY_HUMID = 70                # % 이상이면 강하게

# 실내 건조(가습 권고)
DRY_HUMIDITY_LOW = 35          # % 이하

# 온도차 멘트 기준
TEMP_DIFF_NOTICE = 3.0         # 3도 이상 차이 나면 언급
TEMP_DIFF_STRONG = 7.0         # 7도 이상이면 강하게

# =========================
# 오디오 재생(강제 출력)
# =========================
PLAYER_CMD = f'aplay -D {AUDIO_DEVICE} -q "{{file}}"'

# =========================
# 실내(아두이노) 파서
# RAW 예: '습도: 38.70 %  |  온도: 25.90 °C'
# =========================
RE_INDOOR = re.compile(
    r"습도\s*:\s*(\d+(?:\.\d+)?)\s*%.*?온도\s*:\s*(-?\d+(?:\.\d+)?)\s*°?\s*C",
    re.IGNORECASE
)

@dataclass
class IndoorReading:
    t: float
    h: float
    ts: float  # epoch seconds

class IndoorSerialReader:
    """백그라운드에서 Arduino 시리얼을 계속 읽어 최신 실내 온습도 유지"""
    def __init__(self, port: str, baud: int):
        self.port = port
        self.baud = baud
        self._latest: Optional&#91;IndoorReading] = None
        self._lock = threading.Lock()
        self._stop = threading.Event()
        self._thread: Optional&#91;threading.Thread] = None

    def start(self) -> None:
        self._thread = threading.Thread(target=self._run, daemon=True)
        self._thread.start()

    def latest(self) -> Optional&#91;IndoorReading]:
        with self._lock:
            return self._latest

    def _set_latest(self, t: float, h: float) -> None:
        with self._lock:
            self._latest = IndoorReading(t=t, h=h, ts=time.time())

    def _run(self) -> None:
        while not self._stop.is_set():
            try:
                with serial.Serial(self.port, self.baud, timeout=1) as ser:
                    # 포트 열면 보드 리셋될 수 있어 여유
                    time.sleep(2.0)
                    try:
                        ser.reset_input_buffer()
                    except Exception:
                        pass

                    while not self._stop.is_set():
                        raw = ser.readline()
                        if not raw:
                            continue

                        line = raw.decode(errors="ignore").strip()
                        if not line:
                            continue

                        m = RE_INDOOR.search(line)
                        if m:
                            h = float(m.group(1))
                            t = float(m.group(2))
                            self._set_latest(t=t, h=h)
                        # 디버그 필요 시:
                        # else:
                        #     print("&#91;SERIAL RAW]", repr(line))

            except serial.SerialException as e:
                print(f"&#91;SERIAL] {type(e).__name__}: {e}")
                time.sleep(2.0)
            except Exception as e:
                print(f"&#91;SERIAL] Unexpected {type(e).__name__}: {e}")
                time.sleep(2.0)

# =========================
# 실외(Open-Meteo) 현재값
# =========================
def geocode_city(name: str, country: str) -> Tuple&#91;float, float, str]:
    url = "https://geocoding-api.open-meteo.com/v1/search"
    params = {"name": name, "count": 5, "language": "en", "format": "json"}
    r = requests.get(url, params=params, timeout=10)
    r.raise_for_status()
    data = r.json()
    results = data.get("results") or &#91;]

    for it in results:
        if (it.get("country_code") or "").upper() == country.upper():
            return float(it&#91;"latitude"]), float(it&#91;"longitude"]), it.get("name", name)

    if results:
        it = results&#91;0]
        return float(it&#91;"latitude"]), float(it&#91;"longitude"]), it.get("name", name)

    raise RuntimeError(f"Geocoding failed for {name}")

def weathercode_to_korean(code: Optional&#91;int]) -> str:
    mapping = {
        0: "맑음",
        1: "대체로 맑음",
        2: "부분적으로 흐림",
        3: "흐림",
        45: "안개",
        48: "착빙 안개",
        51: "이슬비(약함)",
        53: "이슬비(보통)",
        55: "이슬비(강함)",
        61: "비(약함)",
        63: "비(보통)",
        65: "비(강함)",
        71: "눈(약함)",
        73: "눈(보통)",
        75: "눈(강함)",
        80: "소나기(약함)",
        81: "소나기(보통)",
        82: "소나기(강함)",
        95: "뇌우",
    }
    return mapping.get(code, "날씨 정보") if code is not None else "날씨 정보"

def fetch_outdoor_current(lat: float, lon: float) -> Tuple&#91;Optional&#91;float], Optional&#91;float], Optional&#91;int]]:
    """Open-Meteo current: temperature_2m, relative_humidity_2m, weather_code"""
    url = "https://api.open-meteo.com/v1/forecast"
    params = {
        "latitude": lat,
        "longitude": lon,
        "current": "temperature_2m,relative_humidity_2m,weather_code",
        "timezone": TIMEZONE_NAME,
    }
    r = requests.get(url, params=params, timeout=10)
    r.raise_for_status()
    data = r.json()

    cur = data.get("current") or {}
    temp = cur.get("temperature_2m")
    hum = cur.get("relative_humidity_2m")
    wcode = cur.get("weather_code")

    try:
        temp = float(temp) if temp is not None else None
    except Exception:
        temp = None
    try:
        hum = float(hum) if hum is not None else None
    except Exception:
        hum = None
    try:
        wcode = int(wcode) if wcode is not None else None
    except Exception:
        wcode = None

    return temp, hum, wcode

# =========================
# TTS: mp3 -> wav(PCM) -> aplay
# =========================
async def speak(text: str) -> None:
    with tempfile.TemporaryDirectory() as td:
        mp3_path = os.path.join(td, "tts.mp3")
        wav_path = os.path.join(td, "tts.wav")

        communicate = edge_tts.Communicate(text, VOICE, rate=RATE, volume=VOLUME)
        await communicate.save(mp3_path)

        # 호환성 위해 WAV를 모노/48k/16bit PCM으로 변환
        cmd = f'ffmpeg -y -loglevel error -i "{mp3_path}" -ac 1 -ar 48000 -sample_fmt s16 "{wav_path}"'
        ret = os.system(cmd)
        if ret != 0:
            print("&#91;AUDIO] ffmpeg convert failed")
            return

        os.system(PLAYER_CMD.format(file=wav_path))

# =========================
# 권고 멘트
# =========================
def temp_diff_advice(indoor_t: float, outdoor_t: Optional&#91;float]) -> Optional&#91;str]:
    if outdoor_t is None:
        return None

    diff = indoor_t - outdoor_t
    adiff = abs(diff)

    if adiff &lt; TEMP_DIFF_NOTICE:
        return None

    if diff > 0:
        # 실내가 더 따뜻
        if adiff >= TEMP_DIFF_STRONG:
            return (
                f"실내가 실외보다 약 {adiff:.0f}도 정도 더 따뜻해요. "
                f"갑자기 밖으로 나가실 땐 온도 차이에만 조금 주의해 주세요."
            )
        return f"실내가 실외보다 {adiff:.0f}도 정도 더 따뜻한 편이에요."
    else:
        # 실내가 더 차가움
        if adiff >= TEMP_DIFF_STRONG:
            return (
                f"실내가 실외보다 약 {adiff:.0f}도 정도 더 서늘해요. "
                f"외부에서 들어오실 때 체감 온도 차이가 클 수 있어요."
            )
        return f"실내가 실외보다 {adiff:.0f}도 정도 더 낮은 편이에요."

def humidity_advice(indoor_h: float) -> Optional&#91;str]:
    # 습함 -> 환기 권고
    if indoor_h >= VERY_HUMID:
        return (
            "실내 습도가 꽤 높아요. 결로나 곰팡이를 예방하려면 "
            "잠깐 창문을 열고 환기해 주시면 좋겠어요."
        )
    if indoor_h >= VENTILATE_HUMIDITY_HIGH:
        return (
            "실내가 조금 습한 편이에요. 잠깐이라도 환기해 주시면 "
            "더 쾌적해질 것 같아요."
        )

    # 건조 -> 가습 권고
    if indoor_h &lt;= DRY_HUMIDITY_LOW:
        return (
            "실내가 조금 건조한 편이에요. 가습기나 물컵을 두면 "
            "도움이 될 수 있어요."
        )

    return None

# =========================
# 멘트 생성
# =========================
def build_message(outdoor_name: str,
                  out_t: Optional&#91;float],
                  out_h: Optional&#91;float],
                  out_desc: str,
                  indoor: Optional&#91;IndoorReading]) -> str:
    now = datetime.now()
    now_str = now.strftime("%H시 %M분")
    parts = &#91;f"지금 시각은 {now_str}이에요."]

    # 실외
    if out_t is None or out_h is None:
        parts.append(f"{outdoor_name} 바깥 온습도 정보를 지금은 가져오지 못했어요.")
    else:
        parts.append(
            f"{outdoor_name} 바깥 기온은 {out_t:.0f}도, 습도는 {out_h:.0f}퍼센트 정도고, "
            f"날씨는 {out_desc}이에요."
        )

    # 실내
    if indoor is None:
        parts.append("아직 실내 센서 값이 들어오지 않아서, 실내 온습도는 안내해 드리기 어렵네요.")
        return " ".join(parts)

    age = time.time() - indoor.ts
    if age > INDOOR_STALE_SEC:
        parts.append(
            "실내 온습도 값이 조금 오래되어서, 최신 값인지 확신하기가 어려워요."
        )
        return " ".join(parts)

    parts.append(
        f"실내는 온도 {indoor.t:.0f}도, 습도 {indoor.h:.0f}퍼센트 정도예요."
    )

    # 온도차 멘트
    td = temp_diff_advice(indoor.t, out_t)
    if td:
        parts.append(td)

    # 습도 멘트(환기/가습)
    ha = humidity_advice(indoor.h)
    if ha:
        parts.append(ha)

    return " ".join(parts)

# =========================
# 스케줄/대기
# =========================
def sleep_until_next_boundary(minutes_step: int) -> None:
    """00/30 분에 맞춰 다음 실행까지 sleep"""
    now = datetime.now()
    next_minute = ((now.minute // minutes_step) + 1) * minutes_step

    if next_minute >= 60:
        target = (now.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1))
        target = target.replace(minute=next_minute - 60)
    else:
        target = now.replace(minute=next_minute, second=0, microsecond=0)

    wait = (target - now).total_seconds()
    time.sleep(max(1, wait))

def wait_for_indoor(reader: "IndoorSerialReader", max_wait_sec: int) -> Optional&#91;IndoorReading]:
    end = time.time() + max_wait_sec
    while time.time() &lt; end:
        v = reader.latest()
        if v is not None:
            return v
        time.sleep(0.2)
    return reader.latest()

# =========================
# 메인
# =========================
async def main() -> None:
    lat, lon, resolved_name = geocode_city(CITY_NAME, COUNTRY_CODE)
    # 실외 API는 resolved_name을 써도 되지만, 말할 때는 한국어 도시명 사용
    outdoor_name_for_speech = DISPLAY_CITY_NAME

    print(f"&#91;INFO] Outdoor location: {resolved_name} ({lat:.4f}, {lon:.4f}) -> speech name: {outdoor_name_for_speech}")
    print(f"&#91;INFO] Serial port: {SERIAL_PORT} @ {SERIAL_BAUD}")
    print(f"&#91;INFO] Audio device forced: {AUDIO_DEVICE}")
    print(f"&#91;INFO] Speak every {SPEAK_EVERY_MIN} min")

    reader = IndoorSerialReader(SERIAL_PORT, SERIAL_BAUD)
    reader.start()

    first = True
    while True:
        try:
            out_t, out_h, wcode = fetch_outdoor_current(lat, lon)
            out_desc = weathercode_to_korean(wcode)

            indoor_latest = wait_for_indoor(reader, INDOOR_WAIT_ON_START_SEC) if first else reader.latest()

            msg = build_message(outdoor_name_for_speech, out_t, out_h, out_desc, indoor_latest)
            print("&#91;SAY]", msg)
            await speak(msg)

        except Exception as e:
            print("&#91;ERR]", type(e).__name__, e)
            await speak("온습도 정보를 확인하는 중에 오류가 생겼어요.")

        if first:
            first = False
            time.sleep(SPEAK_EVERY_MIN * 60)
        else:
            sleep_until_next_boundary(SPEAK_EVERY_MIN)

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n&#91;INFO] 종료합니다.")
</code></pre>
<p>게시물 <a href="https://howinfo.kr/orange-pi%eb%a1%9c-%eb%a7%8c%eb%93%9c%eb%8a%94-%ec%8b%a4%eb%82%b4%c2%b7%ec%8b%a4%ec%99%b8-%ed%99%98%ea%b2%bd-%ec%9d%8c%ec%84%b1-%ec%95%88%eb%82%b4-%ec%8b%9c%ec%8a%a4%ed%85%9c/">Orange Pi로 만드는 실내·실외 환경 음성 안내 시스템</a>이 <a href="https://howinfo.kr">하우인포-IT·테크</a>에 처음 등장했습니다.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://howinfo.kr/orange-pi%eb%a1%9c-%eb%a7%8c%eb%93%9c%eb%8a%94-%ec%8b%a4%eb%82%b4%c2%b7%ec%8b%a4%ec%99%b8-%ed%99%98%ea%b2%bd-%ec%9d%8c%ec%84%b1-%ec%95%88%eb%82%b4-%ec%8b%9c%ec%8a%a4%ed%85%9c/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
