이 글은 Today I Learn 시리즈의 213번째 기록입니다. (총 213개)

KeyBloom에서 뽑은 영상이 편집툴에서 한 장 깨지는 걸 이틀에 걸쳐 쫓아 고쳤고, 그 수정을 새 건반과 함께 0.1.8로 냈다.

편집툴에서만 한 장이 깨졌다

파일을 열어 보니 멀쩡했다

추출한 영상을 편집툴에 올렸더니 한 장면이 이상했다. 건반이 통째로 사라지고 물결만 떠 있었다. 두 번 뽑아도 같은 자리였다.

먼저 우리가 그린 그림이 틀렸는지를 봤다. ffmpeg(영상을 읽고 변환하는 명령줄 도구)로 그 자리 앞뒤 프레임을 한 장씩 뽑아 보고, 영상 전체에서 앞뒤와 동떨어진 장면이 있는지도 훑었다. 전부 정상이었다.

앞선 TIL 30에서 느린 이유를 재서 갈랐던 것과 같은 결이다. 이상한 게 보이면 고칠 곳을 짐작하기 전에 “어느 단계까지는 멀쩡한가”를 먼저 확인한다. 파일이 멀쩡하면 그림을 그린 KeyBloom이 아니라 파일을 읽는 쪽을 봐야 한다.


편집툴이 말한 번호는 파일의 번호가 아니었다

편집툴 화면에는 371번 프레임이라고 나와 있었는데, 파일의 371번을 뽑아 보니 멀쩡했다. 편집툴 타임라인 앞쪽에 빈 구간을 두고 영상을 얹어 놔서 번호가 그만큼 밀려 보인 것이었다. 파일로는 239번이었다.

편집툴의 프레임 번호는 타임라인 기준이고 파일 안의 번호와 다를 수 있다. 문제 위치를 넘길 때는 편집툴 번호가 아니라 파일 기준 시각이나 번호로 넘겨야 같은 장면을 본다.


깨진 모양이 원인을 가리켰다

239번은 4초(240번) 바로 앞이다. 영상 압축은 몇 초마다 한 장을 통째로 저장하고(키프레임), 그 사이 프레임은 앞 장면과 달라진 부분만 저장한다. 이 앱은 4초마다 키프레임을 넣는다.

달라진 부분만 담긴 프레임을 기준 없이 풀면 가만히 있던 건반은 빠지고 움직인 물결만 남는다. 편집툴에서 본 모습이 정확히 그랬다. 그러니 그림이 틀린 게 아니라, 편집툴이 키프레임 경계에서 기준을 놓친 것이다.

깨진 모양은 그냥 “깨졌다”가 아니라 단서다. 무엇이 사라지고 무엇이 남았는지를 보면 어느 단계에서 틀렸는지가 드러난다.


한 가지씩만 바꾼 파일로 갈랐다

원인 후보가 여럿이었다. 그래서 딱 한 가지씩만 다른 파일을 만들어 편집툴에서 239번을 하나씩 확인했다.

  • A 같은 데이터를 MP4로 다시 포장만 → 깨짐
  • B 다른 인코더(영상을 압축하는 프로그램)로 다시 압축 → 정상
  • C 멀쩡한 B를 앱과 같은 AMD 하드웨어 인코더로 다시 압축 → 깨짐
  • D A의 데이터는 한 바이트도 안 바꾸고 파일 머리 정보만 통일 → 정상
  • E AMD 인코더에 B프레임(앞뒤 프레임을 둘 다 참고하는 프레임)을 켬 → 정상

A와 B로 “앱의 인코더가 원인”까지 좁혔고, D 하나가 결정적이었다. 데이터는 그대로인데 머리 정보만 맞췄더니 멀쩡해졌다.

D에서 맞춘 「머리 정보」가 SPS다. SPS(Sequence Parameter Set)는 H.264(지금 가장 널리 쓰이는 영상 압축 방식) 영상 전체에 걸리는 설정 묶음으로, 디코더(압축된 영상을 풀어 그림으로 되돌리는 쪽)가 첫 프레임을 풀기 전에 알아야 하는 값이 들어 있다. 해상도, 프로필(어떤 압축 기법까지 썼는지의 등급), 참고할 수 있는 이전 프레임 개수, 초당 몇 장인지 같은 것들이다. 이게 없으면 프레임 데이터를 받아도 어떻게 풀지 모른다.

SPS는 보통 두 군데에 들어간다. 하나는 MP4 파일 맨 앞 머리 부분이다. 편집툴이나 플레이어는 파일을 열자마자 여기를 읽고 디코더를 준비한다. 다른 하나는 영상 데이터 안, 키프레임 앞이다. 방송처럼 중간부터 받아 보는 경우에도 풀 수 있게 스트림 안에 한 번 더 넣는 것이다. 원래 둘은 같은 내용이어야 한다.

AMD 인코더가 만든 파일은 둘이 달랐다. 영상 안 SPS에는 초당 프레임 수 정보가 있는데, 파일 머리 SPS에는 그게 빠져 있었다. 게다가 머리 쪽은 규격 검사 도구가 값이 맞지 않는다고 걸 정도로 어긋나 있었다. 편집툴은 파일 머리의 설정으로 디코더를 준비했다가 영상 안에서 다른 설정을 만나고, 그 사이 어딘가에서 앞 프레임을 기준으로 잡지 못한다. 편집툴 내부라 정확히 어떤 순서로 꼬이는지는 밖에서 알 수 없다. 다만 머리만 영상 안 SPS와 똑같이 맞춘 D가 멀쩡했으니, 두 SPS가 다르다는 것이 원인이다.

여러 개를 한꺼번에 바꾼 파일은 고쳐져도 무엇 때문인지 모른다. 편집툴처럼 내가 직접 열어 봐야 하는 확인일수록 파일 하나에 바뀐 것 하나만 두는 게 확인 횟수를 줄인다.


둘 다 통했는데 하나만 골랐다

D(머리 정보 정리)와 E(B프레임 켜기)가 둘 다 멀쩡했다. D를 골랐다.

E는 이 PC의 AMD 칩에서는 됐지만, B프레임 지원이 칩마다 달라 다른 사람 PC에서는 실패할 수 있다. D는 어떤 인코더가 만든 파일이든 머리만 정리하니 기기를 가리지 않는다. 화질도 그대로고 추출 끝에 몇 초가 더 걸릴 뿐이다.

내 PC에서 통한 방법이 사용자 PC에서도 통한다는 보장은 없다. 해결책이 여럿이면 “어디서나 같은 결과가 나오는 쪽”을 먼저 본다.


0.1.8을 내며

미뤘던 건반이 같이 나갈 이유가 생겼다

건반에 입체감을 넣은 건 열흘 전인데 출시는 미뤘었다. 건반 모양 하나로 업데이트 알림을 띄우기엔 값어치가 약하다고 봤기 때문이다.

이번 수정이 붙으면서 사정이 달라졌다. 영상 편집툴에서 깨지는 건 KeyBloom으로 영상을 만드는 사람에게 직접 걸리는 문제고, KeyBloom을 사는 사람이 바로 그들이다. 그래서 둘을 묶어 0.1.8로 냈다.

혼자서는 약한 변경도 버리지 않고 기다리게 두면, 사용자에게 필요한 수정이 생겼을 때 같이 태워 보낼 수 있다. 업데이트 한 번의 값어치는 묶음으로 따진다.


고친 것을 너무 자세히 적었다

릴리스 노트 초안의 「고친 것」에는 “앱에서 내보낸 MP4를 일부 편집 프로그램에서 열면 4초 부근의 한 프레임이 깨져 보이던 문제”라고 적혀 있었다. 너무 디테일하다고 했다. “앱에서 내보낸 영상이 일부 편집 프로그램에서 깨져 보이던 문제”로 줄였다.

사용자가 알아야 하는 건 “내가 겪던 그 문제가 고쳐졌나”다. MP4, 4초, 한 프레임은 원인을 쫓은 사람에게나 의미 있는 말이다. 사용자용 노트에는 증상을 사용자가 부르는 말로 한 줄만 쓰고, 원인과 과정은 프로젝트 기록에 남긴다.


사본은 원본을 따라오지 않았다

출시하고 나서 로그스톤 샵이 짚었다. 제품 페이지에서 움직이는 데모 그림의 건반이 옛날 사각형 모양이라는 것이다. 그 데모는 앱 화면을 가져다 쓰는 게 아니라 앱의 그리기 코드를 샵이 옮겨 둔 사본이라, 앱을 바꿔도 따라가지 않는다.

같은 일이 파동 모션을 넣을 때도 있었다(앞선 TIL 31의 「말로 넘긴 사양」 — 샵이 데모를 자기 렌더러로 따로 그린다는 걸 그때 알았다). 그때 “모션을 바꾸면 데모 값도 같이 보낸다”고 정했는데, 규칙을 모션에만 걸어 두는 바람에 건반을 바꾼 이번엔 빠졌다.

사본이 있다는 걸 알면서도 빠뜨리는 건, 규칙을 그때 사건의 모양대로 좁게 적었기 때문이다. 그래서 규칙을 넓혔다. 화면에 그려지는 것(건반이든 파티클이든)을 바꾼 릴리스는 체크리스트에 “샵 데모 코드 전달”까지 넣는다.


요약

  • 이상한 게 보이면 “어느 단계까지는 멀쩡한가”부터 확인한다. 파일이 멀쩡하면 만든 쪽이 아니라 읽는 쪽을 본다
  • 편집툴의 프레임 번호는 타임라인 기준이라 파일 번호와 다를 수 있다. 위치는 파일 기준으로 넘긴다
  • 깨진 모양은 단서다. 무엇이 사라지고 무엇이 남았는지가 어느 단계에서 틀렸는지를 말해 준다
  • 비교 파일에는 바뀐 것을 하나만 둔다. 여러 개를 한꺼번에 바꾸면 고쳐져도 이유를 모른다
  • 해결책이 여럿이면 내 PC가 아니라 어디서나 같은 결과가 나오는 쪽을 고른다
  • 혼자서는 약한 변경도 기다리게 두면 필요한 수정과 함께 보낼 수 있다. 업데이트의 값어치는 묶음으로 따진다
  • 사용자용 노트에는 증상만 사용자가 부르는 말로 쓰고, 원인은 프로젝트 기록에 둔다
  • 사본은 원본을 따라오지 않는다. 사본 규칙을 사건 모양대로 좁게 적으면 다음 사건에서 또 빠진다

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 습관만들기 Pawbit TIL 1 27 insertOnConflictUpdate vs DoUpdate(target) 28 세션 필터 29 아코디언(Accordiaon) UI를 펼친상태로 만들기 30 input의 step 31 습관만들기 Pawbit TIL 2 32 습관만들기 Pawbit TIL 3 33 Word Cloud 34 Google Sheets를 데이터 버스로(with AppSheet) 35 Django 모델 텍스트 필드 자동 수집 패턴 36 localStorage로 섹션 토글 상태 유지 37 순차 ID 생성(`select_for_update()` + `max()` 조합) 38 역참조 검색과 distinct() 39 xlsx 다운로드와 로딩 오버레이 충돌 40 Android 파일 공유 MIME 타입 41 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 42 크레센도 Phaser + TypeScript TIL 1 43 크레센도 Phaser + TypeScript TIL 2 44 크레센도 Phaser + TypeScript TIL 3 45 크레센도 Phaser + TypeScript TIL 4 46 크레센도 Phaser + TypeScript TIL 5 47 여둘까 Office Layout TIL 1 48 습관만들기 Pawbit TIL 4 49 여둘까 Office Layout TIL 2 50 여둘까 Office Layout TIL 3 51 크레센도 Phaser + TypeScript TIL 6 52 UTF-8 BOM과 PowerShell 파일 쓰기 53 소리꽃 KeyBloom TIL 1 54 습관만들기 Pawbit TIL 5 55 여둘까 Office Layout TIL 4 56 소리꽃 KeyBloom TIL 2 57 소리꽃 KeyBloom TIL 3 58 메트로놈 Simple Metronome TIL 1 59 소리꽃 KeyBloom TIL 4 60 메트로놈 Simple Metronome TIL 2 61 소리꽃 KeyBloom TIL 5 62 습관만들기 Pawbit TIL 6 63 크레센도 Phaser + TypeScript TIL 7 64 메트로놈 Simple Metronome TIL 3 65 소설 타임라인 관리 도구 Lumior TIL 1 66 습관만들기 Pawbit TIL 7 67 정적 블로그 SEO 정비와 Pagefind 검색 도입 68 크레센도 Phaser + TypeScript TIL 8 69 메트로놈 Simple Metronome TIL 4 70 습관만들기 Pawbit TIL 8 71 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 72 메트로놈 Simple Metronome TIL 5 73 모래게임 Sandrop TIL 1 74 습관만들기 Pawbit TIL 9 75 메트로놈 Simple Metronome TIL 6 76 모래게임 Sandrop TIL 2 77 모래게임 Sandrop TIL 3 78 습관만들기 Pawbit TIL 10 79 습관만들기 Pawbit TIL 11 80 메트로놈 Simple Metronome TIL 7 81 모래게임 Sandrop TIL 5 82 모래게임 Sandrop TIL 4 83 습관만들기 Pawbit TIL 12 84 모래게임 Sandrop TIL 6 85 소리꽃 KeyBloom TIL 6 86 모래게임 Sandrop TIL 7 87 모래게임 Sandrop TIL 8 88 소리꽃 KeyBloom TIL 7 89 모래게임 Sandrop TIL 9 90 소리꽃 KeyBloom TIL 8 91 모래게임 Sandrop TIL 10 92 소리꽃 KeyBloom TIL 9 93 콘티온 ContiOn TIL 1 94 소리꽃 KeyBloom TIL 10 95 콘티온 ContiOn TIL 2 96 습관만들기 Pawbit TIL 13 97 콘티온 ContiOn TIL 3 98 모래게임 Sandrop TIL 11 99 소리꽃 KeyBloom TIL 11 100 여둘까 Office Layout TIL 5 101 모래게임 Sandrop TIL 12 102 모래게임 Sandrop TIL 13 103 콘티온 ContiOn TIL 4 104 모래게임 Sandrop TIL 14 105 소리꽃 KeyBloom TIL 12 106 습관만들기 Pawbit TIL 14 107 모래게임 Sandrop TIL 15 108 모래게임 Sandrop TIL 16 109 소리꽃 KeyBloom TIL 13 110 온실 GreenHouse TIL 1 111 콘티온 ContiOn TIL 5 112 모래게임 Sandrop TIL 17 113 소리꽃 KeyBloom TIL 14 114 온실 GreenHouse TIL 2 115 콘티온 ContiOn TIL 6 116 모래게임 Sandrop TIL 18 117 콘티온 ContiOn TIL 7 118 모래게임 Sandrop TIL 19 119 소리꽃 KeyBloom TIL 15 120 모래게임 Sandrop TIL 20 121 모래게임 Sandrop TIL 21 122 소리꽃 KeyBloom TIL 16 123 소리꽃 KeyBloom TIL 17 124 소리꽃 KeyBloom TIL 18 125 여둘까 Office Layout TIL 6 126 소리꽃 KeyBloom TIL 19 127 소리꽃 KeyBloom TIL 20 128 콘티온 ContiOn TIL 8 129 콘티온 ContiOn TIL 9 130 로그스톤 상표 셀프 출원 TIL 1 131 소리꽃 KeyBloom TIL 21 132 로그스톤 샵 LogStone Shop TIL 1 133 소리꽃 KeyBloom TIL 22 134 로그스톤 샵 LogStone Shop TIL 2 135 소리꽃 KeyBloom TIL 23 136 모래게임 Sandrop TIL 22 137 모래게임 Sandrop TIL 23 138 소리꽃 KeyBloom TIL 24 139 로그스톤 샵 LogStone Shop TIL 3 140 모래게임 Sandrop TIL 24 141 소리꽃 KeyBloom TIL 25 142 모래게임 Sandrop TIL 25 143 MiniMacro TIL 1 144 모래게임 Sandrop TIL 26 145 콘티온 ContiOn TIL 10 146 Astro가 무엇인지, 왜 옮기는지 147 모래게임 Sandrop TIL 27 148 소리꽃 KeyBloom TIL 26 149 MiniMacro TIL 2 150 로그스톤 샵 LogStone Shop TIL 4 151 메트로놈 Simple Metronome TIL 8 152 모래게임 Sandrop TIL 28 153 여둘까 Office Layout TIL 7 154 메트로놈 Simple Metronome TIL 9 155 블로그 Astro 이관 TIL 156 여둘까 Office Layout TIL 8 157 로그스톤 샵 LogStone Shop TIL 5 158 모래게임 Sandrop TIL 29 159 오픈데이 Openday TIL 1 160 로그스톤 샵 LogStone Shop TIL 6 161 모래게임 Sandrop TIL 30 162 오픈데이 Openday TIL 2 163 모래게임 Sandrop TIL 31 164 Unreal Engine MCP TIL 1 165 모래게임 Sandrop TIL 32 166 소리꽃 KeyBloom TIL 27 167 콘티온 ContiOn TIL 11 168 오픈데이 Openday TIL 3 169 로그스톤 샵 LogStone Shop TIL 7 170 모래게임 Sandrop TIL 33 171 소리꽃 KeyBloom TIL 28 172 오픈데이 Openday TIL 4 173 여둘까 Office Layout TIL 9 174 오픈데이 Openday TIL 5 175 ScorePlayer TIL 1 176 모래게임 Sandrop TIL 34 177 BuildingHub TIL 1 178 ScorePlayer TIL 2 179 여둘까 Office Layout TIL 10 180 BuildingHub TIL 2 181 ScorePlayer TIL 3 182 Unreal Engine MCP TIL 2 183 궁 미로 PalaceMaze TIL 1 184 메트로놈 Simple Metronome TIL 10 185 메트로놈 Simple Metronome TIL 11 186 오픈데이 Openday TIL 6 187 BuildingHub TIL 3 188 Obsidian vault에서 코드만 빼기 — directory junction 189 ScorePlayer TIL 4 190 여둘까 Office Layout TIL 11 191 오픈데이 Openday TIL 7 192 URL은 바뀔 수 있는 것에 묶지 않는다 193 도구는 기능이 아니라 내 일에 맞는지로 고른다 194 돌이키기 힘든 변경은 작게 먼저 확인한다 195 궁 미로 PalaceMaze TIL 2 196 궁 미로 PalaceMaze TIL 3 197 소리꽃 KeyBloom TIL 29 198 오픈데이 Openday TIL 8 199 BuildingHub TIL 4 200 ScorePlayer TIL 5 201 로그스톤 샵 LogStone Shop TIL 8 202 여둘까 Office Layout TIL 12 203 오픈데이 Openday TIL 9 204 BuildingHub TIL 5 205 MiniMacro TIL 3 206 소리꽃 KeyBloom TIL 30 207 오픈데이 Openday TIL 10 208 여둘까 Office Layout TIL 13 209 소리꽃 KeyBloom TIL 31 210 궁 미로 PalaceMaze TIL 4 211 로그스톤 샵 LogStone Shop TIL 9 212 로그스톤 샵 LogStone Shop TIL 10 213 소리꽃 KeyBloom TIL 32 읽는 중