WIP: Submit transcoding jobs via a HTTP API #6
@ -6,6 +6,7 @@ import se.su.dsv.whisperapi.core.Job;
|
||||
import se.su.dsv.whisperapi.core.JobCompletion;
|
||||
import se.su.dsv.whisperapi.core.NotificationStatus;
|
||||
import se.su.dsv.whisperapi.core.OutputFormat;
|
||||
import se.su.dsv.whisperapi.core.SourceFile;
|
||||
import se.su.dsv.whisperapi.core.TranscriptionRepository;
|
||||
import se.su.dsv.whisperapi.core.Transcription;
|
||||
|
||||
@ -55,14 +56,16 @@ public class JDBCTranscriptionRepository implements TranscriptionRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFileToTranscription(Transcription transcription, String filename) {
|
||||
public void addFileToTranscription(Transcription transcription, SourceFile sourceFile) {
|
||||
jdbcClient.sql("""
|
||||
INSERT INTO transcriptions_files (transcription_id, filename)
|
||||
VALUES (:transcription_id, :filename)
|
||||
INSERT INTO transcriptions_files (id, transcription_id, filename, transcribed_result_uri)
|
||||
VALUES (:id, :transcription_id, :filename, :result_file_download_uri)
|
||||
ON DUPLICATE KEY UPDATE filename = :filename
|
||||
""")
|
||||
.param("transcription_id", transcription.id())
|
||||
.param("filename", filename)
|
||||
.param("id", sourceFile.id())
|
||||
.param("filename", sourceFile.filename())
|
||||
.param("result_file_download_uri", sourceFile.downloadUri().toString())
|
||||
.update();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package se.su.dsv.whisperapi.api;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
@ -15,7 +17,7 @@ import se.su.dsv.whisperapi.core.JobCompletion;
|
||||
import se.su.dsv.whisperapi.core.JobNotFound;
|
||||
import se.su.dsv.whisperapi.core.OutputFormat;
|
||||
import se.su.dsv.whisperapi.core.Transcription;
|
||||
import se.su.dsv.whisperapi.core.TranscriptionFile;
|
||||
import se.su.dsv.whisperapi.core.SourceFileUpload;
|
||||
import se.su.dsv.whisperapi.core.TranscriptionService;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -78,6 +80,7 @@ public class ApiController {
|
||||
Principal owner,
|
||||
@PathVariable("id") String id,
|
||||
@RequestHeader("X-Filename") String filename,
|
||||
UriComponentsBuilder uriComponentsBuilder,
|
||||
InputStream fileData)
|
||||
{
|
||||
try {
|
||||
@ -87,7 +90,22 @@ public class ApiController {
|
||||
UUID uuid = UUID.fromString(id);
|
||||
Transcription transcription = transcriptionService.getTranscription(owner, uuid)
|
||||
.orElseThrow(() -> new TranscriptionNotFound(id));
|
||||
transcriptionService.addFileToBeTranscribed(transcription, new TranscriptionFile(filename, fileData));
|
||||
transcriptionService.addFileToBeTranscribed(
|
||||
transcription,
|
||||
new SourceFileUpload(filename, fileData),
|
||||
// The URI is generated here at upload since we need to be in a web context to generate it.
|
||||
// It is likely that the source who uploaded is the same as who will download the result
|
||||
// so using this as the web context seem like the best spot, the other option would be at
|
||||
// submission time.
|
||||
transcribedFileId -> uriComponentsBuilder.cloneBuilder()
|
||||
.pathSegment("api")
|
||||
.pathSegment("transcriptions")
|
||||
.pathSegment(id)
|
||||
.pathSegment("file")
|
||||
.pathSegment(transcribedFileId.toString())
|
||||
.pathSegment("result")
|
||||
.build()
|
||||
.toUri());
|
||||
return ResponseEntity.accepted().build();
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// Invalid UUID
|
||||
@ -98,6 +116,30 @@ public class ApiController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transcribed result for a given source file if it was transcribed successfully.
|
||||
*
|
||||
* @param owner owner of the transcription job
|
||||
* @param transcriptionId id of the transcription job
|
||||
* @param fileId id of the source file
|
||||
* @return the transcribed result
|
||||
*/
|
||||
@GetMapping(value = "/api/transcriptions/{transcriptionId}/file/{fileId}/result",
|
||||
consumes = MediaType.ALL_VALUE,
|
||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||
public InputStream getTranscribedResult(
|
||||
Principal owner,
|
||||
@PathVariable("transcriptionId") String transcriptionId,
|
||||
@PathVariable("fileId") String fileId)
|
||||
{
|
||||
// access control
|
||||
transcriptionService.getTranscription(owner, UUID.fromString(transcriptionId))
|
||||
.orElseThrow(() -> new TranscriptionNotFound(transcriptionId));
|
||||
|
||||
// todo
|
||||
throw new UnsupportedOperationException("NYI");
|
||||
}
|
||||
|
||||
@PostMapping(value = "/api/transcriptions/{id}/job", consumes = "*/*")
|
||||
public ResponseEntity<Void> submitTranscriptionJob(
|
||||
Principal owner,
|
||||
|
29
src/main/java/se/su/dsv/whisperapi/core/Callback.java
Normal file
29
src/main/java/se/su/dsv/whisperapi/core/Callback.java
Normal file
@ -0,0 +1,29 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public record Callback(
|
||||
@JsonProperty("transcription_id") UUID transcriptionId,
|
||||
@JsonProperty("files") List<File> files)
|
||||
{
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "result")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = File.Transcribed.class, name = "success"),
|
||||
@JsonSubTypes.Type(value = File.Failed.class, name = "failure")
|
||||
})
|
||||
sealed interface File {
|
||||
record Transcribed(
|
||||
@JsonProperty("original_file_name") String originalFilename,
|
||||
@JsonProperty("transcription_download_link") String transcriptionDownloadLink)
|
||||
implements File {}
|
||||
record Failed(
|
||||
@JsonProperty("original_file_name") String originalFilename,
|
||||
@JsonProperty("error_message") String errorMessage)
|
||||
implements File {}
|
||||
}
|
||||
}
|
11
src/main/java/se/su/dsv/whisperapi/core/SourceFile.java
Normal file
11
src/main/java/se/su/dsv/whisperapi/core/SourceFile.java
Normal file
@ -0,0 +1,11 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @param filename the user provided file name
|
||||
* @param downloadUri a pre-generated download link for any potential future transcription result
|
||||
*/
|
||||
public record SourceFile(UUID id, String filename, URI downloadUri) {
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public record SourceFileUpload(String filename, InputStream data) {
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TranscribedFileDownloadUriGenerator {
|
||||
URI generateDownloadUri(UUID transcribedFileId);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package se.su.dsv.whisperapi.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public record TranscriptionFile(String filename, InputStream data) {
|
||||
}
|
@ -11,7 +11,7 @@ public interface TranscriptionRepository {
|
||||
|
||||
Optional<Transcription> findByOwnerAndId(Principal owner, UUID uuid);
|
||||
|
||||
void addFileToTranscription(Transcription transcription, String filename);
|
||||
void addFileToTranscription(Transcription transcription, SourceFile sourceFile);
|
||||
|
||||
/**
|
||||
* @return the list of filenames that have been {@link #addFileToTranscription added}
|
||||
|
@ -50,16 +50,22 @@ public class TranscriptionService {
|
||||
return transcriptionRepository.findByOwnerAndId(owner, uuid);
|
||||
}
|
||||
|
||||
public void addFileToBeTranscribed(Transcription transcription, TranscriptionFile file)
|
||||
public void addFileToBeTranscribed(
|
||||
Transcription transcription,
|
||||
SourceFileUpload file,
|
||||
TranscribedFileDownloadUriGenerator downloadUriGenerator)
|
||||
throws IOException
|
||||
{
|
||||
Path fileToBeTranscribed = fileDirectory.resolve(transcription.id().toString()).resolve(file.filename());
|
||||
UUID uuid = UUID.randomUUID();
|
||||
URI downloadUri = downloadUriGenerator.generateDownloadUri(uuid);
|
||||
Path fileToBeTranscribed = fileDirectory.resolve(transcription.id().toString()).resolve(uuid.toString());
|
||||
Files.createDirectories(fileToBeTranscribed.getParent());
|
||||
try (var out = Files.newOutputStream(fileToBeTranscribed, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
var in = file.data())
|
||||
{
|
||||
in.transferTo(out);
|
||||
transcriptionRepository.addFileToTranscription(transcription, file.filename());
|
||||
SourceFile sourceFile = new SourceFile(uuid, file.filename(), downloadUri);
|
||||
transcriptionRepository.addFileToTranscription(transcription, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +156,21 @@ public class TranscriptionService {
|
||||
|
||||
private boolean notifyOwner(final Transcription transcription, List<Job> jobs) {
|
||||
URI callbackUri = transcription.callbackUri();
|
||||
List<Callback.File> files = jobs.stream()
|
||||
.<Callback.File>map(job -> {
|
||||
switch (job.status()) {
|
||||
case Job.Status.Completed(JobCompletion.Success ignored) -> {
|
||||
return new Callback.File.Transcribed("<unknown>", "http://"); //job.transcribedDownloadLink());
|
||||
}
|
||||
case Job.Status.Completed(JobCompletion.Failure(String errorMessage)) -> {
|
||||
return new Callback.File.Failed("<unknown>", errorMessage);
|
||||
}
|
||||
case Job.Status.Pending() -> throw new IllegalStateException("Job should be completed");
|
||||
}
|
||||
})
|
||||
.toList();
|
||||
Callback callback = new Callback(transcription.id(), files);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(callbackUri)
|
||||
|
@ -0,0 +1,7 @@
|
||||
ALTER TABLE transcriptions_files
|
||||
ADD COLUMN id UUID NOT NULL FIRST,
|
||||
ADD COLUMN transcribed_result_uri VARCHAR(255) NOT NULL AFTER filename;
|
||||
|
||||
ALTER TABLE transcriptions_files ADD INDEX FK_transcriptions_files_transcription (transcription_id);
|
||||
ALTER TABLE transcriptions_files DROP PRIMARY KEY;
|
||||
ALTER TABLE transcriptions_files ADD PRIMARY KEY (id);
|
Loading…
x
Reference in New Issue
Block a user