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 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(); 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(); 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 diffs) { SetInteractable(false); progressGroup.SetActive(true); Debug.Log("[SongCreator] GenerateFlow 시작"); // 1단계: Beat Sage 전송 → 변환 Dictionary> 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> 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; } }