feat: add library scanning mechanic
This commit is contained in:
126
Tools/MediaParser.cs
Normal file
126
Tools/MediaParser.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using Shadow.Data;
|
||||
using Shadow.Entities;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Shadow.Tools;
|
||||
|
||||
public static class MediaParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a random hex string (with length 32 by default)
|
||||
/// </summary>
|
||||
/// <param name="length">Optional: hexstring length</param>
|
||||
/// <returns>A hexstring of given length</returns>
|
||||
public static string HexStr(int length = 32)
|
||||
{
|
||||
return RandomNumberGenerator.GetHexString(length).ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get metadata content opportunistically
|
||||
/// </summary>
|
||||
/// <param name="metadata">Dictionary to search in</param>
|
||||
/// <param name="searchStrings">Keywords to search for</param>
|
||||
/// <returns>Retrieved value (string) on success, otherwise null</returns>
|
||||
public static string? GetAny(Dictionary<string, string> metadata, List<string> searchStrings)
|
||||
{
|
||||
foreach (string searchString in searchStrings)
|
||||
{
|
||||
if (metadata.TryGetValue(searchString, out string? value) && !string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map exiftool metadata to Song entity.
|
||||
/// </summary>
|
||||
/// <param name="db">Database context</param>
|
||||
/// <param name="exif">ExifTool metadata</param>
|
||||
/// <returns>New Song entity, or null if song already exists in db</returns>
|
||||
public static Song? CreateSong(ApplicationDbContext db, Dictionary<string, string> exif)
|
||||
{
|
||||
|
||||
// First of all, check if song already exists in db
|
||||
string uri = GetAny(exif, ["_shadow:fileHash"])
|
||||
?? throw new Exception("Fatal error: could not get file hash!");
|
||||
if (db.Songs.FirstOrDefault(s => s.Uri == uri) != null) return null;
|
||||
|
||||
// If not, extract exif data
|
||||
string title = GetAny(exif, [
|
||||
"ItemList:Title", // iTunes m4a
|
||||
"ItemList:SortName", // iTunes m4a
|
||||
"Vorbis:Title", // Bandcamp ogg
|
||||
"ID3v2_3:Title", // Generic mp3/wav ID3 v2.3.0
|
||||
])
|
||||
?? Path.Combine(exif["System:Directory"], exif["System:FileName"]);
|
||||
string filepath = Path.GetFullPath(
|
||||
Path.Combine(exif["System:Directory"], exif["System:FileName"])
|
||||
); // TODO: bulletproof this
|
||||
string filetype = exif["File:FileType"].ToLower();
|
||||
|
||||
// Album/artist related
|
||||
string artistName = GetAny(exif, [
|
||||
"ItemList:Artist", // iTunes m4a
|
||||
"ItemList:AlbumArtist", // iTunes m4a
|
||||
"Vorbis:Artist", // Bandcamp m4a
|
||||
"Vorbis:Albumartist", // Bandcamp m4a
|
||||
"ID3v2_3:Artist", // Generic mp3/wav ID3 v2.3.0
|
||||
]) ?? "[Unknown Artist]"; // this is a weak line of defense against deliberately crafted
|
||||
|
||||
string albumName = GetAny(exif, [
|
||||
"ItemList:Album", // iTunes m4a
|
||||
"Vorbis:Album", // Bandcamp m4a
|
||||
"ID3v2_3:Album", // Generic mp3/wav ID3 v2.3.0
|
||||
]) ?? "[Unknown Album]"; // again, weak line of defense
|
||||
|
||||
// Try to find relevant artists and albums
|
||||
Artist artist = db.Artists
|
||||
.FirstOrDefault(a => a.NormalizedName == artistName.ToLower())
|
||||
?? new Artist
|
||||
{
|
||||
Name = artistName,
|
||||
NormalizedName = artistName.ToLower()
|
||||
};
|
||||
|
||||
Album album = db.Albums
|
||||
.FirstOrDefault(a => a.Name == albumName && a.Artist == artist)
|
||||
?? new Album
|
||||
{
|
||||
Name = albumName,
|
||||
Uri = HexStr(),
|
||||
Artist = artist
|
||||
};
|
||||
|
||||
Song song = new()
|
||||
{
|
||||
Title = title,
|
||||
Uri = uri,
|
||||
Filepath = filepath,
|
||||
Filetype = filetype,
|
||||
Album = album,
|
||||
Artist = artist
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Is Update() safe here?
|
||||
db.Artists.Update(artist);
|
||||
db.Albums.Update(album);
|
||||
db.Songs.Update(song);
|
||||
artist.Albums.Add(album);
|
||||
artist.Songs.Add(song);
|
||||
db.SaveChanges();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("[Error: MediaParser] Failed to extract metadata from {filepath}:\n" +
|
||||
$"{e}");
|
||||
}
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user