implemented search artists in MusicBrainz

This commit is contained in:
2026-08-17 01:10:30 +02:00
parent b02feb0b9f
commit 2d12ec2818
11 changed files with 419 additions and 30 deletions
+5
View File
@@ -4,6 +4,11 @@ root = true
# All files
[*]
trim_trailing_whitespace = true
indent_style = tab
indent_size = 4
tab_width = 4
end_of_line = lf
insert_final_newline = true
# C# files
[*.cs]
+7
View File
@@ -36,6 +36,7 @@
<None Remove="Pages\FavoriteAlbums.xaml" />
<None Remove="Pages\FavoriteArtists.xaml" />
<None Remove="Pages\FavoriteTracks.xaml" />
<None Remove="Pages\Search.xaml" />
<None Remove="Pages\Settings.xaml" />
</ItemGroup>
@@ -89,4 +90,10 @@
<None Include="D:\work\Harmony\HarmonyWinUI\.editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="Pages\Search.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>
+3
View File
@@ -17,6 +17,9 @@
<None Update="Package.appxmanifest">
<SubType>Designer</SubType>
</None>
<Page Update="Pages\Search.xaml">
<SubType>Designer</SubType>
</Page>
<Page Update="Pages\Settings.xaml">
<SubType>Designer</SubType>
</Page>
+5 -2
View File
@@ -30,7 +30,9 @@
PaneDisplayMode="Left"
PaneTitle="Harmony"
x:Name="MainNavigation"
SelectionChanged="MainNavigation_SelectionChanged">
SelectionChanged="MainNavigation_SelectionChanged"
IsBackButtonVisible="Visible"
BackRequested="MainNavigation_BackRequested">
<NavigationView.MenuItems>
@@ -50,7 +52,8 @@
<NavigationViewItem Content="Homepage" Tag="PageHomepage" Icon="Globe" />
</NavigationView.FooterMenuItems>
<Frame x:Name="MainNavigationContentFrame" />
<Frame x:Name="MainNavigationContentFrame"
Navigated="MainNavigationContentFrame_Navigated"/>
</NavigationView>
+13
View File
@@ -69,6 +69,19 @@ namespace HarmonyWinUI
break;
}
}
private void MainNavigation_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
if (MainNavigationContentFrame.CanGoBack)
{
MainNavigationContentFrame.GoBack();
}
}
private void MainNavigationContentFrame_Navigated(object sender, Microsoft.UI.Xaml.Navigation.NavigationEventArgs e)
{
MainNavigation.IsBackEnabled = MainNavigationContentFrame.CanGoBack;
}
}
public class PlayListItem
+96
View File
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HarmonyWinUI.Models
{
class MusicBrainz
{
private String URLprefix = "https://musicbrainz.org/ws/2/";
private static readonly HttpClient httpClient = createHttpClient();
public async Task<List<MusicBrainzArtist>> getArtists(String query)
{
ArgumentException.ThrowIfNullOrWhiteSpace(query);
string searchQuery = Uri.EscapeDataString($"artist:{query}");
MusicBrainzArtistResponse? response = await getData<MusicBrainzArtistResponse>(
$"artist?query={searchQuery}&fmt=json&limit=10"
);
return response?.Artists ?? new List<MusicBrainzArtist>();
}
private async Task<T?> getData<T>(String relativeURL)
{
Uri requestURL = new Uri(new Uri(URLprefix), relativeURL);
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestURL);
using HttpResponseMessage response = await httpClient.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead
);
response.EnsureSuccessStatusCode();
await using Stream jsonStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(jsonStream);
}
private static HttpClient createHttpClient()
{
HttpClient client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"HarmonyWinUI/1.0 (https://harmony.tpsoft.org/)"
);
return client;
}
private sealed class MusicBrainzArtistResponse
{
[JsonPropertyName("artists")]
public List<MusicBrainzArtist>? Artists { get; set; }
}
}
public class MusicBrainzArtist
{
public string? ID { get; set; }
public string? Type { get; set; }
[JsonPropertyName("type-id")]
public string? TypeID { get; set; }
public int? Score { get; set; }
public string? Name { get; set; } = "";
public string? Gender { get; set; }
public string? Country { get; set; }
public string? ImageURL { get; set; }
public MusicBrainzArtist() { }
public MusicBrainzArtist(
string? id,
string? type,
string? typeID,
int? score,
string? name,
string? gender,
string? country)
{
this.ID = id;
this.Type = type;
this.TypeID = typeID;
this.Score = score;
this.Name = name;
this.Gender = gender;
this.Country = country;
}
}
}
+43
View File
@@ -0,0 +1,43 @@
using System;
using System.IO;
namespace HarmonyWinUI.Models
{
class YTDLP
{
private String binPath = string.Empty;
public YTDLP()
{
this.binPath = this.findBinnary();
System.Diagnostics.Debug.WriteLine("binPath = " + this.binPath);
if (this.binPath.Equals(string.Empty))
{
throw new Exception("Binnary for YT-DLP not found.");
}
}
private String findBinnary()
{
DirectoryInfo? currentDirectory = new DirectoryInfo(AppContext.BaseDirectory);
while (currentDirectory != null)
{
string candidatePath = Path.Combine(
currentDirectory.FullName,
"vendor",
"yt-dlp.exe"
);
if (File.Exists(candidatePath))
{
return candidatePath;
}
currentDirectory = currentDirectory.Parent;
}
return string.Empty;
}
}
}
+14 -12
View File
@@ -19,14 +19,16 @@
<!-- Search -->
<TextBox
PlaceholderText="🔎 Search artists, albums and songs..."
HorizontalAlignment="Stretch" />
x:Name="SearchBox"
PlaceholderText="🔎 Search artists, albums and songs..."
HorizontalAlignment="Stretch"
KeyDown="SearchBox_KeyDown"/>
<!-- Last Artists -->
<StackPanel Spacing="12">
<TextBlock
Text="Last artists"
Style="{StaticResource TitleTextBlockStyle}" />
Text="Last artists"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView>
<!-- Artists -->
@@ -36,30 +38,30 @@
<!-- Last Albums -->
<StackPanel Spacing="12">
<TextBlock
Text="Last albums"
Style="{StaticResource TitleTextBlockStyle}" />
Text="Last albums"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView>
<!-- Albums -->
</ListView>
</StackPanel>
<!-- Last Songs -->
<!-- Last Tracks -->
<StackPanel Spacing="12">
<TextBlock
Text="Last songs"
Style="{StaticResource TitleTextBlockStyle}" />
Text="Last tracks"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView>
<!-- Songs -->
<!-- Tracks -->
</ListView>
</StackPanel>
<!-- New Releases -->
<StackPanel Spacing="12">
<TextBlock
Text="New releases"
Style="{StaticResource TitleTextBlockStyle}" />
Text="New releases"
Style="{StaticResource TitleTextBlockStyle}" />
<!-- Content loaded from internet -->
</StackPanel>
+18 -12
View File
@@ -1,17 +1,6 @@
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 System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -27,5 +16,22 @@ namespace HarmonyWinUI.Pages
{
InitializeComponent();
}
private void SearchBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter)
{
string searchText = SearchBox.Text;
Search(searchText);
}
}
private void Search(string searchText)
{
// Implement your search logic here
// For example, you can navigate to a search results page or filter a list of items
System.Diagnostics.Debug.WriteLine($"Searching for: {searchText}");
Frame.Navigate(typeof(Search), searchText);
}
}
}
+97
View File
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="HarmonyWinUI.Pages.Search"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HarmonyWinUI.Pages"
xmlns:models="using:HarmonyWinUI.Models"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:ImageUrlToImageSourceConverter x:Key="ImageUrlToImageSourceConverter" />
</Page.Resources>
<ScrollViewer>
<StackPanel
Padding="24"
Spacing="24">
<TextBlock Text="Search" FontFamily="Arial"
FontSize="24" TextWrapping="WrapWholeWords"
CharacterSpacing="200" Foreground="LightGray" />
<TextBlock x:Name="TextBlockQuery" Text="Query: " FontFamily="Arial"
FontSize="14" TextWrapping="WrapWholeWords"
CharacterSpacing="100" Foreground="LightGray" />
<!-- Artists -->
<StackPanel Spacing="12">
<TextBlock
Text="Artists"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView
x:Name="ArtistsListView"
ItemsSource="{x:Bind Artists}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:MusicBrainzArtist">
<StackPanel
Orientation="Horizontal"
Spacing="12"
Padding="4">
<Grid
Width="48"
Height="48">
<Border
Background="{ThemeResource SystemControlBackgroundBaseMediumBrush}"
CornerRadius="24">
<FontIcon
FontSize="24"
Glyph="&#xE77B;" />
</Border>
<Image
Source="{Binding ImageURL, Converter={StaticResource ImageUrlToImageSourceConverter}}"
Stretch="UniformToFill" />
</Grid>
<TextBlock
Text="{x:Bind Name}"
VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<!-- Albums -->
<StackPanel Spacing="12">
<TextBlock
Text="Albums"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView>
<!-- Albums -->
</ListView>
</StackPanel>
<!-- Tracks -->
<StackPanel Spacing="12">
<TextBlock
Text="Tracks"
Style="{StaticResource TitleTextBlockStyle}" />
<ListView>
<!-- Tracks -->
</ListView>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Page>
+114
View File
@@ -0,0 +1,114 @@
using HarmonyWinUI.Models;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace HarmonyWinUI.Pages;
public sealed class ImageUrlToImageSourceConverter : IValueConverter
{
public object? Convert(object value, Type targetType, object parameter, string language)
{
if (value is string imageUrl
&& Uri.TryCreate(imageUrl, UriKind.Absolute, out Uri? imageUri))
{
return new BitmapImage(imageUri);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotSupportedException();
}
}
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Search : Page
{
public ObservableCollection<MusicBrainzArtist> Artists { get; } = new();
private string? pendingSearchText;
public Search()
{
InitializeComponent();
Loaded += Search_Loaded;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
pendingSearchText = e.Parameter as string;
}
private async void Search_Loaded(object sender, RoutedEventArgs e)
{
if (pendingSearchText is not string searchText)
{
return;
}
pendingSearchText = null;
await SearchText(searchText);
}
private async Task SearchText(string searchText)
{
TextBlockQuery.Text = $"Query: '{searchText}'";
var mb = new MusicBrainz();
while (true)
{
try
{
var artists = await mb.getArtists(searchText);
this.Artists.Clear();
foreach (var artist in artists)
{
this.Artists.Add(artist);
}
return;
}
catch (Exception exception) when (
exception is HttpRequestException
or IOException
or JsonException
or TaskCanceledException
)
{
ContentDialog errorDialog = new ContentDialog
{
XamlRoot = XamlRoot,
Title = "MusicBrainz connection failed",
Content = $"{exception.GetType().Name}: {exception.Message}",
PrimaryButtonText = "Retry",
CloseButtonText = "Cancel",
DefaultButton = ContentDialogButton.Primary
};
ContentDialogResult result = await errorDialog.ShowAsync();
if (result != ContentDialogResult.Primary)
{
this.Artists.Clear();
return;
}
}
}
}
}