WIP: Submit transcoding jobs via a HTTP API #6

Draft
ansv7779 wants to merge 22 commits from api-submission into master
9 changed files with 132 additions and 3 deletions
Showing only changes of commit c2ace10ff1 - Show all commits

View File

@ -7,6 +7,7 @@ import se.su.dsv.whisperapi.core.Transcription;
import java.net.URI;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -59,4 +60,16 @@ public class JDBCTransactionRepository implements TransactionRepository {
.param("filename", filename)
.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();
}
}

View File

@ -45,7 +45,7 @@ public class WhisperApiApplication {
TransactionRepository transcriptionRepository,
WhisperFrontendConfiguration config)
{
return new TranscriptionService(transcriptionRepository, config.transcriptionFilesDirectory());
return new TranscriptionService(transcriptionRepository, config.transcriptionFilesDirectory(), config.jobsDirectory());
}
@Bean

View File

@ -6,6 +6,7 @@ import java.nio.file.Path;
@ConfigurationProperties(prefix = "whisper.frontend")
public record WhisperFrontendConfiguration(
Path transcriptionFilesDirectory)
Path transcriptionFilesDirectory,
Path jobsDirectory)
{
}

View File

@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
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.OutputFormat;
import se.su.dsv.whisperapi.core.Transcription;
@ -92,4 +93,34 @@ public class ApiController {
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);
}
}

View File

@ -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 {
}
}

View File

@ -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);
}

View File

@ -1,6 +1,7 @@
package se.su.dsv.whisperapi.core;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@ -10,4 +11,10 @@ public interface TransactionRepository {
Optional<Transcription> findByOwnerAndId(Principal owner, UUID uuid);
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);
}

View File

@ -1,20 +1,29 @@
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.UncheckedIOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class TranscriptionService {
private final TransactionRepository transactionRepository;
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.fileDirectory = fileDirectory;
this.jobsDirectory = jobsDirectory;
}
@ -45,4 +54,45 @@ public class TranscriptionService {
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";
};
}
}

View File

@ -17,3 +17,4 @@ spring.mvc.problemdetails.enabled=true
# that is correctly handled will be logged as a warning
spring.mvc.log-resolved-exception=false
whisper.frontend.transcription-files-directory=/tmp
whisper.frontend.jobs-directory=/tmp