问题描述
本人利用Python开发基于人工智能的音乐创作辅助系统项目时,调用Flask框架,在前端网页按下确认按钮后网页前端报错如图所示:

编译器终端输出的信息如图所示:

编译环境
| 编译环境 | 版本 |
|---|---|
| Python 版本 | 3.11 |
| PyCharm | 2023.1.4 (Professional Edition) |
| Flask | 2.3.2 |
主要代码
后端
- AIMusic/app.py
from flask import Flask, request, jsonify, send_from_directory, url_for, render_template
from flask_socketio import SocketIO
from celery import Celery
from music_logic import MusicLogic
from ai_model import AIModel
from ai_model import CustomModel # 引入自定义模型类
from storage_manager import StorageManager
import os
import logging
# Flask 初始化
app = Flask(__name__)
# 启用调试模式
app.debug = True
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['STATIC_FOLDER'] = 'static/assets'
# 创建上传和静态目录(如不存在)
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['STATIC_FOLDER'], exist_ok=True)
# WebSocket 初始化
socketio = SocketIO(app, cors_allowed_origins="*")
# Celery 初始化
celery_app = Celery('tasks', broker='redis://localhost:6379/0')
# 日志配置
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# StorageManager 实例化
storage_manager = StorageManager(app.config['UPLOAD_FOLDER'])
# 初始化模型架构
ai_model = AIModel(
model_class=CustomModel,
input_dim=128, # 根据你的模型输入维度定义
hidden_dim=256, # 隐藏层维度
output_dim=128 # 输出维度
)
ai_model.load_weights(r"E:\AIMusic\models\best_model.pth") # 加载初始权重
music_logic = MusicLogic(ai_model)
# 音乐创作页面路由
@app.route('/music_creation')
def music_creation():
"""渲染音乐创作页面"""
return render_template("music_creation.html")
@app.route('/melody_generation', methods=['GET', 'POST'])
def melody_generation():
if request.method == 'GET':
return render_template("melody_generation.html")
elif request.method == 'POST':
if not request.is_json:
return jsonify({'code': 1, 'msg': 'Invalid request format: JSON required.'}), 400
data = request.get_json()
melody_length = data.get("length")
style_name = data.get("style")
if not isinstance(melody_length, int) or not isinstance(style_name, str):
return jsonify({'code': 1, 'msg': 'Invalid input types'}), 400
if melody_length < 1 or melody_length > 128:
return jsonify({'code': 1, 'msg': 'Length must be between 1 and 128'}), 400
style = style_mapping.get(style_name)
if style is None:
return jsonify({'code': 1, 'msg': 'Invalid style'}), 400
try:
melody = music_logic.generate_melody(melody=melody_length, style=style)
return jsonify({'code': 0, 'msg': 'Melody generated successfully', 'melody': melody})
except Exception as e:
logger.error(f"Melody generation failed: {e}")
return jsonify({'code': 2, 'msg': f'Melody generation failed: {str(e)}'}, 500)
# 和弦编排接口
@app.route('/chord_arrangement', methods=['GET', 'POST'])
def chord_arrangement():
if request.method == 'GET':
return render_template("chord_arrangement.html") # 渲染和弦编排页面
elif request.method == 'POST':
data = request.json
melody = data.get("melody")
if not melody:
return jsonify({'code': 1, 'msg': 'Melody is required for chord arrangement'}), 400
try:
# 生成和弦
chords = music_logic.arrange_chords(melody)
return jsonify({'code': 0, 'msg': 'Chords arranged successfully', 'chords': chords})
except Exception as e:
logger.error(f"Chord arrangement failed: {e}")
return jsonify({'code': 2, 'msg': f'Chord arrangement failed: {str(e)}'})
# 风格定制接口
@app.route('/style_customization', methods=['GET', 'POST'])
def style_customization():
if request.method == 'GET':
return render_template("style_customization.html") # 渲染风格定制页面
elif request.method == 'POST':
data = request.json
style = data.get("style")
if not style:
return jsonify({'code': 1, 'msg': 'Style is required for customization'}), 400
try:
# 模拟风格定制处理逻辑
logger.info(f"Customizing style to: {style}")
return jsonify({'code': 0, 'msg': f'Successfully customized to {style}'})
except Exception as e:
logger.error(f"Style customization failed: {e}")
return jsonify({'code': 2, 'msg': f'Style customization failed: {str(e)}'})
# 系统根目录
@app.route("/")
def home():
"""渲染主页"""
return render_template("index.html")
# 主程序入口
if __name__ == '__main__':
print("Starting server on http://0.0.0.0:5000")
socketio.run(app, host='0.0.0.0', port=5000)
- AIMusic/aimodel.py
import random
import torch
import logging
import os
import pretty_midi
import fluidsynth
from torch import nn
# 初始化日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class CustomModel(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(CustomModel, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
class AIModel:
def __init__(self, model_class=None, input_dim=None, hidden_dim=None, output_dim=None):
"""
初始化 AIModel 实例(仅初始化模型结构)
:param model_class: PyTorch 模型类
:param input_dim: 输入维度
:param hidden_dim: 隐藏层维度
:param output_dim: 输出维度
"""
try:
logger.info("Initializing AIModel...")
# 如果提供了模型类,则实例化模型
if model_class:
# 确保 model_class 是一个类,而不是直接调用自身
self.model = model_class(input_dim, hidden_dim, output_dim)
logger.info("Model architecture initialized.")
else:
# 如果没有提供模型类,则抛出异常
raise ValueError("Model class must be provided to initialize AIModel.")
# 设置设备(支持 GPU)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model.to(self.device)
except Exception as e:
logger.error(f"Failed to initialize AIModel: {str(e)}")
raise RuntimeError(f"Model initialization failed: {str(e)}")
def load_weights(self, model_path):
"""
动态加载模型权重并重建架构
"""
try:
if not os.path.exists(model_path):
raise FileNotFoundError(f"Model file not found: {model_path}")
# 加载模型文件
checkpoint = torch.load(model_path, map_location=self.device)
# 动态重建模型架构
input_size = checkpoint.get('input_size')
hidden_size = checkpoint.get('hidden_size')
output_size = checkpoint.get('output_size')
if not all([input_size, hidden_size, output_size]):
raise ValueError("Invalid checkpoint: missing architecture parameters.")
self.model = CustomModel(input_size, hidden_size, output_size)
self.model.to(self.device)
# 加载权重
state_dict = checkpoint.get('model_state_dict')
if not state_dict:
raise ValueError("Invalid checkpoint: missing state_dict.")
self.model.load_state_dict(state_dict)
self.model.eval()
logger.info(f"Model loaded successfully from {model_path}")
except Exception as e:
logger.error(f"Failed to load model weights: {str(e)}")
raise RuntimeError(f"Failed to load model weights: {str(e)}")
def generate(self, melody, style):
"""
根据输入旋律和风格生成音乐
:param melody: 输入旋律 (str)
:param style: 输入风格 (str)
:return: 生成的音乐数据
"""
try:
# 数据预处理
input_data = self.preprocess(melody, style)
if not isinstance(input_data, torch.Tensor):
raise ValueError("Input data must be a torch.Tensor")
# 确保输入数据是二维张量
if input_data.dim() != 2:
raise ValueError("Input data must be a 2D tensor")
# 模型推理
with torch.no_grad():
logger.info(f"Running inference with melody: {melody} and style: {style}")
output = self.model(input_data)
# 数据后处理
result = self.postprocess(output)
logger.info("Music generation completed successfully.")
return result
except Exception as e:
logger.error(f"Error during music generation: {str(e)}")
raise RuntimeError(f"Music generation failed: {str(e)}")
def generate_audio(self, melody, style, file_path):
# 使用模型生成音乐数据
music_data = self.generate(melody, style)
# 创建一个 PrettyMIDI 对象
midi = pretty_midi.PrettyMIDI()
# 创建一个乐器实例,这里假设使用钢琴
program = pretty_midi.instrument_name_to_program('Acoustic Grand Piano')
instrument = pretty_midi.Instrument(program=program)
# 填充乐器的音符数据
for note in music_data:
note_number = note[0] # MIDI 音符编号
start_time = note[1] # 音符开始时间
end_time = note[2] # 音符结束时间
velocity = note[3] # 音符力度
midi_note = pretty_midi.Note(velocity=velocity, pitch=note_number, start=start_time, end=end_time)
instrument.notes.append(midi_note)
midi.instruments.append(instrument)
# 保存 MIDI 文件
midi.save('temp.mid')
# 使用 FluidSynth 将 MIDI 转换为 WAV
fs = fluidsynth.Synth()
fs.start()
sfid = fs.sfload('path_to_your_soundfont.sf2')
fs.program_select(0, sfid, 0, 0)
fs.midi_to_audio('temp.mid', file_path)
fs.delete()
def generate_melody(self, style, length, filename='output.wav'):
"""
根据音乐风格和长度生成旋律并直接输出音频文件
:param style: 音乐风格 (str)
:param length: 旋律长度 (int)
:param filename: 输出音频文件名 (str)
:return: None, 直接保存音频文件
"""
try:
# 确保长度是整数
if not isinstance(length, int):
length = int(length)
# 调用 generate 方法生成音频文件
self.generate_audio(self.generate_initial_melody(length), style, filename)
logger.info("Melody and audio generation successful.")
except ValueError as ve:
logger.error(f"Invalid length parameter: {str(ve)}")
raise RuntimeError(f"Invalid length parameter: {str(ve)}")
except Exception as e:
logger.error(f"Error in melody and audio generation: {str(e)}")
raise RuntimeError(f"Melody and audio generation failed: {str(e)}")
# def preprocess(self, melody, style):
# # 检查输入的旋律是否为 int 类型,如果是,转换为 str 类型
# if isinstance(melody, int):
# melody = str(melody)
# if isinstance(style, int):
# style = str(style)
# # 以下是可能的预处理器的逻辑,例如将输入拆分为更小的单元
# processed_melody = melody.split()
# processed_style = style.split()
# # 这里可以添加更多的预处理器逻辑
# return processed_melody, processed_style
def generate_initial_melody(self, length):
"""
生成初始随机旋律,用于作为模型输入
:param length: 旋律长度 (int)
:return: 随机生成的初始旋律 (str)
"""
try:
if not isinstance(length, int) or length <= 0:
raise ValueError(f"Length must be a positive integer. Received: {length}")
notes = ["C", "D", "E", "F", "G", "A", "B"]
initial_melody = "-".join(random.choices(notes, k=length))
logger.info(f"Initial melody generated: {initial_melody}")
return initial_melody
except ValueError as ve:
logger.error(f"Invalid length parameter: {str(ve)}")
raise RuntimeError(f"Failed to generate initial melody: {str(ve)}")
except Exception as e:
logger.error(f"Error generating initial melody: {str(e)}")
raise RuntimeError(f"Failed to generate initial melody: {str(e)}")
def preprocess(self, melody, style):
# 检查输入的旋律是否为 int 类型,如果是,转换为 str 类型
if isinstance(melody, int):
melody = str(melody)
if isinstance(style, int):
style = str(style)
# 检查旋律和风格是否已经是列表或可迭代对象
if isinstance(melody, str):
melody = melody.split()
if isinstance(style, str):
style = style.split()
# 确保返回 Tensor 类型
return torch.tensor(melody + style)
def postprocess(self, output):
# 确保输出是 Tensor 类型
if not isinstance(output, torch.Tensor):
output = torch.tensor(output)
# 这里添加后处理逻辑
return output
def encode_melody(self, melody):
"""
编码旋律
:param melody: 旋律字符串或列表
:return: 数值化旋律表示
"""
note_mapping = {"C": 0, "D": 1, "E": 2, "F": 3, "G": 4, "A": 5, "B": 6}
return [note_mapping[note] for note in melody.split("-")]
def encode_style(self, style):
"""
编码风格
:param style: 风格字符串
:return: 数值化风格表示
"""
style_mapping = {
"Pop": 0, "Rock": 1, "Jazz": 2, "Classical": 3,
"Electronic": 4, "Folk": 5, "Hip-Hop": 6,
"Blues": 7, "Country": 8, "World": 9
}
if style not in style_mapping:
raise ValueError(f"Unsupported style: {style}")
return style_mapping[style]
def decode_output(self, output):
"""
解码模型输出
:param output: 模型原始输出
:return: 解码后的旋律或音符序列
"""
reverse_mapping = {0: "C", 1: "D", 2: "E", 3: "F", 4: "G", 5: "A", 6: "B"}
return [reverse_mapping[int(value)] for value in output.squeeze().tolist()]
- AIMusic/musiclogic.py
import os
import time
import logging
# 初始化日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MusicLogic:
SUPPORTED_STYLES = [
"Pop", "Rock", "Jazz", "Classical", "Electronic",
"Folk", "Hip-Hop", "Blues", "Country", "World"
]
def __init__(self, model, output_dir="output"):
"""
初始化 MusicLogic 实例
:param model: AI音乐生成模型实例
:param output_dir: 生成的音乐文件存储目录
"""
self.model = model
self.output_dir = output_dir
# 确保输出目录存在
os.makedirs(self.output_dir, exist_ok=True)
def validate_inputs(self, melody, style):
"""
验证输入的旋律和风格是否有效。
:param melody: 原始旋律
:param style: 音乐风格
:raises ValueError: 如果输入无效
"""
if not melody:
raise ValueError("Melody must be provided.")
if not style:
raise ValueError("Style must be provided.")
if style not in self.SUPPORTED_STYLES:
raise ValueError(f"Unsupported style '{style}'. Supported styles are: {self.SUPPORTED_STYLES}")
def generate_melody(self, melody, style):
try:
file_name = f"music_{int(time.time())}.wav"
file_path = os.path.join(self.output_dir, file_name)
audio_data = self.model.generate_audio(melody, style, file_path)
with open(file_path, "wb") as f:
f.write(audio_data)
return file_path
except Exception as e:
logger.error(f"Error during melody generation: {str(e)}")
raise RuntimeError(f"Error during melody generation: {str(e)}")
def generate_audio_stream(self, melody, style):
"""
流式生成音频数据。
:param melody: 原始旋律(字符串或其他表示形式)
:param style: 音乐风格(字符串)
:yield: 音频数据块
"""
self.validate_inputs(melody, style)
try:
logger.info(f"Starting stream generation for melody: {melody} with style: {style}")
# 调用AI模型生成完整音频
full_audio = self.model.generate_audio(melody, style)
# 模拟将音频分成小块流式输出
chunk_size = len(full_audio) // 10 # 分为10段
for i in range(10):
time.sleep(0.1) # 模拟生成延迟
start = i * chunk_size
end = (i + 1) * chunk_size if i < 9 else len(full_audio)
logger.info(f"Streaming chunk {i + 1}/10")
yield full_audio[start:end]
except Exception as e:
logger.error(f"Error during audio stream generation: {str(e)}")
raise RuntimeError(f"Error during audio stream generation: {str(e)}")
前端
- AIMusic/template/melody_generation.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生成旋律</title>
<link rel="stylesheet" href="../static/layui/css/layui.css">
<style>
/* 自定义样式 */
body {
background-image: url("../static/images/background.webp");
background-size: cover;
background-position: center;
background-attachment: fixed;
opacity: 0;
animation: fadeIn 0.5s ease-out forwards;
}
.layui-card {
background-color: rgba(255, 255, 255, 0.8);
border-radius: 10px;
padding: 20px;
}
.layui-card-header {
font-size: 24px;
font-weight: bold;
color: #333;
}
.layui-card-body {
color: #555;
}
.footer {
text-align: center;
margin-top: 40px;
color: #666;
font-size: 14px;
}
/* 动画:body 渐显 */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="layui-container">
<!-- 页面标题 -->
<div class="layui-row">
<div class="layui-col-xs12">
<div class="layui-card">
<div class="layui-card-header">生成旋律</div>
<div class="layui-card-body">
<p>请选择旋律风格和长度,然后点击“生成旋律”按钮。</p>
<div class="layui-form">
<label class="layui-form-label">风格</label>
<div class="layui-input-block">
<select id="style" class="layui-select">
<option value="Pop">流行 (Pop)</option>
<option value="Rock">摇滚 (Rock)</option>
<option value="Jazz">爵士 (Jazz)</option>
<option value="Classical">古典 (Classical)</option>
<option value="Electronic">电子 (Electronic)</option>
<option value="Folk">民谣 (Folk)</option>
<option value="Hip-Hop">嘻哈 (Hip-Hop)</option>
<option value="Blues">蓝调 (Blues)</option>
<option value="Country">乡村 (Country)</option>
<option value="World">世界音乐 (World)</option>
</select>
</div>
<label class="layui-form-label">长度</label>
<div class="layui-input-block">
<input id="length" type="number" value="16" class="layui-input" min="1" max="128">
</div>
<button id="generate-button" class="layui-btn layui-btn-normal" style="margin-top: 15px;">生成旋律</button>
</div>
<!-- 显示生成结果 -->
<div id="result" style="margin-top: 20px;"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 页脚 -->
<div class="footer">
© 2024 贵池威廉 | 基于人工智能的音乐创作辅助系统
</div>
<!-- 引入 Layui JS -->
<script src="../static/layui/layui.js"></script>
<script>
document.getElementById('generate-button').addEventListener('click', function() {
const style = document.getElementById('style').value.trim();
const lengthInput = document.getElementById('length').value;
const length = parseInt(lengthInput, 10);
if (isNaN(length) || length <= 0) {
document.getElementById('result').innerHTML = `<p style="color: red;">错误: 长度必须是正整数!</p>`;
return;
}
if (!style) {
document.getElementById('result').innerHTML = `<p style="color: red;">错误: 风格不能为空!</p>`;
return;
}
fetch('/melody_generation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
style: style,
length: length
})
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
document.getElementById('result').innerHTML = `<p style="color: green;">生成的旋律: ${data.melody.join(', ')}</p>`;
} else {
document.getElementById('result').innerHTML = `<p style="color: red;">错误: ${data.msg}</p>`;
}
})
.catch(error => {
document.getElementById('result').innerHTML = `<p style="color: red;">请求失败: ${error.message}</p>`;
});
});
// 使用 layui 渲染表单
layui.use(['form'], function() {
const form = layui.form;
form.render(); // 渲染表单
});
</script>
</body>
</html>