2026년 10월 기준으로 진행한 작업 기록입니다.
LG webOS 펌웨어나 유튜브 내부 인터페이스 변경에 따라 이후에 참고하시는 분께는 일부 동작이 다르게 작동할 수 있습니다.
들어가며
요새는 매일 아침 일어나면 유튜브에서 스트레칭 영상을 틀고 따라하고 있습니다. 근데 아침일수록 일어나기 싫고 뒹굴거리려는 마음이 커서... 잘 안하고 넘기는 날이 많아지더라구요.
마침 최근에 남는 클로드 토큰으로 개인 일정과 집 안의 기기 상태를 한 곳에서 관리하는 로컬 대시보드 앱을 만들어서 사용하고 있었는데, 아침마다 자동으로 LG 스탠바이미 2를 켜고 유튜브를 트는 과정까지 자동화해보면 어떨까 하는 생각이 들었습니다.

처음 목표는 꽤 단순했습니다.
- 지정해 둔 아침 시간에 알아서 TV를 켜고 유튜브를 실행하기
- 대시보드 웹 화면에서 전원 켜기/끄기, 볼륨 조절하기
- 가능하다면 유튜브 영상 중간에 나오는 광고의 '건너뛰기' 버튼까지 자동으로 눌러주기
금방 끝날 줄 알았던 작업이었는데, 막상 시작해보니 최신 webOS 펌웨어의 인증서 차단 이슈부터 부팅할 때마다 바뀌는 포트 번호나 기타 네트워크 이슈들이 많더라구요. 분명 어떻게 어떻게 하면 될 것 같은데... 명확한 가이드가 없어서 꽤 긴 삽질로 이어졌습니다. 다행히 결국에는 성공해서, PC에서 스탠바이미 2를 제어하기 위해 겪었던 문제들과 그 해결 과정을 정리해 보겠습니다.
먼저 보는 요약 (TL;DR)
결론만 간단히 정리해 두면,
- TV 켜기(Wake-on-LAN): 처음부터 잘 동작했지만, 오래 꺼져 있던 상태에서는 패킷 한 번으로 깨어나지 않는 문제가 있어 10초 간격으로 최대 90초간 반복 전송하도록 처리했습니다.
- TV 끄기·볼륨·앱 실행(SSAP): 하루 종일 연결 키(
client-key) 발급이 거절되었는데, 원인은 등록 요청(Manifest)의 구성이었습니다. 서명(signed) 항목을 아예 빼고, 36개의 전체 권한 목록을 담아서 요청하니 단 한 번에 키가 발급되었습니다. - 유튜브 광고 건너뛰기: DIAL 프로토콜에서 얻은
screenId로 유튜브 Lounge API에 연결해, 광고 건너뛰기가 활성화되는 시점에 자동으로 스킵 명령을 보내도록 구현했습니다.
Wake-on-LAN으로 TV 깨우기
꺼져 있는 TV는 네트워크 포트가 열려 있지 않기 때문에, 네트워크 카드로 매직 패킷을 보내 깨우는 Wake-on-LAN(WoL)을 사용해야 합니다. 공유기 브로드캐스트 주소(192.168.0.255)로 패킷을 보내는 방식인데, TV 설정에서 'Wi-Fi로 켜기' 옵션이 켜져 있어야 하고 PC와 TV가 같은 공유기 대역 아래에 있어야 합니다.
저는 이걸 모르고 모뎀에 직접 연결한 PC에서 iptime 공유기의 wifi에 연결된 TV를 찾다 안 잡혀서 삽질했습니다. 클로드한테 같은 네트워크 맞다고 고집 부렸는데 역시 나보다 코딩 잘하는 AI 말은 들어봐야 합니다.
아무튼 세팅을 마치고, 처음 테스트할 때는 리모컨으로 TV를 끄고 직후에 패킷을 보냈는데, 한 번에 바로 켜졌습니다. 하지만 시간이 지난 뒤에 다시 시도해 보니 다른 문제가 있었습니다.
# TV가 응답할 때까지 10초 간격으로 최대 90초 동안 매직 패킷 전송
waited = 0
while waited < 90:
send_wol(mac, broadcast_ip)
time.sleep(10)
waited += 10
if is_tv_online(tv_ip):
break
리모컨으로 끈 직후에는 패킷 한 번에 켜졌지만, 오랜 시간 꺼져 있었거나 원격 명령으로 끈 뒤에는 패킷 한 번으로 깨어나지 않는 경우가 종종 발생했습니다. LG의 '빠른 시작+' 기능이 오동작하면서 대기 중 첫 신호를 놓치는 것이 아닐까 추측하고 있는데요.
패킷을 한 번만 보내고 끝내는 대신, 위처럼 TV 포트가 열릴 때까지 10초 간격으로 최대 90초 동안 반복해서 보내도록 수정하니 자고 있던 TV도 안정적으로 켜졌습니다.
DIAL 프로토콜로 유튜브 열기
TV를 켰으니 이제 유튜브 앱을 실행할 차례입니다. 크롬캐스트나 스마트폰 유튜브 앱이 TV를 찾을 때 사용하는 DIAL(Discovery And Launch) 프로토콜을 이용하면 별도의 인증 키 없이도 앱을 띄울 수 있습니다.
처음에는 알려진 대로 2018번 포트로 요청을 보내 앱 실행 주소를 받아오고, 해당 주소의 /YouTube로 POST 요청을 보내 유튜브를 열었습니다. 그런데 여기서도 두 가지 함정이 있었습니다.
- 부팅할 때마다 바뀌는 DIAL 포트: 어떤 날은
2018번 포트로 열리더니, 다음 날 다시 켜니 설명 주소는1494번, 앱 주소는36866번으로 바뀌어 있었습니다. 그래서 고정 포트를 찌르는 대신 SSDP(M-SEARCH,ST: urn:dial-multiscreen-org:service:dial:1)로 먼저 주소를 탐색하고, 응답이 없을 때만1494와2018포트를 직접 확인하도록 수정했습니다. - Origin 헤더 필수: DIAL로 유튜브 앱 정보를 조회할 때
Origin: package:com.google.android.youtube헤더가 없으면 가차 없이403에러를 반환합니다.
이렇게 유튜브 앱을 띄우는 것까지는 성공했지만, DIAL 방식만으로는 '누가 시청하시나요?' 프로필 선택 화면에서 멈춰버리는 문제가 있었습니다. 게다가 TV 전원을 끄거나 볼륨을 조절하는 것도 불가능해서, 결국 webOS의 제어 프로토콜인 SSAP 연결 키를 받아야만 했습니다.
SSAP 연결 키 발급받기 (feat. 403 에러와 무응답)
이번 작업에서 가장 많은 시간을 쏟은 구간입니다. webOS TV를 원격으로 제어하려면 wss://<TV_IP>:3001 웹소켓에 연결한 뒤 register 메시지를 보내고, TV 화면에 뜨는 연결 허용 팝업을 승인해 client-key를 받아야 합니다.
하지만 아무리 요청을 보내도 키를 받을 수 없었습니다.
겪었던 시행착오들
- 구형 서명이 포함된 요청 (
403 blacklisted certificate):
기존 오픈소스 예제들에 들어 있던 LG 테스트 서명(signed)을 넣어서 보냈더니, 최신 펌웨어에서403 Pairing rejected: blacklisted certificate detected또는cancelled에러가 발생했습니다. 찾아보니 최근 webOS 펌웨어 업데이트 이후로 기존 공용 인증서가 차단된 상태였습니다(Home Assistant 이슈 #179669). - LG ThinQ 공식 API(Connect) 시도:
로컬 연결이 막혔다면 공식 API로 우회할 수 있지 않을까 싶어서 ThinQ 개인 액세스 토큰(PAT)을 발급받아 기기 목록을 조회해 보았습니다. 아쉽게도 공기청정기 같은 가전만 목록에 나오고, 스탠바이미 TV는 아예 목록에 뜨지 않더군요. (덕분에 계획에 없던 공청기 연결부터 미리 했습니다. 굿) - 서명 없는 최소 권한(4개) 요청:
레딧에 올라온 한 글에 인증서 서명이 문제라는 글을 참고해 서명 블록을 빼고, 꼭 필요한 권한 4개만 넣은 최소 매니페스트를 만들어 보냈습니다. 그랬더니 이번엔 에러조차 없이 완전 무응답(팝업 없음) 상태가 이어졌습니다.
해결: 전체 권한 목록(36개)을 담은 무서명 매니페스트
'이 TV는 외부 페어링을 아예 막아두었나 보다'라고 포기하려던 참에, 현재 활발히 관리되고 있는 aiowebostv 라이브러리의 최신 메인 브랜치 코드(handshake.py)를 열어 제가 보낸 요청과 한 줄씩 비교해 보았습니다.
차이는 권한 목록의 개수였습니다. 서명(signed, signatures) 필드를 아예 넣지 않되, permissions에 36개의 전체 권한 목록을 모두 채워서 보내니 거짓말처럼 단 한 번에 TV 화면에 연결 허용 팝업이 떴습니다.
{
"type": "register",
"id": "register_0",
"payload": {
"forcePairing": false,
"pairingType": "PROMPT",
"manifest": {
"manifestVersion": 1,
"appVersion": "1.1",
"permissions": [
"LAUNCH", "LAUNCH_WEBAPP", "APP_TO_APP", "CLOSE",
"TEST_OPEN", "TEST_PROTECTED", "CONTROL_AUDIO",
"CONTROL_DISPLAY", "CONTROL_INPUT_JOYSTICK",
"CONTROL_INPUT_MEDIA_RECORDING", "CONTROL_INPUT_MEDIA_PLAYBACK",
"CONTROL_INPUT_TV", "CONTROL_POWER", "CONTROL_TV_SCREEN",
"READ_APP_STATUS", "READ_CURRENT_CHANNEL", "READ_INPUT_DEVICE_LIST",
"READ_NETWORK_STATE", "READ_RUNNING_APPS", "READ_TV_CHANNEL_LIST",
"WRITE_NOTIFICATION_TOAST", "READ_POWER_STATE", "READ_COUNTRY_INFO",
"READ_SETTINGS", "CONTROL_TV_STANBY", "CONTROL_FAVORITE_GROUP",
"CONTROL_USER_INFO", "CHECK_BLUETOOTH_DEVICE",
"CONTROL_BLUETOOTH", "CONTROL_TIMER_INFO", "STB_INTERNAL_CONNECTION",
"CONTROL_RECORDING", "READ_RECORDING_STATE", "WRITE_RECORDING_LIST",
"READ_RECORDING_LIST", "READ_RECORDING_SCHEDULE"
]
}
}
}
결국 TV가 키를 안 내준 것이 아니라, 제가 임의로 줄여서 보냈던 4개짜리 최소 권한 요청을 TV가 불완전한 요청으로 보고 무시했던 것이었습니다. 원인이 요청 JSON의 권한 개수였다니 살짝 허탈했습니다.
한 번 발급받은 client-key는 OS keyring에 안전하게 저장해 두고 이후 요청에 함께 넘겨주면, 다음부터는 팝업 없이 바로 연결됩니다. 키를 얻고 나니 아래 기능들이 모두 정상 동작했습니다.
- 전원 끄기:
ssap://system/turnOff - 볼륨 및 음소거 제어:
ssap://audio/getVolume,volumeUp,volumeDown,setVolume,setMute - 토스트 알림 표시:
ssap://system.notifications/createToast - 현재 실행 앱 조회:
ssap://com.webos.applicationManager/getForegroundAppInfo - 유튜브 영상 바로 재생:
ssap://system.launcher/launch(앱 IDyoutube.leanback.v4에params.contentTarget값을 함께 넘기면 프로필 선택 화면을 건너뛰고 곧바로 영상을 재생합니다)
유튜브 광고 자동으로 건너뛰기
원격으로 영상을 바로 틀 수 있게 되고 나니, 영상이 시작할 때 나오는 광고의 '건너뛰기' 버튼도 자동으로 눌러주면 좋겠다는 생각이 들었습니다.
TV용 유튜브 앱은 스마트폰과의 캐스트 연동을 위해 내부적으로 라운지(Lounge) API 통로를 열어두는데요. 앞서 살펴본 DIAL의 유튜브 앱 정보 응답에서 <screenId> 값을 읽어오면, TV 코드를 입력하지 않고도 바로 이 라운지 세션에 붙을 수 있습니다. 파이썬에서는 pyytlounge 라이브러리를 활용했습니다.
async with YtLoungeApi("Akasha") as api:
await api.pair_with_screen_id(screen_id)
await api.connect()
await api.subscribe()
# 광고 이벤트에서 is_skip_enabled가 활성화되면 즉시 skip_ad() 호출
구현 중에 마주했던 작은 문제들은 다음과 같이 우회했습니다.
get_screen_id_from_dial()조합 오류: 라이브러리 내장 함수를 그대로 쓰니 이 TV에서는Application-URL이/apps로 끝나.../appsYouTube처럼 슬래시가 빠진 잘못된 주소로 요청을 보내는 문제가 있었습니다. 그래서 직접<Application-URL>/YouTube에Origin헤더를 붙여 호출하고 XML에서<screenId>를 파싱하도록 처리했습니다.- 컨텍스트 매니저 사용:
YtLoungeApi는 세션 정리를 위해async with구문으로 감싸서 사용해야 안정적으로 동작합니다.
DIAL 주소 탐색을 보강한 뒤에 테스트해 보니, 리모컨에 전혀 손을 대지 않았는데도 건너뛰기 버튼이 활성화되자마자 광고가 넘어가고 대시보드의 스킵 카운터가 올라가는 것을 확인할 수 있었습니다. 건너뛸 수 없는 광고 동안만 잠시 음소거하는 기능도 넣어볼까 싶었는데, 광고가 끝나는 시점 감지가 어긋나면 음소거가 계속 남을 위험이 있어 일단 보류해 두었습니다.
마치며
이렇게 PC에서 LG 스탠바이미 2를 제어하고 유튜브 광고 스킵까지 붙여본 삽질 과정을 정리해 보았습니다.
돌아보면 "TV가 연결 키를 안 내준다"는 결론을 너무 성급하게 내렸던 것 같습니다. 막히는 구간이 생겼을 때 같은 형태의 요청만 반복하기보다, 현재 잘 동작하고 있는 최신 오픈소스(aiowebostv 등)의 실제 페이로드와 한 줄씩 대조해 보는 편이 훨씬 빠른 길이라는 점을 다시금 느꼈습니다.
그 밖에도 작업 중에 날짜가 넘어가면서 고정 날짜를 사용하던 기존 테스트 13개가 한꺼번에 깨지거나(최근 14일만 수집하는 로직과 충돌), 헤드리스 크롬으로 모바일 UI 스크린샷을 찍어 확인할 때 가로 폭을 500px 미만으로 주면 레이아웃이 잘리는 등 사소하지만 기억해 둘 만한 팁들도 함께 건질 수 있었네요.
비슷하게 LG webOS TV나 스탠바이미를 홈 서버·스크립트에 연동하려다 403 에러나 무응답으로 막혀 계신 분들이 있다면, 매니페스트 구성을 점검해 보셔서 소중한 시간을 아끼셨으면 좋겠습니다. 적당한 아티클이 없어서 클로드가 못 찾은것도 있는것 같아, 혹시 비슷한걸 시도하시는 분들이 계시면 조금이나마 도움이 되셨으면 합니다.

참고 자료
'개발 기록' 카테고리의 다른 글
| 좌충우돌 블로그 Next.js로 마이그레이션하기 (0) | 2023.07.28 |
|---|---|
| 썸네일 생성기를 리뉴얼 했어요 (2) | 2022.12.12 |
| 모임 서비스를 개발하며 (모두 모여라 개발기) (0) | 2022.10.23 |
| 바닥부터 만드는 블로그 개발기 (0) | 2022.10.02 |
| 썸네일 생성기를 만들었어요 (Thumbnail generator) (0) | 2021.07.10 |
댓글