[Feature] Youtube importing and refreshing implementation (ytdlp)
#1
@ -14,10 +14,10 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import com.bivashy.backend.composer.auth.CustomUserDetails;
|
import com.bivashy.backend.composer.auth.CustomUserDetails;
|
||||||
import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest;
|
import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest;
|
||||||
import com.bivashy.backend.composer.dto.track.AddYoutubeTrackRequest;
|
|
||||||
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
||||||
import com.bivashy.backend.composer.dto.track.TrackBulkReorderRequest;
|
import com.bivashy.backend.composer.dto.track.TrackBulkReorderRequest;
|
||||||
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
||||||
|
import com.bivashy.backend.composer.dto.track.YoutubeTrackRequest;
|
||||||
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParamsBuilder;
|
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParamsBuilder;
|
||||||
import com.bivashy.backend.composer.exception.ImportTrackException;
|
import com.bivashy.backend.composer.exception.ImportTrackException;
|
||||||
import com.bivashy.backend.composer.service.TrackService;
|
import com.bivashy.backend.composer.service.TrackService;
|
||||||
@ -47,7 +47,7 @@ public class TrackController {
|
|||||||
public ResponseEntity<List<TrackResponse>> addYoutubeTrack(
|
public ResponseEntity<List<TrackResponse>> addYoutubeTrack(
|
||||||
@AuthenticationPrincipal CustomUserDetails user,
|
@AuthenticationPrincipal CustomUserDetails user,
|
||||||
@PathVariable long playlistId,
|
@PathVariable long playlistId,
|
||||||
@RequestBody AddYoutubeTrackRequest request) throws ImportTrackException {
|
@RequestBody YoutubeTrackRequest request) throws ImportTrackException {
|
||||||
List<TrackResponse> response = trackService.addYoutubeTrack(user, playlistId, request);
|
List<TrackResponse> response = trackService.addYoutubeTrack(user, playlistId, request);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
package com.bivashy.backend.composer.dto.track;
|
|
||||||
|
|
||||||
public record AddYoutubeTrackRequest(String youtubeUrl) {
|
|
||||||
}
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package com.bivashy.backend.composer.dto.track;
|
||||||
|
|
||||||
|
public record YoutubeTrackRequest(String youtubeUrl) {
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ public interface AudioBlobStorageService {
|
|||||||
|
|
||||||
String store(byte[] data, Map<String, String> metadata);
|
String store(byte[] data, Map<String, String> metadata);
|
||||||
|
|
||||||
String store(String key, byte[] data);
|
String store(String key, byte[] data, Map<String, String> metadata);
|
||||||
|
|
||||||
byte[] readRaw(String path) throws IOException;
|
byte[] readRaw(String path) throws IOException;
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ public class AudioS3StorageService implements AudioBlobStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String store(String key, byte[] data) {
|
public String store(String key, byte[] data, Map<String, String> metadata) {
|
||||||
try {
|
try {
|
||||||
ResponseInputStream<GetObjectResponse> response = s3Client.getObject(GetObjectRequest.builder()
|
ResponseInputStream<GetObjectResponse> response = s3Client.getObject(GetObjectRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
@ -89,7 +89,7 @@ public class AudioS3StorageService implements AudioBlobStorageService {
|
|||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(key)
|
.key(key)
|
||||||
.contentType(response.response().contentType())
|
.contentType(response.response().contentType())
|
||||||
.metadata(response.response().metadata())
|
.metadata(metadata)
|
||||||
.build(), RequestBody.fromBytes(data));
|
.build(), RequestBody.fromBytes(data));
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
@ -98,6 +98,7 @@ public class AudioS3StorageService implements AudioBlobStorageService {
|
|||||||
s3Client.putObject(PutObjectRequest.builder()
|
s3Client.putObject(PutObjectRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
.key(key)
|
.key(key)
|
||||||
|
.metadata(metadata)
|
||||||
.build(), RequestBody.fromBytes(data));
|
.build(), RequestBody.fromBytes(data));
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
package com.bivashy.backend.composer.service;
|
package com.bivashy.backend.composer.service;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -20,10 +20,10 @@ import org.springframework.web.server.ResponseStatusException;
|
|||||||
|
|
||||||
import com.bivashy.backend.composer.auth.CustomUserDetails;
|
import com.bivashy.backend.composer.auth.CustomUserDetails;
|
||||||
import com.bivashy.backend.composer.dto.importing.SingleTrackProgress;
|
import com.bivashy.backend.composer.dto.importing.SingleTrackProgress;
|
||||||
import com.bivashy.backend.composer.dto.track.AddYoutubeTrackRequest;
|
|
||||||
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
||||||
import com.bivashy.backend.composer.dto.track.TrackBulkReorderRequest;
|
import com.bivashy.backend.composer.dto.track.TrackBulkReorderRequest;
|
||||||
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
||||||
|
import com.bivashy.backend.composer.dto.track.YoutubeTrackRequest;
|
||||||
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParams;
|
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParams;
|
||||||
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParamsBuilder;
|
import com.bivashy.backend.composer.dto.track.service.AddLocalTrackParamsBuilder;
|
||||||
import com.bivashy.backend.composer.exception.ImportTrackException;
|
import com.bivashy.backend.composer.exception.ImportTrackException;
|
||||||
@ -128,7 +128,7 @@ public class TrackService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<TrackResponse> addYoutubeTrack(CustomUserDetails user, long playlistId,
|
public List<TrackResponse> addYoutubeTrack(CustomUserDetails user, long playlistId,
|
||||||
AddYoutubeTrackRequest request) throws ImportTrackException {
|
YoutubeTrackRequest request) throws ImportTrackException {
|
||||||
List<VideoInfo> videoInfos = Collections.emptyList();
|
List<VideoInfo> videoInfos = Collections.emptyList();
|
||||||
try {
|
try {
|
||||||
videoInfos = YtDlp.getVideoInfo(request.youtubeUrl());
|
videoInfos = YtDlp.getVideoInfo(request.youtubeUrl());
|
||||||
@ -226,7 +226,7 @@ public class TrackService {
|
|||||||
var body = Files.readAllBytes(path);
|
var body = Files.readAllBytes(path);
|
||||||
|
|
||||||
if (isMetadataFile) {
|
if (isMetadataFile) {
|
||||||
s3StorageService.store(downloadedMetadataKey, body);
|
s3StorageService.store(downloadedMetadataKey, body, Map.of());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String fileName = fileNameWithoutExtension(path.getFileName().toString());
|
String fileName = fileNameWithoutExtension(path.getFileName().toString());
|
||||||
@ -239,7 +239,6 @@ public class TrackService {
|
|||||||
|
|
||||||
logger.info("downloaded file {} and info {}, key {}", fileName, videoInfo.getTitle(), audioKey);
|
logger.info("downloaded file {} and info {}, key {}", fileName, videoInfo.getTitle(), audioKey);
|
||||||
|
|
||||||
audioKey = s3StorageService.store(audioKey, body);
|
|
||||||
Optional<Metadata> metadata = Optional.empty();
|
Optional<Metadata> metadata = Optional.empty();
|
||||||
|
|
||||||
try (var inputStream = Files.newInputStream(path)) {
|
try (var inputStream = Files.newInputStream(path)) {
|
||||||
@ -251,8 +250,8 @@ public class TrackService {
|
|||||||
|
|
||||||
TrackSource playlistEntrySource;
|
TrackSource playlistEntrySource;
|
||||||
try {
|
try {
|
||||||
playlistEntrySource = trackSourceService.createLocalTrackSource(
|
playlistEntrySource = trackSourceService.createTrackSourceWithKey(audioKey, body, ffprobeJson,
|
||||||
body, ffprobeJson, OBJECT_MAPPER.writeValueAsString(videoInfo), SourceTypes.PLAYLIST);
|
OBJECT_MAPPER.writeValueAsString(videoInfo), SourceTypes.PLAYLIST);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ImportTrackException("cannot read blob body", e);
|
throw new ImportTrackException("cannot read blob body", e);
|
||||||
}
|
}
|
||||||
@ -266,7 +265,7 @@ public class TrackService {
|
|||||||
// TODO: Recognize music if the duration is less than five minutes
|
// TODO: Recognize music if the duration is less than five minutes
|
||||||
// (configurable), and if not, it is a playlist and should be marked as is
|
// (configurable), and if not, it is a playlist and should be marked as is
|
||||||
trackMetadataService.createTrackMetadata(
|
trackMetadataService.createTrackMetadata(
|
||||||
track, title, fileName, trackSource.getSourceUrl(), artist, null, durationSeconds);
|
track, title, fileName, audioKey, artist, null, durationSeconds);
|
||||||
|
|
||||||
trackPlaylistService.insertTrackAtEnd(playlistId, track.getId());
|
trackPlaylistService.insertTrackAtEnd(playlistId, track.getId());
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,19 @@ public class TrackSourceService {
|
|||||||
return trackSourceRepository.save(new TrackSource(audioPath, type, LocalDateTime.now()));
|
return trackSourceRepository.save(new TrackSource(audioPath, type, LocalDateTime.now()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TrackSource createTrackSourceWithKey(String key, byte[] audioBytes, String ffprobeJson,
|
||||||
|
String ytdlpMetadata, String sourceType) {
|
||||||
|
Map<String, String> metadata = new HashMap<>(Map.of(YTDLP_METADATA_KEY, ffprobeJson));
|
||||||
|
if (ytdlpMetadata != null) {
|
||||||
|
// TODO: Add tag or smth?
|
||||||
|
}
|
||||||
|
String audioPath = s3Service.store(key, audioBytes, metadata);
|
||||||
|
|
||||||
|
SourceType type = sourceTypeRepository.findByName(sourceType)
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Source type not found: " + sourceType));
|
||||||
|
return trackSourceRepository.save(new TrackSource(audioPath, type, LocalDateTime.now()));
|
||||||
|
}
|
||||||
|
|
||||||
public TrackSource createYoutubeTrackSource(String sourceType) {
|
public TrackSource createYoutubeTrackSource(String sourceType) {
|
||||||
String folderPath = s3Service.storeFolder();
|
String folderPath = s3Service.storeFolder();
|
||||||
SourceType type = sourceTypeRepository.findByName(sourceType)
|
SourceType type = sourceTypeRepository.findByName(sourceType)
|
||||||
|
|||||||
Reference in New Issue
Block a user