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 @@ - + + +