Files
BeatSaber/Assets/Script/SongCreatorManager.cs
T

223 lines
8.3 KiB
C#
Raw Normal View History

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class SongCreatorManager : MonoBehaviour
{
[Header("음원 선택")]
[SerializeField] private TMP_Dropdown audioDropdown;
[SerializeField] private Button refreshBtn;
[SerializeField] private TMP_Text inputPathHint; // 파일 넣는 경로 안내
[Header("메타데이터")]
[SerializeField] private TMP_InputField titleInput;
[SerializeField] private TMP_InputField artistInput;
[SerializeField] private TMP_InputField bpmInput;
[Header("난이도")]
[SerializeField] private Toggle toggleNormal;
[SerializeField] private Toggle toggleHard;
[SerializeField] private Toggle toggleExpert;
[SerializeField] private Toggle toggleExpertPlus;
[Header("액션")]
[SerializeField] private Button generateButton;
[SerializeField] private Button manualEditorButton; // 작은 버튼
[SerializeField] private Button backButton;
[SerializeField] private string introSceneName = "Intro";
[Header("진행 상태")]
[SerializeField] private GameObject progressGroup;
[SerializeField] private TMP_Text statusText;
[SerializeField] private Slider progressSlider;
[Header("연결")]
[SerializeField] private BeatSageUploader beatSageUploader;
[SerializeField] private NasPublisher nasPublisher;
[Header("씬")]
[SerializeField] private string mapEditorScene = "MapEditorScene";
// Quest: /sdcard/Android/data/{packageName}/files/input/ 로 ADB 복사
private static string InputPath =>
Path.Combine(Application.persistentDataPath, "input");
private readonly List<string> audioFiles = new();
// ── Unity ────────────────────────────────────────────────
private void Start()
{
Directory.CreateDirectory(InputPath);
if (inputPathHint != null)
inputPathHint.text = $"음원 경로: {InputPath}";
refreshBtn.onClick.AddListener(RefreshAudioList);
generateButton.onClick.AddListener(OnGenerateClicked);
manualEditorButton.onClick.AddListener(() => SceneManager.LoadScene(mapEditorScene));
backButton?.onClick.AddListener(() => SceneManager.LoadScene(introSceneName));
progressGroup.SetActive(false);
RefreshAudioList();
}
// ── 음원 목록 갱신 ───────────────────────────────────────
private void RefreshAudioList()
{
audioFiles.Clear();
audioDropdown.ClearOptions();
string[] files = Directory.GetFiles(InputPath, "*.mp3");
var options = new List<string>();
foreach (string f in files)
{
audioFiles.Add(f);
options.Add(Path.GetFileNameWithoutExtension(f));
}
if (options.Count == 0)
options.Add("-- .mp3 파일 없음 --");
audioDropdown.AddOptions(options);
}
// ── 생성 버튼 ────────────────────────────────────────────
private void OnGenerateClicked()
{
if (audioFiles.Count == 0) { SetStatus("음원 파일이 없습니다."); return; }
if (string.IsNullOrEmpty(titleInput.text)) { SetStatus("곡 제목을 입력해주세요."); return; }
if (!float.TryParse(bpmInput.text, out float bpm) || bpm <= 0)
{ SetStatus("BPM을 올바르게 입력해주세요."); return; }
var diffs = new List<string>();
if (toggleNormal.isOn) diffs.Add("normal");
if (toggleHard.isOn) diffs.Add("hard");
if (toggleExpert.isOn) diffs.Add("expert");
if (toggleExpertPlus.isOn) diffs.Add("expertplus");
if (diffs.Count == 0) { SetStatus("난이도를 하나 이상 선택해주세요."); return; }
string audioPath = audioFiles[audioDropdown.value];
Debug.Log($"[SongCreator] 생성 시작 — 파일: {audioPath}, BPM: {bpm}, 난이도: {string.Join(",", diffs)}");
Debug.Log($"[SongCreator] beatSageUploader={beatSageUploader}, nasPublisher={nasPublisher}");
StartCoroutine(GenerateFlow(audioPath, bpm, diffs));
}
// ── 생성 플로우 ───────────────────────────────────────────
private IEnumerator GenerateFlow(string audioPath, float bpm, List<string> diffs)
{
SetInteractable(false);
progressGroup.SetActive(true);
Debug.Log("[SongCreator] GenerateFlow 시작");
// 1단계: Beat Sage 전송 → 변환
Dictionary<string, List<NoteData>> maps = null;
bool failed = false;
Debug.Log("[SongCreator] BeatSage Upload 호출");
yield return beatSageUploader.Upload(
audioPath, diffs, bpm,
onProgress: p =>
{
progressSlider.value = p * 0.8f;
SetStatus($"{beatSageUploader.CurrentStatus} ({(int)(p * 80)}%)");
},
onComplete: result =>
{
maps = result;
Debug.Log($"[SongCreator] BeatSage 완료 — 난이도 수: {result?.Count}");
},
onError: err =>
{
Debug.LogError($"[SongCreator] BeatSage 오류: {err}");
SetStatus($"오류: {err}");
failed = true;
});
Debug.Log($"[SongCreator] BeatSage 단계 끝 — failed={failed}, maps={maps?.Count}");
if (failed) { SetInteractable(true); yield break; }
// 2단계: NAS 업로드
SongInfo song = BuildSongInfo(audioPath, bpm, maps);
Debug.Log($"[SongCreator] NAS Publish 호출 — song.id={song.id}");
yield return nasPublisher.Publish(
song, audioPath, maps,
onProgress: p =>
{
progressSlider.value = 0.8f + p * 0.2f;
SetStatus($"[4/4] NAS 업로드 중... ({(int)((0.8f + p * 0.2f) * 100)}%)");
},
onComplete: () =>
{
progressSlider.value = 1f;
SetStatus($"완료! '{song.title}' 생성 성공 (100%)");
Debug.Log($"[SongCreator] NAS 업로드 완료");
},
onError: err =>
{
Debug.LogError($"[SongCreator] NAS 오류: {err}");
SetStatus($"NAS 업로드 실패: {err}");
failed = true;
});
SetInteractable(true);
}
// ── 유틸 ─────────────────────────────────────────────────
private SongInfo BuildSongInfo(string audioPath, float bpm,
Dictionary<string, List<NoteData>> maps)
{
string id = titleInput.text.ToLower().Replace(" ", "_");
var diffMap = new DifficultyMap();
foreach (var kv in maps)
{
var info = new DifficultyInfo { noteCount = kv.Value.Count };
switch (kv.Key)
{
case "normal": diffMap.normal = info; break;
case "hard": diffMap.hard = info; break;
case "expert": diffMap.expert = info; break;
case "expertplus": diffMap.expertplus = info; break;
}
}
return new SongInfo
{
id = id,
title = titleInput.text,
artist = artistInput.text,
bpm = bpm,
audioFile = $"music/{id}.mp3",
difficulties = diffMap,
addedAt = DateTime.Now.ToString("yyyy-MM-dd")
};
}
private void SetStatus(string msg)
{
if (statusText != null) statusText.text = msg;
}
private void SetInteractable(bool value)
{
generateButton.interactable = value;
manualEditorButton.interactable = value;
audioDropdown.interactable = value;
refreshBtn.interactable = value;
}
}