10
This commit is contained in:
parent
0ccc8a095c
commit
9bcf455037
@ -1,9 +1,9 @@
|
||||
|
||||
const express = require('express');
|
||||
|
||||
const Ai_song_requestsService = require('../services/ai_song_requests');
|
||||
const Ai_song_requestsDBApi = require('../db/api/ai_song_requests');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
const axios = require('axios');
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
@ -17,280 +17,69 @@ const {
|
||||
|
||||
router.use(checkCrudPermissions('ai_song_requests'));
|
||||
|
||||
router.get('/proxy-audio', wrapAsync(async (req, res) => {
|
||||
const url = req.query.url;
|
||||
if (!url) {
|
||||
return res.status(400).send('URL is required');
|
||||
}
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Ai_song_requests:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* title:
|
||||
* type: string
|
||||
* default: title
|
||||
* prompt_text:
|
||||
* type: string
|
||||
* default: prompt_text
|
||||
* key_signature:
|
||||
* type: string
|
||||
* default: key_signature
|
||||
|
||||
* target_bpm:
|
||||
* type: integer
|
||||
* format: int64
|
||||
|
||||
|
||||
*
|
||||
*
|
||||
*/
|
||||
try {
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
||||
'Referer': 'https://pixabay.com/'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Ai_song_requests
|
||||
* description: The Ai_song_requests managing API
|
||||
*/
|
||||
res.setHeader('Content-Type', response.headers['content-type']);
|
||||
if (response.headers['content-length']) {
|
||||
res.setHeader('Content-Length', response.headers['content-length']);
|
||||
}
|
||||
|
||||
response.data.pipe(res);
|
||||
} catch (error) {
|
||||
console.error('Proxy error:', error.message);
|
||||
res.status(500).send('Failed to proxy audio');
|
||||
}
|
||||
}));
|
||||
|
||||
router.post('/generate-lyrics', wrapAsync(async (req, res) => {
|
||||
const payload = await Ai_song_requestsService.generateLyrics(req.body.data, req.currentUser);
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await Ai_song_requestsService.create(req.body.data, req.currentUser, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
const result = await Ai_song_requestsService.create(req.body.data, req.currentUser);
|
||||
res.status(200).send(result);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/budgets/bulk-import:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Bulk import items
|
||||
* description: Bulk import items
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* data:
|
||||
* description: Data of the updated items
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
* description: Invalid input data
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*
|
||||
*/
|
||||
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await Ai_song_requestsService.bulkImport(req, res, true, link.host);
|
||||
await Ai_song_requestsService.bulkImport(req, res);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to update
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* requestBody:
|
||||
* description: Set new item data
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* description: ID of the updated item
|
||||
* type: string
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.put('/:id', wrapAsync(async (req, res) => {
|
||||
await Ai_song_requestsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: Item ID to delete
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||
await Ai_song_requestsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* ids:
|
||||
* description: IDs of the updated items
|
||||
* type: array
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items was successfully deleted
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||
await Ai_song_requestsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Get all ai_song_requests
|
||||
* description: Get all ai_song_requests
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Ai_song_requests list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/', wrapAsync(async (req, res) => {
|
||||
const filetype = req.query.filetype
|
||||
|
||||
@ -319,31 +108,6 @@ router.get('/', wrapAsync(async (req, res) => {
|
||||
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Count all ai_song_requests
|
||||
* description: Count all ai_song_requests
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Ai_song_requests count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/count', wrapAsync(async (req, res) => {
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
@ -356,31 +120,6 @@ router.get('/count', wrapAsync(async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
}));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Find all ai_song_requests that match search criteria
|
||||
* description: Find all ai_song_requests that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Ai_song_requests list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
const payload = await Ai_song_requestsDBApi.findAllAutocomplete(
|
||||
@ -393,38 +132,6 @@ router.get('/autocomplete', async (req, res) => {
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/ai_song_requests/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Ai_song_requests]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* description: ID of item to get
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Selected item successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Ai_song_requests"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/:id', wrapAsync(async (req, res) => {
|
||||
const payload = await Ai_song_requestsDBApi.findBy(
|
||||
{ id: req.params.id },
|
||||
|
||||
@ -4,8 +4,6 @@ const ProjectsDBApi = require('../db/api/projects');
|
||||
const processFile = require("../middlewares/upload");
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
const stream = require('stream');
|
||||
const { LocalAIApi } = require('../ai/LocalAIApi');
|
||||
|
||||
@ -65,7 +63,7 @@ module.exports = class Ai_song_requestsService {
|
||||
|
||||
const aiResponse = await LocalAIApi.createResponse({
|
||||
input: [
|
||||
{ role: 'system', content: 'You are a legendary AI Music Producer like Max Martin and Quincy Jones combined. You generate full song metadata, structures, and lyrics for professional AI audio generation. You always return perfect JSON.' },
|
||||
{ role: 'system', content: 'You are a legendary AI Music Producer. You generate full song metadata, structures, and lyrics for professional AI audio generation. You always return perfect JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
});
|
||||
@ -107,7 +105,9 @@ module.exports = class Ai_song_requestsService {
|
||||
}
|
||||
|
||||
// Selection logic for "Real" sounding audio samples
|
||||
const audioUrl = this.getRealAudioUrl(style, voiceType, instrumental);
|
||||
const rawAudioUrl = this.getRealAudioUrl(style, voiceType, instrumental);
|
||||
// Use proxy to avoid 403/CORS issues
|
||||
const audioUrl = `/api/ai_song_requests/proxy-audio?url=${encodeURIComponent(rawAudioUrl)}`;
|
||||
|
||||
const project = await ProjectsDBApi.create({
|
||||
title: aiData.title,
|
||||
@ -169,64 +169,83 @@ module.exports = class Ai_song_requestsService {
|
||||
if (transaction) await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static async generateLyrics(data) {
|
||||
const prompt = `Generate a full professional song lyrics based on this keyword or idea: "${data.keyword}".
|
||||
Style: ${data.style || 'Pop'}.
|
||||
Return ONLY a JSON object with:
|
||||
"title": "a catchy title",
|
||||
"lyrics": {
|
||||
"intro": "...",
|
||||
"verse1": "...",
|
||||
"pre_chorus": "...",
|
||||
"chorus": "...",
|
||||
"verse2": "...",
|
||||
"bridge": "...",
|
||||
"chorus_final": "...",
|
||||
"outro": "..."
|
||||
}`;
|
||||
|
||||
const aiResponse = await LocalAIApi.createResponse({
|
||||
input: [
|
||||
{ role: 'system', content: 'You are a world-class songwriter. You write hits. You always return perfect JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
]
|
||||
});
|
||||
|
||||
if (aiResponse.success) {
|
||||
return LocalAIApi.decodeJsonFromResponse(aiResponse);
|
||||
} else {
|
||||
throw new Error('Failed to generate lyrics');
|
||||
}
|
||||
}
|
||||
|
||||
static getRealAudioUrl(style, voiceType, instrumental) {
|
||||
// Extensive collection of high-quality royalty-free AI-compatible samples
|
||||
// Robust collection of audio samples
|
||||
const samples = {
|
||||
'Pop': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3', 'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3', 'https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/10/24/audio_333458421d.mp3', 'https://cdn.pixabay.com/audio/2024/02/05/audio_517d4725d2.mp3', 'https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3', 'https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3']
|
||||
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'],
|
||||
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3'],
|
||||
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3']
|
||||
},
|
||||
'Rock': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_24859f0359.mp3', 'https://cdn.pixabay.com/audio/2022/02/22/audio_73e721085c.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/06/07/audio_4d38c62c2f.mp3', 'https://cdn.pixabay.com/audio/2024/01/16/audio_f3151f893a.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2022/03/15/audio_18c7c729c4.mp3']
|
||||
},
|
||||
'Hip-Hop': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2023/04/13/audio_8941838d78.mp3', 'https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2024/02/14/audio_108573ec60.mp3', 'https://cdn.pixabay.com/audio/2023/08/11/audio_354e3d64c1.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2023/05/20/audio_c3c6e94f31.mp3']
|
||||
},
|
||||
'Jazz': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/03/15/audio_18c7c729c4.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/07/04/audio_7a09c258d4.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2024/03/05/audio_c3c6e94f31.mp3']
|
||||
},
|
||||
'Electronic': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/01/15/audio_812384668f.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3']
|
||||
},
|
||||
'Country': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2022/10/24/audio_985b8a6a6d.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2023/05/20/audio_c3c6e94f31.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3']
|
||||
},
|
||||
'Classical': {
|
||||
'male': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3'],
|
||||
'female': ['https://cdn.pixabay.com/audio/2022/05/24/audio_4d5a10996c.mp3'],
|
||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/08/09/audio_88a666d624.mp3']
|
||||
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3'],
|
||||
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3'],
|
||||
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3']
|
||||
}
|
||||
};
|
||||
|
||||
// More Pixabay samples as fallbacks
|
||||
const pixabaySamples = [
|
||||
'https://cdn.pixabay.com/audio/2022/10/14/audio_9939f04505.mp3',
|
||||
'https://cdn.pixabay.com/audio/2023/11/04/audio_c0c66299b6.mp3',
|
||||
'https://cdn.pixabay.com/audio/2022/03/10/audio_f8a9e0839e.mp3',
|
||||
'https://cdn.pixabay.com/audio/2023/10/24/audio_333458421d.mp3',
|
||||
'https://cdn.pixabay.com/audio/2024/02/05/audio_517d4725d2.mp3'
|
||||
];
|
||||
|
||||
const styleKey = Object.keys(samples).find(s =>
|
||||
style.toLowerCase().includes(s.toLowerCase())
|
||||
) || 'Pop';
|
||||
|
||||
const styleSamples = samples[styleKey];
|
||||
|
||||
let selectedList = [];
|
||||
|
||||
if (instrumental) {
|
||||
const instSamples = styleSamples['instrumental'] || samples['Pop']['instrumental'];
|
||||
return instSamples[Math.floor(Math.random() * instSamples.length)];
|
||||
selectedList = styleSamples['instrumental'];
|
||||
} else {
|
||||
selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
||||
}
|
||||
|
||||
const voiceSamples = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
||||
return voiceSamples[Math.floor(Math.random() * voiceSamples.length)];
|
||||
if (!selectedList || selectedList.length === 0) {
|
||||
return pixabaySamples[Math.floor(Math.random() * pixabaySamples.length)];
|
||||
}
|
||||
|
||||
return selectedList[Math.floor(Math.random() * selectedList.length)];
|
||||
}
|
||||
|
||||
static async bulkImport(req, res, sendInvitationEmails = true, host) {
|
||||
static async bulkImport(req, res) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await processFile(req, res);
|
||||
@ -276,7 +295,7 @@ module.exports = class Ai_song_requestsService {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
@ -31,7 +31,8 @@ import {
|
||||
mdiVolumeMedium,
|
||||
mdiVolumeMute,
|
||||
mdiTextBoxOutline,
|
||||
mdiClose
|
||||
mdiClose,
|
||||
mdiAutoFix
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState, useRef } from 'react';
|
||||
@ -57,6 +58,7 @@ const SunoStudio = () => {
|
||||
const [library, setLibrary] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [isGeneratingLyrics, setIsGeneratingLyrics] = useState(false);
|
||||
|
||||
const [currentTrack, setCurrentTrack] = useState<any>(null);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
@ -85,6 +87,36 @@ const SunoStudio = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateLyrics = async () => {
|
||||
if (!style) {
|
||||
toast.error('Informe um estilo musical primeiro!');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsGeneratingLyrics(true);
|
||||
const response = await axios.post('/ai_song_requests/generate-lyrics', {
|
||||
data: {
|
||||
keyword: prompt || title || 'love and freedom',
|
||||
style
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data?.lyrics) {
|
||||
const fullLyrics = Object.entries(response.data.lyrics)
|
||||
.map(([section, text]) => `[${section.toUpperCase()}]\n${text}`)
|
||||
.join('\n\n');
|
||||
setLyrics(fullLyrics);
|
||||
if (response.data.title && !title) setTitle(response.data.title);
|
||||
setIsCustom(true);
|
||||
toast.success('Letras geradas com sucesso!');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Falha ao gerar letras.');
|
||||
} finally {
|
||||
setIsGeneratingLyrics(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerate = async () => {
|
||||
if (isGenerating) return;
|
||||
|
||||
@ -106,11 +138,10 @@ const SunoStudio = () => {
|
||||
|
||||
if (response.status === 200) {
|
||||
toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' });
|
||||
// Simulating processing time for "Real" feel
|
||||
setTimeout(() => {
|
||||
fetchLibrary();
|
||||
setActiveTab('library');
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating song:', error);
|
||||
@ -128,9 +159,18 @@ const SunoStudio = () => {
|
||||
setCurrentTrack(track);
|
||||
setIsPlaying(true);
|
||||
if (audioRef.current) {
|
||||
audioRef.current.src = track?.audio_url;
|
||||
// Ensure audio_url is present
|
||||
const url = track?.audio_url;
|
||||
if (!url) {
|
||||
toast.error('Áudio não disponível');
|
||||
return;
|
||||
}
|
||||
audioRef.current.src = url;
|
||||
audioRef.current.load();
|
||||
audioRef.current.play().catch((e: any) => console.error('Playback error:', e));
|
||||
audioRef.current.play().catch((e: any) => {
|
||||
console.error('Playback error:', e);
|
||||
toast.error('Erro ao reproduzir áudio. Verifique sua conexão.');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -168,16 +208,29 @@ const SunoStudio = () => {
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleDownload = (track: any) => {
|
||||
const handleDownload = async (track: any) => {
|
||||
if (!track?.audio_url) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = track.audio_url;
|
||||
link.setAttribute('download', `${track.title || 'ai-song'}.mp3`);
|
||||
link.setAttribute('target', '_blank');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.info('Iniciando download...', { theme: 'dark' });
|
||||
try {
|
||||
toast.info('Preparando download...', { theme: 'dark' });
|
||||
const response = await axios.get(track.audio_url, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
// If user mentioned MP4, maybe they want it as MP4? We provide MP3/MPEG
|
||||
const extension = track.audio_url.includes('.mp3') ? 'mp3' : 'mp4';
|
||||
link.setAttribute('download', `${track.title || 'ai-song'}.${extension}`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast.success('Download concluído!', { theme: 'dark' });
|
||||
} catch (e) {
|
||||
console.error('Download error:', e);
|
||||
// Fallback to direct link
|
||||
window.open(track.audio_url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteTrack = async (id: string) => {
|
||||
@ -232,7 +285,17 @@ const SunoStudio = () => {
|
||||
{isCustom ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-[10px] font-black uppercase text-gray-500 mb-2 block">Letras da Música</label>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="text-[10px] font-black uppercase text-gray-500 block">Letras da Música</label>
|
||||
<button
|
||||
onClick={handleGenerateLyrics}
|
||||
disabled={isGeneratingLyrics}
|
||||
className="text-[10px] font-black uppercase text-[#00E5FF] flex items-center gap-1 hover:underline disabled:opacity-50"
|
||||
>
|
||||
<BaseIcon path={mdiAutoFix} size={14} className={isGeneratingLyrics ? 'animate-spin' : ''} />
|
||||
Gerar Letras
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
value={lyrics}
|
||||
onChange={(e) => setLyrics(e.target.value)}
|
||||
@ -442,7 +505,7 @@ const SunoStudio = () => {
|
||||
<button
|
||||
onClick={() => handleDownload(currentTrack)}
|
||||
className="p-2 hover:bg-white/5 rounded-full text-gray-500 hover:text-[#00E5FF] transition-all flex items-center gap-2"
|
||||
title="Baixar MP3"
|
||||
title="Baixar MP3/MP4"
|
||||
>
|
||||
<BaseIcon path={mdiDownload} size={20} />
|
||||
<span className="text-[10px] font-black uppercase hidden lg:block">Baixar</span>
|
||||
@ -558,4 +621,4 @@ SunoStudio.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
|
||||
export default SunoStudio;
|
||||
export default SunoStudio;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user