WIP: Submit transcoding jobs via a HTTP API #6
@ -7,6 +7,7 @@ import se.su.dsv.whisperapi.core.Transcription;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -59,4 +60,16 @@ public class JDBCTransactionRepository implements TransactionRepository {
|
|||||||
.param("filename", filename)
|
.param("filename", filename)
|
||||||
.update();
|
.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getFiles(Transcription transcription) {
|
||||||
|
return jdbcClient.sql("""
|
||||||
|
SELECT filename
|
||||||
|
FROM transcriptions_files
|
||||||
|
WHERE transcription_id = :transcription_id
|
||||||
|
""")
|
||||||
|
.param("transcription_id", transcription.id())
|
||||||
|
.query(String.class)
|
||||||
|
.list();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public class WhisperApiApplication {
|
|||||||
TransactionRepository transcriptionRepository,
|
TransactionRepository transcriptionRepository,
|
||||||
WhisperFrontendConfiguration config)
|
WhisperFrontendConfiguration config)
|
||||||
{
|
{
|
||||||
return new TranscriptionService(transcriptionRepository, config.transcriptionFilesDirectory());
|
return new TranscriptionService(transcriptionRepository, config.transcriptionFilesDirectory(), config.jobsDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -6,6 +6,7 @@ import java.nio.file.Path;
|
|||||||
|
|
||||||
@ConfigurationProperties(prefix = "whisper.frontend")
|
@ConfigurationProperties(prefix = "whisper.frontend")
|
||||||
public record WhisperFrontendConfiguration(
|
public record WhisperFrontendConfiguration(
|
||||||
Path transcriptionFilesDirectory)
|
Path transcriptionFilesDirectory,
|
||||||
|
Path jobsDirectory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
import se.su.dsv.whisperapi.core.CreateTranscription;
|
import se.su.dsv.whisperapi.core.CreateTranscription;
|
||||||
import se.su.dsv.whisperapi.core.OutputFormat;
|
import se.su.dsv.whisperapi.core.OutputFormat;
|
||||||
import se.su.dsv.whisperapi.core.Transcription;
|
import se.su.dsv.whisperapi.core.Transcription;
|
||||||
@ -92,4 +93,34 @@ public class ApiController {
|
|||||||
throw new FileUploadFailed();
|
throw new FileUploadFailed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/transcriptions/{id}/job", consumes = "*/*")
|
||||||
|
public ResponseEntity<Void> submitTranscriptionJob(
|
||||||
|
Principal owner,
|
||||||
|
UriComponentsBuilder uriComponentsBuilder,
|
||||||
|
@PathVariable("id") String id)
|
||||||
|
{
|
||||||
|
UUID uuid = UUID.fromString(id);
|
||||||
|
Transcription transcription = transcriptionService.getTranscription(owner, uuid)
|
||||||
|
.orElseThrow(() -> new TranscriptionNotFound(id));
|
||||||
|
transcriptionService.submitTranscriptionJob(transcription, jobId -> uriComponentsBuilder.cloneBuilder()
|
||||||
|
.path("api")
|
||||||
|
.pathSegment("transcriptions")
|
||||||
|
.pathSegment(id)
|
||||||
|
.pathSegment("job")
|
||||||
|
.pathSegment("callback")
|
||||||
|
.pathSegment(jobId.toString())
|
||||||
|
.build()
|
||||||
|
.toUri());
|
||||||
|
return ResponseEntity.accepted().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/api/transcriptions/{id}/job/callback/{jobId}")
|
||||||
|
public ResponseEntity<JobCallbackResponse> jobCallback(
|
||||||
|
@PathVariable("id") String transcriptionId,
|
||||||
|
@PathVariable("jobId") String jobId,
|
||||||
|
@RequestBody JobCallbackResponse jobCallbackResponse)
|
||||||
|
{
|
||||||
|
return ResponseEntity.ok(jobCallbackResponse);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package se.su.dsv.whisperapi.api;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "result")
|
||||||
|
@JsonSubTypes({
|
||||||
|
@JsonSubTypes.Type(value = JobCallbackResponse.Success.class, name = "Success"),
|
||||||
|
@JsonSubTypes.Type(value = JobCallbackResponse.Failure.class, name = "Failure")
|
||||||
|
})
|
||||||
|
public sealed interface JobCallbackResponse {
|
||||||
|
record Success(@JsonProperty("resultfile") String resultFileAbsolutePath) implements JobCallbackResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
record Failure(@JsonProperty("errormessage") String errorMessage) implements JobCallbackResponse {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package se.su.dsv.whisperapi.core;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface CallbackUriGenerator {
|
||||||
|
URI generateCallbackUri(UUID jobId);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package se.su.dsv.whisperapi.core;
|
package se.su.dsv.whisperapi.core;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -10,4 +11,10 @@ public interface TransactionRepository {
|
|||||||
Optional<Transcription> findByOwnerAndId(Principal owner, UUID uuid);
|
Optional<Transcription> findByOwnerAndId(Principal owner, UUID uuid);
|
||||||
|
|
||||||
void addFileToTranscription(Transcription transcription, String filename);
|
void addFileToTranscription(Transcription transcription, String filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the list of filenames that have been {@link #addFileToTranscription added}
|
||||||
|
* to the transcription.
|
||||||
|
*/
|
||||||
|
List<String> getFiles(Transcription transcription);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,29 @@
|
|||||||
package se.su.dsv.whisperapi.core;
|
package se.su.dsv.whisperapi.core;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class TranscriptionService {
|
public class TranscriptionService {
|
||||||
private final TransactionRepository transactionRepository;
|
private final TransactionRepository transactionRepository;
|
||||||
private final Path fileDirectory;
|
private final Path fileDirectory;
|
||||||
|
private final Path jobsDirectory;
|
||||||
|
|
||||||
public TranscriptionService(TransactionRepository transactionRepository, Path fileDirectory) {
|
public TranscriptionService(TransactionRepository transactionRepository, Path fileDirectory, Path jobsDirectory) {
|
||||||
this.transactionRepository = transactionRepository;
|
this.transactionRepository = transactionRepository;
|
||||||
this.fileDirectory = fileDirectory;
|
this.fileDirectory = fileDirectory;
|
||||||
|
this.jobsDirectory = jobsDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -45,4 +54,45 @@ public class TranscriptionService {
|
|||||||
transactionRepository.addFileToTranscription(transcription, file.filename());
|
transactionRepository.addFileToTranscription(transcription, file.filename());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void submitTranscriptionJob(Transcription transcription, CallbackUriGenerator callbackUriGenerator) {
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
List<String> filenames = transactionRepository.getFiles(transcription);
|
||||||
|
for (String filename : filenames) {
|
||||||
|
UUID jobId = UUID.randomUUID();
|
||||||
|
Path fileToBeTranscribed = fileDirectory.resolve(transcription.id().toString()).resolve(filename);
|
||||||
|
Path jobFile = jobsDirectory.resolve(jobId + ".json");
|
||||||
|
URI callbackUri = callbackUriGenerator.generateCallbackUri(jobId);
|
||||||
|
|
||||||
|
record Job(
|
||||||
|
@JsonProperty("jobfile") String absolutePathToFileToBeTranscribed,
|
||||||
|
@JsonProperty("outputformat") String outputFormat,
|
||||||
|
@JsonProperty("origin") String origin,
|
||||||
|
@JsonProperty("callback") String callbackUri)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
Job job = new Job(
|
||||||
|
fileToBeTranscribed.toAbsolutePath().toString(),
|
||||||
|
toWhisperFormat(transcription.outputFormat()),
|
||||||
|
transcription.owner().getName(),
|
||||||
|
callbackUri.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
objectMapper.writeValue(jobFile.toFile(), job);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toWhisperFormat(OutputFormat outputFormat) {
|
||||||
|
return switch (outputFormat) {
|
||||||
|
case PLAIN_TEXT -> "text";
|
||||||
|
case VTT -> "vtt";
|
||||||
|
case SRT -> "srt";
|
||||||
|
case TSV -> "tsv";
|
||||||
|
case JSON -> "json";
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,3 +17,4 @@ spring.mvc.problemdetails.enabled=true
|
|||||||
# that is correctly handled will be logged as a warning
|
# that is correctly handled will be logged as a warning
|
||||||
spring.mvc.log-resolved-exception=false
|
spring.mvc.log-resolved-exception=false
|
||||||
whisper.frontend.transcription-files-directory=/tmp
|
whisper.frontend.transcription-files-directory=/tmp
|
||||||
|
whisper.frontend.jobs-directory=/tmp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user