Viewgery — 올리는 길에서 환자 정보를 덜어 내는 의료인 전용 수술 영상 플랫폼
면허를 인증한 의료인만 보고 올리는 수술 영상 웹앱. 작업한 6일 동안 커밋 172개로 업로드·1080p 인코딩·스트리밍부터 검색·댓글까지 만들어 시연 환경에서 돌리고 있다. 자동 비식별·게시 전 검수·시청 워터마크는 아직이라 시험 영상만 올린다.
- 날짜
- 역할
- 기획 · 개발
- 기술
- React · TypeScript · FastAPI · PostgreSQL · ffmpeg · hls.js · Claude Code

왜 만들었나
수술 술기는 남의 수술 영상을 보며 익힌다. 그런데 의료인이 믿고 볼 수 있는 수술 영상은 흩어져 있다. 유튜브에는 검증이 없고 누구나 볼 수 있으며, 학회 자료는 닫혀 있다.
영상을 가진 의료인도 선뜻 올리지 못한다. 수술 영상 파일에는 촬영 장비가 남긴 정보와 수술실 소리, 환자 몸이 비치는 체외 장면이 함께 들어 있는데, 그것을 덜어 내고 올릴 안전한 절차가 없다.
그래서 이렇게 가정했다. 인증한 의료인끼리만 보는 닫힌 곳을 만들고, 올리는 과정 자체에 환자 정보를 덜어 내는 장치를 넣으면, 의료인이 걱정 없이 영상을 올리고 다시 찾아와 볼 것이다.
무엇을 만들었나
Viewgery는 면허를 인증한 의료인만 보고 올리는 수술 영상 웹앱이다. 회사와 관련된 프로젝트를 개인으로 맡아 기획부터 개발까지 진행하고 있다. 10월 20일부터 29일까지 작업한 6일 동안 커밋 172개가 쌓였고, 지금도 개발 중이다.
직군은 의사·치과의사·한의사·간호사·의료기사·의대생·간호학생 일곱 가지이고, 학생은 보기만 한다. 가입해 면허 서류로 인증을 받으면 채널을 열어 영상을 올리고, 12개 전문분야로 모아 보고, 검색하고, 구독하고, 댓글을 단다. 화면 주소는 30개이고, 그중 6개가 인증 검토·신고 처리 같은 운영자 화면이다. 화면 문구는 한국어와 영어 두 벌이다.

홈. 전문분야 칩과 큰 카드 격자, 왼쪽에 구독·내 페이지 메뉴가 있다. 카드에 마우스를 올리면 썸네일 자리에서 10초짜리 미리보기가 소리 없이 돈다.
업로드는 유튜브처럼 파일부터 고르지만, 바이트를 보내기 전에 서약 네 항목을 받는다. 환자 동의 또는 비식별 처리, 소속 기관의 공개 허가, 저작권, 수술실 CCTV 영상이 아님. 서약은 그 업로드 하나에만 쓰이고 시각과 함께 남는다. 올라가는 동안 같은 화면에서 제목·설명·태그를 적고, 다른 화면으로 옮겨도 오른쪽 아래 진행 창에서 업로드가 이어진다.

업로드 서약. 네 항목을 모두 체크해야 업로드를 시작할 수 있다.
재생 화면의 재생 막대는 직접 만들었다. 설명에 적은 시간(0:30 레치우스 공간 박리)으로 막대를 구간으로 나누고,
마우스를 올린 자리의 장면을 작은 그림으로 미리 보여 준다. 배속·영화관 모드·전체 화면·단축키가 있고,
소리 트랙은 처리할 때 지우므로 볼륨 조절은 숨긴다.

재생 화면. 막대 위 장면 미리보기와 구간 이름, 오른쪽의 다른 동영상 목록.
이 글의 화면은 실제 데이터가 아니다. 저장소를 따로 복사한 빈 환경에서 ffmpeg로 만든 합성 시험 영상과 가상의 채널·계정으로 찍었다.
어떻게 만들었나
약속을 먼저 쓰고 나눠 맡겼다. 기능마다 단계 문서에 API 약속(주소, 응답 모양, 오류 코드)을 먼저 적고, 화면·서버·영상 처리를 맡은 Claude Code 서브에이전트 셋이 각자의 git worktree에서 나란히 만들었다. 화면 쪽은 임시 약속 파일에 맞춰 짜다가, 합칠 때 서버의 OpenAPI 명세에서 생성한 타입으로 갈아 끼운다. 서버가 약속과 어긋나면 타입 검사에서 바로 드러난다. DB 마이그레이션 번호는 합치는 쪽이 정해 주며, 지금 20번까지 왔다.
서버는 한 벌, 큐는 DB 테이블. 화면은 React 정적 웹앱이고 서버는 Python 한 벌이다.
FastAPI api와 영상 처리 worker가 같은 코드를 쓰고 프로세스만 나뉜다. 작업 큐는 PostgreSQL 테이블에서
FOR UPDATE SKIP LOCKED로 가져가는 방식이라 Redis 같은 서버를 더 띄우지 않는다.
로그인한 사람만 쓰고 검색엔진 색인도 막는 서비스라 서버 렌더링은 쓸 일이 없었다.
화면은 유튜브를 기준으로. 의료인이 따로 배우지 않아도 쓰게 하려고 채널·구독·카드 격자·재생 화면·댓글을 유튜브와 거의 같은 구조로 두고 어두운 테마를 기본으로 했다. 재생 막대만은 공개 부품(Vidstack)을 쓰려다 직접 만들었다. 확인해 보니 지원 종료를 알린 상태였고, 빌드 결과에 YouTube·Vimeo·Google Cast 주소가 남았다.
환자 정보는 올리는 길에서 덜어 낸다. 원래 파일 이름은 서버로 보내지 않고, 원본은 격리 폴더에만 둔다.
worker는 영상을 1080p H.264로 인코딩하면서 소리·자막·데이터 트랙과 메타데이터를 뺀다.
GPU 인코더(NVENC)를 먼저 쓰고, 안 되면 libx264로 다시 한다. 인코딩본의 길이가 원본과 맞는지 확인되면 원본은 바로 지운다.
끝내 실패한 원본도 7일 뒤 지운다. 썸네일과 미리보기는 0초가 아니라 길이의 10% 지점에서 뽑는다.
수술 영상 앞부분에는 환자 몸이나 수술실 사람이 나오는 체외 장면이 많아서다.
재생 파일과 썸네일은 브라우저 캐시에 남기지 않고(private, no-store), 검색어는 서버 로그에 남기지 않는다.
로그인 전 화면에도 실제 영상은 없다. 소개 화면 뒤에 흐리게 처리한 실제 화면을 깔자는 안이 먼저 나왔다. 흐려도 원본 그림은 브라우저로 내려가니 꺼낼 수 있다. 그래서 썸네일 자리를 단색 도형으로 그린 앱 모형을 흐리게 깔았다.

로그인 전 소개 화면(폰 폭). 뒤에 깔린 카드 격자는 그림으로 그린 모형이다.
화질은 재 보고 정했다. 디테일과 노이즈가 많은 수술 영상은 눈으로 보기에 무손실을 지키면 크기가 원본과 거의 같았다(VMAF 측정). 그래서 손실을 받아들이고 평균 12Mbps로, 원본 비트레이트는 넘지 않게 정했다. 키프레임은 2초마다 고정해 HLS 조각 경계와 맞췄다.
검증은 역할을 나눠서. qa 에이전트는 PRD 수용 기준을 테스트로 만들고 권한 없는 사용자와 경계값을 찌른다. privacy 검토 에이전트는 코드를 읽기만 하면서 환자 정보 노출·인증 우회·영상 주소 유출을 높음·중간·낮음으로 짚는다. 완료 기준은 ’높음·중간 없음’이다. 테스트 영상은 실행 중에 ffmpeg로 합성하고, 실제 환자 영상은 저장소와 테스트에 넣지 않는다. 10월 20일 370개였던 서버 테스트는 10월 28일 2,392개가 됐고, 화면 테스트는 1,423개다.
걸렸던 것
메타데이터는 옵션 한 줄로 지워지지 않았다. 첫날엔 화질을 지키려고 재인코딩 없이 복사하며 메타데이터만 지웠다.
그런데 -map_metadata -1은 컨테이너에 적힌 정보만 지운다. 인코더는 영상 데이터 안(SEI)에도 버전과 설정 문자열을 적는다.
HEVC는 그 SEI가 mp4의 파라미터 집합(hvcC)에도 들어가는데, 이 PC의 ffmpeg 필터로는 거기를 지우지 못했다.
파라미터 집합을 패킷 안으로 옮겨(Annex B) SEI를 빼고, 파라미터 집합 칸이 없는 MPEG-TS를 거쳐 mp4로 다시 담는 길을 만들었다.
인코딩으로 바꾼 뒤에도 한 칸이 더 남았다. HLS로 나눈 머리 조각(init.mp4)에 만든 프로그램 이름이 찍혔다.
ffmpeg의 HLS 먹서가 bitexact 옵션을 머리 조각까지 넘기지 않아서다. 그 칸을 같은 길이의 공백으로 덮어, 파일 구조는 그대로 두고 이름만 지운다.
에이전트를 나란히 돌리자 환경이 섞였다. 첫날 worktree가 원격 기준으로 만들어지는 바람에,
로컬에만 커밋해 둔 파일 저장소 어댑터가 빠진 채 작업이 시작됐다. 이후 지시의 첫 단계에 로컬 main을 먼저 받는 명령을 넣었다.
10월 27일에는 여러 worktree가 테스트 DB 하나를 같이 쓰다가, 다른 작업의 마이그레이션이 올라가 main의 테스트가 시작조차 못 했다.
테스트 DB를 worktree마다 따로 두게 바꿨다. 그런데 테스트 DB가 개발 DB와 같은지 보는 보호 장치는
localhost와 127.0.0.1, 포트 생략을 다른 서버로 보고 있었다. 뚫리면 테스트가 시연과 같이 쓰는 개발 DB를 비울 수 있었다.
검토에서 잡아, 호스트 표기와 기본 포트를 맞춘 뒤 비교하게 고쳤다.
검증을 하루 끝으로 몰자 낡은 테스트가 걸렸다. 작업마다 qa와 privacy 검토를 돌리니 속도가 나지 않았다. 10월 27~28일에 방식을 바꿔, 합칠 때는 관련 테스트와 짧은 동작 확인만 하고 전체 검증은 하루 끝에 한 번 한다. 10월 28일 저녁 첫 전체 실행에서 서버 테스트 7건이 실패했다. 제품은 약속대로 동작했다. 영상 정보 모양과 화면 주소 목록을 고정해 둔 기존 테스트 세 곳을, 기능을 합칠 때 함께 고치지 않은 탓이었다. 그 세 파일은 해당 부분을 바꿀 때 반드시 같이 돌리는 목록에 올렸다. 대가도 적어 두었다 — 하루 검증 전까지는 민감한 변경이 검토 없이 시연 환경에 올라가 있으니, 그동안은 시험 영상만 올린다.
3GB 업로드가 로그아웃 한 번에 처음으로 돌아갔다. 공용 PC에 흔적을 남기지 않으려고 로그아웃하면 이어 올리기 기억값을 지우게 했다. 그래서 3GB 영상을 올리다 로그아웃하자, 다시 로그인해 같은 파일을 골라도 처음부터 올라갔다. 멈춘 업로드 둘은 동시 업로드 한도 3개 중 두 자리를 차지하고 있었다. 끝나지 않은 업로드가 있으면 로그아웃 전에 확인 창을 띄우고, 서버도 로그아웃할 때 그 세션에서 만든 끝나지 않은 업로드를 지우게 했다.
어디까지 왔나
가설의 앞 절반인 ’올리고 보는 길’은 섰다. 가입과 인증 신청·운영자 검토, 채널, 끊겨도 이어지는 업로드, 1080p 인코딩과 HLS 스트리밍, 홈·검색·태그, 구독·알림·시청 기록, 댓글·좋아요, 신고까지 시연 환경에서 돈다. 1GB와 3GB 영상으로 업로드부터 재생까지 확인했다.
’걱정 없이’는 아직 증명하지 못했다. 기획에 있던 자동 비식별(화면에 박힌 글자 가리기, 체외 장면 찾기)과 게시 전 운영자 검수, 시청자 워터마크와 만료되는 영상 주소는 ’먼저 잘 올라가고 잘 재생되게’를 앞세우며 뒤로 미뤘다. 지금 식별 위험을 막는 것은 업로드 서약과 파일 정보·소리 제거, 원본 즉시 삭제뿐이다. 그래서 실제 환자 영상은 올리지 않고 시험 영상만 쓴다. 실제 의료인이 영상을 올리고 다시 찾아오는지는 아직 아무도 확인하지 않았다.
남은 것
다음은 시청 보호다. 영상·썸네일·미리보기 주소에 짧게 만료되는 서명을 붙이고, 플레이어 위에 시청자를 식별하는 워터마크를 띄우고, 워터마크를 피해 가는 PiP·AirPlay·iPhone 기본 전체 화면을 막는다. 그다음이 자동 비식별 제안과 게시 전 검수다. 검색은 아직 키워드뿐이다. 한국어로 찾아 영어 제목 영상까지 찾는 의미 검색은, 이 PC에서 돌릴 다국어 임베딩 모델 후보 둘을 비교하는 일부터 남았다. 영상마다 고를 술식 분류 목록은 초안을 만들어 의료진 확인을 기다린다. 10월 28일 하루 검증에서 나온 고칠 것 9건도 마지막 커밋 기준으로 아직 반영 전이다. 실제 환자 영상을 받기 전에는 외부 보안 점검을 받는다.