diff --git a/convert_result/09c953a0.pdf b/convert_result/09c953a0.pdf
new file mode 100644
index 0000000..0a0982c
Binary files /dev/null and b/convert_result/09c953a0.pdf differ
diff --git a/convert_result/09c953a0.xml b/convert_result/09c953a0.xml
new file mode 100644
index 0000000..0e76b55
--- /dev/null
+++ b/convert_result/09c953a0.xml
@@ -0,0 +1,714 @@
+
+
+
+
+ 09c953a0
+
+ 09c953a0
+
+ Music21
+
+ 2025-05-19
+ music21 v.9.5.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+ 4
+
+
+
+ G
+ 2
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ E
+ 4
+
+ 5040
+ eighth
+ up
+ begin
+
+
+
+
+
+
+ E
+ 4
+
+ 5040
+ eighth
+ up
+ end
+
+
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 리
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 가
+
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 한
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+ single
+ 집
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+ single
+ 에
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 잎
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+ single
+ 아
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 5040
+ eighth
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+ single
+ 엄
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 5040
+ eighth
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 애
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ J
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 아
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 베
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 동
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 동
+
+
+
+
+ B
+ 4
+
+ 20160
+ half
+
+ single
+ 해
+
+
+
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 엄
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 날
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 씨
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 해
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 애
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 기
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+ down
+ begin
+
+
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+ down
+ end
+
+ single
+ 무
+
+
+
+
+ B
+ 4
+
+ 5040
+ eighth
+ down
+ begin
+
+ single
+ 귀
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+ down
+ end
+
+ single
+ 여
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+ single
+ 위
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ E
+ 5
+
+ 10080
+ quarter
+
+ single
+ 으
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ F
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 으
+
+
+
+
+ B
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 잘
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 한
+
+
+
+
+ E
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ 10080
+ quarter
+
+
+ light-light
+
+
+
+
+
+
+ 40320
+
+
+
+
\ No newline at end of file
diff --git a/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.pdf b/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.pdf
new file mode 100644
index 0000000..67252ec
Binary files /dev/null and b/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.pdf differ
diff --git a/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.xml b/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.xml
new file mode 100644
index 0000000..5e31ca6
--- /dev/null
+++ b/convert_result/35f4eef0-ce39-41fb-9a14-a4fb6c85ad29.xml
@@ -0,0 +1,783 @@
+
+
+
+
+ 35f4eef0-ce39-41fb-9a14-a4fb6c85ad29
+
+ 35f4eef0-ce39-41fb-9a14-a4fb6c85ad29
+
+ Music21
+
+ 2025-05-19
+ music21 v.9.5.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+ 9
+
+
+
+ G
+ 2
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 곰
+
+
+
+
+ E
+ -1
+ 4
+
+ 5040
+ eighth
+ up
+ begin
+
+
+
+
+
+
+ E
+ -1
+ 4
+
+ 5040
+ eighth
+ up
+ end
+
+
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 리
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 가
+
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 한
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+
+ single
+ 집
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+
+ single
+ 에
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 잎
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+
+ single
+ 아
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 5040
+ eighth
+ natural
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+
+ single
+ 엄
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 5040
+ eighth
+ natural
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 애
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ J
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 아
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 베
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 곰
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 동
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 동
+
+
+
+
+ B
+ -1
+ 4
+
+ 20160
+ half
+
+ single
+ 해
+
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 엄
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 곰
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 날
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 씨
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 해
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 애
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 기
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 곰
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 은
+
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+ down
+ begin
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ down
+ end
+
+ single
+ 무
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ down
+ begin
+
+ single
+ 귀
+
+
+
+
+ C
+ 0
+ 5
+
+ 5040
+ eighth
+ natural
+ down
+ end
+
+ single
+ 여
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 위
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ E
+ -1
+ 5
+
+ 10080
+ quarter
+ flat
+
+ single
+ 으
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+
+
+
+
+
+ F
+ 0
+ 5
+
+ 10080
+ quarter
+ natural
+
+ single
+ 으
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+
+
+
+
+
+ G
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 잘
+
+
+
+
+ F
+ 0
+ 4
+
+ 10080
+ quarter
+ natural
+
+ single
+ 한
+
+
+
+
+ E
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+
+
+
+
+
+ 10080
+ quarter
+
+
+ light-light
+
+
+
+
+
+
+ 40320
+
+
+
+
\ No newline at end of file
diff --git a/src/services/mypage_service.py b/convert_result/98f55ceb-b3a2-4b39-bbba-1768dcee6396.txt
similarity index 100%
rename from src/services/mypage_service.py
rename to convert_result/98f55ceb-b3a2-4b39-bbba-1768dcee6396.txt
diff --git a/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.pdf b/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.pdf
new file mode 100644
index 0000000..389ed86
Binary files /dev/null and b/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.pdf differ
diff --git a/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.xml b/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.xml
new file mode 100644
index 0000000..5b583ce
--- /dev/null
+++ b/convert_result/afbe7119-2470-42f5-a7cb-c5a0a144ac51.xml
@@ -0,0 +1,761 @@
+
+
+
+
+ afbe7119-2470-42f5-a7cb-c5a0a144ac51
+
+ afbe7119-2470-42f5-a7cb-c5a0a144ac51
+
+ Music21
+
+ 2025-05-19
+ music21 v.9.5.0
+
+
+
+
+ 7
+ 40
+
+
+
+
+
+
+
+
+
+
+
+
+ 10080
+
+ 6
+
+
+
+ G
+ 2
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ F
+ 1
+ 4
+
+ 5040
+ eighth
+ up
+ begin
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 5040
+ eighth
+ up
+ end
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 리
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 가
+
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 한
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+ single
+ 집
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+ single
+ 에
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 잎
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+ single
+ 아
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+ single
+ 엄
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 5040
+ eighth
+ flat
+
+ single
+ 곰
+
+
+
+
+ 5040
+ eighth
+
+
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 애
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ J
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 곰
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 아
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 베
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 곰
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 동
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 동
+
+
+
+
+ C
+ 1
+ 5
+
+ 20160
+ half
+
+ single
+ 해
+
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 엄
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 곰
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 날
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 씨
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 해
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 애
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 기
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 곰
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 은
+
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+ down
+ begin
+
+
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+ down
+ end
+
+ single
+ 무
+
+
+
+
+ C
+ 1
+ 5
+
+ 5040
+ eighth
+ down
+ begin
+
+ single
+ 귀
+
+
+
+
+ E
+ -1
+ 5
+
+ 5040
+ eighth
+ flat
+ down
+ end
+
+ single
+ 여
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 위
+
+
+
+
+ 10080
+ quarter
+
+
+
+
+
+
+ F
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 으
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+
+
+
+
+
+ G
+ 1
+ 5
+
+ 10080
+ quarter
+
+ single
+ 으
+
+
+
+
+ C
+ 1
+ 5
+
+ 10080
+ quarter
+
+
+
+
+
+
+ B
+ -1
+ 4
+
+ 10080
+ quarter
+ flat
+
+ single
+ 잘
+
+
+
+
+ G
+ 1
+ 4
+
+ 10080
+ quarter
+
+ single
+ 한
+
+
+
+
+ F
+ 1
+ 4
+
+ 10080
+ quarter
+
+
+
+
+
+
+ 10080
+ quarter
+
+
+ light-light
+
+
+
+
+
+
+ 40320
+
+
+
+
\ No newline at end of file
diff --git a/src/models/__init__.py b/src/models/__init__.py
index a135cf6..d92c010 100644
--- a/src/models/__init__.py
+++ b/src/models/__init__.py
@@ -1,4 +1,9 @@
from .db import db
from .user import User
from .score import Score
-from .result import Result
\ No newline at end of file
+from .result import Result
+from .resultScore_save import ResultScoreSave
+from .uploadScore_save import UploadScoreSave
+from .transform import TransformTranspose
+from .transform import TransformLyrics
+from .transform import TransformMelody
\ No newline at end of file
diff --git a/src/routes/auth.py b/src/routes/auth.py
index fc8bf88..9edcfcf 100644
--- a/src/routes/auth.py
+++ b/src/routes/auth.py
@@ -1,9 +1,9 @@
import os
import requests
from flask import Blueprint, redirect, request, jsonify
-from src.config import Config
-from src.services.auth_service import handle_kakao_login, refresh_access_token
from flasgger import swag_from
+from src.config import Config
+from src.services.auth_service import handle_kakao_login, refresh_access_token
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@@ -79,7 +79,9 @@ def kakao_callback():
'type': 'object',
'properties': {
'access_token': {'type': 'string'},
- 'refresh_token': {'type': 'string'}
+ 'refresh_token': {'type': 'string'},
+ 'user_id': {'type': 'integer'},
+ 'nickname': {'type': 'string'}
}
}
},
@@ -88,23 +90,14 @@ def kakao_callback():
}
}
})
-
def kakao_token():
try:
- # 요청 내용 디버깅 로그
- print("🔥 /auth/kakao/token 요청 도착")
- print("🔥 REQUEST HEADERS:", dict(request.headers))
- print("🔥 RAW BODY:", request.get_data(as_text=True))
-
- # JSON이든 form이든 유연하게 처리
data = request.get_json(silent=True) or request.form or request.values
code = data.get('code') if data else None
if not code:
- print("❌ No code provided")
return jsonify({'error': 'No code provided'}), 400
- # 카카오 토큰 요청
token_url = "https://kauth.kakao.com/oauth/token"
token_data = {
'grant_type': 'authorization_code',
@@ -113,15 +106,11 @@ def kakao_token():
'code': code,
}
token_response = requests.post(token_url, data=token_data)
- print("🔐 Kakao token response:", token_response.status_code, token_response.text)
-
token_json = token_response.json()
access_token = token_json.get('access_token')
if not access_token:
- print("❌ Failed to get access token")
return jsonify({'error': 'Failed to get Kakao access token'}), 400
- # 유저 정보 요청
user_info_url = "https://kapi.kakao.com/v2/user/me"
headers = {"Authorization": f"Bearer {access_token}"}
user_info_response = requests.get(user_info_url, headers=headers)
@@ -135,15 +124,11 @@ def kakao_token():
return jsonify(result), 200
except Exception as e:
- print("❌ Unexpected error in kakao_token:", str(e))
return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
-
-
@auth_bp.route('/refresh', methods=['POST'])
def refresh():
-
"""
JWT 리프레시 토큰을 이용해 액세스 토큰 재발급
---
@@ -155,6 +140,8 @@ def refresh():
application/json:
schema:
type: object
+ required:
+ - refresh_token
properties:
refresh_token:
type: string
@@ -162,15 +149,22 @@ def refresh():
responses:
200:
description: 액세스 토큰 재발급 성공
- schema:
- type: object
- properties:
- access_token:
- type: string
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ access_token:
+ type: string
+ example: "new.access.token"
+ refresh_token:
+ type: string
+ example: "original.refresh.token"
+ 400:
+ description: 리프레시 토큰 누락
401:
description: 토큰 만료 또는 유효하지 않음
"""
-
data = request.get_json()
refresh_token = data.get('refresh_token')
@@ -181,7 +175,11 @@ def refresh():
if error:
return jsonify({"error": error}), 401
- return jsonify({"access_token": new_access_token}), 200
+ return jsonify({
+ "access_token": new_access_token,
+ "refresh_token": refresh_token
+ }), 200
+
@auth_bp.route("/test-token", methods=["POST"])
def issue_test_token():
@@ -191,28 +189,36 @@ def issue_test_token():
tags:
- auth
summary: 테스트용 JWT 토큰 발급
- description: 테스트용 유저 정보를 기반으로 accessToken, refreshToken을 발급합니다.
+ description: 테스트용 유저 정보를 기반으로 access_token, refresh_token을 자동 발급합니다.
responses:
200:
description: 토큰 발급 성공
- schema:
- type: object
- properties:
- access_token:
- type: string
- description: "Access Token"
- refresh_token:
- type: string
- description: "Refresh Token"
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ access_token:
+ type: string
+ description: "Access Token"
+ refresh_token:
+ type: string
+ description: "Refresh Token"
+ user_id:
+ type: integer
+ example: 1
+ nickname:
+ type: string
+ example: "테스트유저"
"""
- # 테스트용 고정 유저 정보
kakao_id = "test_kakao_12345"
nickname = "테스트유저"
profile_image = ""
result = handle_kakao_login(kakao_id, nickname, profile_image)
return jsonify(result), 200
-
+
+
@auth_bp.route("/logout", methods=["POST"])
def logout():
"""
@@ -225,11 +231,13 @@ def logout():
responses:
200:
description: 로그아웃 성공
- schema:
- type: object
- properties:
- message:
- type: string
- example: "로그아웃 완료"
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ example: "로그아웃 완료"
"""
return jsonify({"message": "로그아웃 완료"}), 200
diff --git a/src/routes/mypage_result_score.py b/src/routes/mypage_result_score.py
index 0bb015d..1c147ff 100644
--- a/src/routes/mypage_result_score.py
+++ b/src/routes/mypage_result_score.py
@@ -5,95 +5,107 @@
delete_result_score
)
from src.utils.jwt_util import decode_token
+from flasgger import swag_from
result_score_bp = Blueprint("result_score_bp", __name__, url_prefix="/mypage/result")
-@result_score_bp.route("//save", methods=["POST"])
-def save_result(result_id):
- """
- 변환 결과 저장 (키 변경, 가사, 멜로디)
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- - in: path
- name: result_id
- required: true
- description: 저장할 결과 ID
- schema:
- type: integer
- responses:
- 201:
- description: 변환 결과가 저장되었습니다
- 400:
- description: 이미 저장된 결과입니다
- 401:
- description: 인증 실패
- """
+# ✅ 공통 JWT 인증 함수
+def get_user_id_from_token():
auth_header = request.headers.get("Authorization", None)
if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
+ return None, jsonify({"message": "토큰이 필요합니다"}), 401
token = auth_header.split(" ")[1]
payload, error = decode_token(token)
if error:
- return jsonify({"message": error}), 401
+ return None, jsonify({"message": error}), 401
+
+ return payload["user_id"], None, None
+
+
+@result_score_bp.route("//save", methods=["POST"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '변환 결과 저장 (키 변경, 가사, 멜로디)',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ },
+ {
+ 'name': 'result_id',
+ 'in': 'path',
+ 'required': True,
+ 'description': '저장할 결과 ID',
+ 'schema': {'type': 'integer'}
+ }
+ ],
+ 'responses': {
+ 201: {'description': '변환 결과가 저장되었습니다'},
+ 400: {'description': '이미 저장된 결과입니다'},
+ 401: {'description': '인증 실패'}
+ }
+})
+def save_result(result_id):
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
- user_id = payload["user_id"]
if save_result_score(user_id, result_id):
return jsonify({"message": "변환 결과가 저장되었습니다"}), 201
return jsonify({"message": "이미 저장된 결과입니다"}), 400
@result_score_bp.route("", methods=["GET"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '저장한 변환 결과 목록 조회',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ },
+ {
+ 'name': 'type',
+ 'in': 'query',
+ 'required': False,
+ 'description': '결과 타입 필터 (transpose, lyrics, melody)',
+ 'schema': {
+ 'type': 'string',
+ 'enum': ['transpose', 'lyrics', 'melody']
+ }
+ }
+ ],
+ 'responses': {
+ 200: {
+ 'description': '저장된 변환 결과 목록 반환',
+ 'content': {
+ 'application/json': {
+ 'example': [
+ {
+ 'result_id': 1,
+ 'result_type': 'transpose',
+ 'saved_at': '2025-05-18T12:34:56'
+ }
+ ]
+ }
+ }
+ },
+ 401: {'description': '인증 실패'}
+ }
+})
def get_saved_results():
- """
- 저장한 변환 결과 목록 조회
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- - in: query
- name: type
- required: false
- description: 결과 타입 필터 (transpose, lyrics, melody)
- schema:
- type: string
- responses:
- 200:
- description: 저장된 변환 결과 목록 반환
- content:
- application/json:
- example:
- - result_id: 1
- result_type: "transpose"
- saved_at: "2025-05-18T12:34:56"
- 401:
- description: 인증 실패
- """
- auth_header = request.headers.get("Authorization", None)
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"message": error}), 401
-
- user_id = payload["user_id"]
result_type = request.args.get("type")
saved = get_saved_result_scores(user_id, result_type)
result = [
@@ -108,43 +120,36 @@ def get_saved_results():
@result_score_bp.route("/", methods=["DELETE"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '저장한 변환 결과 삭제',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ },
+ {
+ 'name': 'result_id',
+ 'in': 'path',
+ 'required': True,
+ 'description': '삭제할 변환 결과 ID',
+ 'schema': {'type': 'integer'}
+ }
+ ],
+ 'responses': {
+ 200: {'description': '저장이 해제되었습니다'},
+ 404: {'description': '저장 내역이 없습니다'},
+ 401: {'description': '인증 실패'}
+ }
+})
def delete_result(result_id):
- """
- 저장한 변환 결과 삭제
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- - in: path
- name: result_id
- required: true
- description: 삭제할 변환 결과 ID
- schema:
- type: integer
- responses:
- 200:
- description: 저장이 해제되었습니다
- 404:
- description: 저장 내역이 없습니다
- 401:
- description: 인증 실패
- """
- auth_header = request.headers.get("Authorization", None)
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"message": error}), 401
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
- user_id = payload["user_id"]
if delete_result_score(user_id, result_id):
return jsonify({"message": "저장이 해제되었습니다"}), 200
return jsonify({"message": "저장 내역이 없습니다"}), 404
diff --git a/src/routes/mypage_upload_score.py b/src/routes/mypage_upload_score.py
index 546edd6..a69475b 100644
--- a/src/routes/mypage_upload_score.py
+++ b/src/routes/mypage_upload_score.py
@@ -5,88 +5,90 @@
delete_upload_score
)
from src.utils.jwt_util import decode_token
+from flasgger import swag_from
upload_score_bp = Blueprint("upload_score_bp", __name__, url_prefix="/mypage/score")
-
-@upload_score_bp.route("//save", methods=["POST"])
-def save_score(score_id):
- """
- 업로드한 악보 저장
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- - in: path
- name: score_id
- required: true
- description: 저장할 악보 ID
- schema:
- type: string
- responses:
- 201:
- description: 업로드한 악보가 저장되었습니다
- 400:
- description: 이미 저장된 악보입니다
- 401:
- description: 인증 실패
- """
+# ✅ JWT 인증 공통 함수
+def get_user_id_from_token():
auth_header = request.headers.get("Authorization", None)
if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
-
+ return None, jsonify({"message": "토큰이 필요합니다"}), 401
token = auth_header.split(" ")[1]
payload, error = decode_token(token)
if error:
- return jsonify({"message": error}), 401
+ return None, jsonify({"message": error}), 401
+ return payload["user_id"], None, None
- user_id = payload["user_id"]
+@upload_score_bp.route("//save", methods=["POST"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '업로드한 악보 저장',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ },
+ {
+ 'name': 'score_id',
+ 'in': 'path',
+ 'required': True,
+ 'description': '저장할 악보 ID',
+ 'schema': {'type': 'string'}
+ }
+ ],
+ 'responses': {
+ 201: {'description': '업로드한 악보가 저장되었습니다'},
+ 400: {'description': '이미 저장된 악보입니다'},
+ 401: {'description': '인증 실패'}
+ }
+})
+def save_score(score_id):
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
if save_upload_score(user_id, score_id):
return jsonify({"message": "업로드한 악보가 저장되었습니다"}), 201
return jsonify({"message": "이미 저장된 악보입니다"}), 400
-
@upload_score_bp.route("", methods=["GET"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '저장한 업로드 악보 목록 조회',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ }
+ ],
+ 'responses': {
+ 200: {
+ 'description': '저장된 악보 목록 반환',
+ 'content': {
+ 'application/json': {
+ 'example': [
+ {
+ 'score_id': "abc123",
+ 'saved_at': "2025-05-18T12:34:56"
+ }
+ ]
+ }
+ }
+ },
+ 401: {'description': '인증 실패'}
+ }
+})
def get_saved_scores():
- """
- 저장한 업로드 악보 목록 조회
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- responses:
- 200:
- description: 저장된 악보 목록 반환
- content:
- application/json:
- example:
- - score_id: "abc123"
- saved_at: "2025-05-18T12:34:56"
- 401:
- description: 인증 실패
- """
- auth_header = request.headers.get("Authorization", None)
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"message": error}), 401
-
- user_id = payload["user_id"]
saved = get_saved_upload_scores(user_id)
result = [
{
@@ -97,45 +99,37 @@ def get_saved_scores():
]
return jsonify(result), 200
-
@upload_score_bp.route("/", methods=["DELETE"])
+@swag_from({
+ 'tags': ['Mypage'],
+ 'summary': '저장한 업로드 악보 삭제',
+ 'parameters': [
+ {
+ 'name': 'Authorization',
+ 'in': 'header',
+ 'required': True,
+ 'description': 'Bearer 액세스 토큰',
+ 'schema': {'type': 'string'}
+ },
+ {
+ 'name': 'score_id',
+ 'in': 'path',
+ 'required': True,
+ 'description': '삭제할 악보 ID',
+ 'schema': {'type': 'string'}
+ }
+ ],
+ 'responses': {
+ 200: {'description': '저장이 해제되었습니다'},
+ 404: {'description': '저장 내역이 없습니다'},
+ 401: {'description': '인증 실패'}
+ }
+})
def delete_score(score_id):
- """
- 저장한 업로드 악보 삭제
- ---
- tags:
- - Mypage
- parameters:
- - in: header
- name: Authorization
- required: true
- description: Bearer 액세스 토큰
- schema:
- type: string
- - in: path
- name: score_id
- required: true
- description: 삭제할 악보 ID
- schema:
- type: string
- responses:
- 200:
- description: 저장이 해제되었습니다
- 404:
- description: 저장 내역이 없습니다
- 401:
- description: 인증 실패
- """
- auth_header = request.headers.get("Authorization", None)
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"message": "토큰이 필요합니다"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"message": error}), 401
+ user_id, error_response, status_code = get_user_id_from_token()
+ if error_response:
+ return error_response, status_code
- user_id = payload["user_id"]
if delete_upload_score(user_id, score_id):
return jsonify({"message": "저장이 해제되었습니다"}), 200
return jsonify({"message": "저장 내역이 없습니다"}), 404
diff --git a/src/routes/transform.py b/src/routes/transform.py
index de4bba2..1f17de6 100644
--- a/src/routes/transform.py
+++ b/src/routes/transform.py
@@ -2,7 +2,7 @@
from src.utils.transpose_helper import transpose_key
from src.models.score import Score
from src.models.result import Result
-from src.services.transform_service import perform_transpose, extract_melody
+from src.services.transform_service import perform_transpose, extract_melody, extract_lyrics
transform_bp = Blueprint('transform', __name__)
@@ -48,12 +48,6 @@ def transpose_preview_route():
example: "F → E (shift -1)"
400:
description: 잘못된 요청
- schema:
- type: object
- properties:
- error:
- type: string
- example: "Invalid key: Z"
"""
data = request.get_json()
current_key = data.get('current_key')
@@ -116,12 +110,6 @@ def transform_transpose_route(score_id):
example: "Transpose completed successfully"
404:
description: 악보 ID를 찾을 수 없음
- schema:
- type: object
- properties:
- error:
- type: string
- example: "Score not found"
"""
score = Score.query.get(score_id)
if not score:
@@ -140,6 +128,7 @@ def transform_transpose_route(score_id):
'message': 'Transpose completed successfully'
}), 201
+
@transform_bp.route('/score//lyrics', methods=['POST'])
def lyrics_extract_route(score_id):
"""
@@ -172,31 +161,23 @@ def lyrics_extract_route(score_id):
example: "Lyrics extracted successfully"
404:
description: 악보 ID를 찾을 수 없음
- schema:
- type: object
- properties:
- error:
- type: string
- example: "Score not found"
"""
score = Score.query.get(score_id)
if not score:
return jsonify({'error': 'Score not found'}), 404
- from src.services.transform_service import extract_lyrics
result_id = extract_lyrics(score)
-
result = Result.query.get(result_id)
- text_path = result.text_path if result else f"convert_result/{result_id}.txt"
+ if not result:
+ return jsonify({"error": "Result not found"}), 500
return jsonify({
'result_id': result_id,
- 'text_path': text_path,
+ 'text_path': result.download_path,
'message': 'Lyrics extracted successfully'
}), 200
-
@transform_bp.route('/score//melody', methods=['POST'])
def melody_extract_route(score_id):
"""
@@ -246,12 +227,6 @@ def melody_extract_route(score_id):
example: "Melody extracted from measure 1 to 8"
404:
description: 악보 ID를 찾을 수 없음
- schema:
- type: object
- properties:
- error:
- type: string
- example: "Score not found"
"""
data = request.get_json()
start = data.get('start_measure')
@@ -262,12 +237,12 @@ def melody_extract_route(score_id):
return jsonify({'error': 'Score not found'}), 404
result_id = extract_melody(score, start, end)
-
result = Result.query.get(result_id)
- mp3_path = result.audio_path if result else f"convert_result/{result_id}.mp3"
+ if not result:
+ return jsonify({"error": "Result not found"}), 500
return jsonify({
'result_id': result_id,
- 'mp3_path': mp3_path,
+ 'mp3_path': result.audio_path,
'message': f'Melody extracted from measure {start} to {end}'
}), 200
diff --git a/src/routes/user.py b/src/routes/user.py
index 4170575..09f442c 100644
--- a/src/routes/user.py
+++ b/src/routes/user.py
@@ -4,9 +4,30 @@
from src.utils.jwt_util import decode_token
from flasgger import swag_from
import os
+import uuid
user_bp = Blueprint('user', __name__, url_prefix='/user')
+UPLOAD_FOLDER = 'static/profile_images'
+os.makedirs(UPLOAD_FOLDER, exist_ok=True)
+
+# ✅ 공통 인증 함수
+def get_current_user():
+ auth_header = request.headers.get("Authorization")
+ if not auth_header or not auth_header.startswith("Bearer "):
+ return None, jsonify({"error": "Missing or invalid Authorization header"}), 401
+
+ token = auth_header.split(" ")[1]
+ payload, error = decode_token(token)
+ if error:
+ return None, jsonify({"error": error}), 401
+
+ user = User.query.get(payload["user_id"])
+ if not user:
+ return None, jsonify({"error": "User not found"}), 404
+
+ return user, None, None
+
@user_bp.route('/me', methods=['GET'])
@swag_from({
@@ -34,18 +55,9 @@
}
})
def get_my_info():
- auth_header = request.headers.get("Authorization")
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"error": "Missing or invalid Authorization header"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"error": error}), 401
-
- user = User.query.get(payload["user_id"])
- if not user:
- return jsonify({"error": "User not found"}), 404
+ user, error_resp, status = get_current_user()
+ if error_resp:
+ return error_resp, status
return jsonify({
"user_id": user.id,
@@ -91,21 +103,10 @@ def get_my_info():
404: {'description': 'User not found'}
}
})
-
-
def update_my_info():
- auth_header = request.headers.get("Authorization")
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"error": "Missing or invalid Authorization header"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"error": error}), 401
-
- user = User.query.get(payload["user_id"])
- if not user:
- return jsonify({"error": "User not found"}), 404
+ user, error_resp, status = get_current_user()
+ if error_resp:
+ return error_resp, status
data = request.get_json()
new_nickname = data.get("nickname")
@@ -121,10 +122,6 @@ def update_my_info():
}), 200
-
-UPLOAD_FOLDER = 'static/profile_images'
-os.makedirs(UPLOAD_FOLDER, exist_ok=True)
-
@user_bp.route("/me/profile-image", methods=["PATCH"])
@swag_from({
'summary' : '프로필 이미지 업로드/변경',
@@ -156,20 +153,10 @@ def update_my_info():
401: {'description': '유효하지 않은 토큰'}
}
})
-
def update_profile_image():
- auth_header = request.headers.get("Authorization")
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"error": "Missing or invalid Authorization header"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"error": error}), 401
-
- user = User.query.get(payload["user_id"])
- if not user:
- return jsonify({"error": "User not found"}), 404
+ user, error_resp, status = get_current_user()
+ if error_resp:
+ return error_resp, status
if 'profile_image' not in request.files:
return jsonify({"error": "No file part"}), 400
@@ -178,8 +165,8 @@ def update_profile_image():
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
- filename = secure_filename(file.filename)
- file_path = os.path.join(UPLOAD_FOLDER, filename)
+ unique_filename = f"{uuid.uuid4().hex}_{secure_filename(file.filename)}"
+ file_path = os.path.join(UPLOAD_FOLDER, unique_filename)
file.save(file_path)
user.profile_image = file_path
@@ -190,10 +177,11 @@ def update_profile_image():
"nickname": user.nickname,
"profile_image": user.profile_image
}), 200
-
+
+
@user_bp.route("/me", methods=["DELETE"])
@swag_from({
- 'summary': '회원 탈회',
+ 'summary': '회원 탈퇴',
'tags': ['user'],
'security': [{'Bearer': []}],
'responses': {
@@ -211,27 +199,15 @@ def update_profile_image():
404: {'description': '사용자 없음'}
}
})
-
-
-
def delete_my_account():
- auth_header = request.headers.get("Authorization")
- if not auth_header or not auth_header.startswith("Bearer "):
- return jsonify({"error": "Missing or invalid Authorization header"}), 401
-
- token = auth_header.split(" ")[1]
- payload, error = decode_token(token)
- if error:
- return jsonify({"error": error}), 401
-
- user = User.query.get(payload["user_id"])
- if not user:
- return jsonify({"error": "User not found"}), 404
+ user, error_resp, status = get_current_user()
+ if error_resp:
+ return error_resp, status
db.session.delete(user)
db.session.commit()
return jsonify({
- "user_id": payload["user_id"],
+ "user_id": user.id,
"message": "User successfully deleted"
- }), 200
\ No newline at end of file
+ }), 200
diff --git a/src/services/result_service.py b/src/services/result_service.py
index b342cdb..4b731dd 100644
--- a/src/services/result_service.py
+++ b/src/services/result_service.py
@@ -9,16 +9,20 @@ def normalize_path(path):
# 5-1: 키 변경된 악보 결과 이미지
def get_transpose_image(result_id):
result = Result.query.get(result_id)
+ if not result or result.type != 'transpose':
+ raise FileNotFoundError("키 변경 악보 이미지 결과를 찾을 수 없습니다")
image_path = normalize_path(result.image_path)
- if not result or result.type != 'transpose' or not image_path or not os.path.exists(image_path):
+ if not image_path or not os.path.exists(image_path):
raise FileNotFoundError("키 변경 악보 이미지 결과를 찾을 수 없습니다")
return send_file(image_path, mimetype='image/png')
# 키 변경된 PDF 파일 다운로드
def download_transpose_file(result_id):
result = Result.query.get(result_id)
+ if not result or result.type != 'transpose':
+ raise FileNotFoundError("키 변경 악보 다운로드 파일을 찾을 수 없습니다")
download_path = normalize_path(result.download_path)
- if not result or result.type != 'transpose' or not download_path or not os.path.exists(download_path):
+ if not download_path or not os.path.exists(download_path):
raise FileNotFoundError("키 변경 악보 다운로드 파일을 찾을 수 없습니다")
return send_file(download_path, as_attachment=True)
@@ -32,8 +36,10 @@ def get_lyrics_text(result_id):
# 가사 다운로드 파일
def download_lyrics_file(result_id):
result = Result.query.get(result_id)
+ if not result or result.type != 'lyrics':
+ raise FileNotFoundError("가사 다운로드 파일을 찾을 수 없습니다")
download_path = normalize_path(result.download_path)
- if not result or result.type != 'lyrics' or not download_path or not os.path.exists(download_path):
+ if not download_path or not os.path.exists(download_path):
raise FileNotFoundError("가사 다운로드 파일을 찾을 수 없습니다")
return send_file(download_path, as_attachment=True)
@@ -47,7 +53,9 @@ def get_melody_meta_info(result_id):
# 멜로디 오디오 MP3
def get_melody_audio(result_id):
result = Result.query.get(result_id)
+ if not result or result.type != 'melody':
+ raise FileNotFoundError("멜로디 오디오 파일을 찾을 수 없습니다")
audio_path = normalize_path(result.audio_path)
- if not result or result.type != 'melody' or not audio_path or not os.path.exists(audio_path):
+ if not audio_path or not os.path.exists(audio_path):
raise FileNotFoundError("멜로디 오디오 파일을 찾을 수 없습니다")
return send_file(audio_path, mimetype='audio/mpeg', as_attachment=True)
diff --git a/src/services/transform_service.py b/src/services/transform_service.py
index d201d7f..acbd62f 100644
--- a/src/services/transform_service.py
+++ b/src/services/transform_service.py
@@ -7,10 +7,9 @@
from music21 import midi, stream, note
from src.models.db import db
from src.models.score import Score
-from src.models.result import Result # ✅ 통합된 Result 모델
+from src.models.result import Result
from ML.src.makexml.MakeScore import MakeScore
-# ✅ OS별 실행 경로 설정
if platform.system() == "Windows":
FFMPEG_CMD = r"C:\ProgramData\chocolatey\lib\ffmpeg\tools\ffmpeg\bin\ffmpeg.exe"
TIMIDITY_CMD = "timidity"
@@ -20,11 +19,7 @@
TIMIDITY_CMD = "timidity"
MSCORE_CMD = os.path.join("squashfs-root", "mscore4portable")
-
def perform_transpose(score: Score, shift: int) -> int:
- """
- 키 변경을 수행하고 결과 PDF를 생성해 Result 테이블에 저장
- """
image_path = os.path.join('uploaded_scores', score.original_filename)
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
img_list = [img]
@@ -41,7 +36,6 @@ def perform_transpose(score: Score, shift: int) -> int:
MakeScore.score_to_xml(transposed_score, result_id)
- print("[Transpose] 실행 명령어:", [MSCORE_CMD, xml_path, "-o", pdf_path])
subprocess.run([MSCORE_CMD, xml_path, "-o", pdf_path], check=True)
result = Result(
@@ -54,11 +48,7 @@ def perform_transpose(score: Score, shift: int) -> int:
return result.id
-
def extract_melody(score: Score, start_measure: int, end_measure: int) -> int:
- """
- 악보에서 특정 마디 범위의 멜로디를 추출하여 MP3 파일로 저장 후 Result 테이블에 저장
- """
image_path = os.path.join('uploaded_scores', score.original_filename)
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
img_list = [img]
@@ -98,11 +88,7 @@ def extract_melody(score: Score, start_measure: int, end_measure: int) -> int:
return result.id
-
def extract_lyrics(score: Score) -> int:
- """
- 악보에서 가사를 추출하여 텍스트로 저장하고 Result 테이블에 저장
- """
image_path = os.path.join('uploaded_scores', score.original_filename)
img = cv2.imread(image_path, cv2.IMREAD_COLOR)
if img is None:
@@ -111,15 +97,14 @@ def extract_lyrics(score: Score) -> int:
img_list = [img]
score_obj = MakeScore.make_score(img_list)
- # 가사 추출
lyrics = []
for el in score_obj.recurse():
if isinstance(el, note.Note) and el.lyric:
lyrics.append(el.lyric.strip())
lyrics_text = "\n".join(filter(None, lyrics)).strip()
- if not lyrics_text:
- raise ValueError("추출된 가사가 없습니다")
+ # if not lyrics_text:
+ # raise ValueError("추출된 가사가 없습니다") # 예외 제거
result_id = str(uuid.uuid4())
convert_dir = 'convert_result'
@@ -132,8 +117,8 @@ def extract_lyrics(score: Score) -> int:
result = Result(
score_id=score.id,
type='lyrics',
- text_path=text_path, # 다운로드용
- text_content=lyrics_text # ✅ API 조회용
+ download_path=text_path,
+ text_content=lyrics_text
)
db.session.add(result)
db.session.commit()
diff --git a/src/services/user_service.py b/src/services/user_service.py
deleted file mode 100644
index e69de29..0000000