Skip to content

Commit

Permalink
fix classical pieces not scrobbling properly (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
PKBeam committed Sep 25, 2023
1 parent 21f1d1e commit d16154a
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 26 deletions.
3 changes: 3 additions & 0 deletions AMWin-RichPresence/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
<setting name="ShowRPWhenMusicPaused" serializeAs="String">
<value>False</value>
</setting>
<setting name="ClassicalComposerAsArtist" serializeAs="String">
<value>True</value>
</setting>
</AMWin_RichPresence.Properties.Settings>
</userSettings>
</configuration>
6 changes: 5 additions & 1 deletion AMWin-RichPresence/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ public App() {

// start Discord RPC
var subtitleOptions = (AppleMusicDiscordClient.RPSubtitleDisplayOptions)AMWin_RichPresence.Properties.Settings.Default.RPSubtitleChoice;
var classicalComposerAsArtist = AMWin_RichPresence.Properties.Settings.Default.ClassicalComposerAsArtist;
discordClient = new(Constants.DiscordClientID, enabled: false, subtitleOptions: subtitleOptions);

// start Last.FM scrobbler
scrobblerClient = new AppleMusicScrobbler();
scrobblerClient.init(lastFmCredentials);

// start Apple Music scraper
amScraper = new(AMWin_RichPresence.Properties.Settings.Default.LastfmAPIKey, Constants.RefreshPeriod, (newInfo) => {
amScraper = new(AMWin_RichPresence.Properties.Settings.Default.LastfmAPIKey, Constants.RefreshPeriod, classicalComposerAsArtist, (newInfo) => {

// don't update scraper if Apple Music is paused or not open
if (newInfo != null && newInfo != null && (AMWin_RichPresence.Properties.Settings.Default.ShowRPWhenMusicPaused || !newInfo.IsPaused)) {
Expand Down Expand Up @@ -74,5 +75,8 @@ internal void UpdateRPSubtitleDisplay(AppleMusicDiscordClient.RPSubtitleDisplayO
internal void UpdateLastfmCreds(bool showMessageBoxOnSuccess) {
scrobblerClient.UpdateCreds(lastFmCredentials, showMessageBoxOnSuccess);
}
internal void UpdateScraperPreferences(bool composerAsArtist) {
amScraper.composerAsArtist = composerAsArtist;
}
}
}
44 changes: 33 additions & 11 deletions AMWin-RichPresence/AppleMusicClientScraper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Timers;
using System.Windows.Automation;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace AMWin_RichPresence {

Expand Down Expand Up @@ -51,14 +53,16 @@ internal class AppleMusicClientScraper {
Timer timer;
RefreshHandler refreshHandler;
AppleMusicInfo? currentSong;
public bool composerAsArtist; // for classical music, treat composer (not performer) as artist

public AppleMusicClientScraper(string lastFmApiKey, int refreshPeriodInSec, RefreshHandler refreshHandler) {
public AppleMusicClientScraper(string lastFmApiKey, int refreshPeriodInSec, bool composerAsArtist, RefreshHandler refreshHandler) {
this.refreshHandler = refreshHandler;
this.lastFmApiKey = lastFmApiKey;
timer = new Timer(refreshPeriodInSec * 1000);
timer.Elapsed += Refresh;
Refresh(this, null);
timer.Start();
this.composerAsArtist = composerAsArtist;
}

public void Refresh(object? source, ElapsedEventArgs? e) {
Expand Down Expand Up @@ -129,24 +133,42 @@ public void Refresh(object? source, ElapsedEventArgs? e) {
var songName = songNameElement.Current.Name;
var songAlbumArtist = songAlbumArtistElement.Current.Name;

string songArtist;
string songArtist;
string songAlbum;

// some classical songs add "By " before the composer's name
string songComposer = null;
string songPerformer = null;
var composerPerformerRegex = new Regex(@"By\s.*?\s\u2014");
var songComposerPerformer = composerPerformerRegex.Matches(songAlbumArtist);
try {
// U+2014 is the emdash, not the standard "-" character on the keyboard!
songArtist = songAlbumArtist.Split(" \u2014 ")[0];
songAlbum = songAlbumArtist.Split(" \u2014 ")[1];
if (songComposerPerformer.Count > 0) {
songComposer = songAlbumArtist.Split(" \u2014 ")[0].Remove(0, 3);
songPerformer = songAlbumArtist.Split(" \u2014 ")[1];
songArtist = composerAsArtist ? songComposer : songPerformer;
songAlbum = songAlbumArtist.Split(" \u2014 ")[2];
} else {
// U+2014 is the emdash used by the Apple Music app, not the standard "-" character on the keyboard!
songArtist = songAlbumArtist.Split(" \u2014 ")[0];
songAlbum = songAlbumArtist.Split(" \u2014 ")[1];
}
} catch {
Trace.WriteLine($"Could not parse '{songAlbumArtist}' into artist and album.");
songArtist = "";
songAlbum = "";
}

// when searching for song info, use the performer as the artist instead of composer
string songSearchArtist = songPerformer ?? songArtist;

// if this is a new song, clear out the current song
if (currentSong == null || currentSong?.SongName != songName || currentSong?.SongSubTitle != songAlbumArtist) {
if (currentSong == null || currentSong?.SongName != songName || currentSong?.SongArtist != songArtist || currentSong?.SongSubTitle != songAlbumArtist) {
currentSong = new AppleMusicInfo(songName, songAlbumArtist, songAlbum, songArtist);
}

if (currentSong.ArtistList == null) {
AppleMusicWebScraper.GetArtistList(songName, songAlbum, songArtist).ContinueWith(t => {

// find artist list... unless it's a classical song
if (currentSong.ArtistList == null && songComposer == null) {
AppleMusicWebScraper.GetArtistList(songName, songAlbum, songSearchArtist).ContinueWith(t => {
currentSong.ArtistList = t.Result;
if (currentSong.ArtistList.Count == 0) {
currentSong.ArtistList = null;
Expand Down Expand Up @@ -176,7 +198,7 @@ public void Refresh(object? source, ElapsedEventArgs? e) {

// try to get song duration if we don't have it
if (currentSong.SongDuration == null) {
AppleMusicWebScraper.GetSongDuration(lastFmApiKey, songName, songAlbum, songArtist).ContinueWith(t => {
AppleMusicWebScraper.GetSongDuration(lastFmApiKey, songName, songAlbum, songSearchArtist).ContinueWith(t => {
string? dur = t.Result;
currentSong.SongDuration = dur == null ? null : ParseTimeString(dur);
});
Expand Down Expand Up @@ -214,7 +236,7 @@ public void Refresh(object? source, ElapsedEventArgs? e) {
// ------------------------------------------------

if (currentSong.CoverArtUrl == null) {
AppleMusicWebScraper.GetAlbumArtUrl(lastFmApiKey, songName, songAlbum, songArtist).ContinueWith(t => {
AppleMusicWebScraper.GetAlbumArtUrl(lastFmApiKey, songName, songAlbum, songSearchArtist).ContinueWith(t => {
currentSong.CoverArtUrl = t.Result;
});
}
Expand Down
5 changes: 5 additions & 0 deletions AMWin-RichPresence/AppleMusicDiscordClient.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Linq;
using AMWin_RichPresence;
using DiscordRPC;
Expand Down Expand Up @@ -65,6 +66,10 @@ public void SetPresence(AppleMusicInfo amInfo, bool showSmallImage) {
subtitle = songAlbum;
break;
}
if (ASCIIEncoding.Unicode.GetByteCount(subtitle) > 128) {
// TODO fix this to account for multibyte unicode characters
subtitle = subtitle.Substring(0, 60) + "...";
}
try {
var rp = new RichPresence() {
Details = songName,
Expand Down
15 changes: 11 additions & 4 deletions AMWin-RichPresence/AppleMusicWebScraper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ private async static Task<JsonDocument> GetURLJson(string url) {
// Apple Music web search functions
private async static Task<HtmlNode?> SearchTopResults(string songName, string songAlbum, string songArtist) {
// search on the Apple Music website for the song
var url = $"https://music.apple.com/us/search?term={songName} {songAlbum} {songArtist}";
var searchTerm = Uri.EscapeDataString($"{songName} {songAlbum} {songArtist}");
var url = $"https://music.apple.com/us/search?term={searchTerm}";
HtmlDocument doc = await GetURL(url);

try {
Expand Down Expand Up @@ -76,7 +77,8 @@ private async static Task<JsonDocument> GetURLJson(string url) {
private async static Task<HtmlNode?> SearchSongs(string songName, string songAlbum, string songArtist) {

// search on the Apple Music website for the song
var url = $"https://music.apple.com/us/search?term={songName} {songAlbum} {songArtist}";
var searchTerm = Uri.EscapeDataString($"{songName} {songAlbum} {songArtist}");
var url = $"https://music.apple.com/us/search?term={searchTerm}";
HtmlDocument doc = await GetURL(url);

try {
Expand Down Expand Up @@ -223,8 +225,13 @@ private static string GetLargestImageUrl(HtmlNode nodeWithSource) {
public async static Task<string?> GetSongDurationLastFm(string apiKey, string songName, string songArtist) {
var url = $"http://ws.audioscrobbler.com/2.0/?method=track.getinfo&api_key={apiKey}&artist={Uri.EscapeDataString(songArtist)}&track={Uri.EscapeDataString(songName)}&format=json";
var j = await GetURLJson(url);
var dur = int.Parse(j.RootElement.GetProperty("track").GetProperty("duration").ToString())/1000;
return dur == 0 ? null : $"{dur / 60}:{$"{dur % 60}".PadLeft(2, '0')}";
var track = j.RootElement.GetProperty("track");
try {
var dur = int.Parse(track.GetProperty("duration").ToString()) / 1000;
return dur == 0 ? null : $"{dur / 60}:{$"{dur % 60}".PadLeft(2, '0')}";
} catch {
return null;
}
}
public async static Task<string?> GetSongDurationAppleMusic(string songName, string songAlbum, string songArtist) {
try {
Expand Down
12 changes: 12 additions & 0 deletions AMWin-RichPresence/Properties/Settings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions AMWin-RichPresence/Properties/Settings.settings
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@
<Setting Name="ShowRPWhenMusicPaused" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="ClassicalComposerAsArtist" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
</Settings>
</SettingsFile>
24 changes: 14 additions & 10 deletions AMWin-RichPresence/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
xmlns:properties="clr-namespace:AMWin_RichPresence.Properties"
mc:Ignorable="d"
Icon="/Resources/AMWinRP.ico"
Title="AMWin-RichPresence" Height="510" Width="440" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
Title="AMWin-RichPresence" Height="535" Width="440" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<DockPanel>
<Border DockPanel.Dock="Top" Height="64" Padding="10" Background="{DynamicResource {x:Static SystemColors.ControlLightBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}" BorderThickness="0 0 0 1.5">
Expand All @@ -33,24 +33,28 @@
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>

<TextBlock Grid.Row="0" Grid.Column="0">Run when Windows starts</TextBlock>
<CheckBox Grid.Row="0" Grid.Column="1" x:Name="CheckBox_RunOnStartup" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=RunOnStartup, Mode=TwoWay}" Click="CheckBox_RunOnStartup_Click"/>

<TextBlock Grid.Row="1" VerticalAlignment="Top" FontWeight="Bold" Padding="0 3 0 0">Discord settings</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">Treat composer as artist</TextBlock>
<CheckBox Grid.Row="1" Grid.Column="1" x:Name="CheckBox_ClassicalComposerAsArtist" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=ClassicalComposerAsArtist, Mode=TwoWay}" Click="CheckBox_ClassicalComposerAsArtist_Click"/>

<TextBlock Grid.Row="2" VerticalAlignment="Top" FontWeight="Bold" Padding="0 3 0 0">Discord settings</TextBlock>

<TextBlock Grid.Row="2" Grid.Column="0" Padding="10 0 0 0">Enable Discord RP</TextBlock>
<CheckBox Grid.Row="2" Grid.Column="1" x:Name="CheckBox_EnableDiscordRP" Margin="0,2,0,0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=EnableDiscordRP, Mode=TwoWay}" Click="CheckBox_EnableDiscordRP_Click" HorizontalAlignment="Right" Width="120"/>
<TextBlock Grid.Row="3" Grid.Column="0" Padding="10 0 0 0">Enable Discord RP</TextBlock>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="CheckBox_EnableDiscordRP" Margin="0,2,0,0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=EnableDiscordRP, Mode=TwoWay}" Click="CheckBox_EnableDiscordRP_Click" HorizontalAlignment="Right" Width="120"/>

<TextBlock Grid.Row="3" Grid.Column="0" Padding="10 0 0 0">RP when music paused</TextBlock>
<CheckBox Grid.Row="3" Grid.Column="1" x:Name="CheckBox_ShowRPWhenMusicPaused" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=ShowRPWhenMusicPaused, Mode=TwoWay}" Click="CheckBox_ShowRPWhenMusicPaused_Click"></CheckBox>
<TextBlock Grid.Row="4" Grid.Column="0" Padding="10 0 0 0">RP when music paused</TextBlock>
<CheckBox Grid.Row="4" Grid.Column="1" x:Name="CheckBox_ShowRPWhenMusicPaused" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=ShowRPWhenMusicPaused, Mode=TwoWay}" Click="CheckBox_ShowRPWhenMusicPaused_Click"></CheckBox>

<TextBlock Grid.Row="4" Grid.Column="0" Padding="10 0 0 0">Apple Music icon in status</TextBlock>
<CheckBox Grid.Row="4" Grid.Column="1" x:Name="CheckBox_ShowAppleMusicIcon" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=ShowAppleMusicIcon, Mode=TwoWay}" Click="CheckBox_ShowAppleMusicIcon_Click"></CheckBox>
<TextBlock Grid.Row="5" Grid.Column="0" Padding="10 0 0 0">Apple Music icon in status</TextBlock>
<CheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckBox_ShowAppleMusicIcon" Margin="0 2 0 0" IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=ShowAppleMusicIcon, Mode=TwoWay}" Click="CheckBox_ShowAppleMusicIcon_Click"></CheckBox>

<TextBlock Grid.Row="5" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 0 2" Padding="10 0 0 0">Rich Presence subtitle</TextBlock>
<ComboBox Grid.Row="5" Grid.Column="1" x:Name="ComboBox_RPSubtitleChoice" SelectedIndex="{Binding Source={x:Static properties:Settings.Default}, Path=RPSubtitleChoice, Mode=TwoWay}" Height="22" SelectionChanged="ComboBox_RPSubtitleChoice_SelectionChanged">
<TextBlock Grid.Row="6" Grid.Column="0" VerticalAlignment="Center" Margin="0 0 0 2" Padding="10 0 0 0">Rich Presence subtitle</TextBlock>
<ComboBox Grid.Row="6" Grid.Column="1" x:Name="ComboBox_RPSubtitleChoice" SelectedIndex="{Binding Source={x:Static properties:Settings.Default}, Path=RPSubtitleChoice, Mode=TwoWay}" Height="22" SelectionChanged="ComboBox_RPSubtitleChoice_SelectionChanged">
<!-- The order of these items matters! (check the enum in AppleMusicDiscordClient.cs) -->
<ComboBoxItem>Artist and album</ComboBoxItem>
<ComboBoxItem>Artist only</ComboBoxItem>
Expand Down
4 changes: 4 additions & 0 deletions AMWin-RichPresence/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ private void CheckBox_RunOnStartup_Click(object sender, RoutedEventArgs e) {

SaveSettings();
}
private void CheckBox_ClassicalComposerAsArtist_Click(object sender, RoutedEventArgs e) {
((App)Application.Current).UpdateScraperPreferences(CheckBox_ClassicalComposerAsArtist.IsChecked == true);
SaveSettings();
}
private void CheckBox_EnableDiscordRP_Click(object sender, RoutedEventArgs e) {
SaveSettings();
}
Expand Down

0 comments on commit d16154a

Please sign in to comment.