New endpoint

New endpoint to upload song with metadata and cover art as separate entities
This commit is contained in:
kdeng00
2021-12-29 14:06:52 -05:00
parent aa0a1ab5fe
commit 3a6e0f64bc
10 changed files with 221 additions and 40 deletions
+31 -1
View File
@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Icarus.Constants;
using Icarus.Controllers.Utilities;
using Icarus.Database.Contexts;
@@ -118,6 +120,34 @@ namespace Icarus.Controllers.Managers
return null;
}
public CoverArt SaveCoverArt(IFormFile data, Song song)
{
var cover = new CoverArt { SongTitle = song.Title };
try
{
var dirMgr = new DirectoryManager(_rootCoverArtPath);
var defaultExtension = ".png";
dirMgr.CreateDirectory(song);
var segment = cover.GenerateFilename(0);
var imagePath = dirMgr.SongDirectory + segment + defaultExtension;
cover.ImagePath = imagePath;
using (var fileStream = new FileStream(imagePath, FileMode.Create))
{
data.CopyTo(fileStream);
}
}
catch (Exception ex)
{
_logger.Error(ex.Message, "An error occurred");
}
return cover;
}
public CoverArt GetCoverArt(Song song)
{
return _coverArtContext.CoverArtImages.FirstOrDefault(cov => cov.SongTitle.Equals(song.Title));
+63 -18
View File
@@ -176,27 +176,24 @@ namespace Icarus.Controllers.Managers
_logger.Info("Starting the process of saving the song to the filesystem");
var song = await SaveSongTemp(songFile);
System.IO.File.Delete(song.SongPath());
DirectoryManager dirMgr = new DirectoryManager(_config, song);
dirMgr.CreateDirectory();
var fileDir = dirMgr.SongDirectory;
var filePath = fileDir;
var songFilename = song.Filename;
if (!songFilename.EndsWith(".mp3"))
filePath += $"{songFilename}.mp3";
else
filePath += $"{songFilename}";
var filePath = dirMgr.SongDirectory;
filePath += $"{song.Filename}";
_logger.Info($"Absolute song path: {filePath}");
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await (songFile.CopyToAsync(fileStream));
song.SongDirectory = fileDir;
var songBytes = await System.IO.File.ReadAllBytesAsync(song.SongPath());
System.IO.File.WriteAllBytesAsync(filePath, songBytes);
System.IO.File.Delete(song.SongPath());
song.SongDirectory = dirMgr.SongDirectory;
_logger.Info("Song successfully saved to filesystem");
}
@@ -214,6 +211,55 @@ namespace Icarus.Controllers.Managers
}
}
public async Task SaveSongToFileSystem(IFormFile songFile, IFormFile coverArtData, Song song)
{
if (string.IsNullOrEmpty(song.Filename))
{
song.Filename = song.GenerateFilename(1);
}
song.SongDirectory = _tempDirectoryRoot;
song.DateCreated = DateTime.Now;
using (var filestream = new FileStream(song.SongPath(), FileMode.Create))
{
_logger.Info("Saving song to temporary directory");
await songFile.CopyToAsync(filestream);
}
var coverMgr = new CoverArtManager(_config);
var coverArt = coverMgr.SaveCoverArt(coverArtData, song);
var meta = new MetadataRetriever();
song.Duration = meta.RetrieveSongDuration(song.SongPath());
// const int trackCount = meta.RetrieveTrackCount(song.)
meta.UpdateMetadata(song, song);
meta.UpdateCoverArt(song, coverArt);
DirectoryManager dirMgr = new DirectoryManager(_config, song);
dirMgr.CreateDirectory();
var filePath = dirMgr.SongDirectory + song.Filename;
_logger.Info($"Absolute song path: {filePath}");
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
var songBytes = await System.IO.File.ReadAllBytesAsync(song.SongPath());
await System.IO.File.WriteAllBytesAsync(filePath, songBytes);
System.IO.File.Delete(song.SongPath());
song.SongDirectory = dirMgr.SongDirectory;
_logger.Info("Song successfully saved to filesystem");
}
coverMgr.SaveCoverArtToDatabase(ref song, ref coverArt);
SaveSongToDatabase(song);
}
public async Task<SongData> RetrieveSong(Song songMetaData)
@@ -248,22 +294,21 @@ namespace Icarus.Controllers.Managers
private async Task<Song> SaveSongTemp(IFormFile songFile)
{
var song = new Song();
var filename = song.GenerateFilename();
var filePath = Path.Combine(_tempDirectoryRoot, filename);
_logger.Info("Assigning song filename");
song.SongDirectory = _tempDirectoryRoot;
song.Filename = song.GenerateFilename(1);
using (var filestream = new FileStream(filePath, FileMode.Create))
using (var filestream = new FileStream(song.SongPath(), FileMode.Create))
{
_logger.Info("Saving song to temporary directory");
await songFile.CopyToAsync(filestream);
}
MetadataRetriever meta = new MetadataRetriever();
song = meta.RetrieveMetaData(filePath);
song = meta.RetrieveMetaData(song.SongPath());
_logger.Info("Assigning song filename");
song.Filename = filename;
song.SongDirectory = _tempDirectoryRoot;
song.Filename = song.GenerateFilename(1);
song.DateCreated = DateTime.Now;
return song;
+62 -6
View File
@@ -81,13 +81,21 @@ namespace Icarus.Controllers.Utilities
_genre = string.Join("", fileTag.Tag.Genres);
_year = (int)fileTag.Tag.Year;
_duration = (int)fileTag.Properties.Duration.TotalSeconds;
var albumArtist = string.Join("", fileTag.Tag.AlbumArtists);
var track = (int)fileTag.Tag.Track;
var disc = (int)fileTag.Tag.Disc;
song.Title = _title;
song.Artist = _artist;
song.AlbumTitle = _album;
song.AlbumArtist = albumArtist;
song.Genre = _genre;
song.Year = _year;
song.Duration = _duration;
song.Track = track;
song.Disc = disc;
song.TrackCount = (int)fileTag.Tag.TrackCount;
song.DiscCount = (int)fileTag.Tag.DiscCount;
}
catch (Exception ex)
{
@@ -100,6 +108,15 @@ namespace Icarus.Controllers.Utilities
return song;
}
public int RetrieveSongDuration(string filepath)
{
var duration = 0;
var fileTag = TagLib.File.Create(filepath);
duration = (int)fileTag.Properties.Duration.TotalSeconds;
return duration;
}
public byte[] RetrieveCoverArtBytes(Song song)
{
try
@@ -162,6 +179,11 @@ namespace Icarus.Controllers.Utilities
var album = updatedSong.AlbumTitle;
var genre = updatedSong.Genre;
var year = updatedSong.Year;
var albumArtist = updatedSong.AlbumArtist;
var track = updatedSong.Track;
var trackCount = updatedSong.TrackCount;
var disc = updatedSong.Disc;
var discCount = updatedSong.DiscCount;
TagLib.File fileTag = TagLib.File.Create(filePath);
try
@@ -196,6 +218,26 @@ namespace Icarus.Controllers.Utilities
_updatedSong.Year = year;
fileTag.Tag.Year = (uint)year;
break;
case "albumartist":
_updatedSong.AlbumArtist = albumArtist;
fileTag.Tag.AlbumArtists = new []{albumArtist};
break;
case "track":
_updatedSong.Track = track;
fileTag.Tag.Track = (uint)(track);
break;
case "trackcount":
_updatedSong.TrackCount = trackCount;
fileTag.Tag.TrackCount = (uint)(trackCount);
break;
case "disc":
_updatedSong.Disc = disc;
fileTag.Tag.Disc = (uint)(disc);
break;
case "disccount":
_updatedSong.DiscCount = discCount;
fileTag.Tag.DiscCount = (uint)(discCount);
break;
}
}
@@ -264,13 +306,13 @@ namespace Icarus.Controllers.Utilities
songValues["Artists"] = String.IsNullOrEmpty(song.Artist);
songValues["Album"] = String.IsNullOrEmpty(song.AlbumTitle);
songValues["Genre"] = String.IsNullOrEmpty(song.Genre);
songValues["AlbumArtist"] = String.IsNullOrEmpty(song.AlbumArtist);
if (song.Year == null)
songValues["Year"] = true;
else if (song.Year == 0)
songValues["Year"] = true;
else
songValues["Year"] = false;
songValues["Year"] = CheckIntField(song.Year);
songValues["Track"] = CheckIntField(song.Track);
songValues["TrackCount"] = CheckIntField(song.TrackCount);
songValues["Disc"] = CheckIntField(song.Disc);
songValues["DiscCount"] = CheckIntField(song.Disc);
Console.WriteLine("Checking for null data completed");
_logger.Info("Checking for null data completed");
@@ -284,6 +326,20 @@ namespace Icarus.Controllers.Utilities
return songValues;
}
private bool CheckIntField(int? value)
{
if (value == null)
{
return true;
}
else if (value == 0)
{
return true;
}
return false;
}
#endregion
}
}
+51
View File
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Icarus.Controllers.Managers;
using Icarus.Models;
@@ -60,6 +61,18 @@ namespace Icarus.Controllers.V1
return File(song.Data, "application/x-msdownload", songMetaData.Filename);
}
// Assumes that the song already has metadata such as
// Title
// Artist
// Album
// Genre
// Year
// Track
// Track count
// Disc
// Disc count
// Cover art
//
[HttpPost("upload"), DisableRequestSizeLimit]
[Route("private-scoped")]
[Authorize("upload:songs")]
@@ -93,6 +106,33 @@ namespace Icarus.Controllers.V1
return NotFound();
}
// The client is expected to send the file, metadata, and cover art separately.
// Any metadata already on the file will be overwritten with values from the metadata
// as well as the cover art
//
[HttpPost("upload/with/data")]
[Route("private-scoped")]
[Authorize("upload:songs")]
public async Task<IActionResult> Post ([FromForm] UploadSongWithDataForm up)
{
try
{
if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
{
var song = Newtonsoft.Json.JsonConvert.DeserializeObject<Song>(up.SongFile);
_logger.LogInformation($"Song title: {song.Title}");
await _songMgr.SaveSongToFileSystem(up.SongData, up.CoverArtData, song);
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message, "An error occurred");
}
return Ok();
}
[HttpDelete("delete/{id}")]
[Authorize("delete:songs")]
public IActionResult Delete(int id)
@@ -117,6 +157,17 @@ namespace Icarus.Controllers.V1
return Ok();
}
}
public class UploadSongWithDataForm
{
[FromForm(Name = "file")]
public IFormFile SongData { get; set; }
[FromForm(Name = "cover")]
public IFormFile CoverArtData { get; set; }
[FromForm(Name = "metadata")]
public string SongFile { get; set; }
}
}
}
+1
View File
@@ -11,6 +11,7 @@
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="ID3" Version="0.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
-14
View File
@@ -1,14 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
+6
View File
@@ -18,6 +18,8 @@ namespace Icarus.Models
public string AlbumTitle { get; set; }
[JsonProperty("artist")]
public string Artist { get; set; }
[JsonProperty("album_artist")]
public string AlbumArtist { get; set; }
[JsonProperty("year")]
public int? Year { get; set; }
[JsonProperty("genre")]
@@ -30,8 +32,12 @@ namespace Icarus.Models
public string SongDirectory { get; set; }
[JsonProperty("track")]
public int Track { get; set; } = 0;
[JsonProperty("track_count")]
public int TrackCount { get; set; } = 0;
[JsonProperty("disc")]
public int Disc { get; set; } = 0;
[JsonProperty("disc_count")]
public int DiscCount { get; set; } = 0;
[JsonIgnore]
public int? AlbumID { get; set; }
[JsonIgnore]
+3
View File
@@ -39,11 +39,14 @@ CREATE TABLE Song (
Title TEXT NOT NULL,
Artist TEXT NOT NULL,
Album TEXT NOT NULL,
AlbumArtist TEXT NOT NULL,
Genre TEXT NOT NULL,
Year INT NOT NULL,
Duration INT NOT NULL,
Track INT NOT NULL,
TrackCount INT NOT NULL,
Disc INT NOT NULL,
DiscCount INT NOT NULL,
SongDirectory TEXT NOT NULL,
Filename TEXT NOT NULL,
CoverArtID INT NOT NULL,
-1
View File
@@ -2,5 +2,4 @@ delete from Song;
delete from Album;
delete from Artist;
delete from Genre;
delete from Year;
delete from CoverArt;
+4
View File
@@ -17,6 +17,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using NLog;
using NLog.Web;
using NLog.Web.AspNetCore;
@@ -112,6 +113,9 @@ namespace Icarus
services.AddDbContext<UserContext>(options => options.UseMySQL(connString));
services.AddDbContext<GenreContext>(options => options.UseMySQL(connString));
services.AddDbContext<CoverArtContext>(options => options.UseMySQL(connString));
services.AddControllers()
.AddNewtonsoftJson();
}
// Called by the runtime. Use this method to configure the HTTP request pipeline.