Skip to content

Commit

Permalink
Implemented strict volume matching for slskd results
Browse files Browse the repository at this point in the history
  • Loading branch information
Meyn committed Feb 26, 2025
1 parent 2eb438d commit 2261ede
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 49 deletions.
90 changes: 58 additions & 32 deletions Tubifarry/Core/Model/AudioMetadataHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,30 @@ internal class AudioMetadataHandler
public byte[]? AlbumCover { get; set; }
public bool UseID3v2_3 { get; set; }


public AudioMetadataHandler(string originalPath)
{
TrackPath = originalPath;
_logger = NzbDroneLogger.GetLogger(this); ;
_logger = NzbDroneLogger.GetLogger(this);
}

private static readonly Dictionary<AudioFormat, string[]> ConversionParameters = new()
{
{ AudioFormat.AAC, new[] { "-codec:a aac", "-q:a 0", "-movflags +faststart" } },
{ AudioFormat.MP3, new[] { "-codec:a libmp3lame", "-q:a 0", "-preset insane" } },
{ AudioFormat.Opus, new[] { "-codec:a libopus", "-vbr on", "-compression_level 10", "-application audio" } },
{ AudioFormat.Vorbis, new[] { "-codec:a libvorbis", "-q:a 7" } },
{ AudioFormat.FLAC, new[] { "-codec:a flac" } },
{ AudioFormat.ALAC, new[] { "-codec:a alac" } },
{ AudioFormat.WAV, new[] { "-codec:a pcm_s16le" } },
{ AudioFormat.MP4, new[] { "-codec:a aac", "-q:a 0", "-movflags +faststart" } },
{ AudioFormat.AIFF, new[] { "-codec:a pcm_s16le" } },
{ AudioFormat.OGG, new[] { "-codec:a libvorbis", "-q:a 7" } },
{ AudioFormat.AMR, new[] { "-codec:a libopencore_amrnb", "-ar 8000", "-ab 12.2k" } },
{ AudioFormat.WMA, new[] { "-codec:a wmav2", "-b:a 192k" } }
};

{
{ AudioFormat.AAC, new[] { "-codec:a aac", "-q:a 0", "-movflags +faststart" } },
{ AudioFormat.MP3, new[] { "-codec:a libmp3lame", "-q:a 0", "-preset insane" } },
{ AudioFormat.Opus, new[] { "-codec:a libopus", "-vbr on", "-compression_level 10", "-application audio" } },
{ AudioFormat.Vorbis, new[] { "-codec:a libvorbis", "-q:a 7" } },
{ AudioFormat.FLAC, new[] { "-codec:a flac" } },
{ AudioFormat.ALAC, new[] { "-codec:a alac" } },
{ AudioFormat.WAV, new[] { "-codec:a pcm_s16le" } },
{ AudioFormat.MP4, new[] { "-codec:a aac", "-q:a 0", "-movflags +faststart" } },
{ AudioFormat.AIFF, new[] { "-codec:a pcm_s16le" } },
{ AudioFormat.OGG, new[] { "-codec:a libvorbis", "-q:a 7" } },
{ AudioFormat.AMR, new[] { "-codec:a libopencore_amrnb", "-ar 8000", "-ab 12.2k" } },
{ AudioFormat.WMA, new[] { "-codec:a wmav2", "-b:a 192k" } }
};

private static readonly string[] ExtractionParameters = new[]
{
{
"-codec:a copy",
"-vn",
"-movflags +faststart"
Expand All @@ -59,12 +57,16 @@ public AudioMetadataHandler(string originalPath)

public async Task<bool> TryConvertToFormatAsync(AudioFormat audioFormat)
{
_logger?.Trace($"Converting {Path.GetFileName(TrackPath)} to {audioFormat}");

if (!CheckFFmpegInstalled())
return false;

if (!await TryExtractAudioFromVideoAsync())
return false;

_logger?.Trace($"Looking up audio format: {audioFormat}");

if (audioFormat == AudioFormat.Unknown)
return true;

Expand All @@ -83,6 +85,7 @@ public async Task<bool> TryConvertToFormatAsync(AudioFormat audioFormat)
foreach (string parameter in ConversionParameters[audioFormat])
conversion.AddParameter(parameter);

_logger?.Trace($"Starting FFmpeg conversion to {audioFormat}");
await conversion.Start();

if (File.Exists(TrackPath))
Expand Down Expand Up @@ -118,7 +121,10 @@ public async Task<bool> IsVideoContainerAsync()
string containerType = kvp.Key;
byte[] signature = kvp.Value;
if (header.Skip(4).Take(signature.Length).SequenceEqual(signature))
{
_logger?.Trace($"Detected {containerType} video container via signature");
return true;
}
}
return false;
}
Expand All @@ -138,13 +144,18 @@ public async Task<bool> TryExtractAudioFromVideoAsync()
if (!isVideo)
return true;

_logger?.Trace($"Extracting audio from video file: {Path.GetFileName(TrackPath)}");

try
{
IMediaInfo mediaInfo = await FFmpeg.GetMediaInfo(TrackPath);
IAudioStream? audioStream = mediaInfo.AudioStreams.FirstOrDefault();

if (audioStream == null)
{
_logger?.Trace("No audio stream found in video file");
return false;
}

string codec = audioStream.Codec.ToLower();
string finalOutputPath = Path.ChangeExtension(TrackPath, AudioFormatHelper.GetFileExtensionForCodec(codec));
Expand All @@ -164,11 +175,12 @@ public async Task<bool> TryExtractAudioFromVideoAsync()

File.Move(tempOutputPath, finalOutputPath, true);
TrackPath = finalOutputPath;
_logger?.Trace($"Successfully extracted audio to {Path.GetFileName(TrackPath)}");
return true;
}
catch (Exception ex)
{
_logger?.Error(ex, $"Failed to extract audio from MP4: {TrackPath}");
_logger?.Error(ex, $"Failed to extract audio from video: {TrackPath}");
return false;
}
}
Expand All @@ -182,9 +194,16 @@ public async Task<bool> TryCreateLrcFileAsync(CancellationToken token)
string lrcContent = string.Join(Environment.NewLine, Lyric.SyncedLyrics
.Where(lyric => !string.IsNullOrEmpty(lyric.LrcTimestamp) && !string.IsNullOrEmpty(lyric.Line))
.Select(lyric => $"{lyric.LrcTimestamp} {lyric.Line}"));
await File.WriteAllTextAsync(Path.ChangeExtension(TrackPath, ".lrc"), lrcContent, token);

string lrcPath = Path.ChangeExtension(TrackPath, ".lrc");
await File.WriteAllTextAsync(lrcPath, lrcContent, token);
_logger?.Trace($"Created LRC file with {Lyric.SyncedLyrics.Count} synced lyrics");
}
catch (Exception ex)
{
_logger?.Error(ex, $"Failed to create LRC file: {Path.ChangeExtension(TrackPath, ".lrc")}");
return false;
}
catch (Exception) { return false; }
return true;
}

Expand All @@ -207,17 +226,23 @@ public async Task<bool> EnsureFileExtAsync()
if (!string.Equals(currentExtension, correctExtension, StringComparison.OrdinalIgnoreCase))
{
string newPath = Path.ChangeExtension(TrackPath, correctExtension);
_logger?.Trace($"Correcting file extension from {currentExtension} to {correctExtension} for codec {codec}");
File.Move(TrackPath, newPath);
TrackPath = newPath;
}
return true;
}
catch (Exception) { return false; }
catch (Exception ex)
{
_logger?.Error(ex, $"Failed to ensure correct file extension: {TrackPath}");
return false;
}
}


public bool TryEmbedMetadata(AlbumInfo albumInfo, AlbumSongInfo trackInfo, ReleaseInfo releaseInfo)
{
_logger?.Trace($"Embedding metadata for track: {trackInfo.Name}");

try
{
using TagLib.File file = TagLib.File.Create(TrackPath);
Expand Down Expand Up @@ -264,23 +289,22 @@ public bool TryEmbedMetadata(AlbumInfo albumInfo, AlbumSongInfo trackInfo, Relea
}
catch (Exception ex)
{
_logger?.Error(ex, $"Failed to embed cover in track: {TrackPath}");
_logger?.Error(ex, "Failed to embed album cover");
}

if (!string.IsNullOrEmpty(Lyric?.PlainLyrics))
file.Tag.Lyrics = Lyric.PlainLyrics;

file.Save();
return true;
}
catch (Exception ex)
{
_logger?.Error(ex, $"Failed to embed metadata in track: {TrackPath}");
return false;
}
return true;
}


public static bool CheckFFmpegInstalled()
{
if (_isFFmpegInstalled.HasValue)
Expand All @@ -293,7 +317,9 @@ public static bool CheckFFmpegInstalled()
string[] ffmpegPatterns = new[] { "ffmpeg", "ffmpeg.exe", "ffmpeg.bin" };
string[] files = Directory.GetFiles(FFmpeg.ExecutablesPath);
if (files.Any(file => ffmpegPatterns.Contains(Path.GetFileName(file), StringComparer.OrdinalIgnoreCase) && IsExecutable(file)))
{
isInstalled = true;
}
}

if (!isInstalled)
Expand All @@ -314,11 +340,13 @@ public static bool CheckFFmpegInstalled()
}
}

if (!isInstalled)
NzbDroneLogger.GetLogger(typeof(AudioMetadataHandler)).Trace("FFmpeg not found in configured path or system PATH");

_isFFmpegInstalled = isInstalled;
return isInstalled;
}


private static bool IsExecutable(string filePath)
{
try
Expand All @@ -336,20 +364,18 @@ private static bool IsExecutable(string filePath)
if (magicNumber[0] == 0xCE && magicNumber[1] == 0xFA && magicNumber[2] == 0xED && magicNumber[3] == 0xFE)
return true;
}
catch
{
}

catch { }
return false;
}

public static void ResetFFmpegInstallationCheck() => _isFFmpegInstalled = null;

public static Task InstallFFmpeg(string path)
{
NzbDroneLogger.GetLogger(typeof(AudioMetadataHandler)).Trace($"Installing FFmpeg to: {path}");
ResetFFmpegInstallationCheck();
FFmpeg.SetExecutablesPath(path);
return CheckFFmpegInstalled() ? Task.CompletedTask : FFmpegDownloader.GetLatestVersion(FFmpegVersion.Official, path);
}
}
}
}
Loading

0 comments on commit 2261ede

Please sign in to comment.