Add ffmpeg prep and music ogg download to rp installer
Add src/installer-rp/ffmpeg.ts that downloads BtbN/FFmpeg-Builds win64-gpl zip into %appdata%/.mc_custom/, extracts ffmpeg.exe out of bin/, drops it at %appdata%/.mc_custom/ffmpeg.exe and verifies with `ffmpeg -version`. Reuses existing extract-zip dep. Add src/installer-rp/music.ts that spawns yt-dlp with --extract-audio --audio-format vorbis --ffmpeg-location <ffmpeg.exe> to produce <tempDir>/NN.ogg per track. Streams yt-dlp stdout to the log channel and reports stderr on non-zero exit. Wire both into the install IPC handler: step 2-1 now preps both binaries, step 2-2 iterates the music list and downloads each track. Track the currently running child process in state so the cancel button can kill it instead of waiting for it to finish. Image / zip / place steps remain stubbed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,13 @@ import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import fsp from 'node:fs/promises'
|
||||
import { URL } from 'node:url'
|
||||
import type { ChildProcess } from 'node:child_process'
|
||||
import type { Manifest, PackList } from '../shared/types.js'
|
||||
import { getAppDataDir, getMcCustomDir } from '../shared/paths.js'
|
||||
import type { RpFetchedPack } from './types.js'
|
||||
import { ensureYtDlpExe } from './ytdlp.js'
|
||||
import { ensureFfmpegExe } from './ffmpeg.js'
|
||||
import { downloadMusicTrack } from './music.js'
|
||||
|
||||
interface RpInstallerState {
|
||||
manifestUrl: string
|
||||
@@ -17,6 +20,8 @@ interface RpInstallerState {
|
||||
selectedKey: string | null
|
||||
/** 현재 설치 진행 중인지 여부. 취소 신호로 사용. */
|
||||
cancelRequested: boolean
|
||||
/** 현재 실행 중인 외부 프로세스(yt-dlp/ffmpeg). 취소 시 kill 대상. */
|
||||
currentChild: ChildProcess | null
|
||||
}
|
||||
|
||||
const DEFAULT_MANIFEST_URL = process.env.MANIFEST_URL ?? 'http://127.0.0.1:3000/manifest.json'
|
||||
@@ -26,7 +31,8 @@ const state: RpInstallerState = {
|
||||
baseUrl: deriveBaseUrl(DEFAULT_MANIFEST_URL),
|
||||
packs: new Map(),
|
||||
selectedKey: null,
|
||||
cancelRequested: false
|
||||
cancelRequested: false,
|
||||
currentChild: null
|
||||
}
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
@@ -142,17 +148,42 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
|
||||
await fsp.mkdir(tempRoot, { recursive: true })
|
||||
|
||||
try {
|
||||
// 2-1. yt-dlp 준비 (%appdata%/.mc_custom/yt-dlp.exe)
|
||||
// 2-1. yt-dlp / ffmpeg 준비 (%appdata%/.mc_custom/{yt-dlp,ffmpeg}.exe)
|
||||
sendLog('yt-dlp 준비 중…')
|
||||
const ytDlpBin = await ensureYtDlpExe(sendLog)
|
||||
sendLog(`yt-dlp 경로: ${ytDlpBin}`)
|
||||
throwIfCancelled()
|
||||
sendLog('ffmpeg 준비 중…')
|
||||
const ffmpegBin = await ensureFfmpegExe(sendLog)
|
||||
sendLog(`ffmpeg 경로: ${ffmpegBin}`)
|
||||
throwIfCancelled()
|
||||
|
||||
// 2-2. 음악 다운로드 (1번부터 순차, ogg 변환)
|
||||
sendLog(`음악 다운로드 시작 (${pack.list.music.length}곡) … (TODO)`)
|
||||
const musicDir = path.join(tempRoot, 'music')
|
||||
await fsp.mkdir(musicDir, { recursive: true })
|
||||
sendLog(`음악 다운로드 시작 (${pack.list.music.length}곡)`)
|
||||
for (let i = 0; i < pack.list.music.length; i++) {
|
||||
throwIfCancelled()
|
||||
sendLog(`${i + 1}번 노래 다운로드 중… (TODO)`)
|
||||
const entry = pack.list.music[i]
|
||||
sendLog(`${i + 1}번 노래 다운로드 중…`)
|
||||
try {
|
||||
const outPath = await downloadMusicTrack({
|
||||
ytdlpExe: ytDlpBin,
|
||||
ffmpegExe: ffmpegBin,
|
||||
tempDir: musicDir,
|
||||
index: i + 1,
|
||||
url: entry.url,
|
||||
log: sendLog,
|
||||
onChild: (c) => { state.currentChild = c }
|
||||
})
|
||||
state.currentChild = null
|
||||
sendLog(`${i + 1}번 노래 완료: ${path.basename(outPath)}`)
|
||||
} catch (err) {
|
||||
state.currentChild = null
|
||||
// 취소된 경우는 throwIfCancelled 가 일관된 메시지로 다시 던지게 함.
|
||||
if (state.cancelRequested) throwIfCancelled()
|
||||
throw new Error(`${i + 1}번 노래 다운로드 실패: ${(err as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 2-3. 사진 다운로드 + painting variant 정규화
|
||||
@@ -185,6 +216,9 @@ ipcMain.handle('rp:install:start', async (): Promise<{ resourcepackPath: string
|
||||
ipcMain.handle('rp:install:cancel', async () => {
|
||||
state.cancelRequested = true
|
||||
sendLog('취소 요청됨. 진행 중 작업을 중단합니다…')
|
||||
if (state.currentChild && !state.currentChild.killed) {
|
||||
state.currentChild.kill()
|
||||
}
|
||||
})
|
||||
|
||||
function throwIfCancelled(): void {
|
||||
|
||||
Reference in New Issue
Block a user