Files
Shadow/Tools/MediaParser.cs

130 lines
3.7 KiB
C#

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
// TODO: Try and find genres
// 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.SaveChanges();
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;
}
}