diff --git a/App.xaml.cs b/App.xaml.cs
index cb665e3..7b40857 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -21,13 +21,14 @@
using Windows.Media.Playback;
using Windows.Storage;
using System.Threading.Tasks;
+using System.Net.Http;
namespace Musium
{
public partial class App : Application
{
public static Window MainWindow { get; private set; }
-
+ public static HttpClient LyricHttpClient { get; private set; }
public App()
{
InitializeComponent();
@@ -37,6 +38,9 @@ protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventA
MainWindow = new MainWindow();
MainWindow.Activate();
+ LyricHttpClient = new HttpClient();
+ LyricHttpClient.BaseAddress = new Uri("https://lrclib.net/api/");
+
var Audio = AudioService.Instance;
await Task.Run(async () =>
{
diff --git a/Controls/QueueListItemControl.xaml b/Controls/QueueListItemControl.xaml
index 9a9dac5..909900d 100644
--- a/Controls/QueueListItemControl.xaml
+++ b/Controls/QueueListItemControl.xaml
@@ -29,7 +29,7 @@
-
+
diff --git a/Converters/LyricsPassConverter.cs b/Converters/LyricsPassConverter.cs
new file mode 100644
index 0000000..769d70a
--- /dev/null
+++ b/Converters/LyricsPassConverter.cs
@@ -0,0 +1,31 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Data;
+using System;
+using System.Diagnostics;
+
+namespace Musium.Converters
+{
+ public class LyricsPassConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is string s)
+ {
+ if (s.Equals("[INSTRUMENTAL]"))
+ {
+ return "This song does not contain any lyrics.";
+ }
+ return s;
+ }
+
+ return null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return value is Visibility visibility && visibility == Visibility.Visible;
+ }
+ }
+}
+
+
\ No newline at end of file
diff --git a/Converters/StringFilledToVisibilityConverter.cs b/Converters/StringFilledToVisibilityConverter.cs
new file mode 100644
index 0000000..2ae22c7
--- /dev/null
+++ b/Converters/StringFilledToVisibilityConverter.cs
@@ -0,0 +1,35 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Data;
+using System;
+using System.Diagnostics;
+
+namespace Musium.Converters
+{
+ public class StringFilledToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if (value is string s)
+ {
+ bool isFilled = !string.IsNullOrEmpty(s) || !string.IsNullOrWhiteSpace(s);
+
+ if (parameter is string param && param.Equals("Inverse", StringComparison.OrdinalIgnoreCase))
+ {
+ isFilled = !isFilled;
+ }
+
+ return isFilled ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ if (parameter is string par && par.Equals("Inverse", StringComparison.OrdinalIgnoreCase)) return Visibility.Visible;
+ return Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language)
+ {
+ return value is Visibility visibility && visibility == Visibility.Visible;
+ }
+ }
+}
+
+
\ No newline at end of file
diff --git a/Models/Song.cs b/Models/Song.cs
index 830c335..bb3d9c4 100644
--- a/Models/Song.cs
+++ b/Models/Song.cs
@@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
using Windows.Devices.Radios;
using Windows.Media.Core;
@@ -35,6 +36,17 @@ public Album Album
}
}
+ private string _artistName;
+ public string ArtistName
+ {
+ get => _artistName;
+ set
+ {
+ _artistName = value;
+ OnPropertyChanged();
+ }
+ }
+
private string _filePath;
public string FilePath
{
@@ -57,6 +69,36 @@ public string? Genre
}
}
+ private string? _lyrics;
+ public string? Lyrics
+ {
+ get => _lyrics;
+ set
+ {
+ _lyrics = value;
+ OnPropertyChanged();
+ }
+ }
+ public IOException? AttemptApplyLyricsToFile(string lyrics)
+ {
+ try
+ {
+ using (var file = TagLib.File.Create(FilePath))
+ {
+ file.Tag.Lyrics = lyrics;
+ file.Save();
+ Lyrics = lyrics;
+ }
+
+ Debug.WriteLine("lyrics saved successfully.");
+ return null;
+ }
+ catch (IOException ex)
+ {
+ return ex;
+ }
+ }
+
private int? _trackNumber;
public int? TrackNumber
{
diff --git a/Musium.csproj b/Musium.csproj
index a5d96e9..e2068a7 100644
--- a/Musium.csproj
+++ b/Musium.csproj
@@ -22,7 +22,13 @@
+
+
+
+
+
+
@@ -48,6 +54,7 @@
+
@@ -62,6 +69,24 @@
Always
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+
MSBuild:Compile
diff --git a/Package.appxmanifest b/Package.appxmanifest
index 89e1916..08fe838 100644
--- a/Package.appxmanifest
+++ b/Package.appxmanifest
@@ -10,7 +10,7 @@
+ Version="0.4.0.0" />
diff --git a/Pages/Lyrics.xaml b/Pages/Lyrics.xaml
new file mode 100644
index 0000000..70bddb9
--- /dev/null
+++ b/Pages/Lyrics.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pages/Lyrics.xaml.cs b/Pages/Lyrics.xaml.cs
new file mode 100644
index 0000000..3d21fd8
--- /dev/null
+++ b/Pages/Lyrics.xaml.cs
@@ -0,0 +1,240 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Controls.Primitives;
+using Microsoft.UI.Xaml.Data;
+using Microsoft.UI.Xaml.Input;
+using Microsoft.UI.Xaml.Media;
+using Microsoft.UI.Xaml.Navigation;
+using Musium.Models;
+using Musium.Popups;
+using Musium.Services;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Runtime.InteropServices.WindowsRuntime;
+using System.Threading.Tasks;
+using Windows.ApplicationModel.DataTransfer;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+
+namespace Musium.Pages
+{
+ public sealed partial class Lyrics : Page
+ {
+ public readonly AudioService Audio = AudioService.Instance;
+ public Lyrics()
+ {
+ InitializeComponent();
+ }
+
+ private async void AddLyricsButton_Click(object sender, RoutedEventArgs e)
+ {
+ await EditLyrics(true);
+ }
+
+ private async void DisplayError(IOException ex)
+ {
+ ContentDialog dialog = new ContentDialog();
+
+ dialog.XamlRoot = XamlRoot;
+ dialog.Title = "An error has occurred.";
+ dialog.CloseButtonText = "Sad";
+ dialog.DefaultButton = ContentDialogButton.Close;
+ dialog.Content = new LyricErrorPopup();
+ LyricErrorPopup content = (LyricErrorPopup)dialog.Content;
+ content.SetText(ex.Message);
+
+ await dialog.ShowAsync();
+ }
+ private async Task EditLyrics(bool adding = false)
+ {
+ if (Audio.CurrentSongPlaying == null) return;
+ ContentDialog dialog = new ContentDialog();
+
+ dialog.XamlRoot = XamlRoot;
+ dialog.Title = adding ? "Add lyrics" : "Edit lyrics";
+ dialog.PrimaryButtonText = "Online";
+ dialog.SecondaryButtonText = "Clipboard";
+ dialog.CloseButtonText = "Cancel";
+ dialog.DefaultButton = ContentDialogButton.Primary;
+ dialog.Content = new AddLyricsPopup();
+
+ var result = await dialog.ShowAsync();
+
+ switch (result)
+ {
+ case ContentDialogResult.None:
+ break;
+ case ContentDialogResult.Primary: // online
+ Song currentSong = Audio.CurrentSongPlaying;
+ if (currentSong == null) return;
+ var artist = WebUtility.UrlEncode(currentSong.ArtistName);
+ var track = WebUtility.UrlEncode(currentSong.Title);
+ var album = WebUtility.UrlEncode(currentSong.Album.Title);
+ var duration = currentSong.Duration.TotalSeconds;
+
+ var requestUrl = $"get?artist_name={artist}&track_name={track}&album_name={album}&duration={duration}";
+
+ LyricResult? response = null;
+ HttpStatusCode? code = null;
+ try
+ {
+ response = await App.LyricHttpClient.GetFromJsonAsync(requestUrl);
+ }
+ catch (HttpRequestException ex)
+ {
+ Console.WriteLine($"api request for lyrics failed: {ex.StatusCode}");
+ code = ex.StatusCode;
+ }
+
+ if (response?.plainLyrics is string lyrics)
+ {
+ ContentDialog confirmDialog = new ContentDialog();
+
+ confirmDialog.XamlRoot = XamlRoot;
+ confirmDialog.Title = "Are these correct?";
+ confirmDialog.PrimaryButtonText = "Yes, use these";
+ confirmDialog.CloseButtonText = "No";
+ confirmDialog.DefaultButton = ContentDialogButton.Primary;
+ confirmDialog.Content = new ConfirmLyricsPopup();
+ ConfirmLyricsPopup content = (ConfirmLyricsPopup)confirmDialog.Content;
+ content.SetContent(lyrics);
+
+ var confirmResult = await confirmDialog.ShowAsync();
+ switch (confirmResult)
+ {
+ case ContentDialogResult.None:
+ break;
+ case ContentDialogResult.Primary:
+ if (Audio.CurrentSongPlaying == null) return;
+ if (dialog.Content is AddLyricsPopup popup)
+ {
+ if (!popup.SessionChecked)
+ {
+ var ex = Audio.CurrentSongPlaying.AttemptApplyLyricsToFile(lyrics);
+ if (ex != null) DisplayError(ex);
+ } else
+ {
+ Audio.CurrentSongPlaying.Lyrics = lyrics;
+ }
+ }
+ break;
+ case ContentDialogResult.Secondary:
+ break;
+ default:
+ break;
+ }
+ } else
+ {
+ ContentDialog confirmDialog = new ContentDialog();
+
+ confirmDialog.XamlRoot = XamlRoot;
+ confirmDialog.Title = code switch
+ {
+ HttpStatusCode.NotFound => "Lyrics unavailable",
+
+ HttpStatusCode.RequestTimeout => "No connection",
+ HttpStatusCode.ServiceUnavailable => "No connection",
+ HttpStatusCode.GatewayTimeout => "No connection",
+
+ _ => "Lyrics unavailable"
+ };
+ confirmDialog.CloseButtonText = ":(";
+ confirmDialog.DefaultButton = ContentDialogButton.Close;
+ confirmDialog.Content = new ConfirmLyricsPopup();
+ ConfirmLyricsPopup content = (ConfirmLyricsPopup)confirmDialog.Content;
+
+ await confirmDialog.ShowAsync();
+ }
+
+ break;
+ case ContentDialogResult.Secondary: // clipboard
+ var package = Clipboard.GetContent();
+ if (package.Contains(StandardDataFormats.Text))
+ {
+ if (Audio.CurrentSongPlaying == null) return;
+ var text = await package.GetTextAsync();
+ if (dialog.Content is AddLyricsPopup popup)
+ {
+ if (!popup.SessionChecked)
+ {
+ var ex = Audio.CurrentSongPlaying.AttemptApplyLyricsToFile(text);
+ if (ex != null) DisplayError(ex);
+ }
+ else
+ {
+ Audio.CurrentSongPlaying.Lyrics = text;
+ };
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ private void ClearLyrics()
+ {
+ Song currentSong = Audio.CurrentSongPlaying;
+ if (currentSong == null) return;
+ var ex = currentSong.AttemptApplyLyricsToFile("");
+ if (ex != null) DisplayError(ex);
+ }
+ private void MarkAsInstrumental()
+ {
+ Song currentSong = Audio.CurrentSongPlaying;
+ if (currentSong == null) return;
+ var ex = currentSong.AttemptApplyLyricsToFile("[INSTRUMENTAL]");
+ if (ex != null) DisplayError(ex);
+ }
+ private void MarkInstrumentalButton_Click(object sender, RoutedEventArgs e)
+ {
+ MarkAsInstrumental();
+ }
+
+ private async void MenuFlyoutItem_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is MenuFlyoutItem selectedItem)
+ {
+ string sortOption = selectedItem.Tag.ToString();
+ switch (sortOption)
+ {
+ case "edit":
+ await EditLyrics();
+ break;
+ case "clear":
+ ContentDialog confirmDialog = new ContentDialog();
+
+ confirmDialog.XamlRoot = XamlRoot;
+ confirmDialog.Title = "Clear lyrics?";
+ confirmDialog.PrimaryButtonText = "Yes";
+ confirmDialog.CloseButtonText = "No, cancel";
+ confirmDialog.DefaultButton = ContentDialogButton.Close;
+ confirmDialog.Content = new ClearLyricsPopup();
+
+ var result = await confirmDialog.ShowAsync();
+ switch (result)
+ {
+ case ContentDialogResult.None:
+ break;
+ case ContentDialogResult.Primary:
+ ClearLyrics();
+ break;
+ case ContentDialogResult.Secondary:
+ break;
+ default:
+ break;
+ }
+ break;
+ case "instrumental":
+ MarkAsInstrumental();
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Pages/NowPlaying.xaml b/Pages/NowPlaying.xaml
index 98c6d03..6b8d5a4 100644
--- a/Pages/NowPlaying.xaml
+++ b/Pages/NowPlaying.xaml
@@ -17,7 +17,9 @@
-
+
+
+