Implement proper ordering
This commit is contained in:
@ -1,17 +1,19 @@
|
||||
package com.bivashy.backend.composer.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest;
|
||||
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
||||
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
||||
import com.bivashy.backend.composer.model.User;
|
||||
import com.bivashy.backend.composer.service.TrackService;
|
||||
@ -24,7 +26,7 @@ public class TrackController {
|
||||
this.trackService = trackService;
|
||||
}
|
||||
|
||||
@PostMapping(path = "/playlist/{playlistId}/track", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PostMapping(path = "/playlist/{playlistId}/track/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<TrackResponse> addLocalTrack(
|
||||
@AuthenticationPrincipal User user,
|
||||
@PathVariable Long playlistId,
|
||||
@ -32,4 +34,12 @@ public class TrackController {
|
||||
TrackResponse response = trackService.addLocalTrack(user, playlistId, request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@GetMapping("/playlist/{playlistId}/tracks")
|
||||
public ResponseEntity<List<PlaylistTrackResponse>> getPlaylistTracks(
|
||||
@AuthenticationPrincipal User user,
|
||||
@PathVariable Long playlistId) {
|
||||
List<PlaylistTrackResponse> tracks = trackService.getPlaylistTracks(user, playlistId);
|
||||
return ResponseEntity.ok(tracks);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.bivashy.backend.composer.dto.track;
|
||||
|
||||
public record PlaylistTrackResponse(
|
||||
Long trackId,
|
||||
String title,
|
||||
String artist,
|
||||
String audioPath,
|
||||
Integer durationSeconds,
|
||||
String fileName) {
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bivashy.backend.composer.model;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.bivashy.backend.composer.model.key.PlaylistTrackId;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
@ -24,8 +26,8 @@ public class TrackPlaylist {
|
||||
@Column(name = "track_id", nullable = false)
|
||||
private Long trackId;
|
||||
|
||||
@Column(name = "order_index", nullable = false)
|
||||
private Long order;
|
||||
@Column(name = "order_index", nullable = false, precision = 1000, scale = 500)
|
||||
private BigDecimal order;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "playlist_id", insertable = false, updatable = false)
|
||||
@ -38,7 +40,7 @@ public class TrackPlaylist {
|
||||
TrackPlaylist() {
|
||||
}
|
||||
|
||||
public TrackPlaylist(Long playlistId, Long trackId, Long order) {
|
||||
public TrackPlaylist(Long playlistId, Long trackId, BigDecimal order) {
|
||||
this.playlistId = playlistId;
|
||||
this.trackId = trackId;
|
||||
this.order = order;
|
||||
@ -52,10 +54,14 @@ public class TrackPlaylist {
|
||||
return trackId;
|
||||
}
|
||||
|
||||
public Long getOrder() {
|
||||
public BigDecimal getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(BigDecimal order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public Playlist getPlaylist() {
|
||||
return playlist;
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bivashy.backend.composer.repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@ -7,4 +9,5 @@ import com.bivashy.backend.composer.model.TrackMetadata;
|
||||
|
||||
@Repository
|
||||
public interface TrackMetadataRepository extends JpaRepository<TrackMetadata, Long> {
|
||||
Optional<TrackMetadata> findByTrackId(Long trackId);
|
||||
}
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
package com.bivashy.backend.composer.repository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import com.bivashy.backend.composer.model.TrackPlaylist;
|
||||
import com.bivashy.backend.composer.model.key.PlaylistTrackId;
|
||||
|
||||
public interface TrackPlaylistRepository extends JpaRepository<TrackPlaylist, PlaylistTrackId> {
|
||||
List<TrackPlaylist> findByPlaylistIdOrderByOrderAsc(Long playlistId);
|
||||
|
||||
@Query("SELECT COALESCE(MAX(tp.order), 0) FROM TrackPlaylist tp WHERE tp.playlistId = :playlistId")
|
||||
BigDecimal findMaxOrderByPlaylistId(@Param("playlistId") Long playlistId);
|
||||
|
||||
@Query("SELECT MIN(tp.order) FROM TrackPlaylist tp WHERE tp.playlistId = :playlistId AND tp.order > :order")
|
||||
BigDecimal findNextOrderByPlaylistIdAndOrderGreaterThan(@Param("playlistId") Long playlistId,
|
||||
@Param("order") BigDecimal order);
|
||||
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package com.bivashy.backend.composer.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
@ -10,10 +11,14 @@ public interface AudioBlobStorageService {
|
||||
|
||||
String store(byte[] data);
|
||||
|
||||
String store(InputStream inputStream, Map<String, String> metadata);
|
||||
|
||||
String store(byte[] data, Map<String, String> metadata);
|
||||
|
||||
byte[] readRaw(String path) throws IOException;
|
||||
|
||||
Blob read(String path);
|
||||
|
||||
public record Blob(InputStream stream, MediaType contentType) {
|
||||
public record Blob(InputStream stream, MediaType contentType, Map<String, String> metadata) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package com.bivashy.backend.composer.service;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.tika.Tika;
|
||||
@ -37,17 +38,28 @@ public class AudioS3StorageService implements AudioBlobStorageService {
|
||||
|
||||
@Override
|
||||
public String store(InputStream inputStream) {
|
||||
return store(new ByteArrayInputStream(DEFAULT_BUFFER).readAllBytes());
|
||||
return store(inputStream, Map.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(byte[] data) {
|
||||
return store(data, Map.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(InputStream inputStream, Map<String, String> metadata) {
|
||||
return store(new ByteArrayInputStream(DEFAULT_BUFFER).readAllBytes(), metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String store(byte[] data, Map<String, String> metadata) {
|
||||
String objectKey = newObjectName();
|
||||
String contentType = detectContentType(data);
|
||||
s3Client.putObject(PutObjectRequest.builder()
|
||||
.bucket(bucket)
|
||||
.key(objectKey)
|
||||
.contentType(contentType)
|
||||
.metadata(metadata)
|
||||
.build(), RequestBody.fromBytes(data));
|
||||
return String.join("/", bucket, objectKey);
|
||||
}
|
||||
@ -73,7 +85,7 @@ public class AudioS3StorageService implements AudioBlobStorageService {
|
||||
} catch (InvalidMediaTypeException e) {
|
||||
logger.error("invalid media type", e);
|
||||
}
|
||||
return new Blob(response, mediaType);
|
||||
return new Blob(response, mediaType, response.response().metadata());
|
||||
}
|
||||
|
||||
private String detectContentType(byte[] data) {
|
||||
|
||||
@ -2,32 +2,68 @@ package com.bivashy.backend.composer.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.github.kokorin.jaffree.ffprobe.FFprobe;
|
||||
import com.github.kokorin.jaffree.ffprobe.FFprobeResult;
|
||||
import com.github.kokorin.jaffree.ffprobe.Format;
|
||||
import com.github.kokorin.jaffree.ffprobe.PipeInput;
|
||||
|
||||
@Service
|
||||
public class MetadataParseService {
|
||||
private final ObjectMapper ffprobeObjectMapper;
|
||||
|
||||
public MetadataParseService() {
|
||||
var result = new ObjectMapper();
|
||||
result.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
result.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
this.ffprobeObjectMapper = result;
|
||||
}
|
||||
|
||||
public Optional<Metadata> extractMetadata(InputStream input) throws IOException {
|
||||
Path tempFile = Files.createTempFile("metadata-file", "");
|
||||
Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
FFprobeResult result = FFprobe.atPath()
|
||||
.setShowFormat(true)
|
||||
.setInput(PipeInput.pumpFrom(input))
|
||||
.setShowStreams(true)
|
||||
.setInput(tempFile)
|
||||
.execute();
|
||||
|
||||
Format format = result.getFormat();
|
||||
Files.deleteIfExists(tempFile);
|
||||
|
||||
if (format == null) {
|
||||
return Optional.empty();
|
||||
var format = Optional.ofNullable(result.getFormat());
|
||||
|
||||
Optional<Float> formatDuration = format.map(f -> f.getDuration());
|
||||
List<Optional<Float>> streamDuration = Optional.ofNullable(result.getStreams())
|
||||
.map(streams -> streams.stream()
|
||||
.map(s -> s.getDuration())
|
||||
.map(Optional::ofNullable).toList())
|
||||
.orElse(List.of());
|
||||
|
||||
var foundDuration = Stream.of(Collections.singletonList(formatDuration), streamDuration)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst();
|
||||
|
||||
var jsonResult = ffprobeObjectMapper.writeValueAsString(result);
|
||||
|
||||
var title = format.map(f -> f.getTag("title")).orElse(null);
|
||||
var artist = format.map(f -> f.getTag("artist")).orElse(null);
|
||||
|
||||
return Optional.of(new Metadata(title, artist, foundDuration.orElse(0f), jsonResult));
|
||||
}
|
||||
|
||||
return Optional.of(new Metadata(format.getTag("title"), format.getTag("artist"), format.getDuration()));
|
||||
}
|
||||
|
||||
public static record Metadata(String title, String artist, Float durationSeconds) {
|
||||
public static record Metadata(String title, String artist, Float durationSeconds, String rawJson) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.bivashy.backend.composer.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.bivashy.backend.composer.model.Track;
|
||||
import com.bivashy.backend.composer.model.TrackMetadata;
|
||||
import com.bivashy.backend.composer.repository.TrackMetadataRepository;
|
||||
|
||||
@Service
|
||||
public class TrackMetadataService {
|
||||
private final TrackMetadataRepository trackMetadataRepository;
|
||||
|
||||
public TrackMetadataService(TrackMetadataRepository trackMetadataRepository) {
|
||||
this.trackMetadataRepository = trackMetadataRepository;
|
||||
}
|
||||
|
||||
public TrackMetadata createTrackMetadata(Track track, String title, String fileName, String audioPath,
|
||||
String artist, String thumbnailPath, int durationSeconds) {
|
||||
return trackMetadataRepository.save(
|
||||
new TrackMetadata(track, title, fileName, audioPath, artist, thumbnailPath, durationSeconds));
|
||||
}
|
||||
|
||||
public TrackMetadata getTrackMetadata(Long trackId) {
|
||||
return trackMetadataRepository.findByTrackId(trackId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Metadata not found"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
package com.bivashy.backend.composer.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bivashy.backend.composer.model.TrackPlaylist;
|
||||
import com.bivashy.backend.composer.model.key.PlaylistTrackId;
|
||||
import com.bivashy.backend.composer.repository.TrackPlaylistRepository;
|
||||
|
||||
@Service
|
||||
public class TrackPlaylistService {
|
||||
private final TrackPlaylistRepository repository;
|
||||
|
||||
public TrackPlaylistService(TrackPlaylistRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public List<TrackPlaylist> getPlaylistTracks(Long playlistId) {
|
||||
return repository.findByPlaylistIdOrderByOrderAsc(playlistId);
|
||||
}
|
||||
|
||||
public void insertTrackAtEnd(Long playlistId, Long trackId) {
|
||||
addTrack(playlistId, trackId, null);
|
||||
}
|
||||
|
||||
private BigDecimal findIntermediateOrder(BigDecimal prev, BigDecimal next) {
|
||||
if (prev == null && next == null) {
|
||||
return BigDecimal.ONE;
|
||||
} else if (prev == null) {
|
||||
return next.divide(BigDecimal.valueOf(2), 1000, RoundingMode.HALF_UP);
|
||||
} else if (next == null) {
|
||||
return prev.add(BigDecimal.ONE);
|
||||
} else {
|
||||
return prev.add(next).divide(BigDecimal.valueOf(2), 1000, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTrack(Long playlistId, Long trackId, BigDecimal afterOrder) {
|
||||
BigDecimal newOrder;
|
||||
if (afterOrder == null) {
|
||||
// Insert at the end
|
||||
BigDecimal maxOrder = repository.findMaxOrderByPlaylistId(playlistId);
|
||||
newOrder = findIntermediateOrder(maxOrder, null);
|
||||
} else {
|
||||
// Insert after a specific track
|
||||
BigDecimal nextOrder = repository.findNextOrderByPlaylistIdAndOrderGreaterThan(playlistId, afterOrder);
|
||||
newOrder = findIntermediateOrder(afterOrder, nextOrder);
|
||||
}
|
||||
TrackPlaylist trackPlaylist = new TrackPlaylist(playlistId, trackId, newOrder);
|
||||
repository.save(trackPlaylist);
|
||||
}
|
||||
|
||||
public void reorderTrack(Long playlistId, Long trackId, BigDecimal afterOrder) {
|
||||
TrackPlaylist trackPlaylist = repository.findById(new PlaylistTrackId(playlistId, trackId))
|
||||
.orElseThrow(() -> new RuntimeException("TrackPlaylist not found"));
|
||||
|
||||
BigDecimal nextOrder = repository.findNextOrderByPlaylistIdAndOrderGreaterThan(playlistId, afterOrder);
|
||||
BigDecimal newOrder = findIntermediateOrder(afterOrder, nextOrder);
|
||||
|
||||
trackPlaylist.setOrder(newOrder);
|
||||
repository.save(trackPlaylist);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,88 +1,88 @@
|
||||
package com.bivashy.backend.composer.service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import com.bivashy.backend.composer.dto.track.AddLocalTrackRequest;
|
||||
import com.bivashy.backend.composer.dto.track.PlaylistTrackResponse;
|
||||
import com.bivashy.backend.composer.dto.track.TrackResponse;
|
||||
import com.bivashy.backend.composer.model.SourceType;
|
||||
import com.bivashy.backend.composer.model.SourceTypes;
|
||||
import com.bivashy.backend.composer.model.Track;
|
||||
import com.bivashy.backend.composer.model.TrackMetadata;
|
||||
import com.bivashy.backend.composer.model.TrackPlaylist;
|
||||
import com.bivashy.backend.composer.model.TrackSource;
|
||||
import com.bivashy.backend.composer.model.User;
|
||||
import com.bivashy.backend.composer.repository.SourceTypeRepository;
|
||||
import com.bivashy.backend.composer.repository.TrackMetadataRepository;
|
||||
import com.bivashy.backend.composer.repository.TrackPlaylistRepository;
|
||||
import com.bivashy.backend.composer.repository.TrackRepository;
|
||||
import com.bivashy.backend.composer.repository.TrackSourceRepository;
|
||||
import com.bivashy.backend.composer.service.MetadataParseService.Metadata;
|
||||
|
||||
@Service
|
||||
public class TrackService {
|
||||
private final AudioBlobStorageService s3Service;
|
||||
private final TrackSourceRepository trackSourceRepository;
|
||||
private final TrackRepository trackRepository;
|
||||
private final TrackMetadataRepository trackMetadataRepository;
|
||||
private final TrackPlaylistRepository trackPlaylistRepository;
|
||||
private final SourceTypeRepository sourceTypeRepository;
|
||||
private final TrackSourceService trackSourceService;
|
||||
private final TrackMetadataService trackMetadataService;
|
||||
private final TrackPlaylistService trackPlaylistService;
|
||||
private final MetadataParseService metadataParseService;
|
||||
|
||||
public TrackService(AudioBlobStorageService s3Service, TrackSourceRepository trackSourceRepository,
|
||||
TrackRepository trackRepository, TrackMetadataRepository trackMetadataRepository,
|
||||
TrackPlaylistRepository trackPlaylistRepository,
|
||||
SourceTypeRepository sourceTypeRepository, MetadataParseService metadataParseService) {
|
||||
this.s3Service = s3Service;
|
||||
this.trackSourceRepository = trackSourceRepository;
|
||||
public TrackService(TrackRepository trackRepository,
|
||||
TrackSourceService trackSourceService,
|
||||
TrackMetadataService trackMetadataService,
|
||||
TrackPlaylistService trackPlaylistService,
|
||||
MetadataParseService metadataParseService) {
|
||||
this.trackRepository = trackRepository;
|
||||
this.trackMetadataRepository = trackMetadataRepository;
|
||||
this.trackPlaylistRepository = trackPlaylistRepository;
|
||||
this.sourceTypeRepository = sourceTypeRepository;
|
||||
this.trackSourceService = trackSourceService;
|
||||
this.trackMetadataService = trackMetadataService;
|
||||
this.trackPlaylistService = trackPlaylistService;
|
||||
this.metadataParseService = metadataParseService;
|
||||
}
|
||||
|
||||
public TrackResponse addLocalTrack(User user, Long playlistId, AddLocalTrackRequest request) throws IOException {
|
||||
String audioPath = s3Service.store(request.source().getBytes());
|
||||
|
||||
Optional<Metadata> metadata = metadataParseService.extractMetadata(request.source().getInputStream());
|
||||
String ffprobeJson = metadata.map(Metadata::rawJson).orElse("{}");
|
||||
|
||||
Optional<SourceType> possibleSourceType = sourceTypeRepository.findByName(SourceTypes.FILE);
|
||||
if (possibleSourceType.isEmpty()) {
|
||||
throw new IllegalStateException("cannot find source type " + SourceTypes.FILE);
|
||||
}
|
||||
SourceType sourceType = possibleSourceType.get();
|
||||
TrackSource trackSource = new TrackSource(audioPath, sourceType, LocalDateTime.now());
|
||||
trackSource = trackSourceRepository.save(trackSource);
|
||||
TrackSource trackSource = trackSourceService.createTrackSource(
|
||||
request.source().getBytes(), ffprobeJson, SourceTypes.FILE);
|
||||
|
||||
Track track = new Track(trackSource);
|
||||
track = trackRepository.save(track);
|
||||
Track track = trackRepository.save(new Track(trackSource));
|
||||
|
||||
String fileName = request.source().getOriginalFilename();
|
||||
String title = metadata.map(m -> m.title()).orElse(fileName);
|
||||
String artist = metadata.map(m -> m.artist()).orElse(null);
|
||||
String thumbnailPath = null; // TODO:?
|
||||
int durationSeconds = metadata.map(m -> m.durationSeconds()).map(Float::intValue).orElse(0);
|
||||
|
||||
TrackMetadata trackMetadata = new TrackMetadata(track, title, fileName, audioPath,
|
||||
artist, thumbnailPath,
|
||||
durationSeconds);
|
||||
trackMetadata = trackMetadataRepository.save(trackMetadata);
|
||||
|
||||
// TODO: use linked list instead of int order?
|
||||
TrackPlaylist playlistTrack = new TrackPlaylist(playlistId, track.getId(), /* order */ 0L);
|
||||
playlistTrack = trackPlaylistRepository.save(playlistTrack);
|
||||
String fileName = fileNameWithoutExtension(request.source().getOriginalFilename());
|
||||
String title = metadata.map(Metadata::title).orElse(fileName);
|
||||
String artist = metadata.map(Metadata::artist).orElse(null);
|
||||
int durationSeconds = metadata.map(Metadata::durationSeconds).map(Float::intValue).orElse(0);
|
||||
trackMetadataService.createTrackMetadata(
|
||||
track, title, fileName, trackSource.getSourceUrl(), artist, null, durationSeconds);
|
||||
|
||||
trackPlaylistService.insertTrackAtEnd(playlistId, track.getId());
|
||||
return new TrackResponse(
|
||||
track.getId(),
|
||||
trackMetadata.getTitle(),
|
||||
trackMetadata.getArtist(),
|
||||
trackMetadata.getAudioPath(),
|
||||
trackMetadata.getDurationSeconds(),
|
||||
trackMetadata.getFileName());
|
||||
title,
|
||||
artist,
|
||||
trackSource.getSourceUrl(),
|
||||
durationSeconds,
|
||||
fileName);
|
||||
}
|
||||
|
||||
public List<PlaylistTrackResponse> getPlaylistTracks(User user, Long playlistId) {
|
||||
return trackPlaylistService.getPlaylistTracks(playlistId).stream()
|
||||
.map(pt -> {
|
||||
Track track = trackRepository.findById(pt.getTrackId())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Track not found"));
|
||||
TrackMetadata metadata = trackMetadataService.getTrackMetadata(track.getId());
|
||||
return new PlaylistTrackResponse(
|
||||
track.getId(),
|
||||
metadata.getTitle(),
|
||||
metadata.getArtist(),
|
||||
metadata.getAudioPath(),
|
||||
metadata.getDurationSeconds(),
|
||||
metadata.getFileName());
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String fileNameWithoutExtension(String fileName) {
|
||||
return fileName.replaceFirst("[.][^.]+$", "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.bivashy.backend.composer.service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bivashy.backend.composer.model.SourceType;
|
||||
import com.bivashy.backend.composer.model.TrackSource;
|
||||
import com.bivashy.backend.composer.repository.SourceTypeRepository;
|
||||
import com.bivashy.backend.composer.repository.TrackSourceRepository;
|
||||
|
||||
@Service
|
||||
public class TrackSourceService {
|
||||
private final TrackSourceRepository trackSourceRepository;
|
||||
private final SourceTypeRepository sourceTypeRepository;
|
||||
private final AudioBlobStorageService s3Service;
|
||||
|
||||
public TrackSourceService(TrackSourceRepository trackSourceRepository,
|
||||
SourceTypeRepository sourceTypeRepository,
|
||||
AudioBlobStorageService s3Service) {
|
||||
this.trackSourceRepository = trackSourceRepository;
|
||||
this.sourceTypeRepository = sourceTypeRepository;
|
||||
this.s3Service = s3Service;
|
||||
}
|
||||
|
||||
public TrackSource createTrackSource(byte[] audioBytes, String ffprobeJson, String sourceType) {
|
||||
String audioPath = s3Service.store(audioBytes, Map.of("ffprobe", ffprobeJson));
|
||||
SourceType type = sourceTypeRepository.findByName(sourceType)
|
||||
.orElseThrow(() -> new IllegalStateException("Source type not found: " + sourceType));
|
||||
return trackSourceRepository.save(new TrackSource(audioPath, type, LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,10 @@ spring:
|
||||
access-key: ${S3_ACCESS_KEY}
|
||||
secret-key: ${S3_SECRET_KEY}
|
||||
bucket: ${S3_BUCKET}
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 8096MB
|
||||
max-request-size: 8096MB
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
@ -64,11 +64,11 @@ CREATE TABLE IF NOT EXISTS "playlist" (
|
||||
CREATE TABLE IF NOT EXISTS "playlist_track" (
|
||||
"playlist_id" bigint NOT NULL,
|
||||
"track_id" bigint NOT NULL,
|
||||
"order_index" bigint NOT NULL,
|
||||
CONSTRAINT "pk_playlist_track" PRIMARY KEY ("playlist_id", "track_id"),
|
||||
CONSTRAINT "fk_playlist_track_playlist_id"
|
||||
"order_index" numeric NOT NULL,
|
||||
CONSTRAINT "pk_playlist_track_new" PRIMARY KEY ("playlist_id", "track_id"),
|
||||
CONSTRAINT "fk_playlist_track_playlist_id_new"
|
||||
FOREIGN KEY ("playlist_id") REFERENCES "playlist" ("id"),
|
||||
CONSTRAINT "fk_playlist_track_track_id"
|
||||
CONSTRAINT "fk_playlist_track_track_id_new"
|
||||
FOREIGN KEY ("track_id") REFERENCES "track" ("id")
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user