본문으로 건너뛰기
5분 AI.
TRENDSHIFT DAILY #25 · 2026-10-25 · peach-mitao/peach

Peach: 파일 위치가 달라도
하나의 영상 라이브러리로

앱

빠른 판단

무엇
로컬·마운트한 클라우드 영상을 SQLite에 등록해 검색·재생·메타데이터·개인 기록을 관리하는 성인 컬렉션 특화 셀프호스팅 앱
이럴 때
성인 사용자가 본인 영상 컬렉션을 여러 디스크·클라우드 폴더에 걸쳐 관리하거나, 파일 경로 매핑·읽기 전용 DB·재시작 가능한 작업의 구현을 공부할 때
입력
로컬·마운트한 클라우드의 영상 폴더와 선택한 외부 메타데이터
결과
브라우저 라이브러리·재생 화면과 로컬 DB의 개인 기록
도입 전 확인
18+ 단일 사용자용이며 AGPL-3.0-or-later다. 기술 진단은 기본 켜짐, 컬렉션 공유는 별도 동의로 기본 꺼짐. 외부 서비스 계정·권한·비용을 확인한다.
FastAPI·SQLite·React로 만든 개인 영상 관리 앱. 로컬 파일과 마운트한 클라우드를 통합하고, 경로 변환·재시작 가능한 작업·개인 기록의 보존 경계를 구현합니다.

분석 기준: 공식 repository ID 1339970099, 커밋 de22796c04919aaa057d77930c33c81313765569. README·설계·운영 문서와 API 조립·DB·미디어·경로·작업 복구·진단 코드 및 의존성을 읽었습니다. 설치·빌드·외부 사이트 수집·모델 실행은 하지 않았습니다.

1프로젝트 한줄 요약

개인용 영상 라이브러리와 재생·자료 관리 기능을 함께 제공하는 앱

Peach는 로컬 디스크와 마운트한 클라우드 드라이브의 영상을 SQLite에 등록하고, 브라우저에서 검색·재생·메타데이터·시청 기록을 관리하는 셀프호스팅 애플리케이션입니다. 기본은 단일 사용자이며 Windows 앱, macOS 앱/소스 실행, Linux Docker 경로가 문서화돼 있습니다.

범용 미디어 기술 위에 성인 영상 컬렉션에 특화한 작품 식별·인물·제작사·취향 기능이 있습니다. README는 명시적으로 18세 이상 사용자의 본인 컬렉션 관리용이라고 설명합니다. 저장소는 영상이나 사이트 데이터를 포함하지 않는다고 밝힙니다. 이 글은 데이터 모델과 파일·작업·플레이어의 구현을 다루며 성인 이미지나 실제 컬렉션을 사용하지 않습니다.

이럴 때 검토도입 판단
여러 디스크·클라우드 폴더영상 파일 위치와 라이브러리 기록을 분리하고, 마운트가 끊겨도 기록을 유지하고 싶을 때
개인용 브라우저 앱NAS나 데스크톱 한 곳에서 관리하고 같은 네트워크의 다른 기기에서 보고 싶을 때
구현 학습FastAPI 모듈 경계·SQLite 단일 writer·HTTP Range·작업 체크포인트를 실제 소스로 배울 때
확인할 한계다중 사용자·테넌트 격리 없음, 외부 메타데이터/피드의 가용성과 권한에 의존, 모든 데이터가 무조건 기기 안에만 남는 것은 아님

2왜 주목받는가

Daily 관측 25위 — 트렌딩 순위와 실제 기능 근거는 구분합니다

저장소가 보여주는 차별점은 파일·작품·개인 기록을 한 행으로 뭉개지 않는 것입니다. 같은 작품의 여러 버전·분할 파일을 묶고, 사라진 파일의 개인 기록을 남기며, 새로 등록된 파일과 재연결하는 흐름이 있습니다. scan.py의 머리 설명은 스캔이 크기·수정 시각·마지막 관측을 갱신하되 검토된 자료 필드를 함부로 덮어쓰지 않는다고 명시합니다.

일반 동영상 플레이어가 파일 하나를 여는 데 집중한다면 Peach는 라이브러리의 장기 상태를 관리합니다. 여러 사용자에게 서비스하는 미디어 서버보다도 개인 컬렉션과 외부 자료의 연결에 초점이 있습니다. 클라우드 통합은 드라이브를 Peach가 전부 직접 구현한다는 뜻이 아닙니다. CloudDrive2 등으로 마운트한 폴더를 파일 시스템으로 읽고, 별도의 클라우드 다운로드 경로를 제공합니다.

README의 115·PikPak 다운로드와 외부 피드 기능도 실제 제품의 일부입니다. resource_search.py에는 사용자 설정 Torznab 인덱서, follow_providers.py에는 공식 공개 피드와 아카이브 사이트가 함께 등록됩니다. 기능이 있다는 사실과 각 콘텐츠를 받을 권리가 있다는 판단은 별개입니다. 외부 연결을 쓰지 않고도 로컬 파일 관리 구조를 살펴볼 수 있습니다.

3기술 스택 전체 지도

배포는 한 프로세스, 책임은 여러 모듈로 분리된 modular monolith
영역고정 소스의 기술역할
백엔드Python 3.14+, FastAPI·Starlette·Uvicorn·PydanticJSON API·페이지·인증·미디어 응답, 서비스 조립
영구 저장SQLite, repository.py, migrations/라이브러리·개인 기록·자료 출처, 읽기 전용 연결과 쓰기 트랜잭션 경계
프론트엔드React 19·React Router·TanStack Query/Table·TypeScript·Vite·Tailwind v4라우팅·조회 상태·표·상세 화면. web/dist/ 산출물을 서버에서 제공
플레이어Video.js 10, FFmpeg/ffprobe브라우저 재생, 미디어 탐색·추출·변환. FFmpeg는 별도 실행 도구
작업·외부 IOAPScheduler·watchdog·filelock·HTTPX, 일부 curl_cffi·gRPC주기 작업·파일 변화·프로세스 간 잠금·외부 소스/CloudDrive2 연결
데스크톱·컨테이너pystray·PyInstaller, macOS Cocoa 확장, Docker Compose트레이에서 로컬 서비스를 열거나 NAS에서 브라우저로 접근
선택 AIproviders.py의 Inference/Agent 구분모델 목록·능력·설정 상태를 분리해 노출. 기본 영상 관리에 모델 실행을 요구하지 않음
검증 도구Python 테스트, Vitest, Node test·Playwright 기반 e2e경로·DB·작업·UI 계약 검사. 이 분석에서는 실행하지 않음

의존성의 정확한 버전은 Python 쪽 pyproject.toml·uv.lock, UI 쪽 frontend/package.json이 기준입니다. 루트 package.json은 Video.js·폰트·아이콘 등 웹 vendor 자산용으로, 프론트엔드 전체 의존성 목록과 다릅니다.

4아키텍처 심화 분석

DB에 기록된 정체·실제 파일 위치·화면의 요청을 서로 다른 경계로 둡니다
로컬 / 마운트한 클라우드 폴더 │ scan / probe v SQLite ledger.db ← 검토된 자료 / 개인 기록 │ LedgerDatabase / Repository │ FastAPI create_app ── app.state에 서비스 조립 ├─ pages / auth / configuration / JSON routes └─ media routes → MediaEngine → FilesystemBackend │ 경로 변환 + 허용 루트 검사 v 로컬 파일 / HTTP Range / 선택 HLS │ React UI + Video.js 플레이어 장기 작업 → 본인 기기의 state/ 체크포인트 → 재시작 시 복구 선택 외부 연결 → metadata / feed / cloud / 진단 전송

1. API 등록 순서도 계약입니다

api.py는 서비스들을 만들고 request.app.state로 공급하는 조립 지점입니다. 라우터를 기능별로 나누지만 routes_api의 /api/{route:path} catch-all은 마지막에 등록해야 합니다. 그렇지 않으면 먼저 처리해야 할 미디어 경로까지 잡아버립니다. “파일을 나눴다”보다 라우팅 우선순위와 의존성의 소유자를 보는 것이 핵심입니다.

2. 읽기 연결은 SQLite 자체에서 쓰기를 막습니다

# src/peach/repository.py:22-30
 def connect(self, *, write: bool = False, timeout: float = 30):
     target = (
         str(self.db_path) if write
         else self.db_path.resolve().as_uri() + "?mode=ro"
     )
     connection = sqlite3.connect(
         target, timeout=timeout, check_same_thread=False, uri=not write,
     )

위 코드는 타입 표기를 일부 생략한 발췌입니다. 읽기 경로를 관례로만 유지하지 않고 URI의 mode=ro로 제한합니다. 쓰기는 write_transaction의 프로세스 내 lock과 commit/rollback으로 감싸며, 성공 시 필요할 때만 after_commit을 부릅니다. 주기적인 작업 진행 기록까지 매번 집계 캐시를 지우지 않도록 notify=False를 둘 수 있습니다.

두 기기의 DB 복제는 별개의 계약입니다. 설계 문서는 한 번에 한 기기만 쓰고, 양쪽이 바뀌면 읽기 전용으로 전환해 보존할 DB를 선택하도록 한다고 명시합니다. 이 lock 하나가 여러 기기의 충돌을 자동 해결하는 것은 아닙니다.

3. DB의 경로와 로컬 경로를 구분합니다

DB의 asset.path는 Windows 형태로 유지됩니다. 예를 들어 R:\media\a.mp4를 Linux에서는 /media/a.mp4로 읽습니다. [media.locations]는 DB 접두사, [media.mounts]는 현재 기기의 실제 디렉터리입니다. platform.translate_ledger_path는 등록된 대응이 없으면 존재하지 않는 UNMAPPED_ROOT 아래로 보내 읽기를 거부하게 합니다.

FilesystemBackend.file_for는 변환한 경로가 허용 루트 안에 있는지 검사합니다. MediaOffline과 MediaUnavailable도 구분하므로 마운트 전체가 끊긴 경우와 파일 하나가 없는 경우를 같은 삭제 사건으로 취급하지 않습니다. 스캔 결과·개인 기록·원본 파일의 수명이 다른 이유가 코드에 드러납니다.

4. HLS가 언제나 기본값은 아닙니다

MediaEngine.stream_plan의 기본 결과는 HTTP Range입니다. HLS는 명시적으로 요청한 모드이면서 115/PikPak의 MP4/M4V이고 알려진 양수 duration이 있는 경우에 선택합니다. 코드 주석은 HEVC를 그대로 MPEG-TS에 넣었을 때 Chromium MSE에서 조용히 검은 화면이 되는 사례를 이유로 듭니다. 확장자나 스트리밍 방식 하나만 바꾼다고 브라우저 코덱 지원까지 해결되는 것은 아닙니다.

5. 재시작 복구는 체크포인트와 실행 소유권을 함께 봅니다

job_recovery.py는 요청·job ID·PID·진행 위치를 로컬 JSON에 원자적으로 저장합니다. resume은 admission lock 안에서 상태와 PID를 확인하고, 살아 있는 다른 프로세스나 현재 프로세스가 이미 시작한 작업을 건너뜁니다. 실패하면 blocked와 오류를 남깁니다. 모든 외부 부작용이 정확히 한 번 실행된다는 보장은 아니며, 재실행 가능한 범위와 결과 저장 위치를 작업별로 봐야 합니다.

5디렉토리 구조 해부

실행 데이터와 공개 소스는 별도입니다
peach/
├─ src/peach/
│  ├─ api.py · routes_*.py       앱 조립 / HTTP 경계
│  ├─ repository.py · scan.py    SQLite / 파일 관측
│  ├─ platform.py · media.py     경로 대응 / 재생 원본
│  ├─ jobs.py · job_recovery.py  작업 수명 / 복구
│  ├─ follow_*.py · metadata*.py 외부 자료와 피드
│  ├─ remote_reporting.py       기술 진단 선택·전송
│  └─ library_sharing.py        별도의 컬렉션 공유 동의
├─ frontend/                    React·플레이어·테스트·Vite
├─ web/                         페이지 자원 / dist 빌드 결과
├─ migrations/                  DB 스키마 이력
├─ docs/                        설계·운영·ADR·라이선스 설명
├─ tests/ · scripts/             회귀 테스트 / 개발·배포 도구
└─ pyproject.toml · uv.lock · compose.yaml

실행 데이터는 별도 peach-data 디렉터리에 DB·캐시·state·secrets·로그 등으로 나뉩니다. Docker는 /data 영구 볼륨, 소스 실행과 데스크톱 앱은 각 경로를 사용합니다. 소스 폴더만 백업했다고 개인 기록과 설정까지 보존되는 것이 아닙니다.

공식 docs/adr/0085-organization-and-three-code-repositories.md는 2026-09-30 GitHub 조직으로 앱을 이전하며 커밋·태그·릴리스·이슈를 보존했다고 기록합니다. 앱은 peach-mitao/peach, 웹사이트와 데모는 별도 저장소입니다. 앱의 기본 브랜치는 master이며 서로 다른 저장소의 산출물을 혼동하지 않아야 합니다.

6학습 포인트

주제읽을 파일자신의 앱에 적용할 질문
단일체의 모듈 경계api.py, docs/ARCHITECTURE.md서비스 조립·라우팅·영역 로직의 책임이 섞이지 않는가?
개인 기록 보존scan.py, repository.py파일이 잠시 안 보일 때 사용자가 남긴 기록도 지울 것인가?
경로의 정체platform.py, media.py저장된 식별자와 이번 기기의 경로를 어떻게 분리하는가?
복구 가능한 작업job_recovery.py진행 위치를 결과 저장 전후 언제 기록하며 중복 실행은 누가 막는가?
프로토콜과 코덱MediaEngine.stream_planRange·HLS·컨테이너·코덱을 서로 독립적으로 판단하는가?
동의의 분리remote_reporting.py, library_sharing.py기술 진단 동의를 개인 데이터 제공 동의로 확대하지 않는가?

AI 기능을 읽을 때도 능력 탐색과 모델 호출을 구분해야 합니다. providers.py의 OpenCodeGoClient.list_models는 목록을 조회하는 코드이고, registry의 available·configured·experimental은 서로 다른 상태입니다. 실행 파일이 있다는 이유만으로 로그인과 실제 추론 성공을 판정하면 안 됩니다.

7하드웨어 / 시스템 요구사항

앱 사용과 개발 빌드, 선택 외부 기능의 요구를 분리합니다
경로조건·제약
Windows 배포본x64 테스트 패키지. 전체 설치본은 미디어 도구 포함, lite는 설치 중 필요 도구 다운로드. 테스트 빌드는 미서명
macOSarm64/x86_64별 앱·DMG 빌드 경로. 테스트 앱은 공증되지 않았으며 FFmpeg 등 미디어 도구는 별도 설치
소스 실행Git·uv·Python 3.14+. pyproject.toml의 uv 하한은 0.12.13, macOS는 macos extra 필요
프론트엔드 개발Node.js 24+, frontend 의존성. 소스 UI를 수정·재빌드할 때의 조건이며 일반 설치본 사용 조건과 다름
Docker문서화된 이미지 대상 linux/amd64, ARM NAS 미검증. 기본 media bind는 읽기 전용, 데이터는 별도 영구 볼륨
전환·추출FFmpeg/ffprobe 필요. CPU·GPU·코덱에 따라 비용이 다르며 보편적 최소 RAM/CPU 수치는 소스에서 확인하지 못함
외부 서비스클라우드 마운트/다운로드는 해당 계정·도구·권한 필요. 구독·용량·속도 비용은 서비스별 조건을 별도 확인
라이선스AGPL-3.0-or-later. 외부 컴포넌트는 자체 라이선스 유지. 배포된 FFmpeg 등 도구 조건은 docs/REUSE.md 등에서 구분

“로컬 우선”의 데이터 전송 조건

README와 진단 코드는 버전 통계·오류 진단 선택이 기본 켜짐이라고 명시합니다. Sentry 수신점이 설정돼 있을 때 기술 필드를 전송하고 설정에서 끌 수 있습니다. 전체 로그·영상 정보·시청 기록을 기술 진단에 넣지 않는다는 범위가 별도로 설명돼 있습니다.

컬렉션·취향 공유는 library_sharing.py의 별도 동의이며 기본 꺼짐입니다. 켜면 작품·평가·시청 요약·선호 차원의 주간 스냅샷을 보낼 수 있습니다. 식별자를 빼도 상세 취향이 재식별될 수 있고 수신자는 연결 IP를 볼 수 있다고 공식 문서가 명시합니다. 외부 메타데이터·피드 조회와 선택 AI 연결도 네트워크 경로이므로 모든 동작을 완전 오프라인이라고 부를 수 없습니다.

앱은 단일 사용자용이고 공개 인터넷에 직접 포트를 노출하지 않도록 설계돼 있습니다. 기본 Compose는 호스트의 127.0.0.1에만 공개합니다. 여러 사용자의 독립된 권한·라이브러리를 제공해야 한다면 이 구조가 맞는지 먼저 판단해야 합니다.

8직접 해볼 수 있는 실습 과제

정적 분석 세 개와, 별도 환경에서 선택할 수 있는 첫 실행 한 개

8-1. 고정 소스와 실제 요구 버전 확인 기초

공식 저장소를 확보한 디렉터리에서 읽기 명령으로 요구사항을 확인합니다.

PIN=de22796c04919aaa057d77930c33c81313765569
git cat-file -t "$PIN"
git show "$PIN:pyproject.toml"
git show "$PIN:frontend/package.json"
git show "$PIN:compose.yaml"

완료 기준: Python 3.14+·Node 24+와 /data·/media의 역할을 설명합니다. read_only: true가 media bind의 속성인지 확인합니다. Python 3.12로 이 앱을 실행할 수 있다고 해석하면 안 됩니다.

8-2. 읽기 전용 경계·경로 매핑을 따라가기 중급

git show "$PIN:src/peach/repository.py"
git show "$PIN:src/peach/platform.py"
git show "$PIN:src/peach/media.py"

입력은 가상의 R:\media\sample.mp4, DB 접두사는 R:\media, Linux 마운트는 /media라고 둡니다. 완료 기준: 결과 경로 /media/sample.mp4까지의 변환을 그리고, 마운트 설정이 없는 경우와 허용 루트 밖 경로가 어느 함수에서 거부되는지 적습니다. 실제 DB를 열거나 수정하지 않아도 됩니다.

8-3. 별도 데이터 디렉터리로 첫 실행 중급 · 선택

이 과제는 자신의 테스트 환경에서만 선택합니다. 현재 분석에서는 실행하지 않았습니다. Python 3.14+·uv가 필요하고 의존성 및 인터프리터 다운로드가 발생할 수 있습니다. macOS에서 공식 운영 문서의 소스 실행 경로는 다음과 같습니다.

uv sync --locked --python 3.14 --extra macos
PEACH_DATA_ROOT="$PWD/../peach-lab-data" ./.venv/bin/peach-tray

Windows에서는 uv sync --locked --python 3.14 후 별도 PEACH_DATA_ROOT를 지정해 .venv\Scripts\peach-tray.exe를 실행합니다. 브라우저 설정에서 이 컴퓨터만 접근하도록 하고, 테스트용으로 직접 만든 비민감 영상 사본 한 개가 든 폴더만 추가합니다. 외부 피드·브라우저 기록·클라우드를 연결할 필요는 없습니다. 진단·공유 선택을 확인한 뒤 스캔합니다.

완료 기준: 영상 사본이 라이브러리에 나타나고 재생되는지, 앱을 재시작한 뒤 기록이 남는지 확인합니다. 실패하면 코덱·FFmpeg·실제 마운트·허용 루트·로그를 각각 확인합니다. 일반 원본 디렉터리를 대상으로 이동·삭제·정리 작업을 시험하지 않습니다.

8-4. 복구와 전송 동의를 상태 전이 표로 만들기 심화

git show "$PIN:src/peach/job_recovery.py"
git show "$PIN:src/peach/remote_reporting.py"
git show "$PIN:src/peach/library_sharing.py"

완료 기준: 작업의 active·살아 있는 PID·현재 PID·blocked 처리를 표로 정리합니다. 이어서 기술 진단은 기록이 없을 때 기본 켜짐, 컬렉션 공유는 명시적 동의가 있을 때만 켜짐이라는 차이를 실제 조건으로 짚습니다. 모델을 실행하거나 개인 기록을 전송하지 않고도 재시작·개인정보 설계의 경계를 평가할 수 있습니다.

9관련 기술 심화 학습 로드맵

주차읽기·실습과 산출물
1주docs/ARCHITECTURE.md와 api.py를 읽고 app.state·라우터·영역별 로직의 의존 방향 작성
2주SQLite 읽기/쓰기 경계, 스캔과 개인 기록을 비교하고 “파일 소실·마운트 해제·같은 작품의 새 버전” 처리 표 작성
3주platform.py와 media.py에서 경로·허용 루트·Range/HLS 조건을 따라 가상의 입력별 결과 표 작성
4주체크포인트·동의·외부 IO를 묶어 재시작 중복 방지와 데이터 전송에 대한 검증 계획 작성. 실제 네트워크·기기 동작은 별도 환경에서 확인

10핵심 키워드 사전

용어이 프로젝트에서의 의미
Modular monolith영역별 코드는 나누되 한 애플리케이션으로 배포하는 구조
Ledger파일·작품·개인 기록·자료 출처를 보존하는 SQLite DB. 단순 파일 목록보다 수명이 긴 기록
마운트 / 경로 매핑외부 저장소를 폴더처럼 제공하고 DB의 경로를 현재 시스템의 디렉터리에 대응시키는 것
HTTP Range파일 일부 바이트를 요청하는 방식. 원본을 브라우저가 재생할 때 탐색·이어보기에 활용
HLS재생 목록과 작은 미디어 조각을 주고받는 방식. 코덱 지원 여부와는 별도 문제
체크포인트긴 작업의 요청·진행 위치를 보존해 재시작 후 미완 부분을 이어갈 근거
NFO / Torznab미디어 메타데이터 sidecar 파일 / 인덱서 검색 API 규약. 둘은 라이브러리 정리와 외부 검색의 다른 입력
AGPL / ADR프로젝트가 선언한 소프트웨어 라이선스 / 설계 결정의 배경·대안·영향을 남긴 문서

11참고 링크

소스 링크는 분석 커밋에 고정돼 있습니다