feat: voiceclone implement comprehensive voice cloning service with full API support (#38)
Co-authored-by: tomsun28 <tomsun28@outlook.com>
This commit is contained in:
parent
6430107d74
commit
ce66a0142c
18 changed files with 789 additions and 1 deletions
|
|
@ -23,6 +23,8 @@ import ai.z.openapi.service.videos.VideosService;
|
|||
import ai.z.openapi.service.videos.VideosServiceImpl;
|
||||
import ai.z.openapi.service.assistant.AssistantService;
|
||||
import ai.z.openapi.service.assistant.AssistantServiceImpl;
|
||||
import ai.z.openapi.service.voiceclone.VoiceCloneService;
|
||||
import ai.z.openapi.service.voiceclone.VoiceCloneServiceImpl;
|
||||
import ai.z.openapi.core.config.ZaiConfig;
|
||||
import ai.z.openapi.core.model.BiFlowableClientResponse;
|
||||
import ai.z.openapi.core.model.ClientRequest;
|
||||
|
|
@ -98,6 +100,9 @@ public abstract class AbstractAiClient extends AbstractClientBaseService {
|
|||
/** Assistant service for AI assistant functionality */
|
||||
private AssistantService assistantService;
|
||||
|
||||
/** Voice clone service for voice cloning operations */
|
||||
private VoiceCloneService voiceCloneService;
|
||||
|
||||
/**
|
||||
* Constructs a new AbstractAiClient with the specified configuration.
|
||||
* @param config the configuration object containing API keys, timeouts, and other
|
||||
|
|
@ -238,6 +243,18 @@ public abstract class AbstractAiClient extends AbstractClientBaseService {
|
|||
return assistantService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the voice clone service for voice cloning operations. This service handles
|
||||
* voice cloning creation, deletion, and listing functionality.
|
||||
* @return the VoiceCloneService instance (lazily initialized)
|
||||
*/
|
||||
public synchronized VoiceCloneService voiceClone() {
|
||||
if (voiceCloneService == null) {
|
||||
this.voiceCloneService = new VoiceCloneServiceImpl(this);
|
||||
}
|
||||
return voiceCloneService;
|
||||
}
|
||||
|
||||
// ==================== Utility Methods ====================
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package ai.z.openapi.api.voiceclone;
|
||||
|
||||
import ai.z.openapi.service.voiceclone.VoiceCloneRequest;
|
||||
import ai.z.openapi.service.voiceclone.VoiceCloneResult;
|
||||
import ai.z.openapi.service.voiceclone.VoiceDeleteRequest;
|
||||
import ai.z.openapi.service.voiceclone.VoiceDeleteResult;
|
||||
import ai.z.openapi.service.voiceclone.VoiceListResult;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
/**
|
||||
* Voice Clone API for voice cloning and management operations. Provides endpoints for
|
||||
* creating voice clones from audio samples, deleting existing voice clones, and listing
|
||||
* available voice clones.
|
||||
*/
|
||||
public interface VoiceCloneApi {
|
||||
|
||||
/**
|
||||
* Creates a new voice clone from provided audio sample and parameters. Uses advanced
|
||||
* neural voice cloning technology to generate a custom voice model that can
|
||||
* synthesize speech matching the characteristics of the provided sample.
|
||||
* @param request voice clone creation parameters including voice name, sample text,
|
||||
* target text, and audio file reference
|
||||
* @return voice clone creation result with voice ID and preview file information
|
||||
*/
|
||||
@POST("voice/clone")
|
||||
Single<VoiceCloneResult> cloneVoice(@Body VoiceCloneRequest request);
|
||||
|
||||
/**
|
||||
* Deletes an existing voice clone by voice ID. Permanently removes the voice model
|
||||
* and associated data from the system. This operation cannot be undone.
|
||||
* @param request voice deletion parameters containing the voice ID to delete
|
||||
* @return voice deletion result with confirmation and deletion timestamp
|
||||
*/
|
||||
@POST("voice/delete")
|
||||
Single<VoiceDeleteResult> deleteVoice(@Body VoiceDeleteRequest request);
|
||||
|
||||
/**
|
||||
* Retrieves a list of available voice clones with optional filtering. Returns
|
||||
* metadata and details for voice clones including voice IDs, names, types, download
|
||||
* URLs, and creation timestamps.
|
||||
* @param voiceType optional voice type filter
|
||||
* @param voiceName optional voice name filter
|
||||
* @return list of voice clone data with comprehensive metadata
|
||||
*/
|
||||
@GET("voice/list")
|
||||
Single<VoiceListResult> listVoices(@Query("voiceType") String voiceType, @Query("voiceName") String voiceName);
|
||||
|
||||
/**
|
||||
* Retrieves a list of available voice clones with optional filtering and Request-Id
|
||||
* header. Returns metadata and details for voice clones including voice IDs, names,
|
||||
* types, download URLs, and creation timestamps.
|
||||
* @param voiceType optional voice type filter
|
||||
* @param voiceName optional voice name filter
|
||||
* @param requestId unique request identifier for tracking and monitoring
|
||||
* @return list of voice clone data with comprehensive metadata
|
||||
*/
|
||||
@GET("voice/list")
|
||||
Single<VoiceListResult> listVoices(@Query("voiceType") String voiceType, @Query("voiceName") String voiceName,
|
||||
@Header("Request-Id") String requestId);
|
||||
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@ package ai.z.openapi.service.file;
|
|||
|
||||
public enum UploadFilePurpose {
|
||||
|
||||
BATCH("batch"), FILE_EXTRACT("file-extract"), CODE_INTERPRETER("code-interpreter"), AGENT("agent");
|
||||
BATCH("batch"), FILE_EXTRACT("file-extract"), CODE_INTERPRETER("code-interpreter"), AGENT("agent"),
|
||||
VOICE_CLONE_INPUT("voice-clone-input");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientRequest;
|
||||
import ai.z.openapi.service.CommonRequest;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Request parameters for voice cloning API. This class contains all the necessary
|
||||
* parameters for voice cloning operations, including voice name, sample audio text,
|
||||
* target preview text, and audio file information.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class VoiceCloneRequest extends CommonRequest implements ClientRequest<VoiceCloneRequest> {
|
||||
|
||||
/** Voice name */
|
||||
@JsonProperty("voice_name")
|
||||
private String voiceName;
|
||||
|
||||
/** Text content corresponding to the sample audio */
|
||||
@JsonProperty("voice_text_input")
|
||||
private String voiceTextInput;
|
||||
|
||||
/** Target text for preview audio */
|
||||
@JsonProperty("voice_text_output")
|
||||
private String voiceTextOutput;
|
||||
|
||||
/** File ID of the audio file */
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientResponse;
|
||||
import ai.z.openapi.service.batches.BatchPage;
|
||||
import ai.z.openapi.service.model.ChatError;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Response wrapper for voice cloning API operations. This class contains the standard
|
||||
* response structure including status code, message, success flag, result data, and error
|
||||
* information.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VoiceCloneResponse implements ClientResponse<VoiceCloneResult> {
|
||||
|
||||
private int code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private VoiceCloneResult data;
|
||||
|
||||
private ChatError error;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Result data for voice cloning operations. This class contains the voice cloning result
|
||||
* information including voice ID, audio file ID, and file purpose.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class VoiceCloneResult {
|
||||
|
||||
/** Voice ID */
|
||||
@JsonProperty("voice_id")
|
||||
private String voiceId;
|
||||
|
||||
/** Audio preview file ID */
|
||||
@JsonProperty("file_id")
|
||||
private String fileId;
|
||||
|
||||
/** File purpose */
|
||||
@JsonProperty("file_purpose")
|
||||
private String filePurpose;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
/**
|
||||
* Voice clone service interface for voice cloning operations. This service provides
|
||||
* methods for creating, deleting, and listing voice clones.
|
||||
*/
|
||||
public interface VoiceCloneService {
|
||||
|
||||
/**
|
||||
* Creates a new voice clone based on the provided audio sample and parameters.
|
||||
* @param request the voice clone creation request containing voice name, sample audio
|
||||
* text, target text, and audio file information
|
||||
* @return VoiceCloneResponse containing the voice clone result and metadata
|
||||
*/
|
||||
VoiceCloneResponse cloneVoice(VoiceCloneRequest request);
|
||||
|
||||
/**
|
||||
* Deletes an existing voice clone by voice ID.
|
||||
* @param request the voice deletion request containing the voice ID to delete
|
||||
* @return VoiceDeleteResponse containing the deletion result and timestamp
|
||||
*/
|
||||
VoiceDeleteResponse deleteVoice(VoiceDeleteRequest request);
|
||||
|
||||
/**
|
||||
* Retrieves a list of available voice clones with optional filtering.
|
||||
* @param request the voice list request containing optional voice type and name
|
||||
* filters
|
||||
* @return VoiceListResponse containing the filtered list of voice data
|
||||
*/
|
||||
VoiceListResponse listVoice(VoiceListRequest request);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.AbstractAiClient;
|
||||
import ai.z.openapi.api.voiceclone.VoiceCloneApi;
|
||||
import ai.z.openapi.service.deserialize.MessageDeserializeFactory;
|
||||
import ai.z.openapi.utils.RequestSupplier;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Voice clone service implementation for voice cloning operations. This service provides
|
||||
* methods for creating, deleting, and listing voice clones using the VoiceClone API
|
||||
* endpoints.
|
||||
*/
|
||||
public class VoiceCloneServiceImpl implements VoiceCloneService {
|
||||
|
||||
protected static final ObjectMapper mapper = MessageDeserializeFactory.defaultObjectMapper();
|
||||
|
||||
private final AbstractAiClient zAiClient;
|
||||
|
||||
private final VoiceCloneApi voiceCloneApi;
|
||||
|
||||
public VoiceCloneServiceImpl(AbstractAiClient zAiClient) {
|
||||
this.zAiClient = zAiClient;
|
||||
this.voiceCloneApi = zAiClient.retrofit().create(VoiceCloneApi.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoiceCloneResponse cloneVoice(VoiceCloneRequest request) {
|
||||
validateCreateVoiceParams(request);
|
||||
RequestSupplier<VoiceCloneRequest, VoiceCloneResult> supplier = voiceCloneApi::cloneVoice;
|
||||
return this.zAiClient.executeRequest(request, supplier, VoiceCloneResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoiceDeleteResponse deleteVoice(VoiceDeleteRequest request) {
|
||||
validateDeleteVoiceParams(request);
|
||||
RequestSupplier<VoiceDeleteRequest, VoiceDeleteResult> supplier = voiceCloneApi::deleteVoice;
|
||||
return this.zAiClient.executeRequest(request, supplier, VoiceDeleteResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VoiceListResponse listVoice(VoiceListRequest request) {
|
||||
RequestSupplier<VoiceListRequest, VoiceListResult> supplier = (params) -> {
|
||||
String voiceType = params != null ? params.getVoiceType() : null;
|
||||
String voiceName = params != null ? params.getVoiceName() : null;
|
||||
String requestId = params != null ? params.getRequestId() : "";
|
||||
|
||||
return voiceCloneApi.listVoices(voiceType, voiceName, requestId);
|
||||
};
|
||||
return this.zAiClient.executeRequest(request, supplier, VoiceListResponse.class);
|
||||
}
|
||||
|
||||
private void validateCreateVoiceParams(VoiceCloneRequest request) {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("request cannot be null");
|
||||
}
|
||||
if (request.getVoiceName() == null || request.getVoiceName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("voice name cannot be null or empty");
|
||||
}
|
||||
if (request.getVoiceTextInput() == null || request.getVoiceTextInput().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("voice text input cannot be null or empty");
|
||||
}
|
||||
if (request.getFileId() == null || request.getFileId().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("file ID cannot be null or empty");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDeleteVoiceParams(VoiceDeleteRequest request) {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("request cannot be null");
|
||||
}
|
||||
if (request.getVoiceId() == null || request.getVoiceId().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("voice ID cannot be null or empty");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Voice data entity containing voice information. This class contains voice details
|
||||
* including voice ID, name, type, download URL, and creation time.
|
||||
*/
|
||||
@Data
|
||||
public class VoiceData {
|
||||
|
||||
@JsonProperty("voice_id")
|
||||
private String voiceId;
|
||||
|
||||
@JsonProperty("voice_name")
|
||||
private String voiceName;
|
||||
|
||||
@JsonProperty("voice_type")
|
||||
private String voiceType;
|
||||
|
||||
@JsonProperty("download_url")
|
||||
private String downloadUrl;
|
||||
|
||||
@JsonProperty("create_time")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientRequest;
|
||||
import ai.z.openapi.service.CommonRequest;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Request parameters for voice deletion API. This class contains the necessary parameters
|
||||
* for deleting a voice, specifically the voice ID.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class VoiceDeleteRequest extends CommonRequest implements ClientRequest<VoiceDeleteRequest> {
|
||||
|
||||
@JsonProperty("voice_id")
|
||||
private String voiceId;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientResponse;
|
||||
import ai.z.openapi.service.model.ChatError;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Response wrapper for voice deletion API operations. This class contains the standard
|
||||
* response structure including status code, message, success flag, result data, and error
|
||||
* information.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VoiceDeleteResponse implements ClientResponse<VoiceDeleteResult> {
|
||||
|
||||
private int code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private VoiceDeleteResult data;
|
||||
|
||||
private ChatError error;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Result data for voice deletion operations. This class contains the deletion result
|
||||
* information including voice ID and deletion timestamp.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class VoiceDeleteResult {
|
||||
|
||||
@JsonProperty("voice_id")
|
||||
private String voiceId;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonProperty("delete_time")
|
||||
private Date deleteTime;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientRequest;
|
||||
import ai.z.openapi.service.CommonRequest;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Request parameters for voice list API. This class contains optional filter parameters
|
||||
* for retrieving voice clones by type and name.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class VoiceListRequest extends CommonRequest implements ClientRequest<VoiceListRequest> {
|
||||
|
||||
/** Voice type filter,"PRIVATE" or "OFFICIAL" or null */
|
||||
@JsonProperty("voice_type")
|
||||
private String voiceType;
|
||||
|
||||
/** Voice name filter */
|
||||
@JsonProperty("voice_name")
|
||||
private String voiceName;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.core.model.ClientResponse;
|
||||
import ai.z.openapi.service.model.ChatError;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Response wrapper for voice list API operations. This class contains the standard
|
||||
* response structure including status code, message, success flag, result data, and error
|
||||
* information.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class VoiceListResponse implements ClientResponse<VoiceListResult> {
|
||||
|
||||
private int code;
|
||||
|
||||
private String msg;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private VoiceListResult data;
|
||||
|
||||
private ChatError error;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Result data for voice list operations. This class contains the list of voice data
|
||||
* returned from voice listing API calls.
|
||||
*/
|
||||
@Data
|
||||
public class VoiceListResult {
|
||||
|
||||
@JsonProperty("voice_list")
|
||||
private List<VoiceData> voiceList;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package ai.z.openapi.service.voiceclone;
|
||||
|
||||
import ai.z.openapi.ZaiClient;
|
||||
import ai.z.openapi.core.config.ZaiConfig;
|
||||
import ai.z.openapi.service.file.FileApiResponse;
|
||||
import ai.z.openapi.service.file.FileService;
|
||||
import ai.z.openapi.service.file.FileUploadParams;
|
||||
import ai.z.openapi.service.file.UploadFilePurpose;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Test class for VoiceCloneService functionality. Tests voice cloning operations
|
||||
* including file upload, voice creation, deletion, and listing. Requires ZAI_API_KEY
|
||||
* environment variable to be set for integration tests.
|
||||
*/
|
||||
@DisplayName("VoiceCloneService Tests")
|
||||
public class VoiceCloneServiceTest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(VoiceCloneServiceTest.class);
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private VoiceCloneService voiceCloneService;
|
||||
|
||||
private FileService fileService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
ZaiConfig zaiConfig = new ZaiConfig();
|
||||
String apiKey = zaiConfig.getApiKey();
|
||||
if (apiKey == null) {
|
||||
zaiConfig.setApiKey("id.test-api-key");
|
||||
}
|
||||
ZaiClient client = new ZaiClient(zaiConfig);
|
||||
voiceCloneService = client.voiceClone();
|
||||
fileService = client.files();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test voice clone creation with file upload")
|
||||
@EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$")
|
||||
void testCloneVoice() throws JsonProcessingException {
|
||||
// Step 1: Upload the voice input audio file
|
||||
// First, we need to upload the voice sample audio file to get a file ID
|
||||
String voiceInputFilePath = "src/test/resources/voice_clone_input.mp3";
|
||||
|
||||
FileUploadParams uploadRequest = FileUploadParams.builder()
|
||||
.filePath(voiceInputFilePath)
|
||||
.purpose(UploadFilePurpose.VOICE_CLONE_INPUT.value())
|
||||
.requestId("voice-clone-test-" + System.currentTimeMillis())
|
||||
.build();
|
||||
|
||||
FileApiResponse uploadResponse = fileService.uploadFile(uploadRequest);
|
||||
assertNotNull(uploadResponse, "File upload response should not be null");
|
||||
assertTrue(uploadResponse.isSuccess(), "File upload should be successful");
|
||||
assertNotNull(uploadResponse.getData(), "Uploaded file data should not be null");
|
||||
|
||||
String fileId = uploadResponse.getData().getId();
|
||||
logger.info("Voice input file uploaded successfully with ID: {}", fileId);
|
||||
|
||||
// Step 2: Create voice clone using the uploaded file
|
||||
// Now we can use the uploaded file ID to create a voice clone
|
||||
VoiceCloneRequest request = new VoiceCloneRequest();
|
||||
request.setVoiceName("Test");
|
||||
request.setVoiceTextInput("This is sample text for voice cloning training");
|
||||
request.setVoiceTextOutput("This is target text for voice preview generation");
|
||||
request.setFileId(fileId); // Use the actual file ID from upload
|
||||
|
||||
// Execute voice cloning
|
||||
VoiceCloneResponse response = voiceCloneService.cloneVoice(request);
|
||||
|
||||
// Verify the voice cloning response
|
||||
assertNotNull(response, "Voice clone response should not be null");
|
||||
assertTrue(response.isSuccess(), "Voice cloning should be successful");
|
||||
assertNotNull(response.getData(), "Voice clone data should not be null");
|
||||
logger.info("Voice clone created successfully: {}", mapper.writeValueAsString(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test voice list")
|
||||
@EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$")
|
||||
void testListVoice() throws JsonProcessingException {
|
||||
// Create a voice list request with filtering parameters
|
||||
// This will retrieve voices filtered by type and name pattern
|
||||
VoiceListRequest request = new VoiceListRequest();
|
||||
request.setVoiceType("PRIVATE"); // Filter for custom voice clones
|
||||
request.setVoiceName("Test"); // Filter by voice name pattern
|
||||
|
||||
// Execute the voice listing
|
||||
VoiceListResponse response = voiceCloneService.listVoice(request);
|
||||
|
||||
// Verify the listing response
|
||||
logger.info("Voice list response: {}", mapper.writeValueAsString(response));
|
||||
assertNotNull(response, "Voice list response should not be null");
|
||||
assertTrue(response.isSuccess(), "Voice listing should be successful");
|
||||
assertNotNull(response.getData(), "Voice list data should not be null");
|
||||
logger.info("Voice list retrieved successfully: {}", mapper.writeValueAsString(response));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test voice deletion")
|
||||
@EnabledIfEnvironmentVariable(named = "ZAI_API_KEY", matches = "^[^.]+\\.[^.]+$")
|
||||
void testDeleteVoice() throws JsonProcessingException {
|
||||
// Create a voice deletion request with a test voice ID
|
||||
// In a real scenario, this would be an ID from a previously created voice clone
|
||||
VoiceDeleteRequest request = new VoiceDeleteRequest();
|
||||
request.setVoiceId("Test voice Id");
|
||||
|
||||
// Execute the voice deletion
|
||||
VoiceDeleteResponse response = voiceCloneService.deleteVoice(request);
|
||||
|
||||
// Verify the deletion response
|
||||
assertNotNull(response, "Voice deletion response should not be null");
|
||||
assertTrue(response.isSuccess(), "Voice deletion should be successful");
|
||||
assertNotNull(response.getData(), "Voice deletion data should not be null");
|
||||
logger.info("Voice deleted successfully: {}", mapper.writeValueAsString(response));
|
||||
}
|
||||
|
||||
}
|
||||
BIN
core/src/test/resources/voice_clone_input.mp3
Normal file
BIN
core/src/test/resources/voice_clone_input.mp3
Normal file
Binary file not shown.
176
samples/src/main/ai.z.openapi.samples/VoiceCloneExample.java
Normal file
176
samples/src/main/ai.z.openapi.samples/VoiceCloneExample.java
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
package ai.z.openapi.samples;
|
||||
|
||||
import ai.z.openapi.ZaiClient;
|
||||
import ai.z.openapi.service.file.FileApiResponse;
|
||||
import ai.z.openapi.service.file.FileService;
|
||||
import ai.z.openapi.service.file.FileUploadParams;
|
||||
import ai.z.openapi.service.file.UploadFilePurpose;
|
||||
import ai.z.openapi.service.voiceclone.*;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Voice Clone Example
|
||||
* Demonstrates how to use ZaiClient for voice cloning operations including:
|
||||
* - Uploading voice samples
|
||||
* - Creating voice clones
|
||||
* - Listing existing voices
|
||||
* - Deleting voice clones
|
||||
*/
|
||||
public class VoiceCloneExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create client, recommended to set API Key via environment variable
|
||||
// export ZAI_API_KEY=your.api_key
|
||||
ZaiClient client = ZaiClient.builder().build();
|
||||
|
||||
// Or set API Key via code
|
||||
// ZaiClient client = ZaiClient.builder()
|
||||
// .apiKey("your.api_key")
|
||||
// .build();
|
||||
|
||||
VoiceCloneService voiceCloneService = client.voiceClone();
|
||||
FileService fileService = client.files();
|
||||
|
||||
try {
|
||||
// Example 1: Create a voice clone
|
||||
System.out.println("=== Voice Clone Creation Example ===");
|
||||
createVoiceCloneExample(voiceCloneService, fileService);
|
||||
|
||||
// Example 2: List existing voices
|
||||
System.out.println("\n=== Voice List Example ===");
|
||||
listVoicesExample(voiceCloneService);
|
||||
|
||||
// Example 3: Delete a voice clone
|
||||
System.out.println("\n=== Voice Deletion Example ===");
|
||||
deleteVoiceExample(voiceCloneService);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Exception occurred: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of creating a voice clone with file upload
|
||||
*/
|
||||
private static void createVoiceCloneExample(VoiceCloneService voiceCloneService, FileService fileService) {
|
||||
try {
|
||||
// Step 1: Upload the voice input audio file
|
||||
String voiceInputFilePath = Paths.get("samples", "resources", "voice_clone_input.mp3").toString();
|
||||
|
||||
FileUploadParams uploadRequest = FileUploadParams.builder()
|
||||
.filePath(voiceInputFilePath)
|
||||
.purpose(UploadFilePurpose.VOICE_CLONE_INPUT.value())
|
||||
.requestId("voice-clone-example-" + System.currentTimeMillis())
|
||||
.build();
|
||||
|
||||
System.out.println("Uploading voice input file...");
|
||||
FileApiResponse uploadResponse = fileService.uploadFile(uploadRequest);
|
||||
|
||||
if (uploadResponse.isSuccess()) {
|
||||
String fileId = uploadResponse.getData().getId();
|
||||
System.out.println("Voice input file uploaded successfully with ID: " + fileId);
|
||||
|
||||
// Step 2: Create voice clone using the uploaded file
|
||||
VoiceCloneRequest request = new VoiceCloneRequest();
|
||||
request.setVoiceName("My Custom Voice");
|
||||
request.setVoiceTextInput("Hello, this is a sample text for voice cloning training");
|
||||
request.setVoiceTextOutput("Welcome to our voice synthesis system");
|
||||
request.setFileId(fileId);
|
||||
request.setRequestId("clone-request-" + System.currentTimeMillis());
|
||||
|
||||
System.out.println("Creating voice clone...");
|
||||
VoiceCloneResponse response = voiceCloneService.cloneVoice(request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
System.out.println("Voice clone created successfully!");
|
||||
System.out.println("Voice ID: " + response.getData().getVoiceId());
|
||||
} else {
|
||||
System.err.println("Voice clone creation failed: " + response.getMsg());
|
||||
if (response.getError() != null) {
|
||||
System.err.println("Error details: " + response.getError().getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.err.println("File upload failed: " + uploadResponse.getMsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in voice clone creation: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of listing existing voice clones
|
||||
*/
|
||||
private static void listVoicesExample(VoiceCloneService voiceCloneService) {
|
||||
try {
|
||||
// List all private voices
|
||||
VoiceListRequest request = new VoiceListRequest();
|
||||
request.setVoiceType("PRIVATE"); // Filter for custom voice clones
|
||||
request.setRequestId("list-request-" + System.currentTimeMillis());
|
||||
|
||||
System.out.println("Retrieving voice list...");
|
||||
VoiceListResponse response = voiceCloneService.listVoice(request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
System.out.println("Voice list retrieved successfully!");
|
||||
if (response.getData().getVoiceList() != null && !response.getData().getVoiceList().isEmpty()) {
|
||||
System.out.println("Found " + response.getData().getVoiceList().size() + " voices:");
|
||||
for (VoiceData voice : response.getData().getVoiceList()) {
|
||||
System.out.println("- Voice ID: " + voice.getVoiceId());
|
||||
System.out.println(" Name: " + voice.getVoiceName());
|
||||
System.out.println(" Type: " + voice.getVoiceType());
|
||||
if (voice.getDownloadUrl() != null) {
|
||||
System.out.println(" Download URL: " + voice.getDownloadUrl());
|
||||
}
|
||||
if (voice.getCreateTime() != null) {
|
||||
System.out.println(" Created: " + voice.getCreateTime());
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
} else {
|
||||
System.out.println("No voices found.");
|
||||
}
|
||||
} else {
|
||||
System.err.println("Voice list retrieval failed: " + response.getMsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error in voice list retrieval: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of deleting a voice clone
|
||||
* Note: Replace "your-voice-id" with an actual voice ID from your account
|
||||
*/
|
||||
private static void deleteVoiceExample(VoiceCloneService voiceCloneService) {
|
||||
try {
|
||||
// Note: This is just an example - replace with actual voice ID
|
||||
String voiceIdToDelete = "your-voice-id-here";
|
||||
|
||||
VoiceDeleteRequest request = new VoiceDeleteRequest();
|
||||
request.setVoiceId(voiceIdToDelete);
|
||||
request.setRequestId("delete-request-" + System.currentTimeMillis());
|
||||
|
||||
System.out.println("Deleting voice clone with ID: " + voiceIdToDelete);
|
||||
VoiceDeleteResponse response = voiceCloneService.deleteVoice(request);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
System.out.println("Voice clone deleted successfully!");
|
||||
if (response.getData().getDeleteTime() != null) {
|
||||
System.out.println("Deletion time: " + response.getData().getDeleteTime());
|
||||
}
|
||||
} else {
|
||||
System.err.println("Voice deletion failed: " + response.getMsg());
|
||||
if (response.getError() != null) {
|
||||
System.err.println("Error details: " + response.getError().getMessage());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Expected to fail with example voice ID
|
||||
System.out.println("Note: This example uses a placeholder voice ID and is expected to fail.");
|
||||
System.out.println("Replace 'your-voice-id-here' with an actual voice ID to test deletion.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue