소개
미디어 최적화 (wdopt) — 개발 리포트
최종 버전: v1.0.4
개발일: 2026-05-27
제작: 불패의초인 (BSplus) — https://bsplus.net/
환경: Rhymix 2.1.33 / PHP 7.4 / Windows XAMPP / Cloudflare
📌 프로젝트 배경
Rhymix 공식 자료실의 "미디어 최적화 위자드" 모듈(40만원, PHP 8.2+ 전용)을 참고하여, PHP 7.4~8.4 호환으로 자체 개발. 원본 모듈에 없는 동영상 변환 기능(H.264/H.265) 까지 추가.
요구 인코더
| 인코더 | 경로 | 버전 |
|---|---|---|
| cwebp | C:\cwebp\bin\cwebp.exe |
1.6.0 |
| ffmpeg | C:\ffmpeg\bin\ffmpeg.exe |
기존 설치 |
| ffprobe | C:\ffmpeg\bin\ffprobe.exe |
기존 설치 |
🗂 모듈 구조 (최종)
modules/wdopt/
├── conf/
│ ├── info.xml # 모듈 정보 (v1.0.4)
│ └── module.xml # 액션, 권한 정의
├── schemas/
│ ├── wdopt_queue.xml # 처리 큐 테이블
│ └── wdopt_history.xml # 변환 히스토리 테이블
├── queries/ # XML 쿼리 (12개)
│ ├── insertQueue.xml
│ ├── updateQueueStatus.xml
│ ├── getPendingQueue.xml
│ ├── getQueueList.xml
│ ├── getQueueCount.xml
│ ├── getQueueByFileSrl.xml
│ ├── deleteQueue.xml
│ ├── insertHistory.xml
│ ├── getHistoryList.xml
│ ├── getHistoryStats.xml
│ ├── deleteAllHistory.xml
│ └── updateFileRecord.xml # v1.0.3 추가
├── lang/
│ └── ko.php # 한국어 언어팩
├── tpl/
│ ├── css/admin.css # 관리자 스타일
│ ├── _tabs.html # 탭 네비게이션 (미사용, 직접 삽입)
│ ├── dashboard.html # 대시보드
│ ├── queue.html # 처리 큐
│ ├── history.html # 변환 히스토리
│ └── config.html # 기본 설정
├── wdopt.class.php # 메인 클래스 (설치/업데이트/트리거 등록)
├── wdopt.model.php # 모델 (설정/조회/인코더 감지)
├── wdopt.controller.php # 컨트롤러 (트리거/워커/변환 엔진)
├── wdopt.admin.view.php # 관리자 뷰
├── wdopt.admin.controller.php # 관리자 처리
└── README.md # 소개/사용법
🔧 주요 기능
미디어 변환
| 타입 | 입력 | 출력 | 방법 |
|---|---|---|---|
| 이미지 | JPG, PNG, BMP | WebP (또는 원본 유지) | GD 리사이즈 + cwebp 변환 |
| GIF | GIF | Animated WebP (또는 GIF 최적화) | ffmpeg 변환 |
| 동영상 | MP4, AVI, MKV, MOV 등 | MP4 (H.264 또는 H.265) | ffmpeg 인코딩 |
관리자 화면 (4개 탭)
| 탭 | 기능 |
|---|---|
| 📊 대시보드 | 큐 상태 카드(대기/처리중/완료/실패), 용량 절감량, 인코더 상태 |
| 📋 처리 큐 | 상태별 필터, 일괄 재시도, 실패 비우기, 워커 수동 실행 |
| 📜 변환 히스토리 | 파일별 원본→변환 크기/절감률, 타입별 필터, 히스토리 초기화 |
| ⚙ 기본 설정 | 인코더 경로, 품질, 코덱, 비트레이트, 워커, 백업, 제외 모듈 |
동작 흐름
파일 첨부 업로드
↓
file.insertFile 트리거 (after)
↓
wdopt 큐에 자동 등록 (status: pending)
↓
워커 실행 (수동 버튼 또는 Cron)
↓
cwebp / ffmpeg 변환
↓
원본 파일 → 변환본으로 교체 (같은 경로)
↓
DB 업데이트 (uploaded_filename, file_size, source_filename)
↓
원본 백업 (files/wdopt_backup/YYYYMMDD/)
↓
히스토리 기록
📊 테스트 결과
| # | 파일 | 타입 | 원본 | 변환 | 절감 | 절감률 |
|---|---|---|---|---|---|---|
| 1 | 3c883f...91e.png | 이미지 | 2.0MB | 229.1KB | -1.8MB | -89% |
| 2 | 4ed4ca...96d.png | 이미지 | 1.2MB | 134.7KB | -1.0MB | -88.8% |
| 3 | 2ee79a...3c8.png | 이미지 | 142.4KB | 52.1KB | -90.3KB | -63.4% |
| 4 | 2bc6f3...fc9.mp4 | 동영상 | 166.7MB | 15.1MB | -151.6MB | -91% |
| 5 | 8aacf3...201.mp4 | 동영상 | 242.0MB | 22.3MB | -219.7MB | -90.8% |
| 합계 | 412.0MB | 37.8MB | -374.2MB | -90.8% |
🔄 버전 이력
v1.0.0 — 최초 릴리즈
- 전체 모듈 구조 생성 (conf, schemas, queries, PHP, tpl)
- 이미지(WebP), GIF, 동영상(H.264/H.265) 변환 지원
- 관리자 대시보드, 처리 큐, 변환 히스토리, 기본 설정 화면
file.insertFile/file.deleteFile트리거 연동- 원본 백업, 인코더 자동 감지
v1.0.1 — 트리거 및 DB 호환성 수정
문제: 트리거가 호출되지 않음 (큐에 파일이 등록 안 됨)
module.xml의eventHandlers방식 → Rhymix 2.1.33에서type=wdopt.controller로 잘못 등록됨- 수정: eventHandlers 제거,
wdopt.class.php에서 PHPinsertTrigger()코드로 직접 등록 moduleInstall(),checkUpdate(),moduleUpdate()에 트리거 등록/확인 로직 추가
문제: 워커 실행 시 DB::escapeString() 에러
- 수정:
_updateFileRecord()에서$oDB->escapeString()→addslashes()로 교체
문제: 히스토리/큐 목록이 안 나옴
- 수정: XML 쿼리 3개(
getHistoryList,getQueueList,deleteQueue)에서notnull="notnull"제거notnull이 있으면 필터 없이 전체 조회 시에도 WHERE 조건이 걸려 결과 0건
v1.0.2 — UI 수정
문제: 탭 메뉴(대시보드/큐/히스토리/설정)가 안 보임
<!--@include('_tabs.html')-->문법이 Rhymix 2.1.33 템플릿 엔진에서 동작 안 함- 수정: 4개 템플릿(dashboard/queue/history/config.html)에 탭 HTML 직접 삽입
문제: 변환일 컬럼에 "INVALID FILTER (date_format)" 표시
- Rhymix 템플릿에서
date_format필터 미지원 - 수정:
{$item->regdate|date_format:"Y-m-d H:i"}→{zdate($item->regdate,"Y-m-d H:i")}
문제: select 드롭다운 글씨가 안 보임 (설정 화면)
- Rhymix 관리자 테마 CSS가 select 요소의 padding을 덮어써서 글씨가 밀려남
- 수정:
admin.css에서 select padding8px→3px,!important추가
v1.0.3 — DB 업데이트 수정
문제: 변환 후 DB file_size가 원본 크기 그대로 (동영상 재생 불가)
$oDB->query()직접 SQL 실행이 Rhymix에서 정상 동작하지 않음- 수정:
_updateFileRecord()전면 개편$oDB->query()제거 →executeQuery('wdopt.updateFileRecord')XML 쿼리 방식queries/updateFileRecord.xml신규 추가
v1.0.4 — 파일 경로 형식 수정
문제: 변환 후 파일 다운로드/재생 시 "요청한 파일을 찾을 수 없습니다" 에러
- wdopt가 DB에
/files/attach/...형태로 저장 (앞에.없음) - Rhymix 원본은
./files/attach/...형태를 사용 (file.controller.php:1096) - 수정: Rhymix 원본과 동일한 경로 생성 방식 적용
===============================================================================================
v1.0.5 — 파일 저장 경로 표준화 (Rhymix 커뮤니티 규칙 준수)
배경: Rhymix 핵심 개발자(기진곰)로부터 피드백 수신
"files/attach, files/cache 이외의 경로에 파일을 생성하는 서드파티 자료는 모두 경고하겠습니다."
대형 사이트에서는 files/attach/와 files/cache/를 별도 디스크로 마운트하여 운영하는 경우가 많습니다. 서드파티 모듈이 files/ 하위에 임의 폴더를 생성하면 디스크 분리 구조를 깨뜨리고, 백업 누락 및 용량 관리에 문제가 발생합니다.
변경 사항:
| 항목 | 변경 전 | 변경 후 | 이유 |
|---|---|---|---|
| 백업 기본 경로 | files/wdopt_backup |
files/attach/wdopt_backup |
영구 보존 파일 → files/attach/ 하위 |
| 임시 파일 경로 | files/wdopt_temp |
files/cache/wdopt_temp |
임시/캐시 파일 → files/cache/ 하위 |
수정 파일:
wdopt.class.php—getDefaultConfig()의backup_dir기본값 변경wdopt.controller.php—_getTempDir()경로 변경
추가 보안 참고:
files/attach/는 Rhymix가.htaccess로 디렉토리 리스팅을 차단하고 있어 외부 접근이 자동으로 보호됨- 기존
files/wdopt_backup/은 디렉토리 리스팅이 열려있어 원본 파일이 외부에 노출되는 보안 취약점이 있었음 files/attach/하위로 이동함으로써 별도.htaccess없이도 보안이 보장됨
기존 사용자 마이그레이션 (수동):
- 관리자 → 미디어 최적화 → 기본 설정에서 백업 디렉토리를
files/attach/wdopt_backup으로 변경 후 저장 files/wdopt_backup/안의 백업 파일들을files/attach/wdopt_backup/으로 이동files/wdopt_backup/(빈 폴더) 삭제files/wdopt_temp/(빈 폴더) 삭제 — 임시 파일이므로 삭제해도 무방
📋 전체 버전 요약
| 버전 | 주요 변경 |
|---|---|
| v1.0.0 | 최초 릴리즈 — 이미지(WebP), GIF, 동영상(H.264/H.265) 변환 지원 |
| v1.0.1 | 트리거 등록 방식 변경 (eventHandlers → insertTrigger), DB 호환성 수정 |
| v1.0.2 | 탭 메뉴 직접 삽입, date_format → zdate(), select CSS padding 수정 |
| v1.0.3 | DB file_size 업데이트 수정 ($oDB→query → executeQuery XML 방식) |
| v1.0.4 | uploaded_filename 경로 형식 수정 (./files/... Rhymix 표준 준수) |
| v1.0.5 | 파일 저장 경로 표준화 (백업→files/attach, 임시→files/cache) |
==================================================================================
v1.0.6 — 모듈명 변경 (2026-05-28)
기존 wdopt → slimmer로 전면 리네임. 얼음조각티 님의 피드백 반영 (wdopt는 기존 판매 모듈의 약자와 동일).
- 모듈명, 클래스명, 액션명, 쿼리명 전부
slimmer로 변경 - DB 테이블명:
wdopt_queue→slimmer_queue,wdopt_history→slimmer_history - 모듈 타이틀: "WD 미디어 최적화" → "Slimmer 미디어 최적화"
- 백업 경로:
files/attach/slimmer_backup - 임시 경로:
files/cache/slimmer_temp - 기존 wdopt와 별개 모듈 (신규 설치 필요)
==================================================================================
php
// Rhymix 원본 (file.controller.php:1096)
$args->uploaded_filename = './' . substr($uploaded_filename, strlen(RX_BASEDIR));
// wdopt v1.0.4 (동일 방식)
$relative_path = './' . substr($new_path, strlen(RX_BASEDIR));
⚠ 알려진 이슈 / 향후 과제
| 항목 | 상태 | 설명 |
|---|---|---|
| Cron 자동 실행 | 미설정 | Windows 작업 스케줄러 또는 Rhymix Cron에 등록 필요 |
| 동영상 인코딩 부하 | 주의 | 대용량 영상 변환 시 서버 CPU 부하 → 배치 크기 1, 야간 실행 권장 |
| Cloudflare 캐시 | 주의 | CSS/파일 수정 시 Cloudflare 캐시 퍼지 필요 |
| 처리 중 2건 잔류 | 확인 필요 | 기존 처리 중 상태로 남은 항목 정리 필요 |
| 이미지 WebP 변환 후 본문 | 확인 필요 | 본문에 이미 삽입된 img 태그의 확장자가 .png인데 실제 파일이 .webp인 경우 |
📋 PHP 7.4 호환 규칙 (적용됨)
strpos()사용 (str_starts_with()대신)new BaseObject()사용 (new Object()대신)switch사용 (match대신)$args = new stdClass()초기화- named arguments 미사용
이 모듈은 BSplus 서버 전용으로 개발되었으며, Rhymix 2.1.33 + PHP 7.4 + Windows XAMPP 환경에서 테스트되었습니다.
=================== 2026. 06.05 ===================
Slimmer 미디어 최적화 — 개발 리포트 (계속)
제작: 불패의초인 (BSplus) — https://bsplus.net/ 환경: Rhymix 2.1.33 / PHP 7.4 / Windows XAMPP / Cloudflare
v1.1.x 이전 — MKV 다중 트랙 / 큐 비동기 / 워커 고도화
v1.0.6(모듈명 slimmer 변경) 이후 v1.1.x 계열에서 아래 기능들이 순차 추가됨.
| 항목 | 내용 |
|---|---|
| MKV 다중 트랙 선택 | ffprobe로 오디오/자막 트랙 자동 감지, 큐 화면에서 트랙 선택 UI 제공 |
| GPU NVENC 인코딩 | h264_nvenc / hevc_nvenc 코덱 지원 추가 |
| 큐 AJAX 비동기 처리 | 파일별 AJAX 순차 처리 방식으로 변경 (브라우저 타임아웃 방지) |
| worker.php | PHP CLI 백그라운드 워커 추가 (proc_open + FFmpeg progress 실시간 파싱) |
| 진행률 표시 | out_time_us 파싱으로 인코딩 진행% DB 저장 및 UI 표시 |
| Cron 워커 | 외부 URL 호출로 큐 자동 처리 (worker_secret 인증키 지원) |
| 히스토리 재인코딩 | 원본 백업 보존 시 히스토리에서 재인코딩 큐 재등록 가능 |
| 전체 스캔 | 기존 미변환 파일 일괄 스캔 후 큐 등록 기능 |
| PHP 8 호환 | &getModel → getModel, new Object → new BaseObject 등 전면 점검 |
Slimmer 미디어 최적화 모듈 — 패치 노트
v1.1.9 (2026-06-07)
🐛 버그 수정
[핵심] API 모드에서 변환 완료 후 큐가 사라지는 문제 수정
worker.php에서api_submitted결과를 체크하지 않아 큐를 즉시 삭제하던 문제 수정_processItemViaApi()에서 파일 전송 전에 먼저api_pending상태로 변경하도록 순서 수정triggerAfterDeleteFile()에서file_srl대신queue_srl로 삭제하고status명시적으로 넘기도록 수정 —api_pending상태 큐 보호slimmer_api_worker.php콜백 전송 실패 시 응답 내용을 로그에 남기도록 개선 (재시도 1회 포함)
[핵심] API 모드 동작 방식 변경 — 콜백 방식 → 동기 폴링 방식
- 기존: SAS 서버가 완료 후 콜백 URL 호출 → Rhymix가 파일 교체
- 변경: Rhymix worker가 SAS에 직접 폴링하여 완료 시 파일 교체
- 콜백 타이밍 이슈 및
queue not found오류 근본 해결 - 카페24 등 외부 웹호스팅 환경에서도 SAS API 키만으로 사용 가능
✨ 개선
slimmer_api_worker.php: 콜백 응답 로깅 강화 (성공/실패/재시도 응답 모두 기록)worker.php:api_pending큐 자동 폴링 블록 추가 — 워커가 직접 SAS 상태 확인 후 다운로드+파일교체 수행
# Slimmer 미디어 최적화 — 패치 노트
---
## v1.2.2.1 (2026-06-18)
### 버그 수정
- **레이스컨디션 — 파일 손상** : 로컬 워커와 JS 폴링이 동시에 `_apiDownloadResult()`를 호출해 변환된 파일을 JSON 오류 응답으로 덮어쓰는 문제 수정
- Atomic mutex (`claimDownloading.xml`) 추가 — 첫 번째 호출만 다운로드 진행, 나머지는 즉시 반환
- Content-Type 검증 추가 — `application/json` 응답을 파일로 저장하는 케이스 차단
- 파일 크기 검증 추가 — 다운로드 불완전 시 원본 보존
- **큐 항목 소실 (`pollAndFinishApiJob`)** : JS 폴링이 워커보다 먼저 SAS 'done' 상태를 감지해 다운로드 후 `deleteQueue`까지 실행, 큐 항목이 사라지는 문제 수정
- 워커 lock 파일(60초 TTL) 체크 추가 — 워커 활성 상태면 다운로드 위임
- SAS 'downloaded' 상태 핸들러 추가 — DB 재확인 후 적절한 상태 반환
- **재인코딩 버튼 동작 안 함** : `procSlimmerAdminReEncode()`에서 `backup_path`가 절대경로일 때 기준 경로를 이중으로 붙이는 문제 수정; 필드명 오타 `original_size` → `source_size` 수정
- **`deleteQueue.xml` 조건 무시** : `operation="not_equal"` → `operation="notequal"` 수정 — `api_pending` 상태 보호 조건이 실제로 적용되지 않던 문제
- **설정 완료하기 버튼이 사라지지 않는 문제** : `_hasIndex()` 함수가 항상 `false`를 반환해 `checkUpdate()`가 영구적으로 `true`를 반환하던 문제 수정. `_hasIndex()` 제거 후 `moduleUpdate()`에서 `@` 에러 억제로 재작성
- **미존재 액션 참조** : `module.xml`에서 구현 없이 등록만 된 `getSlimmerDashboardData` 액션 제거
### 보안
- `module.xml`에 `<grants>` 블록 추가 및 모든 admin/API 액션에 `grant="manager"` 적용 — URL 직접 호출로 비인가 접근이 가능하던 문제 차단
- `procSlimmerCronWorker`, `dispSlimmerApiCallback`은 독립 키 인증 방식 유지 (grant 미적용)
- 전체 PHP 파일(`controller`, `admin.controller`, `view`, `admin.view`, `model`, `class`)에 `if (!defined('__XE__')) exit;` 가드 추가
### 개선
- **SAS 워커 중복 실행 방지** : `slimmer_api_worker.php`에 lock 파일 30초 TTL 체크 추가 — 재시도 버튼 연타 시 워커가 20개 이상 동시 실행되어 FFmpeg 오류(E05)가 발생하던 문제 근본 차단
- **SAS 응답 timeout 단축** : `procSlimmerAdminGetProgress` SAS 상태 조회 timeout 5초 → 2초 (로컬 서버 기준 충분한 여유)
- **`worker_alive` 폴링 필드 추가** : `procSlimmerAdminGetProgress` AJAX 응답에 `worker_alive` 필드 추가 — 서버 lock 파일 기준 30초 이내면 `true`. JS가 이 값을 받아 브라우저/탭에 관계없이 3초 내에 버튼 상태를 동기화
- **관리자 UI — 버튼 연타 방지**
- 실행 버튼 클릭 후 30초 카운트다운 비활성화 (같은 탭 내 연타 차단)
- `localStorage` 기반 탭 간 쿨다운 공유 (같은 브라우저 멀티탭 차단)
- `worker_alive` 서버 응답 기반 버튼 동기화 (다른 브라우저/기기에서도 동기화)
- 큐 페이지 상단에 동시 실행 주의 공지 추가
- **`procSlimmerAdminRetryFailed()` 구조 개선** : 날쿼리 → `retryFailedQueue.xml` + `executeQuery()`로 교체
- **`idx_api_job_id` 인덱스 자동 적용** : `moduleUpdate()`에서 `slimmer_queue.api_job_id` 인덱스를 자동 생성 — 기존 설치 환경에서 수동 SQL 없이 적용됨 (이미 존재하면 에러 억제)
- **`dispSlimmerApiCallback()` 뮤텍스 가드** : `status !== 'processing'` 상태 체크 추가 — 중복 콜백 처리 차단
### 내부 구조
- `claimDownloading.xml` 쿼리 추가 (조건부 UPDATE atomic mutex)
- `retryFailedQueue.xml` 쿼리 추가
- `slimmer_queue` 스키마에 `idx_api_job_id` 인덱스 정의 추가
- `slimmer.admin.view.php`의 중복 `dispSlimmerApiCallback()` 함수 제거 (dead code — `type="view"` 라우팅으로 도달 불가)
---
===============================================================================
다운로드 :
===============================================================================
미리보기


파일 필요하신분 문의 바랍니다. 그리고 버그 발생 또한 문의 바랍니다. 세상에 완벽은 없습니다.