feat: expand backend admin marketplace and scaling
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
هذا الالتزام موجود في:
@@ -1,4 +1,133 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
BadGatewayException,
|
||||
Injectable,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { GoogleAuth } from 'google-auth-library';
|
||||
import { generateWaveformPeaksFromBuffer } from '../../common/utils/waveform.util';
|
||||
import { ManagedStorageService } from '../../infrastructure/storage/managed-storage.service';
|
||||
import { TextToMusicDto } from './dto/text-to-music.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {}
|
||||
export class MediaService {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly storageService: ManagedStorageService,
|
||||
) {}
|
||||
|
||||
async generateMusicFromText(userId: string, dto: TextToMusicDto) {
|
||||
const enabled = this.configService.get<boolean>('aiMusic.enabled', { infer: true });
|
||||
if (!enabled) {
|
||||
throw new ServiceUnavailableException('AI music generation is disabled');
|
||||
}
|
||||
|
||||
const apiKey = this.configService.get<string>('aiMusic.apiKey', { infer: true }) ?? '';
|
||||
const projectId = this.configService.get<string>('aiMusic.projectId', { infer: true }) ?? '';
|
||||
const location = this.configService.get<string>('aiMusic.location', { infer: true }) ?? '';
|
||||
const model = this.configService.get<string>('aiMusic.model', { infer: true }) ?? 'lyria-002';
|
||||
if (!projectId || !location) {
|
||||
throw new ServiceUnavailableException('AI music settings are not configured');
|
||||
}
|
||||
|
||||
let url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${model}:predict`;
|
||||
let authorizationHeader: string | null = null;
|
||||
|
||||
if (apiKey) {
|
||||
url = `${url}?key=${encodeURIComponent(apiKey)}`;
|
||||
} else {
|
||||
const auth = new GoogleAuth({
|
||||
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
||||
});
|
||||
|
||||
const client = await auth.getClient();
|
||||
const accessTokenRaw = await client.getAccessToken();
|
||||
const accessToken =
|
||||
typeof accessTokenRaw === 'string' ? accessTokenRaw : accessTokenRaw?.token ?? '';
|
||||
|
||||
if (!accessToken) {
|
||||
throw new ServiceUnavailableException('Failed to authenticate with Google Cloud');
|
||||
}
|
||||
authorizationHeader = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
instances: [{ prompt: dto.prompt }],
|
||||
parameters: {
|
||||
sampleCount: 1,
|
||||
durationSeconds: dto.durationSeconds ?? 12,
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof dto.seed === 'number') {
|
||||
(requestBody.parameters as Record<string, unknown>).seed = dto.seed;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(authorizationHeader ? { Authorization: authorizationHeader } : {}),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
const result = (await response.json()) as {
|
||||
error?: { message?: string };
|
||||
predictions?: Array<{
|
||||
bytesBase64Encoded?: string;
|
||||
mimeType?: string;
|
||||
audio?: { bytesBase64Encoded?: string; mimeType?: string };
|
||||
}>;
|
||||
};
|
||||
|
||||
if (!response.ok) {
|
||||
throw new BadGatewayException(
|
||||
result?.error?.message ?? 'Google AI returned an unexpected error',
|
||||
);
|
||||
}
|
||||
|
||||
const first = result?.predictions?.[0];
|
||||
const audioBase64 = first?.bytesBase64Encoded ?? first?.audio?.bytesBase64Encoded ?? '';
|
||||
const mimeType = first?.mimeType ?? first?.audio?.mimeType ?? 'audio/wav';
|
||||
|
||||
if (!audioBase64) {
|
||||
throw new BadGatewayException('Google AI did not return audio content');
|
||||
}
|
||||
|
||||
const extension = this.resolveAudioExtension(mimeType);
|
||||
const buffer = Buffer.from(audioBase64, 'base64');
|
||||
const audioUrl = await this.storageService.saveFile({
|
||||
folderSegments: ['ai-music'],
|
||||
extension: `.${extension}`,
|
||||
buffer,
|
||||
contentType: mimeType,
|
||||
fileNamePrefix: `ai-${userId}`,
|
||||
});
|
||||
|
||||
return {
|
||||
prompt: dto.prompt,
|
||||
durationSeconds: dto.durationSeconds ?? 12,
|
||||
mimeType,
|
||||
sizeBytes: buffer.length,
|
||||
audioUrl,
|
||||
waveformPeaks: generateWaveformPeaksFromBuffer(buffer),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveAudioExtension(mimeType: string): string {
|
||||
if (mimeType.includes('mpeg') || mimeType.includes('mp3')) {
|
||||
return 'mp3';
|
||||
}
|
||||
if (mimeType.includes('ogg')) {
|
||||
return 'ogg';
|
||||
}
|
||||
if (mimeType.includes('aac')) {
|
||||
return 'aac';
|
||||
}
|
||||
if (mimeType.includes('wav')) {
|
||||
return 'wav';
|
||||
}
|
||||
return 'wav';
|
||||
}
|
||||
}
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم