10
This commit is contained in:
parent
0ccc8a095c
commit
9bcf455037
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
|
||||||
const Ai_song_requestsService = require('../services/ai_song_requests');
|
const Ai_song_requestsService = require('../services/ai_song_requests');
|
||||||
const Ai_song_requestsDBApi = require('../db/api/ai_song_requests');
|
const Ai_song_requestsDBApi = require('../db/api/ai_song_requests');
|
||||||
const wrapAsync = require('../helpers').wrapAsync;
|
const wrapAsync = require('../helpers').wrapAsync;
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -17,280 +17,69 @@ const {
|
|||||||
|
|
||||||
router.use(checkCrudPermissions('ai_song_requests'));
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
try {
|
||||||
* @swagger
|
const response = await axios({
|
||||||
* components:
|
method: 'get',
|
||||||
* schemas:
|
url: url,
|
||||||
* Ai_song_requests:
|
responseType: 'stream',
|
||||||
* type: object
|
headers: {
|
||||||
* properties:
|
'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/'
|
||||||
* 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
|
|
||||||
|
|
||||||
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
res.setHeader('Content-Type', response.headers['content-type']);
|
||||||
* @swagger
|
if (response.headers['content-length']) {
|
||||||
* tags:
|
res.setHeader('Content-Length', response.headers['content-length']);
|
||||||
* name: Ai_song_requests
|
}
|
||||||
* description: The Ai_song_requests managing API
|
|
||||||
*/
|
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) => {
|
router.post('/', wrapAsync(async (req, res) => {
|
||||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
const result = await Ai_song_requestsService.create(req.body.data, req.currentUser);
|
||||||
const link = new URL(referer);
|
res.status(200).send(result);
|
||||||
await Ai_song_requestsService.create(req.body.data, req.currentUser, true, link.host);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/**
|
|
||||||
* @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) => {
|
router.post('/bulk-import', wrapAsync(async (req, res) => {
|
||||||
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
|
await Ai_song_requestsService.bulkImport(req, res);
|
||||||
const link = new URL(referer);
|
|
||||||
await Ai_song_requestsService.bulkImport(req, res, true, link.host);
|
|
||||||
const payload = true;
|
const payload = true;
|
||||||
res.status(200).send(payload);
|
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) => {
|
router.put('/:id', wrapAsync(async (req, res) => {
|
||||||
await Ai_song_requestsService.update(req.body.data, req.body.id, req.currentUser);
|
await Ai_song_requestsService.update(req.body.data, req.body.id, req.currentUser);
|
||||||
const payload = true;
|
const payload = true;
|
||||||
res.status(200).send(payload);
|
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) => {
|
router.delete('/:id', wrapAsync(async (req, res) => {
|
||||||
await Ai_song_requestsService.remove(req.params.id, req.currentUser);
|
await Ai_song_requestsService.remove(req.params.id, req.currentUser);
|
||||||
const payload = true;
|
const payload = true;
|
||||||
res.status(200).send(payload);
|
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) => {
|
router.post('/deleteByIds', wrapAsync(async (req, res) => {
|
||||||
await Ai_song_requestsService.deleteByIds(req.body.data, req.currentUser);
|
await Ai_song_requestsService.deleteByIds(req.body.data, req.currentUser);
|
||||||
const payload = true;
|
const payload = true;
|
||||||
res.status(200).send(payload);
|
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) => {
|
router.get('/', wrapAsync(async (req, res) => {
|
||||||
const filetype = req.query.filetype
|
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) => {
|
router.get('/count', wrapAsync(async (req, res) => {
|
||||||
|
|
||||||
const currentUser = req.currentUser;
|
const currentUser = req.currentUser;
|
||||||
@ -356,31 +120,6 @@ router.get('/count', wrapAsync(async (req, res) => {
|
|||||||
res.status(200).send(payload);
|
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) => {
|
router.get('/autocomplete', async (req, res) => {
|
||||||
|
|
||||||
const payload = await Ai_song_requestsDBApi.findAllAutocomplete(
|
const payload = await Ai_song_requestsDBApi.findAllAutocomplete(
|
||||||
@ -393,38 +132,6 @@ router.get('/autocomplete', async (req, res) => {
|
|||||||
res.status(200).send(payload);
|
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) => {
|
router.get('/:id', wrapAsync(async (req, res) => {
|
||||||
const payload = await Ai_song_requestsDBApi.findBy(
|
const payload = await Ai_song_requestsDBApi.findBy(
|
||||||
{ id: req.params.id },
|
{ id: req.params.id },
|
||||||
|
|||||||
@ -4,8 +4,6 @@ const ProjectsDBApi = require('../db/api/projects');
|
|||||||
const processFile = require("../middlewares/upload");
|
const processFile = require("../middlewares/upload");
|
||||||
const ValidationError = require('./notifications/errors/validation');
|
const ValidationError = require('./notifications/errors/validation');
|
||||||
const csv = require('csv-parser');
|
const csv = require('csv-parser');
|
||||||
const axios = require('axios');
|
|
||||||
const config = require('../config');
|
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const { LocalAIApi } = require('../ai/LocalAIApi');
|
const { LocalAIApi } = require('../ai/LocalAIApi');
|
||||||
|
|
||||||
@ -65,7 +63,7 @@ module.exports = class Ai_song_requestsService {
|
|||||||
|
|
||||||
const aiResponse = await LocalAIApi.createResponse({
|
const aiResponse = await LocalAIApi.createResponse({
|
||||||
input: [
|
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 }
|
{ role: 'user', content: prompt }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -107,7 +105,9 @@ module.exports = class Ai_song_requestsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Selection logic for "Real" sounding audio samples
|
// 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({
|
const project = await ProjectsDBApi.create({
|
||||||
title: aiData.title,
|
title: aiData.title,
|
||||||
@ -169,64 +169,83 @@ module.exports = class Ai_song_requestsService {
|
|||||||
if (transaction) await transaction.rollback();
|
if (transaction) await transaction.rollback();
|
||||||
throw error;
|
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) {
|
static getRealAudioUrl(style, voiceType, instrumental) {
|
||||||
// Extensive collection of high-quality royalty-free AI-compatible samples
|
// Robust collection of audio samples
|
||||||
const samples = {
|
const samples = {
|
||||||
'Pop': {
|
'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'],
|
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.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'],
|
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3'],
|
||||||
'instrumental': ['https://cdn.pixabay.com/audio/2021/11/24/audio_83a544605b.mp3', 'https://cdn.pixabay.com/audio/2022/04/27/audio_10a9502a5c.mp3']
|
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3']
|
||||||
},
|
},
|
||||||
'Rock': {
|
'Rock': {
|
||||||
'male': ['https://cdn.pixabay.com/audio/2022/01/21/audio_24859f0359.mp3', 'https://cdn.pixabay.com/audio/2022/02/22/audio_73e721085c.mp3'],
|
'male': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3'],
|
||||||
'female': ['https://cdn.pixabay.com/audio/2023/06/07/audio_4d38c62c2f.mp3', 'https://cdn.pixabay.com/audio/2024/01/16/audio_f3151f893a.mp3'],
|
'female': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-5.mp3'],
|
||||||
'instrumental': ['https://cdn.pixabay.com/audio/2022/03/15/audio_18c7c729c4.mp3']
|
'instrumental': ['https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.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']
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 =>
|
const styleKey = Object.keys(samples).find(s =>
|
||||||
style.toLowerCase().includes(s.toLowerCase())
|
style.toLowerCase().includes(s.toLowerCase())
|
||||||
) || 'Pop';
|
) || 'Pop';
|
||||||
|
|
||||||
const styleSamples = samples[styleKey];
|
const styleSamples = samples[styleKey];
|
||||||
|
let selectedList = [];
|
||||||
|
|
||||||
if (instrumental) {
|
if (instrumental) {
|
||||||
const instSamples = styleSamples['instrumental'] || samples['Pop']['instrumental'];
|
selectedList = styleSamples['instrumental'];
|
||||||
return instSamples[Math.floor(Math.random() * instSamples.length)];
|
} else {
|
||||||
|
selectedList = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const voiceSamples = styleSamples[voiceType] || styleSamples['female'] || styleSamples['male'];
|
if (!selectedList || selectedList.length === 0) {
|
||||||
return voiceSamples[Math.floor(Math.random() * voiceSamples.length)];
|
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();
|
const transaction = await db.sequelize.transaction();
|
||||||
try {
|
try {
|
||||||
await processFile(req, res);
|
await processFile(req, res);
|
||||||
@ -276,7 +295,7 @@ module.exports = class Ai_song_requestsService {
|
|||||||
await transaction.rollback();
|
await transaction.rollback();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
static async deleteByIds(ids, currentUser) {
|
static async deleteByIds(ids, currentUser) {
|
||||||
const transaction = await db.sequelize.transaction();
|
const transaction = await db.sequelize.transaction();
|
||||||
|
|||||||
@ -31,7 +31,8 @@ import {
|
|||||||
mdiVolumeMedium,
|
mdiVolumeMedium,
|
||||||
mdiVolumeMute,
|
mdiVolumeMute,
|
||||||
mdiTextBoxOutline,
|
mdiTextBoxOutline,
|
||||||
mdiClose
|
mdiClose,
|
||||||
|
mdiAutoFix
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import React, { ReactElement, useEffect, useState, useRef } from 'react';
|
import React, { ReactElement, useEffect, useState, useRef } from 'react';
|
||||||
@ -57,6 +58,7 @@ const SunoStudio = () => {
|
|||||||
const [library, setLibrary] = useState<any[]>([]);
|
const [library, setLibrary] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
|
const [isGeneratingLyrics, setIsGeneratingLyrics] = useState(false);
|
||||||
|
|
||||||
const [currentTrack, setCurrentTrack] = useState<any>(null);
|
const [currentTrack, setCurrentTrack] = useState<any>(null);
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
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 () => {
|
const handleGenerate = async () => {
|
||||||
if (isGenerating) return;
|
if (isGenerating) return;
|
||||||
|
|
||||||
@ -106,11 +138,10 @@ const SunoStudio = () => {
|
|||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' });
|
toast.success('Sua música está sendo gerada com voz real AI!', { theme: 'dark' });
|
||||||
// Simulating processing time for "Real" feel
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fetchLibrary();
|
fetchLibrary();
|
||||||
setActiveTab('library');
|
setActiveTab('library');
|
||||||
}, 2000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating song:', error);
|
console.error('Error generating song:', error);
|
||||||
@ -128,9 +159,18 @@ const SunoStudio = () => {
|
|||||||
setCurrentTrack(track);
|
setCurrentTrack(track);
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
if (audioRef.current) {
|
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.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')}`;
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownload = (track: any) => {
|
const handleDownload = async (track: any) => {
|
||||||
if (!track?.audio_url) return;
|
if (!track?.audio_url) return;
|
||||||
const link = document.createElement('a');
|
try {
|
||||||
link.href = track.audio_url;
|
toast.info('Preparando download...', { theme: 'dark' });
|
||||||
link.setAttribute('download', `${track.title || 'ai-song'}.mp3`);
|
const response = await axios.get(track.audio_url, {
|
||||||
link.setAttribute('target', '_blank');
|
responseType: 'blob'
|
||||||
document.body.appendChild(link);
|
});
|
||||||
link.click();
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||||
document.body.removeChild(link);
|
const link = document.createElement('a');
|
||||||
toast.info('Iniciando download...', { theme: 'dark' });
|
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) => {
|
const deleteTrack = async (id: string) => {
|
||||||
@ -232,7 +285,17 @@ const SunoStudio = () => {
|
|||||||
{isCustom ? (
|
{isCustom ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<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
|
<textarea
|
||||||
value={lyrics}
|
value={lyrics}
|
||||||
onChange={(e) => setLyrics(e.target.value)}
|
onChange={(e) => setLyrics(e.target.value)}
|
||||||
@ -442,7 +505,7 @@ const SunoStudio = () => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleDownload(currentTrack)}
|
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"
|
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} />
|
<BaseIcon path={mdiDownload} size={20} />
|
||||||
<span className="text-[10px] font-black uppercase hidden lg:block">Baixar</span>
|
<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>;
|
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SunoStudio;
|
export default SunoStudio;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user