Skip to content

Commit e21ee96

Browse files
authored
Added Midi Repository tab to download song from original bmp website (#304)
* added midi repo tab to show the list song from original bmp website * fixed wrong download path variable
1 parent 6dc282f commit e21ee96

File tree

6 files changed

+289
-1
lines changed

6 files changed

+289
-1
lines changed

Diff for: BardMusicPlayer.Pigeonhole/BmpPigeonhole.cs

+5
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,9 @@ public static void Initialize(string filename)
126126
/// Enable Dark Mode theme.
127127
/// </summary>
128128
public virtual bool DarkStyle { get; set; }
129+
130+
/// <summary>
131+
/// Midi download path
132+
/// </summary>
133+
public virtual string MidiDownloadPath { get; set; } = "";
129134
}

Diff for: BardMusicPlayer/BardMusicPlayer.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
</PropertyGroup>
4343

4444
<ItemGroup>
45+
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
4546
<PackageReference Include="MaterialDesignThemes" Version="4.8.0-ci172" />
4647
<PackageReference Include="Microsoft.NETCore.Platforms" Version="7.0.0" />
4748
<PackageReference Include="Microsoft.Win32.Primitives" Version="4.3.0" />

Diff for: BardMusicPlayer/Controls/MidiRepository.xaml

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<UserControl x:Class="BardMusicPlayer.Controls.MidiRepository"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
mc:Ignorable="d"
7+
Margin="10,10,10,0"
8+
Background="{DynamicResource MaterialDesignBackground}"
9+
d:DesignHeight="450" d:DesignWidth="800">
10+
<Grid>
11+
<Grid.RowDefinitions>
12+
<RowDefinition Height="32" />
13+
<RowDefinition Height="25" />
14+
<RowDefinition Height="*" />
15+
<RowDefinition Height="140" />
16+
</Grid.RowDefinitions>
17+
18+
<Button Grid.Row="0" x:Name="BtnGetSongList" Content="Get Song List" Grid.Column="1" Click="Button_Click"/>
19+
20+
<ProgressBar Grid.Row="1" x:Name="LoadingProgressBar" Grid.Column="1" IsIndeterminate="True"/>
21+
22+
<ListView Grid.Row="2"
23+
x:Name="MidiRepoContainer"
24+
VerticalAlignment="Stretch" HorizontalAlignment="Stretch" BorderThickness="0"
25+
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
26+
ScrollViewer.VerticalScrollBarVisibility="Visible"
27+
ScrollViewer.CanContentScroll="True" SelectionChanged="MidiRepoContainer_SelectionChanged" MouseDoubleClick="MidiRepoContainer_MouseDoubleClick" />
28+
29+
<Grid Grid.Row="3" x:Name="DownloadPanel" Background="{DynamicResource MaterialDesignPaper}">
30+
<Grid.RowDefinitions>
31+
<RowDefinition Height="10"/>
32+
<RowDefinition Height="24"/>
33+
<RowDefinition Height="30"/>
34+
<RowDefinition Height="30"/>
35+
<RowDefinition Height="*"/>
36+
</Grid.RowDefinitions>
37+
<TextBlock Grid.Row="1" x:Name="SongTitle" VerticalAlignment="Top" Text="song_title" FontWeight="Bold" />
38+
39+
<TextBlock Grid.Row="2" x:Name="SongComment" TextWrapping="WrapWithOverflow" Text="song_comment" VerticalAlignment="Top" />
40+
41+
<Grid Grid.Row="3">
42+
<Grid.ColumnDefinitions>
43+
<ColumnDefinition Width="99" />
44+
<ColumnDefinition Width="*" />
45+
<ColumnDefinition Width="25" />
46+
</Grid.ColumnDefinitions>
47+
48+
<Label Grid.Column="0" Margin="-4,0,0,0" Content="Download Path:" VerticalAlignment="Center" HorizontalAlignment="Left" />
49+
<TextBox IsReadOnly="True" Grid.Column="1" Margin="0,0,4,0" x:Name="DownloadPath" Text="" Padding="5" VerticalAlignment="Center" HorizontalAlignment="Stretch" />
50+
<Button x:Name="BtnDownloadPath" Grid.Column="2" Content="..." Margin="0,0 4,0"
51+
Click="SelectPath_Button_Click" Height="18" Padding="5" />
52+
</Grid>
53+
<Grid Grid.Row="4" >
54+
<Grid.ColumnDefinitions>
55+
<ColumnDefinition Width="100" />
56+
<ColumnDefinition Width="*" />
57+
</Grid.ColumnDefinitions>
58+
<Button x:Name="DownloadButton" Grid.Column="0" Content="Download" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,6,0,0" Click="DownloadButtonClick"/>
59+
<ProgressBar x:Name="DownloadProgressBar" Grid.Column="1" Height="20" Background="White" />
60+
<Label x:Name="DownloadProgressLabel" Grid.Column="1" Content="Download Complete" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#DDFFFFFF"/>
61+
</Grid>
62+
</Grid>
63+
</Grid>
64+
</UserControl>

Diff for: BardMusicPlayer/Controls/MidiRepository.xaml.cs

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using BardMusicPlayer.Pigeonhole;
2+
using BardMusicPlayer.Resources;
3+
using HtmlAgilityPack;
4+
using System.IO;
5+
using System.Net.Http;
6+
using System.Reflection;
7+
using System.Windows;
8+
using System.Windows.Controls;
9+
using System.Windows.Input;
10+
11+
namespace BardMusicPlayer.Controls;
12+
13+
/// <summary>
14+
/// Web scraper to scrape the song list from https://songs.bardmusicplayer.com and populate in the listview
15+
/// </summary>
16+
public partial class MidiRepository : UserControl
17+
{
18+
private const string songNodeXpath = "//div[contains(@class, 'midi-entry')]";
19+
private const string titleNodeXpath = ".//a[contains(@class, 'mtitle')]";
20+
private const string authorNodeXpath = ".//span[contains(@class, 'mauthor')]";
21+
private const string commentNodeXpath = ".//span[contains(@class, 'r4')]";
22+
private readonly HttpClient httpClient;
23+
private readonly string midiRepoUrl = "https://songs.bardmusicplayer.com";
24+
private List<Song> listSong = new List<Song>();
25+
public MidiRepository()
26+
{
27+
InitializeComponent();
28+
httpClient = new HttpClient();
29+
LoadingProgressBar.Visibility = Visibility.Hidden;
30+
DownloadPanel.Visibility = Visibility.Hidden;
31+
DownloadPath.Text = BmpPigeonhole.Instance.MidiDownloadPath;
32+
DownloadProgressLabel.Visibility = Visibility.Hidden;
33+
DownloadProgressBar.Visibility = Visibility.Hidden;
34+
}
35+
private class Song
36+
{
37+
public string Title { get; set; } = "";
38+
public string Author { get; set; } = "";
39+
public string Comment { get; set; } = "";
40+
public string Url { get; set; } = "";
41+
}
42+
43+
/// <summary>
44+
/// Fetch the html from https://songs.bardmusicplayer.com
45+
/// </summary>
46+
/// <returns></returns>
47+
private async Task<string> FetchSongData()
48+
{
49+
var response = await httpClient.GetStringAsync(midiRepoUrl);
50+
return response;
51+
}
52+
53+
/// <summary>
54+
/// Get midi meta data from html result using web scraper
55+
/// </summary>
56+
/// <param name="html"></param>
57+
private void RefreshSongList(string html)
58+
{
59+
listSong.Clear();
60+
HtmlDocument htmlDoc = new HtmlDocument();
61+
htmlDoc.LoadHtml(html);
62+
63+
var songNodes = htmlDoc.DocumentNode.SelectNodes(songNodeXpath);
64+
65+
foreach (var songNode in songNodes)
66+
{
67+
var titleNode = songNode.SelectSingleNode(titleNodeXpath);
68+
var authorNode = songNode.SelectSingleNode(authorNodeXpath);
69+
var commentNode = songNode.SelectSingleNode(commentNodeXpath);
70+
71+
if (titleNode != null && authorNode != null && commentNode != null)
72+
{
73+
listSong.Add(new Song
74+
{
75+
Title = titleNode.GetAttributeValue("title", ""),
76+
Author = authorNode.InnerText,
77+
Comment = commentNode.InnerText,
78+
Url = titleNode.GetAttributeValue("href", ""),
79+
});
80+
}
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Click button to scrape the web and put the result to listSong
86+
/// </summary>
87+
/// <param name="sender"></param>
88+
/// <param name="e"></param>
89+
private async void Button_Click(object sender, RoutedEventArgs e)
90+
{
91+
BtnGetSongList.IsEnabled = false;
92+
LoadingProgressBar.Visibility = Visibility.Visible;
93+
94+
var songData = await FetchSongData();
95+
96+
RefreshSongList(songData);
97+
MidiRepoContainer.ItemsSource = listSong.Select(song => song.Title).ToList();
98+
99+
BtnGetSongList.IsEnabled = true;
100+
BtnGetSongList.Content = "Refresh";
101+
LoadingProgressBar.Visibility = Visibility.Hidden;
102+
}
103+
104+
/// <summary>
105+
/// Show midi details when clicking the listview
106+
/// </summary>
107+
/// <param name="sender"></param>
108+
/// <param name="e"></param>
109+
private void MidiRepoContainer_SelectionChanged(object sender, SelectionChangedEventArgs e)
110+
{
111+
DownloadPanel.Visibility = Visibility.Visible;
112+
Song song = listSong[MidiRepoContainer.SelectedIndex];
113+
SongTitle.Text = $"({song.Author}) {song.Title}";
114+
SongComment.Text = song.Comment;
115+
}
116+
117+
/// <summary>
118+
/// Select download path on click button
119+
/// </summary>
120+
/// <param name="sender"></param>
121+
/// <param name="e"></param>
122+
private void SelectPath_Button_Click(object sender, RoutedEventArgs e)
123+
{
124+
var dlg = new FolderPicker
125+
{
126+
InputPath = Directory.Exists(BmpPigeonhole.Instance.MidiDownloadPath) ? Path.GetFullPath(BmpPigeonhole.Instance.MidiDownloadPath) : Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
127+
};
128+
129+
if (dlg.ShowDialog() == true)
130+
{
131+
var path = dlg.ResultPath;
132+
if (!Directory.Exists(path))
133+
return;
134+
135+
path += path.EndsWith("\\") ? "" : "\\";
136+
DownloadPath.Text = path;
137+
BmpPigeonhole.Instance.MidiDownloadPath = path;
138+
}
139+
}
140+
141+
/// <summary>
142+
/// Start download process
143+
/// </summary>
144+
/// <param name="url"></param>
145+
/// <param name="fileName"></param>
146+
private async void DownloadFile(string url, string fileName)
147+
{
148+
HttpClient client = new HttpClient();
149+
HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
150+
long? contentLength = response.Content.Headers.ContentLength;
151+
string tempFilePath = Path.GetTempFileName();
152+
153+
using (FileStream tempFileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
154+
{
155+
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
156+
{
157+
byte[] buffer = new byte[4096];
158+
long totalBytesRead = 0;
159+
int bytesRead = 0;
160+
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
161+
{
162+
await tempFileStream.WriteAsync(buffer, 0, bytesRead);
163+
totalBytesRead += bytesRead;
164+
double percentComplete = (double)totalBytesRead / (contentLength ?? totalBytesRead) * 100;
165+
Dispatcher.Invoke(() => DownloadProgressBar.Value = percentComplete);
166+
}
167+
}
168+
}
169+
string downloadsPath = BmpPigeonhole.Instance.MidiDownloadPath;
170+
string finalFilePath = $"{downloadsPath}/{fileName}.mid";
171+
172+
File.Move(tempFilePath, finalFilePath, true);
173+
DownloadButton.IsEnabled = true;
174+
DownloadProgressLabel.Visibility = Visibility.Visible;
175+
}
176+
177+
/// <summary>
178+
/// Download selected midi in the listview by clicking download button
179+
/// </summary>
180+
/// <param name="sender"></param>
181+
/// <param name="e"></param>
182+
private void DownloadButtonClick(object sender, RoutedEventArgs e)
183+
{
184+
DownloadSelectedMidi();
185+
}
186+
187+
/// <summary>
188+
/// Download selected midi by double click the list item
189+
/// </summary>
190+
/// <param name="sender"></param>
191+
/// <param name="e"></param>
192+
private void MidiRepoContainer_MouseDoubleClick(object sender, MouseButtonEventArgs e)
193+
{
194+
DownloadSelectedMidi();
195+
}
196+
197+
/// <summary>
198+
/// Download current selected midi in the listview
199+
/// </summary>
200+
private void DownloadSelectedMidi()
201+
{
202+
if (!Directory.Exists(BmpPigeonhole.Instance.MidiDownloadPath))
203+
{
204+
MessageBox.Show("The downloads directory is not valid.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
205+
return;
206+
}
207+
Song selectedSong = listSong[MidiRepoContainer.SelectedIndex];
208+
DownloadButton.IsEnabled = false;
209+
DownloadProgressBar.Visibility = Visibility.Visible;
210+
DownloadProgressBar.Value = 0;
211+
DownloadFile($"{midiRepoUrl}/{selectedSong.Url}", $"({selectedSong.Author}) {selectedSong.Title}");
212+
}
213+
}

Diff for: BardMusicPlayer/MainWindow.xaml.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public MainWindow()
2020
AllowsTransparency = false;
2121
WindowStyle = WindowStyle.SingleBorderWindow;
2222
Height = 738;
23-
Width = 910;
23+
Width = 930;
2424
ResizeMode = ResizeMode.CanResizeWithGrip;
2525

2626
if (!BmpPigeonhole.Instance.DarkStyle)

Diff for: BardMusicPlayer/UI_Classic/Classic_MainView.xaml

+5
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,11 @@
399399
<controls:SongBrowser x:Name="SongBrowser" />
400400
</Grid>
401401
</TabItem>
402+
<TabItem Header="Midi Repo" Width="Auto" Height="35" Padding="0">
403+
<Grid Background="{DynamicResource MaterialDesignBackground}">
404+
<controls:MidiRepository x:Name="MidiRepository" />
405+
</Grid>
406+
</TabItem>
402407
</TabControl>
403408
</Grid>
404409

0 commit comments

Comments
 (0)